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

KB API — HTTP-мост ChromaDB между Lyumi и Pushkin

Построен 28 апреля 2026 как ответ на вопрос: «Как обогатить посты Пушкина каноном из ChromaDB Люми, не раздувая Pushkin до 6.88GB?»

Проблема

Pushkin (lyumi-pushkin, ~200MB) не имеет ChromaDB и embedding-модели e5-large на борту — это сделано специально, чтобы держать его лёгким и независимо деплоить.

Но для качественных постов нужен канон IOGP/OSHA/ISO/НПА РК — то что уже лежит в ChromaDB Люми (167,970 чанков после чистки 28 апреля).

Три варианта были рассмотрены:

Вариант Плюс Минус
А. Раздуть Pushkin (chromadb + e5-large) Независимый сервис Образ 6+GB, дублирование RAM, тяжёлый старт
Б. HTTP API в Lyumi → Pushkin клиент Pushkin 200MB, embed-модель используется один раз +1 точка отказа (если Lyumi падает — KB недоступен)
В. Отказаться от enrichment Простота Теряем канон в постах, остаётся только web search

Выбрали Б. Зрелая архитектура: Pushkin лёгкий, Lyumi уже держит модель в RAM 24/7, добавить HTTP-эндпоинт — пара сотен строк.

Архитектура

┌──────────────────────────┐         ┌─────────────────────────────┐
│  Pushkin (200MB)         │         │  Lyumi (~6.88GB)            │
│  lyumi-pushkin-bot       │         │  lyumi-hse-bot              │
│                          │  HTTP   │                             │
│  digest_retriever.py ────┼────────▶│  kb_api.py (aiohttp.web)    │
│  (httpx client)          │  GET    │  port 8000                  │
│                          │         │     │                       │
│                          │         │     ▼                       │
│                          │         │  retriever.py               │
│                          │         │  (vector search,            │
│                          │         │   e5-large in RAM)          │
│                          │         │     │                       │
│                          │         │     ▼                       │
│                          │         │  ChromaDB (lyumi-chroma)    │
│                          │         │  167,970 чанков             │
└──────────────────────────┘         └─────────────────────────────┘
         ▲                                       ▲
         │                                       │
         └────────── docker network ─────────────┘
                       lyumi-net (external)

Эндпоинты Lyumi (kb_api.py)

GET /healthz

Пинг для проверки что сервис жив.

docker exec lyumi-pushkin-bot python -c \
  "import httpx; print(httpx.get('http://lyumi-hse-bot:8000/healthz').json())"
# {"ok": true, "service": "lyumi-kb-api"}

GET /kb_search?q=...&k=15&max_chars=10000

Vector search по 5 коллекциям ChromaDB. Возвращает форматированный текст с источниками, готовый для инжекта в LLM-контекст.

Response:

{
  "text": "[#1 | score=0.87 | коллекция=hard_space | тип=npa | юрисдикция=KZ]\nИсточник: ...\n...",
  "chunks": 3,
  "top_score": 0.87
}

Параметры: - q — поисковый запрос (обязательный) - k — лимит чанков в выдаче (default 15, max 50) - max_chars — максимальный размер итогового текста (default 10000, max 30000)

Использует retriever.search_with_threshold_async — голый vector search без BM25/Cohere rerank (для канона хватит, экономим время на простых запросах).

Pushkin клиент (digest_retriever.py)

Компактный httpx-клиент:

KB_API_URL = os.getenv("KB_API_URL", "http://lyumi-hse-bot:8000")
KB_API_TIMEOUT = float(os.getenv("KB_API_TIMEOUT", "30"))

async def fetch_from_knowledge_base(query, k=15, max_chars=10000):
    url = f"{KB_API_URL}/kb_search"
    async with httpx.AsyncClient(timeout=KB_API_TIMEOUT) as client:
        resp = await client.get(url, params={"q": query, "k": k, "max_chars": max_chars})
        ...

Используется в digest_bot.enrich_with_knowledge_base() для: - /topic - /analytics - /case - Все автодрафты по расписанию (npa, trends, case, topic, weekly)

Fallback: если Lyumi недоступна (timeout, 5xx, network error) — возвращает только web_content из Sonar. Пушкин не падает, просто без канона в этом посте.

Docker network

Общая сеть lyumi-net создаётся вручную как external:

docker network create lyumi-net

Оба compose-файла подключают сервисы к этой сети:

/opt/lyumi/docker-compose.yml:

services:
  bot:
    container_name: lyumi-hse-bot
    networks: [lyumi-net]
    # + volumes, env_file как раньше

networks:
  lyumi-net:
    external: true

/opt/lyumi-pushkin/docker-compose.yml:

services:
  pushkin:
    container_name: lyumi-pushkin-bot
    networks: [lyumi-net]

networks:
  lyumi-net:
    external: true

DNS работает по container_name: Pushkin зовёт http://lyumi-hse-bot:8000, Docker сам резолвит.

Запуск HTTP-сервера в Lyumi

В bot.py основной await dp.start_polling(bot) обёрнут в asyncio.gather параллельно со стартом aiohttp:

from kb_api import start_kb_api
await asyncio.gather(
    dp.start_polling(bot),
    start_kb_api(retriever),
)

start_kb_api поднимает aiohttp.web.AppRunner на порту 8000 и держит coroutine живой через asyncio.Event().wait(). Один event loop — два сервера (Telegram polling + HTTP).

Боевая проверка

Первый прогон 28 апреля 2026, 03:13:51:

status: 200
chunks: 3
top_score: 0.87
text head: [#1 | score=0.87 | коллекция=hard_space | тип=npa | юрисдикция=KZ]
Источник: Об утверждении Правил по обеспечению безопасности и охраны труда при работе на...

Запрос «наряд-допуск на высоте» отвечает за ~1 секунду, top score 0.87 — высокая релевантность.

Стоимость

  • Pushkin — те же 200MB, +30 строк httpx-клиента, плюсовая зависимость только httpx (уже была)
  • Lyumi — +aiohttp (уже транзитивно через chromadb), +130 строк kb_api.py
  • Сеть — внутренняя docker-net, наружу 8000 НЕ публикуется
  • CPU — 1-2 запроса в день при /topic и /analytics, копейки
  • Latency — ~500-1500мс на запрос (vector search + сериализация)

Что НЕ закрыто

  1. Кеширование запросов — повторные запросы Pushkin'a с похожими q не кешируются. Можно добавить SQLite-кеш в kb_api как в основном semantic_cache.
  2. Rate limiting — нет защиты от стороннего сервиса в lyumi-net (если в сеть подключат третий контейнер). Сейчас только Pushkin использует — норм.
  3. Reranking — kb_api использует только vector search. Если качество выдачи будет страдать — добавить опциональный ?rerank=true параметр с Cohere.
  4. TLS — внутри docker-net не нужно, наружу не публикуется. Если когда-то понадобится — TLS terminating через nginx-proxy.

Связанные

  • [[lyumi/pushkin]] — Pushkin AI-newsroom
  • [[lyumi/bot_v3]] — Lyumi бот
  • [[lyumi/cleanup_ru_apr28]] — чистка RU контента (тот же день)
  • Memory project_pushkin_workflow_stable_apr28 — workflow стабилизировался