Стек нарочно простой: Python-бот на Telegram Bot API (polling, без вебхуков), SQLite для всего состояния, Docker Compose для развёртывания на домашнем сервере.
Главный принцип архитектуры - разделение вероятностного и детерминированного. LLM (OpenAI Responses API, structured output) отвечает только за то, что действительно требует понимания языка: разобрать свободный русский текст в строгую схему (Pydantic), сформулировать похвалу, добавить недельному отчёту немного «человечности», иногда придумать новую тему раунда. Модель никогда не считает баллы, не хранит состояние и не имеет права отрицать то, что уже сохранено в базе - при обычном разговоре ей передаётся точный контекст из SQLite, и по нему она обязана сверяться.
Вся арифметика баллов живёт в одном модуле без единого обращения к LLM. Она детерминирована и покрыта тестами, а документация формул синхронизирована с кодом как источник истины.
Из инженерных деталей: обработка Telegram update идемпотентна по update_id, чтобы повторная доставка не начисляла баллы дважды; частичные отчёты - это upsert, неназванные поля не затираются; запись в уже закрытую неделю блокируется на уровне сервиса; плановые рассылки (утро/вечер/бэкап) идемпотентны через таблицу отправленных сообщений, что переживает рестарт контейнера.
Ежедневный бэкап SQLite с ретеншеном настроен отдельной джобой.
Ну, типа все 🙂
Главный принцип архитектуры - разделение вероятностного и детерминированного. LLM (OpenAI Responses API, structured output) отвечает только за то, что действительно требует понимания языка: разобрать свободный русский текст в строгую схему (Pydantic), сформулировать похвалу, добавить недельному отчёту немного «человечности», иногда придумать новую тему раунда. Модель никогда не считает баллы, не хранит состояние и не имеет права отрицать то, что уже сохранено в базе - при обычном разговоре ей передаётся точный контекст из SQLite, и по нему она обязана сверяться.
Вся арифметика баллов живёт в одном модуле без единого обращения к LLM. Она детерминирована и покрыта тестами, а документация формул синхронизирована с кодом как источник истины.
Из инженерных деталей: обработка Telegram update идемпотентна по update_id, чтобы повторная доставка не начисляла баллы дважды; частичные отчёты - это upsert, неназванные поля не затираются; запись в уже закрытую неделю блокируется на уровне сервиса; плановые рассылки (утро/вечер/бэкап) идемпотентны через таблицу отправленных сообщений, что переживает рестарт контейнера.
Ежедневный бэкап SQLite с ретеншеном настроен отдельной джобой.
Ну, типа все 🙂