Прокачка Telegram-бота на локальной LLM: от простых чатов до мини-игр, генерации фото и выбора нейросетей

Хочу представить вашему вниманию кейс по разработке Telegram-бота, функционирующего на базе исключительно локальных нейросетей. В качестве текстового ядра задействована платформа Ollama, а за визуальный контент отвечает AUTOMATIC1111. Весь программный комплекс реализован на Python с использованием библиотеки python-telegram-bot.

Выбор в пользу Ollama был обусловлен её открытостью, бесплатным доступом к широкому спектру моделей и беспрепятственным развертыванием в локальной среде. В отличие от облачных сервисов вроде ChatGPT, здесь отсутствует зависимость от подписок и внешних API.

Технические характеристики моей рабочей станции: видеокарта RTX 3070 и 32 ГБ оперативной памяти. Поскольку проект хостится на домашнем ПК, бот доступен только во время работы системы.

Проект постоянно трансформируется. Отслеживать актуальные обновления и внедрение новых инструментов можно в моем Telegram-канале: https://t.me/rocet_0

Хронология разработки: от вызовов к решениям.

V1.0 — V1.1: Первые итерации и борьба с «галлюцинациями»

На начальном этапе архитектура была предельно простой: запрос пользователя транслировался напрямую в ответ модели. Первой пробой пера стала легковесная Mistral, однако она часто демонстрировала слабые когнитивные способности и склонность к ложным утверждениям. Функционал ограничивался базовыми командами /start и /help.

Вскоре я перешел на Qwen2.5:14b. Данная модель показала отличные результаты: высокую скорость обработки на моем «железе», глубокое понимание русского языка и более качественную логику рассуждений.

V1.2: Оптимизация ожидания и имитация печати

Длительное ожидание негативно сказывается на пользовательском опыте. Чтобы предотвратить «зависания» при сложных запросах, я установил лимит на время ожидания ответа от Ollama:

timeout=aiohttp.ClientTimeout(total=600)

Для повышения производительности сессия aiohttp инициализируется единожды при запуске бота и поддерживается в активном состоянии на протяжении всего сеанса.

session = context.application.bot_data['session']

Также была реализована визуальная обратная связь: метод send_action активирует статус «печатает…», а благодаря механизму стриминга ответ формируется постепенно, токен за токеном, прямо на глазах у пользователя.

await context.bot.send_chat_action(chat_id=chat_id, action='typing')

V1.2.1: Коррекция математических формул

На этом этапе возникла неожиданная сложность: модель часто использовала разметку LaTeX (например, \( ... \) или \frac), которую интерфейс Telegram не способен интерпретировать корректно.

Попытки решить проблему через системный промт не увенчались успехом — ИИ игнорировал инструкции.

Пришлось внедрить функцию постобработки на базе регулярных выражений для очистки текста.

# Удаление блоков математической разметки
text = re.sub(r'\\\[|\\\]|\\\(|\\\)', '', text)

# Трансформация \frac{a}{b} в (a / b)
text = re.sub(r'\\frac\{(.*?)\}\{(.*?)\}', r'(\1 / \2)', text)

# Конвертация корней \sqrt{a} → √(a)
text = re.sub(r'\\sqrt\{(.*?)\}', r'√(\1)', text)

# Очистка команд \text{...}
text = re.sub(r'\\text\{(.*?)\}', r'\1', text)

# Замена спецсимволов replacements = { r'\pm': '±', r'\times': '×', r'\cdot': '×', r'\div': '÷', r'\approx': '≈', r'\le': '≤', r'\ge': '≥', r'\neq': '≠', r'\quad': ' ', r'\,': '', r'\%': '%' }

# Циклическая замена символов for latex, normal in replacements.items(): text = text.replace(latex, normal)

# Обработка степеней
text = re.sub(r'\^\{(.*?)\}', r'^\1', text)

# Удаление конструкций \left \right
text = re.sub(r'\\left|\\right', '', text)

# Удаление прочих служебных команд
text = re.sub(r'\\[a-zA-Z]+', '', text)

# Финальная очистка фигурных скобок
text = text.replace('{', '').replace('}', '')

В результате математические выкладки стали выглядеть презентабельно.

Важное уточнение: В режиме стриминга процесс очистки мог приводить к тому, что текст сообщения не менялся между итерациями, провоцируя ошибку API Telegram. Я добавил условие: if last_sent_text != new_text: message.edit_text(new_text).

V1.3: Контекстная память и оформление Markdown

