Как собрать Telegram-бота для чата с помощью нейросети

Мы делали не просто Telegram-бота с ответами по API. Мы собирали бота для чата, который общается в заданной манере, помнит недавний контекст и включается только в нужных ситуациях.

Как собрать Telegram-бота для чата с помощью нейросети

Нам был нужен бот с характером. Конкретно - 40-летний скуф-эксперт по нейросетям. Язвительный. Колкий. Без мата. Без прямой грубости. Прям как маскот телеграм-канала Нейроскуф.

Для этого мы использовали:

  • Python.
  • aiogram.
  • Cerebras API.
  • python-dotenv.
  • Gemini как инструмент разработки и сборки логики.

Gemini помогал нам писать и допиливать код. Сам бот потом работал уже на подключенной модели через API.

С чего мы начали

Сначала мы не писали код. Сначала мы сформулировали задачу.

Нужно было определить:

  • где бот отвечает всегда,
  • где бот молчит,
  • как он должен говорить,
  • сколько контекста он должен помнить,
  • как его звать в группе,
  • как не превратить его в спам-машину.

Без этого дальше идти бессмысленно. Бот без правил быстро становится либо скучным, либо раздражающим.

Почему мы делали это через Gemini

Мы собирали бота через Gemini поэтапно.

Сначала дали общее ТЗ. Потом получили каркас. Потом отдельно уточнили поведение в группах. Потом память. Потом триггерное слово. Потом характер. Потом правили формулировки и чистили лишнее.

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

Отдельно аккаунт не покупали сделали все на базе удобной платформы SYNTX (а для моих читателей по промокоду NEIROSKUF - вы получите еще и скидку в 15% на любой тариф).

Вот базовые промпты, с которых можно начать самостоятельно.

Шаг 1. Попросили собрать базовый каркас

Сначала мы дали Gemini самый простой запрос: собрать Telegram-бота на Python с aiogram и подключением внешней модели через API.

Собери базовый Telegram-бот на Python с использованием aiogram. Что нужно: - бот должен отвечать на текстовые сообщения - подключить внешнюю LLM через API - код должен быть рабочим и цельным

На этом этапе нам был нужен только каркас, чтобы проверить - насколько нейрость поняла нашу простую задачу.

Шаг 2. Добавили правила ответа в личке и группе

Когда каркас уже был, мы отдельно уточнили, где бот должен отвечать, а где молчать.

Доработай логику Telegram-бота. Нужно: - в личных сообщениях бот отвечает всегда - в группах бот отвечает только если: 1) его упомянули по username 2) это reply на его сообщение Покажи, как изменить py целиком, без сокращений.

Шаг 3. Добавили кодовое слово

Потом отдельно ввели скрытый триггер для чата.

Добавь в Telegram-бота еще одно условие ответа в группе: - если в тексте есть кодовое слово @skuf, бот тоже должен отвечать Сделай проверку без учета регистра. Покажи полный обновленный код.

Шаг 4. Добавили память диалога

Следующим шагом мы сделали короткую память, чтобы бот не отвечал на каждое сообщение как с чистого листа.

Добавь в Telegram-бота память последних 20 сообщений для каждого чата. Требования: - хранить историю отдельно для каждого chat_id - пока можно хранить в обычном словаре Python - перед отправкой запроса в модель передавать system prompt, историю и текущее сообщение пользователя - покажи полный обновленный main.py

Шаг 5. Отдельно задали характер

Только после этого мы начали настраивать личность бота.

Помоги сделать system prompt для Telegram-бота. Нужен персонаж: - мужчина 40 лет - эксперт по нейросетям, автоматизации и Telegram-ботам - язвительный, колкий, местами токсичный - без мата - без прямой грубости - без ремарок вроде "вздыхает", "усмехается", "*смотрит*" - не клоун - отвечает по делу даже на слабые вопросы Сделай готовый CHARACTER_PROMPT для .env.

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

Какой стек мы выбрали

Python взяли как базу. Aiogram - для работы с Telegram. Cerebras - для генерации ответов через внешнюю модель. Dotenv - для хранения настроек и ключей.

Для первой версии этого хватило.

Как выглядел проект

На старте не нужна сложная архитектура. Сначала нужно получить работающего бота. Потом уже дробить код на модули.

tg_character_bot/ ├─ venv/ ├─ .env ├─ requirements.txt └─ main.py

Потом активировали окружение.

mkdir tg_character_bot cd tg_character_bot python -m venv venv

На Windows:

venv\Scripts\activate

После этого установили зависимости:

pip install aiogram cerebras_cloud_sdk python-dotenv

Можно сразу зафиксировать их в requirements.txt:

aiogram>=3.0.0 cerebras_cloud_sdk>=1.0.0 python-dotenv>=1.0.0

Что мы вынесли в .env

Мы не хранили токены в коде. Все основные настройки вынесли в .env.

Пример:

BOT_TOKEN=ваш_telegram_token CEREBRAS_API_KEY=ваш_cerebras_key BOT_USERNAME=your_bot_username TRIGGER_WORD=@skuf CHARACTER_PROMPT=Тебя зовут Платон Щукин. Тебе 40 лет. Ты эксперт по нейросетям, автоматизации и Telegram-ботам. Ты язвительный, снисходительный, иногда колкий, но всегда отвечаешь по делу. Не материшься. Не грубишь в лоб. Не используешь ремарки. Не изображаешь актера. Если вопрос слабый, можешь это показать интонацией, но все равно даешь полезный ответ.

