Files
boltun-ai/README.md
yunogasai b615518f22 Контекст диалога на Mastra Memory вместо своей таблицы
- Контекст диалога хранит 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>
2026-09-24 20:55:06 +02:00

82 lines
9.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Boltun AI
Telegram-бот для групповых чатов с AI-личностями. Стек: Node.js, TypeScript, [grammY](https://grammy.dev), [Mastra](https://mastra.ai) (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](https://mastra.ai/docs/memory/message-history) (`memory.db` рядом с `DB_PATH`). Подкладывается в каждый запрос, поэтому можно уточнять «а подробнее?». Самые старые реплики Mastra срезает, когда промпт не влезает в `CONTEXT_TOKEN_LIMIT`; результаты инструментов из прошлых ответов (`ToolCallFilter`) и фото в историю не попадают. `/clear` сбрасывает только контекст.
- *Архив чата* — вся переписка (живая и импортированная). Модель читает его инструментами по необходимости; `/clear` его не трогает.
- `/context` — что сейчас в контексте и в архиве, ID чата, видит ли бот всю переписку; `/import` — загрузить старую переписку из экспорта (см. ниже).
- **Личности**: задаются в коде, из чата не редактируются. Сейчас одна — `boltun` (Dirty D).
- `/personas` — список и выбор кнопками
- `/persona <id>` — выбрать
## Импорт старой переписки
Telegram не отдаёт ботам сообщения, написанные до их добавления, — их можно загрузить из экспорта.
1. Telegram Desktop → меню чата → «Экспорт истории чата». Сними галочки с фото, видео и файлов, формат — **«Машиночитаемый JSON»**.
2. Пришли боту `result.json` с подписью `/import` — прямо в этот чат или боту в личку (в личке бот сам поймёт, какой это чат). Можно и ответить `/import` на уже отправленный файл.
3. Бот ответит, сколько сообщений и участников добавил.
Загружаются только сообщения с текстом: стикеры, голосовые, опросы и медиа без подписи пропускаются. 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) **до** добавления в группу или сделайте бота админом группы.
## Запуск локально
```bash
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 — домен и порты не нужны, достаточно одной реплики.
1. Создать проект и приложение, источник — этот git-репозиторий, ветка `main`.
2. Build Type — `Dockerfile`.
3. Environment — переменные из таблицы выше.
4. Volumes — volume, смонтированный в `/app/data` (там SQLite-база, иначе история и личности пропадут при передеплое).
5. Deploy.