Ключевым обновлением стало внедрение истории диалогов. Без памяти бот не способен поддерживать осмысленную беседу. Для надежного хранения данных вместо нестабильной оперативной памяти я использовал SQLite.

with sqlite3.connect('memory.db') as conn: cursor = conn.cursor() cursor.execute(""" CREATE TABLE IF NOT EXISTS messages ( id INTEGER PRIMARY KEY AUTOINCREMENT, chat_id INTEGER, role TEXT, content TEXT, timestamp REAL ) """)

Теперь для каждого пользователя (chat_id) формируется персонализированная цепочка сообщений.

Также была решена проблема с отображением Markdown. Несмотря на то, что модель выделяла текст жирным, Telegram игнорировал разметку до момента явного указания parse_mode="Markdown" в методе edit_text.

Дополнительно в системный промт была добавлена директива отвечать только на русском языке, так как китайские модели иногда переключались на родной язык без видимых причин.

V1.5: Гибкий выбор нейросетевых моделей

Для удобства переключения между различными ИИ-движками я реализовал систему инлайн-кнопок. Каждая кнопка передает идентификатор модели в переменную current_model, которая затем подставляется в запрос к API Ollama.

async with session.post(
OLLAMA_URL,
json={
"model": current_model,

В библиотеку были включены: llama3:8b, mistral:7b, deepseek-coder:6.7b, codellama:7b, qwen2.5:7b-instruct, phi3:mini и другие.

Технический нюанс: Интерактивные элементы заработали корректно только после явного указания allowed_updates=["message", "callback_query"] в настройках поллинга бота.

Для минимизации задержек при первом обращении к новой модели я внедрил этап «прогрева». Боту отправляется скрытый микро-запрос «Привет», что заставляет систему заранее загрузить веса модели в память видеокарты.

V1.6: Мультимодальность, код и кэширование

Я структурировал каталог моделей, разбив их по категориям, и добавил визуальную индикацию текущей активной модели с помощью эмодзи «галочки».

Для обнуления контекста была создана команда очистки истории:

cursor.execute("""
DELETE
FROM messages
WHERE chat_id = ?
""", (chat_id,))

Поскольку процесс выгрузки одной модели и загрузки другой занимает время, я добавил информационное сообщение «Загружаю модель...», чтобы пользователь понимал статус операции.

Для повышения отзывчивости системы было внедрено TTL-кеширование. Модели теперь остаются в памяти на определенный срок, что позволяет мгновенно обслуживать запросы разных пользователей, работающих с одним и тем же ИИ-ядром.

Первым шагом к мультимодальности стала интеграция glm-ocr — модели, способной извлекать печатный текст из изображений. Теперь при получении фото бот автоматически направляет его на обработку визуальным энкодером.

photo = message.photo[-1]
file = await photo.get_file()
file_path = f"temp_{chat_id}.jpg"
await file.download_to_drive(file_path)
with open(file_path, 'rb') as f:
            image_bytes = f.read()
            image_base64 = base64.b64encode(image_bytes).decode('utf-8')

Параллельно была решена проблема оформления программного кода. Я разработал функцию, которая оборачивает блоки кода в HTML-теги <pre><code> для корректного отображения моноширинного шрифта в Telegram.

pattern = r"(?:\w+)?\n(.*?)"
def repl(math):
code = math.group(1)
escaped = html.escape(code)
return f"
{escaped}

"

re.sub(pattern, repl, text, flags=re.DOTALL)

Также появилась специализированная категория переводчиков, лидером которой стала TranslateGemma, обеспечивающая высокоточное лингвистическое преобразование фраз.

Чтобы гарантировать корректное форматирование при использовании HTML, я реализовал конвертер, который переводит Markdown-разметку (решетки для заголовков, звездочки для жирности) в соответствующие HTML-теги.

text = fix_latex(text)
text = re.sub(r'### (.+?)(?:\n|$)', r'\1\n', text)
text = re.sub(r'## (.+?)(?:\n|$)', r'\1\n', text)
text = re.sub(r'# (.+?)(?:\n|$)', r'\1\n', text)

text = re.sub(r'**(.+?)**', r'\1', text, flags=re.DOTALL)

text = re.sub(r'(?<!*)*(?!*)(.+?)(?<!*)*(?!*)', r'\1', text, flags=re.DOTALL)

text = re.sub(r'^- ', '• ', text, flags=re.MULTILINE)

V1.7: Работа с длинными сообщениями

С ростом «интеллекта» моделей увеличился и объем генерируемых текстов. Ответы начали выходить за пределы стандартного лимита Telegram в 4096 символов. Для решения этой проблемы был внедрен алгоритм автоматического дробления сообщений на части по 4000 знаков с сохранением целостности стриминга.

При разрыве текста разметка HTML часто оставалась незавершенной. Чтобы избежать искажения интерфейса, была написана функция автоматического закрытия тегов.

tags = ["b", "i", "pre", "code"]
for tag in tags:
opens = text.count(f"<{tag}>")
closes = text.count(f"</{tag}>")

if opens > closes:
    text += f"</{tag}>" * (opens - closes)
elif closes > opens:
    text = text.replace(f"</{tag}>", "", closes - opens)</code><div class="code-explainer"><a href="https://sourcecraft.dev/" class="tm-button code-explainer__link" style="visibility: hidden;"><img style="width:14px;height:14px;object-fit:cover;object-position:left;"></a></div></pre><h3>V1.8: Геймификация — мини-игра «Детектив»</h3><p>Для демонстрации творческого потенциала ИИ я добавил игру «Детектив». Нейросеть генерирует уникальный криминальный сюжет в формате JSON (улики, подозреваемые, мотивы), а игрок выступает в роли следователя.</p><div class="floating-image"><figure class="float full-width "><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/df3/db9/aa2/df3db9aa25e6699a648847ec32b974fc.jpg" width="925" height="1280" sizes="(max-width: 780px) 100vw, 50vw" srcset="https://habrastorage.org/r/w780/getpro/habr/upload_files/df3/db9/aa2/df3db9aa25e6699a648847ec32b974fc.jpg 780w,
   https://habrastorage.org/r/w1560/getpro/habr/upload_files/df3/db9/aa2/df3db9aa25e6699a648847ec32b974fc.jpg 781w" loading="lazy" decode="async"></figure><p>Игрок располагает ограниченным количеством вопросов. В геймплей включены специальные способности: «Криминалист» открывает случайную улику, «Журналист» проводит допрос, а «Психолог» составляет портрет личности.</p></div><p>За генерацию сценариев отвечает <strong>qwen2.5:14b-instruct</strong>, что обеспечивает высокую вариативность сюжетов и логическую стройность расследований.</p><h3>V1.8.1: Стабилизация и отказоустойчивость</h3><p>Домашний хостинг сопряжен с рисками обрыва связи или сбоев ПО. Чтобы обеспечить непрерывную работу, я реализовал:</p><ol><li><p><strong>Детальное логирование</strong> для оперативной диагностики ошибок.</p></li><li><p><strong>Механизм автоматического перезапуска</strong> при критическом падении процесса поллинга.</p></li></ol><p><code>if name == "main":<br>     while True:<br>         try:<br>             logging.info("Инициализация...")<br>             asyncio.run(run_bot_polling(app))<br>         except Exception as e:<br>             logging.error(f"Сбой: {e}")<br>             time_module.sleep(5)</code></p><h3>V2.0: Визуальное воображение</h3><p>Финальным крупным обновлением стала интеграция <strong>AUTOMATIC1111</strong> для генерации изображений. Доступны классические и современные модели: <em>SDXL</em>, <em>Stable Diffusion 3 Medium</em>, <em>DreamShaper</em>. Взаимодействие происходит через API WebUI.</p><p><code>payload = { "prompt": prompt, "steps": 25, "width": 1024, "height": 1024, "override_settings": {"sd_model_checkpoint": model_name}}</code></p><figure class="full-width "><img src="https://habrastorage.org/r/w1560/getpro/habr/upload_files/9f2/131/457/9f2131457ab351d89c2ca059ec799228.jpg" width="1115" height="1280" sizes="(max-width: 780px) 100vw, 50vw" srcset="https://habrastorage.org/r/w780/getpro/habr/upload_files/9f2/131/457/9f2131457ab351d89c2ca059ec799228.jpg 780w,
   https://habrastorage.org/r/w1560/getpro/habr/upload_files/9f2/131/457/9f2131457ab351d89c2ca059ec799228.jpg 781w" loading="lazy" decode="async"></figure><p>Пользователь отправляет команду <code>/draw [описание]</code>, бот генерирует изображение и отправляет результат в чат.</p><p><strong>Резюме:</strong></p><p>Создание собственного Telegram-бота на базе локальных нейросетей — это не только технически осуществимый, но и крайне увлекательный процесс. Он не требует колоссальных затрат, если у вас уже есть производительный ПК, но при этом предоставляет полную свободу в реализации функций: от текстового ассистента до полноценной игровой платформы и генератора графики.</p></div></div>
 

Источник

Читайте также