Полноценный бот для генерации Anki‑колод из учебных материалов с помощью Telegram‑бота и Mini App. Проект включает веб‑интерфейс, API, асинхронного воркера, хранение файлов и AI‑пайплайн генерации.
- Загрузка PDF/DOCX/TXT и генерация Anki‑колод.
- Асинхронная обработка через Celery + Redis.
- LLM‑генерация через OpenRouter/Gemini API.
- Эмбеддинги (Gemini) и RAG‑retrieval для «привязки» к источнику.
- Дедупликация вопросов и базовая валидация качества.
- Сохранение метрик генерации: latency, throughput, stage timings, dedupe и др.
- Экспорт в
.apkg, готовый к импорту в Anki.
![]() Telegram entry |
![]() Topics |
![]() Empty files |
![]() Files with PDF |
![]() Generation params |
![]() Generation progress |
┌──────────────────────────────────────────────────────────────────────────┐
│ USER / INTERFACE │
└──────────────────────────────────────────────────────────────────────────┘
┌──────────────┐ ┌────────────────────┐ ┌──────────────────────┐
│ Telegram Bot │ ──▶ │ Web Mini App (UI) │ ──▶ │ API (FastAPI) │
└──────────────┘ └────────────────────┘ └─────────┬────────────┘
│
▼
┌──────────────────────┐
│ Queue (Redis) │
└─────────┬────────────┘
▼
┌──────────────────────────────────────────────────────────────────────────┐
│ WORKER (Celery) │
└──────────────────────────────────────────────────────────────────────────┘
┌─────────────────┐ ┌───────────────┐ ┌───────────────────┐
│ Extraction │→ │ Chunking │→ │ Normalize Chunks │
└─────────────────┘ └───────────────┘ └─────────┬─────────┘
│
▼
┌───────────────────────────────────────────────────────────────┐
│ Index / Retrieval (RAG) │
│ • Vector DB (Chroma + Embeddings) │
│ • Fallback: Lexical retrieval │
└───────────────┬──────────────────────────────────────────────┘
│
▼
┌───────────────────────┐ ┌───────────────────────────┐
│ Planner (Topics) │ ─────▶ │ Evidence Builder │
│ LLM: short topic list │ │ top‑k chunks per topic │
└──────────┬────────────┘ └──────────────┬────────────┘
│ │
└────────────────────────────────────┘
▼
┌────────────────────┐
│ QGen (LLM) │
│ open / mcq / tf │
└──────────┬─────────┘
▼
┌────────────────────┐
│ Verify / Tagging │
│ sources / unverified│
└──────────┬─────────┘
▼
┌────────────────────┐
│ Mix + Dedupe │
│ simhash + jaccard │
└──────────┬─────────┘
▼
┌────────────────────┐
│ Export (.apkg) │
└──────────┬─────────┘
▼
┌──────────────────────────────────────────────────────────────────────────┐
│ STORAGE │
│ Encrypted files + Exports │
└──────────────────────────────────────────────────────────────────────────┘
▼
┌──────────────────────────────────────────────────────────────────────────┐
│ STATUS TO USER │
│ Worker → API → Web App → Telegram User │
└──────────────────────────────────────────────────────────────────────────┘
Сервисы
bot/— aiogram‑бот, открывает Mini App и управляет доступом.web/— React Mini App UI (Telegram WebApp SDK).api/— FastAPI backend (auth, темы, файлы, запуск задач).worker/— Celery‑воркер (извлечение, генерация, экспорт).infra/— Dockerfiles, compose, миграции.data//storage/— зашифрованное хранение файлов и экспортов.
┌───────────────┐ ┌───────────────┐ ┌───────────────────┐ ┌─────────────────────┐
│ Extraction │→ │ Chunking │→ │ Normalize Chunks │→ │ Index / Retrieval │
└───────────────┘ └───────────────┘ └───────────────────┘ │ (RAG + fallback) │
└──────────┬──────────┘
│
▼
┌───────────────────────┐ ┌──────────────────────────┐ ┌──────────────────────┐
│ Planner (Topics) │→→ │ Evidence Builder │→→ │ QGen (LLM) │
│ short topic list │ │ top‑k chunks per topic │ │ open / mcq / tf │
└───────────────────────┘ └──────────────────────────┘ └──────────┬───────────┘
▼
┌──────────────────────┐
│ Verify / Tagging │
│ sources / unverified │
└──────────┬───────────┘
▼
┌──────────────────────┐
│ Mix + Dedupe │
│ simhash + jaccard │
└──────────┬───────────┘
▼
┌──────────────────────┐
│ Export (.apkg) │
└──────────────────────┘
Ниже — как устроена генерация и зачем нужен каждый шаг.
Что делает: распаковывает входные файлы (PDF/DOCX/TXT) в чистый текст.
Почему так: нормализация входа нужна, чтобы работать с разными форматами одинаково.
Что делает: режет текст на смысловые куски ~250–450 слов, overlap ~60 слов.
Почему так: LLM лучше работает с небольшими фрагментами; overlap сохраняет контекст на стыках и уменьшает потери смысла.
Что делает: очищает и унифицирует фрагменты (обрезка пустых, нормализация пробелов).
Почему так: уменьшает шум в retrieval и повышает стабильность генерации.
Что делает: строит векторный индекс (Chroma + embeddings); если embeddings недоступны — fallback на лексическое извлечение.
Почему так: retrieval подбирает релевантные фрагменты под каждую тему, снижая «галлюцинации».
Что делает: LLM строит список ключевых тем для каждого файла.
Почему так: обеспечивает равномерное покрытие и снижает повторения.
Что делает: собирает короткие контекстные блоки для каждой темы.
Почему так: QGen получает только релевантные факты, а не весь документ.
Что делает: генерирует open/mcq/tf‑вопросы + ответы + источники.
Почему так: стандартизированный формат облегчает экспорт в Anki и последующую проверку.
Что делает: отмечает вопросы без источников (например, тегом unverified).
Почему так: помогает отсекать «сомнительные» вопросы при финальной сборке.
Что делает: объединяет вопросы с разных файлов и удаляет повторы (simhash + Jaccard по токенам/символьным n‑граммам).
Почему так: экономит место в колоде и повышает разнообразие карточек.
Что делает: формирует .apkg через genanki.
Почему так: Anki‑колода сразу готова к импорту.
cp .env.example .env
# заполнить обязательные секреты:
# BOT_TOKEN, JWT_SECRET, ENCRYPTION_KEY_BASE64
# задай уникальное имя стека для этого проекта (важно при нескольких compose-проектах на одном сервере)
# STACK_NAME=tg_anki_prod
docker compose up -d --build
# или разово без правки .env:
# STACK_NAME=tg_anki_prod docker compose up -d --buildМинимально нужны:
STACK_NAME— уникальное имя docker compose-стека (например,tg_anki_prod)BOT_TOKEN— токен Telegram‑ботаJWT_SECRET— секрет для JWTENCRYPTION_KEY_BASE64— 32 байта (AES‑GCM)ADMIN_TELEGRAM_IDS— список Telegram ID админов через запятую (например,123456789)
Дополнительно:
LLM_PROVIDER=openrouter— генерация через OpenRouter APIOPENROUTER_API_KEY— ключ OpenRouter (если выбранopenrouter)OPENROUTER_MODEL— модель OpenRouter, напримерqwen/qwen3-8b:freeGEMINI_API_KEY— только если используешьLLM_PROVIDER=geminiRAG_USE_EMBEDDINGS=trueвключает embeddingsEMBEDDING_PROVIDER=gemini(рекомендуется для production)RAG_REUSE_VECTOR_STORE=true— переиспользовать существующий Chroma-индекс (сильно ускоряет повторные генерации)JOB_EXTRACT_CONCURRENCY=4— параллелизм извлечения текстаJOB_CHUNK_CONCURRENCY=4— параллелизм chunking
В Mini App для админа доступна кнопка Метрики на главной странице (только если ваш telegram_id есть в ADMIN_TELEGRAM_IDS).
На странице метрик можно посчитать отчёт, скачать JSON/MD и отправить отчёт в Telegram.
Для контрольного end-to-end прогона (создать тему -> загрузить файл -> запустить генерацию -> дождаться результата -> собрать метрики):
uv run python scripts/generation_benchmark_run.py \
--api-base-url https://your-api.example.com \
--bot-token "$BOT_TOKEN" \
--runs 3 \
--questions 20 \
--json-out results/benchmark-summary.jsonОпционально можно передать свои входные файлы (повтори флаг несколько раз):
uv run python scripts/generation_benchmark_run.py \
--api-base-url https://your-api.example.com \
--bot-token "$BOT_TOKEN" \
--file ./samples/doc1.txt \
--file ./samples/doc2.txtПосле нескольких запусков генерации можно собрать агрегированный отчёт:
uv run python scripts/generation_metrics_report.py --limit 50 \
--json-out results/metrics-summary.json \
--md-out results/metrics-summary.mdСкрипт считает core KPI для продукта:
completion_rate,failed_rate,rate_limited_failure_rateTTV (e2e_seconds) p50/p95- нормализованную скорость:
sec_per_question,sec_per_1k_chars - качество:
quality_score,source_coverage_ratio,duplicate_rate - устойчивость:
retries_per_job,failed_calls_per_job
Пример реального агрегированного отчёта (2026-03-22, 22 задач, файл отчёта от Telegram):
- Среднее время до готового результата (
ttv_mean):42.463s - Среднее время на одну карточку (
sec_per_question_mean):2.123s - Среднее время на 1000 символов входного текста (
sec_per_1k_chars_mean):0.557s - Оценка качества карточек (
quality_score):59.800Эта метрика собирается из четырёх факторов: сколько карточек удалось сгенерировать от запрошенного количества, у скольких карточек есть источник, насколько источники покрывают входные файлы и насколько вопросы уникальны. - Доля карточек с привязкой к источнику (
source_coverage_ratio):1.000 - Доля дубликатов в генерации (
duplicate_rate):0.000
Эти цифры отражают текущее фактическое состояние системы, а не целевые значения.
- Файлы хранятся зашифрованно (AES‑GCM).
- При проблемах с embeddings включается лексический fallback.
- Отмена/перезапуск генерации автоматически завершает старые задачи.





