Архитектура Люми — полный срез на 18 апреля 2026
Зафиксированный технический обзор после субботних speed-fix деплоев (семантический кеш + first-token streaming). Дополняет [[lyumi/bot_v3]] — там бэклог и changelog, здесь — цельная картина что и как устроено.
1. Инфраструктура
Hetzner CPX52 в Хельсинки — 12 vCPU AMD, 24GB RAM, 160GB SSD, Ubuntu 24.04, IP 46.62.238.135. Третий апгрейд за проект (CPX32 → CPX42 → CPX52), последний сделан 3 апреля под переиндексацию. При 87 юзерах загрузка CPU редко выше 40%.
Докер: три контейнера — lyumi-hse-bot (основной), lyumi-digest (канал), lyumi-monitor (планируется). Сборка основного через docker compose up -d --build из /opt/lyumi/docker-compose.yml. Волюмы: lyumi-chroma (векторная база), /opt/lyumi/cards (116 JSON-карточек), /opt/lyumi/logs (JSONL-логи + SQLite-кеш), /opt/lyumi/registry_v2.json + /opt/lyumi/registry.json (регистр 4275 документов для /pack). Компоуз-файл — единственный источник правды для маунтов.
2. Путь запроса — 20-шаговый пайплайн
Шаги 1-3. Rate limit (30/день), whitelist check (пустой = all), определение типа (текст / фото / слеш-команда). Слеш-команды уходят в свой handler с шаблонами.
Шаг 4 — семантический кеш (новый, 18 апреля). Нормализация (lowercase + без пунктуации) → SHA-256 → lookup в SQLite. Bypass-паттерны: даты 202[4-9], свежесть (последн, найди, проверь), !expert, слишком короткий/длинный, голый follow-up (подробнее, а почему?). Самодостаточные короткие вопросы (Что такое JSA?) хитаются даже при наличии истории. Hit = ~50мс вместо 22 сек.
Шаг 5 — follow-up detection. Если len(query) < 40 И есть история → помечаем follow-up. Skip классификатор, наследуем пространство от предыдущего запроса, обогащаем контекст (предыдущий вопрос + первые 150 символов ответа Люми + текущий follow-up). Query expansion не применяется.
Шаг 6 — классификатор. Haiku 3.5 определяет одно из 5 пространств + confidence. Явный запрет на multi в промпте. При ошибке fallback → soft+hard.
Шаг 7 — metadata regex. 0мс детекция jurisdiction (KZ / INT) и document_type (npa / standard / book / course). Результат → ChromaDB where-filter + BM25 post-filter.
Шаг 8 — query expansion. Haiku расширяет 2-3 синонимами. HyDE убран из пайплайна (функция сохранена в llm.py).
Шаги 9-11 — параллельный ретривал. asyncio.gather одновременно:
- ChromaDB vector (e5-large, cosine, contextual embeddings)
- Per-collection BM25 (отдельные индексы ~14-53K документов)
- Metadata where-фильтр в обеих ветках
Результаты объединяются через Reciprocal Rank Fusion.
Шаг 12 — Cohere Rerank 4 Fast. Пересортировка, до 1500 символов на документ.
Шаг 13 — Agentic RAG. Если топ score < 0.15 — три попытки: переформулировка через Haiku → снятие metadata-фильтров → расширение на все 5 коллекций.
Шаг 14 — parent-child retrieval. Подтягиваем соседние чанки для полноты.
Шаг 15 — confidence injection. Score → [ВЫСОКАЯ ≥0.5 / СРЕДНЯЯ ≥0.2 / НИЗКАЯ] в промпт.
Шаг 16 — web search trigger. Perplexity Sonar если (а) ключевые слова даты/свежести, ИЛИ (б) weak reranker score < 0.3. Follow-up не триггерит web по слабому retrieval. Доверенные домены (zakon.kz, kadry.mcfr.kz, mybuh.kz, kazpravda.kz, tengrinews.kz, inform.kz, kapital.kz) фетчатся через httpx + BeautifulSoup после Sonar-ответа.
Шаг 17 — роутинг модели. Opus 4 триггеры: расследование, авария, инцидент, RCA, стратегия, BCP, кризис, !expert. Остальное → Sonnet 4. После фикса 16 апреля multi больше не роутит в Opus — экономит $3-4/день.
Шаг 18 — streaming генерация (обновлён 18 апреля). generate_lyumi_response_stream возвращает чанки. Первый edit в Telegram триггерится на первых ≥10 символах (флаг first_edit_done), последующие — каждые 1.5 сек. Раньше все edits ждали 1.5 сек — пользователь видел только ⏳ «на счёт 5-6», теперь «на счёт 2-3».
Шаг 19 — post-processing. Gender fix (regex: Люми о себе в женском роде, к собеседнику в мужском), citation verification (regex → ChromaDB lookup → _strip_citation для ненайденных, см. grounding v1).
Шаг 20 — cache write + logging. Если не bypass — ответ пишется в SQLite. JSONL-лог в /app/logs/queries_YYYY-MM-DD.jsonl. Кнопки 👍/👎 под ответом; 👎 инвалидирует кеш-запись и пишет в фидбек.
3. Хранилища данных
ChromaDB — волюм lyumi-chroma, 169,510 документов в 5 коллекциях: green_space 52K, hard_space 46K, soft_space 36K, emergency_space 20K, health_space 14K. Метаданные: document_type, language, jurisdiction, document_title. Чанки семантические по 1,500 символов, каждый обогащён строкой контекста от Haiku (contextual embeddings).
SQLite семантический кеш — /app/logs/semantic_cache.db. Таблица semantic_cache: query_hash (PK), normalized_query, original_query, response, space, model, created_at, hit_count, last_hit_at. TTL 7 дней, ленивое удаление устаревших, admin-команда /cache для user_id 388321810.
JSONL логи — /app/logs/queries_YYYY-MM-DD.jsonl. Каждая строка — полный контекст запроса для log_analysis.py.
registry_v2.json — 4275 документов, сканированных из ChromaDB. Scoring max(query_confidence, document_confidence). Используется в /pack и в ответах «есть ли такой документ». Volume-маунт обязателен — пересборка контейнера без него = бот без регистра (урок от 17 апреля).
cards/ — 116 JSON-карточек HSE (матрица зрелости, практики, триггеры). Индексируются через indexer.py.
4. LLM-стек через OpenRouter
- Haiku 3.5 — классификатор, query expansion, contextual embeddings, agentic reformulation. $0.001/запрос.
- Sonnet 4 — основной генератор, 95% запросов (после Opus fix), temperature 0.4. $0.031/запрос.
- Opus 4 — 5%: RCA, кризисы,
!expert. $0.177/запрос, в 5.7× дороже Sonnet. - Perplexity Sonar — web search. $0.006/запрос.
- Cohere Rerank 4 Fast — напрямую через Cohere API, не OpenRouter. $3/мес.
- Groq Whisper + OpenAI fallback — voice input, бесплатно/почти бесплатно.
5. Data pipeline (offline)
Четыре шага, локально → scp → на сервере:
archive_extract.pyлокально экстрактит PDF/DOCX/PPTX в JSON-чанки 1,500 символов с метаданными. Конфиг вbest_hse_config.jsonи аналогах.- scp на сервер,
archive_load.pyвнутри контейнера загружает в ChromaDB с--cleanили инкрементально. contextual_embeddings.pyотдельным контейнером (volume-маунт на volume и на свежий скрипт) проходит все чанки и добавляет строку контекста от Haiku. 14-18 часов на полный прогон, работает параллельно с живым ботом.- Перезапуск
lyumi-hse-bot— per-collection BM25 индексы пересобираются.
6. Оптимизации перцепции — 18 апреля
Семантический кеш — SQLite, 7-day TTL, bare-followup-aware. Инвалидация на 👎. Admin /cache показывает total/hits/top-10. Первые данные после деплоя: 3 записи, 2 хита (JSA повторился). Ожидаемый эффект — 10-15% запросов за 50мс между сессиями.
First-token streaming — first_edit_done флаг в handler-ах текста и фото (bot.py строки ~1022 и ~1746). Первый edit срабатывает как только пришло ≥10 символов от Sonnet, не ждёт 1.5 сек. −1.5 сек перцепционно каждому юзеру на каждом запросе. ⏳ пропадает «на счёт 2-3» (было 5-6).
Почему дальше не оптимизируем. Разложение 22 сек: ~1 сек классификатор + expansion, ~2 сек retrieval + rerank, ~17-18 сек Sonnet генерация, ~1 сек post-processing + Telegram. 80% — LLM, сократить без потери качества нельзя. Реальные выигрыши идут через перцепцию (streaming ✓) и кеш (✓ на повторах). Prefetch на typing-event, убрать expansion для обычных запросов — в резерве.
7. Периферия
digest_bot.py — отдельный бот канала @LyumiHSEDigest. /topic (Sonnet, компактный пост ≤3000 символов) и /analytics (Opus, глубокий разбор с подтягиванием контекста из ChromaDB через shared volume). Параметры и счётчики разнесены. 167+ подписчиков за 5 дней.
log_analysis.py — офлайн-аналитика JSONL логов. 13 секций: usage, speed, quality, feedback, топик-клауд, активность по юзерам, модельное распределение.
broadcast.py — рассылка. Нет --help, любой позиционный аргумент = сообщение всем. Флаг --list показывает юзеров. Копируется в контейнер через docker cp, не пересобирая образ. Опасен — нужен --dry-run, откладывается.
query_logger.py — пишет JSONL в /app/logs/queries_*.jsonl. Точка входа для всей офлайн-аналитики.
8. Продуктовые фичи
- 10 slash-команд — /report, /checklist, /tbt, /jsa, /norma, /ptw, /evac, /doc, /pack + sticky menu 3×3
- /pack — 9 комплектов работ (огневые, высота, ОЗП, LOTO, грузоподъёмные, земляные, электро, H₂S, радиография), 5 документов параллельно (TBT+JSA+PTW+Чек-лист+MS), asyncio.gather + sequential send с 1.5s паузой
- Voice input — Groq Whisper + OpenAI fallback, ffmpeg OGG→WAV
- Photo analysis — Vision (Sonnet/Opus) + RAG по caption, severity levels 🔴🟡🟠, anti-hallucination
- Doc generation —
/doc, .docx с брендингом (#16A34A), 10 типов документов (+ MS / Method Statement), Markdown→DOCX (H1-H4) - Language mirroring — RU/KZ/EN
- Follow-up space inheritance — не переклассифицирует, обогащает Q+A Люми
- Feedback — 👍/👎, JSONL логирование, инвалидация кеша на 👎
9. Качество и антигаллюцинация
- Grounding v1 (17 апреля): закрытый список источников + реквизиты Әділет-ID + дата утверждения из реестра, citation stripping (
_strip_citationвместо substitution), жёсткое правило «только из списка» в промпте. См. [[lyumi/grounding_fix_apr17]]. - NPA Checker — автоматическая постфактум проверка ответов
- Факт-якоря — высота 1.3м, ветер 15м/с, ст.156 УК РК
- Temperature 0.4
- Гендерные правила — Люми в женском роде, к собеседнику в мужском (regex post-processing)
- Запрет на выдуманные инциденты, статусы НПА, sycophancy
- Anti-hallucination для фото
10. Бизнес-инфраструктура
- ИП зарегистрировано (ОКВЭД 70221)
- Сайт lyumihse.kz (Lovable.dev + Cloudflare Pages)
- Google Workspace (kamal@lyumihse.kz)
- Kaspi payments
- Brand: Chimney Smoke theme, Arial, #16A34A green accent
- Канал @LyumiHSEDigest — 167+ подписчиков
- Бот бесплатный (отказ от 3,500₸ подписки — лидмагнит под консалтинг)
11. Кодовая база
34 Python-файла, ~12,000 строк. Основные: bot.py, llm.py, retriever.py, doc_generator.py, voice_handler.py, digest_bot.py, npa_checker.py, log_analysis.py, document_registry.py, semantic_cache.py.
12. Слабые места и tech debt
- Скорость 22 сек средняя, цель <15 — упирается в 80% Sonnet generation. Выигрыши только через перцепцию и кеш.
- Cache hit-rate неизвестен — нужна неделя реального трафика. Топ-10 запросов будет полезным инсайтом для контент-плана канала.
- Медицинские формы (075/У, 025/е) — gap в базе, Люми буксует. Дозагрузить приказы МЗ РК.
- Утечка внутренних имён — knttnbv видел «Камал тестировал». Грепнуть промпт и базу.
- Платные стандарты — 8-9 ISO/IOGP за пэйволлом (45002, 14064-1, 50001, 14004, 26000, IOGP 504, 577, 434). Пока — пересказы из вторичных источников.
- broadcast.py опасен — нет
--help, любой позиционный = отправка всем. Надо--dry-run. - Prefetch на typing-event — идея в резерве, сложная логика отмены.
- Query expansion для обычных запросов идёт синхронно до ретривала — можно параллельно.
Косты
~$95/мес: Hetzner $50 + OpenRouter $45 (после Opus fix) + Cohere $3. Бреакевен в подписочной модели был бы 14 юзеров × 3,500₸. От модели отказались — монетизация через консалтинг 200-300K₸/день, бот = лидмагнит.
Связанные страницы
- [[lyumi/bot_v3]] — техническая карта и changelog
- [[lyumi/grounding_fix_apr17]] — grounding v1
- [[lyumi/digest_bot]] — дайджест
- [[lyumi/brand]] — бренд
- [[lyumi/roadmap]] — roadmap и стоимость фич