Перейти к содержанию

Архитектура Люми — полный срез на 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 → на сервере:

  1. archive_extract.py локально экстрактит PDF/DOCX/PPTX в JSON-чанки 1,500 символов с метаданными. Конфиг в best_hse_config.json и аналогах.
  2. scp на сервер, archive_load.py внутри контейнера загружает в ChromaDB с --clean или инкрементально.
  3. contextual_embeddings.py отдельным контейнером (volume-маунт на volume и на свежий скрипт) проходит все чанки и добавляет строку контекста от Haiku. 14-18 часов на полный прогон, работает параллельно с живым ботом.
  4. Перезапуск 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 streamingfirst_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 и стоимость фич