Хочу представить вашему вниманию кейс по разработке 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 активирует статус «печатает…», а благодаря механизму стриминга ответ формируется постепенно, токен за токеном, прямо на глазах у пользователя.
На этом этапе возникла неожиданная сложность: модель часто использовала разметку LaTeX (например, Попытки решить проблему через системный промт не увенчались успехом — ИИ игнорировал инструкции. Пришлось внедрить функцию постобработки на базе регулярных выражений для очистки текста. В результате математические выкладки стали выглядеть презентабельно. Важное уточнение: В режиме стриминга процесс очистки мог приводить к тому, что текст сообщения не менялся между итерациями, провоцируя ошибку API Telegram. Я добавил условие: Ключевым обновлением стало внедрение истории диалогов. Без памяти бот не способен поддерживать осмысленную беседу. Для надежного хранения данных вместо нестабильной оперативной памяти я использовал SQLite. Теперь для каждого пользователя ( Также была решена проблема с отображением Markdown. Несмотря на то, что модель выделяла текст жирным, Telegram игнорировал разметку до момента явного указания Дополнительно в системный промт была добавлена директива отвечать только на русском языке, так как китайские модели иногда переключались на родной язык без видимых причин. Для удобства переключения между различными ИИ-движками я реализовал систему инлайн-кнопок. Каждая кнопка передает идентификатор модели в переменную В библиотеку были включены: llama3:8b, mistral:7b, deepseek-coder:6.7b, codellama:7b, qwen2.5:7b-instruct, phi3:mini и другие. Технический нюанс: Интерактивные элементы заработали корректно только после явного указания Для минимизации задержек при первом обращении к новой модели я внедрил этап «прогрева». Боту отправляется скрытый микро-запрос «Привет», что заставляет систему заранее загрузить веса модели в память видеокарты. Я структурировал каталог моделей, разбив их по категориям, и добавил визуальную индикацию текущей активной модели с помощью эмодзи «галочки». Для обнуления контекста была создана команда очистки истории: Поскольку процесс выгрузки одной модели и загрузки другой занимает время, я добавил информационное сообщение «Загружаю модель...», чтобы пользователь понимал статус операции. Для повышения отзывчивости системы было внедрено TTL-кеширование. Модели теперь остаются в памяти на определенный срок, что позволяет мгновенно обслуживать запросы разных пользователей, работающих с одним и тем же ИИ-ядром. Первым шагом к мультимодальности стала интеграция glm-ocr — модели, способной извлекать печатный текст из изображений. Теперь при получении фото бот автоматически направляет его на обработку визуальным энкодером. Параллельно была решена проблема оформления программного кода. Я разработал функцию, которая оборачивает блоки кода в HTML-теги " re.sub(pattern, repl, text, flags=re.DOTALL)await context.bot.send_chat_action(chat_id=chat_id, action='typing')
V1.2.1: Коррекция математических формул
\( ... \) или \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('}', '')
if last_sent_text != new_text: message.edit_text(new_text).V1.3: Контекстная память и оформление Markdown
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) формируется персонализированная цепочка сообщений.parse_mode="Markdown" в методе edit_text.
V1.5: Гибкий выбор нейросетевых моделей
current_model, которая затем подставляется в запрос к API Ollama.async with session.post(
OLLAMA_URL,
json={
"model": current_model,
allowed_updates=["message", "callback_query"] в настройках поллинга бота.V1.6: Мультимодальность, код и кэширование
cursor.execute("""
DELETE
FROM messages
WHERE chat_id = ?
""", (chat_id,))

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')<pre><code> для корректного отображения моноширинного шрифта в Telegram.pattern = r"(?:\w+)?\n(.*?)"
def repl(math):
code = math.group(1)
escaped = html.escape(code)
return f"{escaped}

Также появилась специализированная категория переводчиков, лидером которой стала 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>


