- Контекст диалога хранит Mastra Memory (LibSQL, memory.db рядом с DB_PATH), тред на чат - Бюджет истории — messageHistory.maxTokens (CONTEXT_TOKEN_LIMIT, по умолчанию 12000) - ToolCallFilter: результаты инструментов не попадают в прошлые реплики, внутри ответа — только свежий шаг - Системный промпт стабилен для всего чата (кешируется провайдером); автор и упоминания — в <turn author_name> - Фото передаются через context и не сохраняются в памяти - maxSteps и maxOutputTokens (ANSWER_TOKEN_LIMIT) — в defaultOptions агента - /context и /clear работают через Memory recall/deleteThread - Удалены свои dialog_messages, dialog-repository и dialog-service Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9.5 KiB
Boltun AI
Telegram-бот для групповых чатов с AI-личностями. Стек: Node.js, TypeScript, grammY, Mastra (OpenRouter или DeepSeek напрямую), SQLite.
Возможности
- Отвечает только администраторам из
ADMIN_IDS, остальных молча игнорирует. - Вопросы:
/ask <вопрос>, упоминание@бота, ответ на сообщение бота, в личке — любое сообщение. - Контекст из чата: бот сохраняет сообщения чата в SQLite (последние
HISTORY_LIMITна чат). Попросите «посмотри последние 100 сообщений и перескажи» — агент сам вызовет инструментread_chat_messagesи загрузит переписку в контекст. Если задать/askответом на чьё-то сообщение, оно тоже попадёт в контекст. - Участники: бот запоминает имя, фамилию и ник каждого, кто пишет в чат (и тех, кого упомянули по имени-ссылке). Можно спрашивать «что писал @ivan?», «что думает Иван про выручку?», «кто тут самый активный?» — агент найдёт человека по нику или имени (инструменты
list_chat_members,read_member_messages) и обращается к людям по имени. - Фото: пришли фото с подписью, где упомянут бот (в личке — просто фото), или ответь на фото через
/ask/ упоминание — модель посмотрит картинку. Нужна модель со зрением (Gemini через OpenRouter по умолчанию); DeepSeek фото не видит и честно об этом скажет. - Память — две части:
- Контекст диалога — вопросы к боту и его ответы в этом чате, хранит Mastra Memory (
memory.dbрядом сDB_PATH). Подкладывается в каждый запрос, поэтому можно уточнять «а подробнее?». Самые старые реплики Mastra срезает, когда промпт не влезает вCONTEXT_TOKEN_LIMIT; результаты инструментов из прошлых ответов (ToolCallFilter) и фото в историю не попадают./clearсбрасывает только контекст. - Архив чата — вся переписка (живая и импортированная). Модель читает его инструментами по необходимости;
/clearего не трогает. /context— что сейчас в контексте и в архиве, ID чата, видит ли бот всю переписку;/import— загрузить старую переписку из экспорта (см. ниже).
- Контекст диалога — вопросы к боту и его ответы в этом чате, хранит Mastra Memory (
- Личности: задаются в коде, из чата не редактируются. Сейчас одна —
boltun(Dirty D)./personas— список и выбор кнопками/persona <id>— выбрать
Импорт старой переписки
Telegram не отдаёт ботам сообщения, написанные до их добавления, — их можно загрузить из экспорта.
- Telegram Desktop → меню чата → «Экспорт истории чата». Сними галочки с фото, видео и файлов, формат — «Машиночитаемый JSON».
- Пришли боту
result.jsonс подписью/import— прямо в этот чат или боту в личку (в личке бот сам поймёт, какой это чат). Можно и ответить/importна уже отправленный файл. - Бот ответит, сколько сообщений и участников добавил.
Загружаются только сообщения с текстом: стикеры, голосовые, опросы и медиа без подписи пропускаются. HTML-экспорт не подходит — в нём нет ID авторов.
- Импорт идемпотентен: повторная загрузка ничего не дублирует, уже сохранённые ботом сообщения и ники не перезаписываются.
- Хранится не больше
HISTORY_LIMITпоследних сообщений на чат — для большой истории подними лимит. - Ников в экспорте нет, только имена: участники из экспорта находятся по имени, ник подтянется, когда человек напишет при боте.
- Файл больше 20 МБ бот скачать не может (ограничение Bot API). Тогда положи его в volume (
/app/data) и запусти в контейнереnode dist/scripts/import-history.js /app/data/result.json(локально —pnpm import-history:dev result.json).
Промпты и личности
prompts/system.md— общие правила бота (формат ответа, инструменты). Читается при старте.prompts/personas/<id>.md— промпт личности; список личностей (id и название) — вsrc/constants/personas.ts.pnpm seedсинхронизирует личности с БД (добавляет, обновляет, удаляет лишние). В Docker seed выполняется при каждом старте контейнера, локально — вpnpm dev.
Поменял промпт — перезапусти бота (или pnpm seed + перезапуск).
Telegram Bot API не отдаёт историю чата задним числом: бот видит только сообщения, пришедшие после его добавления. Чтобы он видел все сообщения группы, отключите privacy mode в @BotFather (
/setprivacy→ Disable) до добавления в группу или сделайте бота админом группы.
Запуск локально
nvm use # Node 24
pnpm install
cp .env.example .env # заполнить BOT_TOKEN, ADMIN_IDS и OPENROUTER_API_KEY (или DEEPSEEK_API_KEY)
pnpm dev
Свой Telegram ID можно узнать, написав боту /whoami в личку.
Переменные окружения
| Переменная | Обязательна | Описание |
|---|---|---|
BOT_TOKEN |
да | токен от @BotFather |
ADMIN_IDS |
да | Telegram ID админов через запятую |
OPENROUTER_API_KEY |
да* | ключ OpenRouter; модель по умолчанию — openrouter/google/gemini-3.8-flash (видит фото) |
DEEPSEEK_API_KEY |
да* | ключ DeepSeek напрямую (api.deepseek.com); используется по умолчанию, если ключа OpenRouter нет — модель deepseek/deepseek-flash, только текст |
AI_MODEL |
нет | явная модель Mastra model router <провайдер>/<модель>, например deepseek/deepseek-v4-pro |
AI_IMAGE_INPUT |
нет | true/false — отправлять ли модели фото. Для моделей по умолчанию выставляется само (Gemini — да, DeepSeek — нет); для своей AI_MODEL по умолчанию false |
* нужен ключ того провайдера, чья модель выбрана; бот проверяет это при старте.
| DB_PATH | нет | путь к SQLite, по умолчанию data/bot.db (в Docker — /app/data/bot.db) |
| HISTORY_LIMIT | нет | сколько сообщений на чат хранить в архиве, по умолчанию 5000 |
| CONTEXT_TOKEN_LIMIT | нет | бюджет токенов на весь промпт (правила, история диалога, вопрос); сверх него срезаются самые старые реплики. По умолчанию 12000 |
| ANSWER_TOKEN_LIMIT | нет | максимальная длина ответа в токенах, по умолчанию 1500 |
Деплой (Dokploy)
Приложение собирается из Dockerfile (Build Type: Dockerfile). Бот работает через long polling — домен и порты не нужны, достаточно одной реплики.
- Создать проект и приложение, источник — этот git-репозиторий, ветка
main. - Build Type —
Dockerfile. - Environment — переменные из таблицы выше.
- Volumes — volume, смонтированный в
/app/data(там SQLite-база, иначе история и личности пропадут при передеплое). - Deploy.