В main.py собрали базовые объекты:

import os from collections import defaultdict from aiogram import Bot, Dispatcher, F from aiogram.types import Message from aiogram.enums import ChatType from dotenv import load_dotenv from cerebras.cloud.sdk import Cerebras load_dotenv() BOT_TOKEN = os.getenv("BOT_TOKEN") CEREBRAS_API_KEY = os.getenv("CEREBRAS_API_KEY") BOT_USERNAME = os.getenv("BOT_USERNAME", "").lower().replace("@", "") TRIGGER_WORD = os.getenv("TRIGGER_WORD", "@skuf").lower() CHARACTER_PROMPT = os.getenv("CHARACTER_PROMPT", "") bot = Bot(token=BOT_TOKEN) dp = Dispatcher() client = Cerebras(api_key=CEREBRAS_API_KEY) chat_histories = defaultdict(list) MAX_HISTORY = 20

Как мы сделали память диалога

Боту нужен контекст. Иначе он отвечает отдельно на каждую реплику и не держит разговор.

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

Вот функция:

def add_to_history(chat_id: int, role: str, content: str): chat_histories[chat_id].append({"role": role, "content": content}) chat_histories[chat_id] = chat_histories[chat_id][-MAX_HISTORY:]

Что это дает:

  • бот помнит последние сообщения,
  • у каждого чата своя история,
  • контекст не разрастается бесконечно.

Задаем правила ответа

Это один из ключевых блоков. Бот не должен отвечать в группе на все подряд.

Мы сделали триггеры.

В личке он отвечает всегда.

В группе - только в трех случаях:

  • если его упомянули по username
  • если пользователь ответил на его сообщение
  • если в тексте есть кодовое слово @skuf

Функция получилась такой:

def should_reply(message: Message) -> bool: text = (message.text or "").lower() if message.chat.type == ChatType.PRIVATE: return True if f"@{BOT_USERNAME}" in text: return True if TRIGGER_WORD in text: return True if message.reply_to_message and message.reply_to_message.from_user: if message.reply_to_message.from_user.id == bot.id: return True return False

Собрали сообщения для модели

Когда фильтр сработал, нужно было собрать данные для запроса к модели.

Мы передавали три части:

  • system prompt,
  • недавнюю историю чата,
  • текущее сообщение пользователя.

Функция выглядела так:

def build_messages(chat_id: int, user_text: str): messages = [{"role": "system", "content": CHARACTER_PROMPT}] messages.extend(chat_histories[chat_id]) messages.append({"role": "user", "content": user_text}) return messages

Этого хватало, чтобы бот не терял нить разговора.

Как мы подключили генерацию ответа

Дальше мы сделали отдельную функцию для вызова модели:

def generate_answer(chat_id: int, user_text: str) -> str: messages = build_messages(chat_id, user_text) response = client.chat.completions.create( model="llama3.1-8b", messages=messages, temperature=0.9, max_completion_tokens=300, ) return response.choices[0].message.content.strip()

Здесь у нас три параметра:

  • model - выбранная модель,
  • temperature - степень вариативности,
  • max_completion_tokens - длина ответа.

Для чата длинные ответы обычно не нужны. Поэтому мы сразу ограничили генерацию.

Как выглядел обработчик сообщений

После этого мы связали все вместе в одном обработчике:

@dp.message(F.text) async def handle_message(message: Message): if not should_reply(message): return user_text = message.text.strip() chat_id = message.chat.id add_to_history(chat_id, "user", user_text) try: answer = generate_answer(chat_id, user_text) except Exception as e: answer = f"Сломалось. Ошибка: {e}" add_to_history(chat_id, "assistant", answer) await message.answer(answer)

Этот блок делает весь основной цикл:

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

Запустили бота

python main.py

И... ничего.

Почему бот сначала не видел @skuf в группе

Это отдельный момент. Кодовое слово не заработало сразу. Причина была не в коде.

Проблема сидела в настройках Telegram-бота. По умолчанию бот в группе может не видеть часть сообщений. Из-за этого он просто не получает текст с вашим триггером.

Мы решили это так:

  1. зашли в @BotFather,
  2. выбрали бота,
  3. открыли Bot Settings,
  4. открыли Group Privacy,
  5. отключили privacy mode,
  6. удалили бота из группы,
  7. добавили его заново.

После этого бот начал видеть обычные сообщения в группе и реагировать на @skuf.

Как собрать Telegram-бота для чата с помощью нейросети

Почему мы выбрали Cerebras

Мы выбрали Cerebras, потому что у него есть бесплатный тариф с лимитами, которых хватает для запуска и тестирования Telegram-бота. Нам не нужно было сразу платить за инференс, поднимать свою модель или собирать отдельную инфраструктуру. Мы хотели быстро проверить механику: ответы в личке, работу в группе, триггерное слово, память диалога и характер. Для этого бесплатных лимитов Cerebras оказалось достаточно.

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

3
2
Начать дискуссию