Ассистент по внутренней базе знаний в контуре
Ответ приходит со ссылкой на файл и страницу, а на вопрос без ответа в базе ассистент отвечает отказом
Проверяется: журнал запросов и контрольный набор вопросов
Средний бизнес, производствоРаботаем по NDA
Регламенты, инструкции, приказы и договоры лежат в сетевых папках, в почте и в головах двух-трёх человек. Сотрудник задаёт вопрос в общий чат, ответ приходит через час и часто по отменённой редакции. Публичные ИИ-сервисы использовать нельзя: внутри документов условия договоров, цены и персональные данные.
Что съедает срок и бюджет на самом деле
Почти никогда — не модель. Ниже места, из-за которых такие проекты встают, и что мы с каждым делаем.
Документы противоречат друг другу
В папке лежат три версии одного регламента без дат в имени файла, а действует последняя. Пока не решён вопрос, какая редакция главная и кто это подтверждает, любой ответ ассистента спорный — и спорить будут с системой, а не с папкой.
Ответ без ссылки на документ и страницу бесполезен
на него нельзя сослаться в разговоре с руководителем или проверяющим. Значит, исходники надо резать на фрагменты с сохранением файла, раздела и номера страницы, а не просто «залить всё в поиск».
Разные форматы ведут себя по-разному
DOCX с таблицами, PDF со сканами приложений, XLSX-прайсы, письма. Таблица, разрезанная на куски между фрагментами, даёт ответ с чужой строкой, поэтому таблицы извлекаются отдельно и хранятся целиком.
Вопрос формулируют не так, как написано в регламенте
«сколько дней на согласование счёта» против «срок визирования расходного документа». На такой лексике чисто векторный поиск промахивается — нужен гибрид с точным поиском по терминам и словарь синонимов, собранный на ваших словах.
Ограничение, которое определяет решение
Модель, индекс и сами документы остаются внутри контура: обращения к внешним API закрываются на уровне сети, а не обещанием. Если в базе есть персональные данные и коммерческая тайна, доступ к фрагментам режется по ролям из вашего каталога до поиска, а не после генерации ответа — иначе документ утечёт через цитату.
Каждый запрос пишется в журнал: кто спросил, что спросил, какие документы получил.
Как проверить, что работа сделана
Контрольная выборка из 100–300 реальных вопросов, собранных у сотрудников до начала работ и не использовавшихся при настройке. Проверяющий с вашей стороны по каждому ответу отмечает три вещи отдельно: ответ верен по существу, ссылка ведёт на тот документ и ту страницу, редакция действующая.
Отдельно в выборку подмешиваются вопросы, ответа на которые в базе заведомо нет, — считается доля честных «не нашёл» вместо выдумки. Порог по доле корректных ответов и запрет на ответы без ссылки записываются в акт до старта, а не подбираются по факту.
О чём спросить любого подрядчика
- Спросите, откуда в ссылке берётся номер страницы и раздела.
- Если ответ «модель сама напишет» — ссылки будут выдуманными, и это выяснится на первом же споре.
- Спросите, где фильтруются права: до поиска или после генерации ответа; правильный ответ только «до».
- Попросите показать поведение на вопросе, ответа на который в базе нет.
- И договоритесь заранее, кто на вашей стороне ведёт реестр действующих редакций после запуска: без этого человека ассистент через полгода отвечает по отменённым инструкциям, причём уверенно.
Порядок работ по этапам
На каждом этапе назван результат, который остаётся у заказчика. На проверке — признак, по которому работу не приняли бы.
- Обследование источниковПрошли по сетевым папкам, порталам и почтовым ящикам и составили реестр наборов документов с владельцем каждого. Отдельно выяснили, где лежит действующая редакция и кто на стороне заказчика её подтверждает: без этого имени ассистент через полгода отвечает по отменённым инструкциям.
- Архитектура поискаРешили резать документы на фрагменты с сохранением файла, раздела и страницы, а таблицы извлекать отдельно и хранить целиком — разрезанная между фрагментами таблица давала ответ с чужой строкой. Права вынесли на этап отбора фрагментов, а не на фильтр готового ответа, иначе закрытый документ утекал бы через пересказ.
- Сборка конвейераСначала подняли извлечение текста, распознавание сканов и обработку таблиц, затем нарезку с метками редакции и даты, затем гибридный поиск с переранжированием и словарём синонимов на словах сотрудников. Ответ настроили так, чтобы он собирался только по найденным фрагментам и давал прямой отказ, если релевантного нет.
- Проверка на вопросахПрогнали контрольную выборку реальных вопросов, собранных у сотрудников до начала работ и не использовавшихся при настройке. Проверяющий отмечал отдельно верность ответа, точность ссылки на файл и страницу и действующую редакцию. Провалом считали ответ без ссылки и выдумку на вопрос, ответа на который в базе нет.
- ПередачаОтдали поисковый сервис в контуре, конвейер загрузки, словари синонимов, набор проверочных вопросов и журнал запросов с кнопкой «ответ неверный». Администратор заказчика сам добавляет источники и пересобирает индекс, реестр действующих редакций ведёт назначенный владелец.
Как это устроено внутри
Контур разложен на четыре службы: загрузчик источников, поиск, шлюз ответа на FastAPI и инференс на vLLM, поднятый на сервере заказчика. Загрузка идёт через RabbitMQ, потому что распознавание сканов и разбор таблиц занимают узел надолго и на прямом вызове держали бы соединение, а очередь переживает перезапуск воркера и не теряет документ. Фрагменты и их метаданные — файл, раздел, страница, редакция — лежат в PostgreSQL, векторы в pgvector рядом, полнотекстовый индекс в OpenSearch: точный поиск по терминам и смысловой по описанию собираются одним запросом без рассинхронизации двух хранилищ. Права из каталога применяются на отборе фрагментов, до сборки контекста, поэтому закрытый документ не доходит до модели; при отказе узла инференса запрос возвращается в очередь и повторяется на соседнем.
- Python
- FastAPI
- PostgreSQL
- pgvector
- OpenSearch
- RabbitMQ
- Docker Compose
- vLLM
- Keycloak
Похожая задача у вас?
Разберём её на созвоне за 40 минут: назовём объём работ, ограничения контура и по каким числам вы будете принимать результат. Если задача не наша — скажем прямо.