Перейти к содержимому
VC
Кейс № 22 · AI / Базы знаний

AI-секретарь по внутренним данным компании: RAG-бот в Telegram с тирами доступа

Бот отвечает на вопросы по внутренним чатам, письмам, голосовым и документам — с цитатами на источник. Поиск переписан с in-memory BM25 на SQLite FTS5: память упала в 20 раз, и бот уехал с ноутбука подрядчика на сервер, где отвечает круглосуточно.

Индустрия
E-commerce, распределённая команда
Стек
Python · SQLite FTS5 · Telegram · MCP
Сроки
≈ 8 недель эволюции
Итог
1044 → 50 МБ, 253 → 13 мс
01 · Боль

Вся фактура бизнеса жила в переписке

US-бренд товаров для активного отдыха, два магазина на Shopify, распределённая команда: владелец бизнеса, операционисты, ad-менеджер, подрядчики. Ни одной системы, где хранились бы решения. Всё — в рабочих чатах: десятки тысяч сообщений, голосовые вместо документов, пересланные PDF и скриншоты, плюс почтовый корпус в более чем 100 тыс. писем.

Ответ на вопрос вида «что мы решили по этому две недели назад» стоил получаса раскопок: пролистать чат, вспомнить, в каком именно, найти голосовое, послушать его целиком. Часть решений просто терялась — их переспрашивали и принимали заново.

Требование владельца было прямое: «важно, чтобы в базе было как можно больше данных». Но в тех же чатах лежала личная переписка и PII — то, что команде показывать нельзя. Задача сразу стала не «прикрутить RAG», а «прикрутить RAG так, чтобы полнота не превратилась в утечку».

02 · Решение

Полный RAG-контур на собственных данных — с приватностью до индекса

Контур односторонний: сырьё собирается, приводится к тексту, чистится от секретов, попадает в индекс с ACL, и только потом LLM синтезирует ответ. Приватность решается на входе, а не фильтром на выходе — так её нельзя обойти удачной формулировкой вопроса.

01
Сбор

Съём чатов по лейнам со слиянием по id сообщения, корпус почты, пересланные документы

02
Нормализация

whisper для голосовых, OCR для скриншотов — всё становится текстом с ссылкой на источник

03
Редактирование

Вырезаются карты, CVV, пароли — до записи в индекс, не при выдаче

04
Индекс + ACL

SQLite FTS5 как хранилище постингов, собственный ранкер, тиры доступа поверх источников

05
Ответ

LLM-синтез с обязательными цитатами, доставка в Telegram

Сбор: слияние по id, а не rsync

Чаты снимаются лейнами и сливаются по id сообщения. Это не педантизм: тупое копирование поверх (rsync-подход) стёрло бы историю при первом же неполном съёме — источник отдаёт последние N сообщений, а не всё. Голосовые прогоняются через whisper, скриншоты через OCR, пересланные документы индексируются как отдельные источники. На выходе всё — текст, у каждого чанка есть ссылка обратно.

Тиры доступа с одним источником правды

Три тира: владелецкомандахаб. Реализованы как ACL поверх источников, а не поверх ответов. Дальше самое важное: над базой работает не только бот, но и дистилляция фактов, и её фильтр — отдельный код, который легко разъедется с ACL бота.

Поэтому обе стороны ходят в одну функцию is_private_source, а на дрейф между ними стоит гард-тест: если кто-то заведёт вторую копию логики приватности, падает CI, а не приватность.

Поиск: BM25 в памяти → SQLite FTS5

Первая версия держала весь индекс в памяти процесса. Работало, пока бот жил на ноутбуке. Переписал хранение на SQLite FTS5, но с нюансом: FTS5 используется только как хранилище постингов (через fts5vocab), а ранжирование остаётся своим.

Причина конкретная: встроенная bm25() в FTS5 отдаёт один скаляр на документ и не даёт по-терминной информации — а она нужна для coverage-фактора (насколько документ покрывает термины запроса, а не просто набрал вес на одном частом слове). Отдать ранжирование движку значило бы ухудшить выдачу.

Миграция сделана без риска: движок переключается одной переменной окружения, старый и новый прогоняются на одних запросах, выдача сверена ранг-в-ранг. Отличий нет — изменилась только цена.

Что надстроено сверху базы

База знаний оказалась платформой, а не фичей. Поверх неё живут:

  • Дистилляция фактов — переписка сворачивается в структурированные факты, а не пересказ
  • Утренние сводки и доски в Telegram-хабе: бизнес-пульс, пульс кампаний, инфра-пульс, дайджест трекера
  • Около двух десятков сторожей по принципу «упало — напиши в личку и в Discord»: живость CRM, зависшие заказы, окно защиты чарджбека, важное сообщение владельца, устаревание индекса
  • MCP-сервер над базой — личные модели владельца видят ту же базу знаний, без второго пайплайна
03 · Стек

Никакой векторной базы — SQLite и собственный ранкер

