← Назад на главную

Простой Telegram-бот с использованием Ollama на Go

Привет! Для того чтобы обращаться к нейросети Ollama (которая установлена на моем компьютере) удаленно, я решил написать простого Telegram-бота. Этот бот понимает русский язык и отвечает на сообщения. Давайте разберём его по частям!

Как установить Ollama себе на ПК я рассказал в статье: Ollama - ваш персональный AI на компьютере. Просто и мощно!

1. Основные компоненты программы

package main

import (
"context"
"fmt"
"log"
"os"
"strings"
"time"

tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
ollama "github.com/prathyushnallamothu/ollamago"
)
  • package main - главный пакет программы
  • import - библиотеки для работы с Telegram, нейросетью Ollama и другими базовыми функциями

2. Настройки бота

const (
llmModel = "deepseek-r1:32b" // Используемая модель нейросети
maxHistoryLen = 30 // Максимальная длина истории сообщений
typingDelay = 5 * time.Second // Задержка перед показом "печатает"
)

var (
botToken = "ВАШ_ТОКЕН" // Ключ от Telegram-бота
targetID int64 = 123456 // ID вашего аккаунта
ollamaURL = "http://127.0.0.1:11434" // Адрес Ollama-сервера
)

Здесь мы задаём:

  • Какую языковую модель использовать (в моем случае это deepseek-r1:32b)
  • Сколько сообщений помнить (чтобы не перегружать память)
  • Токен бота (получаем у @BotFather)
  • Ваш личный ID (чтобы бот отвечал только вам)

3. Сердце бота - структура Bot

type Bot struct {
api *tgbotapi.BotAPI // Подключение к Telegram
client *ollama.Client // Подключение к Ollama
history []ollama.Message // История диалога
}
  • api - общение с Telegram
  • client - общение с нейросетью
  • history - память диалога

4. Запуск программы

func main() {

// Создаём клиент для Ollama
opts := []ollama.Option{ollama.WithTimeout(time.Hour)} // Ждём ответ до 1 часа
if ollamaURL != "" {
opts = append(opts, ollama.WithBaseURL(ollamaURL))
}

bot := &Bot{
client: ollama.NewClient(opts...),
history: make([]ollama.Message, 0, maxHistoryLen+10),
}

// Загружаем инструкции для нейросети
bot.loadPrompts()

// Запускаем бота
if err := bot.start(); err != nil {
log.Fatalf("Bot failed: %v", err)
}
}

Здесь:

  • Настраиваем подключение к Ollama
  • Создаём объект бота
  • Загружаем правила для нейросети
  • Запускаем основную логику

5. Загрузка правил для нейросети

func (b *Bot) loadPrompts() {
prompts := []struct {
role string
content string
}{
{"user", `Строгие правила: 1. Пиши всегда на РУССКОМ! 2. Запрещены иероглифы!`},
{"system", "Хорошо, я буду стого соблюдать все правила"},
}

b.history = make([]ollama.Message, 0, len(prompts))
for _, p := range prompts {
b.history = append(b.history, ollama.Message{
Role: p.role,
Content: p.content,
})
}
}

Это как дать боту инструкцию:

  1. Чтобы нейросеть по умолчанию отвечала на русском языке
  2. DeepSeek часто вставляет иероглифы в текст, избавляемся от них

Нейросеть подтверждает, что поняла правила

Вы можете добавлять свои правила или инструкции для нейросети

6. Основной цикл работы бота

func (b *Bot) start() error {

// Подключаемся к Telegram
api, err := tgbotapi.NewBotAPI(botToken)
if err != nil {
return fmt.Errorf("bot init failed: %w", err)
}
b.api = api

log.Printf("Authorized as %s", b.api.Self.UserName)

u := tgbotapi.NewUpdate(0)
u.Timeout = 60

// Слушаем сообщения
updates := b.api.GetUpdatesChan(u)

for update := range updates {
if update.Message == nil || update.Message.From.ID != targetID {
continue
}

// Обработка команд
if update.Message.IsCommand() {
if update.Message.Command() == "start" {
b.processMessage(update, "Привет!")
}
continue
}

// Обработка обычных сообщений
if text := strings.TrimSpace(update.Message.Text); text != "" {
b.processMessage(update, text)
}
}
return nil
}

Бот постоянно проверяет:

  • Пришло ли новое сообщение
  • От нужного ли пользователя
  • Это команда или обычный текст

7. Обработка сообщений