Python

Ядро: сбор, индекс, ранжирование, доставка — один язык на весь контур

SQLite FTS5 + fts5vocab

Хранилище постингов; ранжирование остаётся своим ради coverage-фактора

Собственный ранкер (BM25-семейство)

По-терминные веса + покрытие запроса, переключение движка одной ENV-переменной

LLM для синтеза (Claude / DeepSeek)

Ответ собирается только из найденных чанков, цитаты обязательны

whisper + OCR

Голосовые и скриншоты становятся полноценными источниками, а не чёрными ящиками

Telegram Bot API

Бот в личке + форум-хаб с топиками под сводки и алерты

MCP-сервер

Та же база знаний доступна личным моделям владельца

systemd / launchd + Discord

Сервисы, таймеры, ~20 сторожей с алертами в канал

pytest

70 тестовых файлов, ~845 тестов — включая гард-тест на дрейф ACL

PythonRAGSQLite FTS5fts5vocabwhisperOCRTelegram Bot APIMCPsystemdpytest
Масштаб кодовой базы

153 python-модуля, около 36,8 тыс. строк, 318 коммитов. Ключевые куски: движок индекса (~1300 строк), бот (~1500), редактор секретов (~560), дистилляция (~430), stdlib-only слой доставки в Telegram, MCP-сервер, сборщик лейнов и ~20 модулей-сторожей.

04 · Результат

Одна и та же выдача за другие деньги

Пик памяти при поиске
1044 МБ 50 МБ

в 20 раз меньше — при идентичной выдаче

Латентность поиска
253 мс 13 мс

выдача сверена ранг-в-ранг, отличий нет

Приватных чанков в тире «команда»
1327 0

замер на живом индексе, до и после правки ACL

Бот съехал с ноутбука

Падение памяти в 20 раз — не бенчмарк ради бенчмарка. Именно оно позволило унести бота с ноутбука подрядчика на сервер, где всего 961 МБ RAM. Хроническая болезнь «ноутбук уснул — бот молчит ночью» закрыта в корне: бот отвечает круглосуточно, а не пока у кого-то открыта крышка.

Приватность: замерили, а не понадеялись

ACL можно долго обсуждать на словах. Вместо этого сделан замер на живом индексе: сколько приватных чанков реально видит тир «команда». Ответ был неприятный — 1327.

После правки ACL доступный тиру объём сжался с 6139 до 4560 чанков, приватных среди них — 0, при этом рабочих данных не потеряно. Отдельно поднят лог обращений: проверка подтвердила, что реального доступа к этим чанкам никто не успел получить.

Что изменилось в работе

Вопрос «что мы решили по этому» теперь задаётся боту и возвращается ответом с цитатами на конкретные сообщения — включая те, что были голосовыми. Утренние сводки приходят сами, сторожа пишут раньше, чем проблему заметит клиент, а личные модели владельца через MCP работают с той же базой — без второго пайплайна и второй копии данных.

05 · Применимость

Где ещё ложится та же методология

Этот кейс — не «бот для чатов». Это типовая задача «знания компании живут в неструктурированной переписке, и часть из них показывать нельзя». Она есть почти везде, где команда старше года:

  • Поддержка и продажи — история переписок с клиентами как база ответов, при этом персональные данные не уходят в общий доступ
  • Агентства и студии — созвоны, брифы и голосовые от клиента в поиске с цитатой на источник вместо «кажется, договаривались так»
  • Производство и сервис — заявки, фото с объектов, переписка прорабов; ответ бота с цитатой заменяет обзвон
  • Онбординг новых сотрудников — тир доступа «команда» даёт рабочий контекст, не открывая личную переписку руководства
  • Компании с ограничением на облако — весь контур, кроме синтеза, работает на своей машине: SQLite-файл, а не внешний векторный сервис
Что переиспользуется на следующих проектах
  • Пайплайн сбора со слиянием по id — история не затирается при неполном съёме источника
  • Редактор секретов до индекса + один источник правды для приватности с гард-тестом на дрейф
  • Схема FTS5-как-хранилище + свой ранкер: дешёвая память без потери качества выдачи
  • Приём с переключением движка через ENV и сверкой выдачи ранг-в-ранг — миграция без веры на слово
  • Слой сторожей и утренних сводок поверх базы — знания начинают приходить сами, а не по запросу
Похожая задача?

Если знания компании лежат в чатах и почте и их страшно открывать всем — это решается

Начинаем с одного источника и одного тира доступа — рабочий бот с цитатами появляется раньше, чем полная база. Дальше подключаются почта, голосовые, документы и сторожа.

Готовы начать?

Аудит за 5 000 ₽ — с конкретным отчётом и сметой

Расскажу что внедрить в вашем бизнесе в первую очередь, какая будет окупаемость, и нужен ли вообще AI для вашей задачи (иногда — нет).

Или просто напишите свой вопрос — отвечу в течение 2 часов