func (b *Bot) processMessage(update tgbotapi.Update, text string) {
// Добавление сообщения пользователя в историю
b.history = append(b.history, ollama.Message{
Role: "user",
Content: text,
})

// Показываем "Печатает..."
typingCtx, cancelTyping := context.WithCancel(context.Background())
go b.showTyping(typingCtx, update.Message.Chat.ID)
defer cancelTyping()

// Получаем ответ от нейросети
resp, err := b.getModelResponse()
if err != nil {
log.Printf("Chat error: %v", err)
b.sendText(update.Message.Chat.ID, "Ошибка обработки запроса")
return
}

// Обрабатываем ответ (Обрезаем размышления DeepSeek)
_, processedResp, _ := strings.Cut(resp, "</think>")

// Сохраняем ответ в историю
b.history = append(b.history, ollama.Message{
Role: "system",
Content: processedResp,
})

// Отправляем ответ
b.sendResponse(update.Message.Chat.ID, processedResp)

// Чистим историю от старых сообщений
b.trimHistory()
}

Этапы обработки:

  1. Сохраняем вопрос пользователя
  2. Показываем анимацию печати
  3. Отправляем вопрос нейросети
  4. Форматируем ответ
  5. Отправляем ответ пользователю
  6. Убираем старые сообщения из памяти

8. Общение с нейросетью

func (b *Bot) getModelResponse() (string, error) {

// Ждём ответ не более 10 минут
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()

// Отправляем запрос
resp, err := b.client.Chat(ctx, ollama.ChatRequest{
Model: llmModel,
Messages: b.history, // Вся история диалога
})
if err != nil {
return "", fmt.Errorf("chat failed: %w", err)
}
return resp.Message.Content, nil
}

Здесь бот:

  1. Ждёт ответ от нейросети
  2. Передаёт всю историю диалога
  3. Возвращает полученный ответ

9. Отправка сообщений

func (b *Bot) sendResponse(chatID int64, response string) {
if len(response) == 0 {
return
}
// Короткие сообщения - отправляем текстом
if len([]rune(response)) < 4096 {
fmt.Println(response)
msg := tgbotapi.NewMessage(chatID, response)
msg.ParseMode = tgbotapi.ModeMarkdown
if _, err := b.api.Send(msg); err != nil {
b.sendAsFile(chatID, response)
}
} else {
// Длинные сообщения - отправляем файлом
b.sendText(chatID, "Слишком длинное сообщение!")
b.sendAsFile(chatID, response)
}
}

Важно: Telegram имеет ограничение на длину сообщений (4096 символов), поэтому длинные ответы отправляются файлом.

10. Полезные фишки

Индикатор печати:

func (b *Bot) showTyping(ctx context.Context, chatID int64) {
ticker := time.NewTicker(typingDelay)
defer ticker.Stop()

for {
select {
case <-ticker.C:
action := tgbotapi.NewChatAction(chatID, tgbotapi.ChatTyping)
b.api.Send(action)
case <-ctx.Done():
return
}
}
}

Каждые 5 секунд бот показывает анимацию "печатает", пока нейросеть думает.

Очистка истории:

func (b *Bot) trimHistory() {
if len(b.history) <= maxHistoryLen {
return
}

// Оставляем первые 5 системных сообщений
// и последние 25 сообщений диалога
keep := 5
if len(b.history) > keep {
b.history = append(b.history[:keep], b.history[len(b.history)-(maxHistoryLen-keep):]...)
}
}

Чтобы нейросеть не "перегружалась", сохраняем только последние 30 сообщений.

Как это работает вместе:

  1. Вы пишете сообщение в Telegram
  2. Бот добавляет его в историю диалога
  3. Бот показывает "печатает..."
  4. Нейросеть обрабатывает запрос с учётом всей истории
  5. Бот форматирует ответ
  6. Ответ отправляется вам
  7. Старые сообщения удаляются из памяти
Изображение

Советы для начинающих:

  • Всегда проверяйте ошибки (как в b.api.Send(msg))
  • Экспериментируйте с разными моделями Ollama
  • Ограничивайте длину истории для экономии памяти

Этот бот - отличная основа для создания умных помощников! Вы можете добавить новые функции, например:

  • Распознавание голосовых сообщений
  • Работу с базами данных
  • Поддержку нескольких пользователей

Весь код бота есть на Boosty, переходите и скачивайте.

Спасибо за ваше время и внимание! Ваша поддержка очень важна для меня! Если вам понравилась статья, пожалуйста, поставьте лайк этой статье на моем канале Дзен

Подпишитесь на мой Телеграм-канал, чтобы быть в курсе новых статей.

Удачи!