Перейти к контенту

RAG-поиск по базе знаний для ИИ-агентов: гибридный поиск, MCP и замер качества

Гибридный RAG-поиск (BM25, векторный поиск и реранкер) по корпоративной базе знаний примерно из 5 300 документов: ИИ-агенты ищут через MCP-сервер, все компоненты поиска работают на локальном оборудовании. Мы построили замер качества поиска на контрольных вопросах, убрали четыре молчаливых дефекта, сверили устройство с практикой по 102 источникам и проверили контекст фрагментов по правилу, записанному до прогона.

18 → 24 из 27
нужный документ в первой пятёрке выдачи, R@5; 27 контрольных вопросов к поисковому серверу (13–14.10.2026)
64 → 67 из 67
нужный документ среди кандидатов до реранка (стенд, 27 + 40 вопросов)
0,54 → 0,79
MRR на тех же 27 вопросах
4 → 0
пустых ответов поискового сервера на тех же 27 вопросах; все четыре давали вопросы к двум соседним корпусам (13.10.2026)
102 источника
сверка устройства поиска с мировой практикой; перепроверенные по живым страницам числа и цитаты подтвердились: 20 из 20 и 15 из 15

Резюме

База знаний инженерной компании насчитывает около 5 300 документов, это около 48 тыс. фрагментов: инженерная документация, архитектурные решения, деловые документы, исследования. Язык русский, с английскими терминами и именами сервисов.

Задача: ИИ-агенты и инженеры должны находить нужный документ по смыслу вопроса, даже когда вопрос сформулирован не словами документа. Агенты обращаются к поиску через MCP-сервер. Поиск гибридный: плотный вектор и BM25, слияние RRF, затем кросс-энкодер переоценивает лучших кандидатов.

Поиск работал и раньше, но никто не знал, как часто агент получает нужный документ. За два дня, 13 и 14 октября 2026 года, мы построили замер полноты. Замер, подготовка эксперимента с контекстом фрагментов и сверка с практикой вскрыли четыре дефекта, которые ничем себя не выдавали; все четыре убраны в поиске по базе знаний. Устройство поиска сверено с международной практикой по 102 источникам.

Результаты. Наборы маленькие, размер указан при каждом числе.

  • Нужный документ в первой пятёрке выдачи: 18 → 24 из 27, MRR 0,54 → 0,79. Это 27 контрольных вопросов к поисковому серверу: 21 к базе знаний и 6 к двум другим корпусам на том же сервере.
  • Пустых ответов сервера на тех же 27 вопросах: 4 → 0. Все четыре давали вопросы к двум другим корпусам.
  • Нужный документ среди кандидатов до реранка: 64 → 67 из 67 (27 + 40 вопросов).
  • Ядро поиска соответствует практике. Чего делать не стоит, тоже записано.

Как перенести ту же схему в закрытый контур, описано в разделе 9.


1. Исходное состояние: «вроде работает»

Поиск, который отдаёт не тот документ, не падает и не пишет ошибок. Поэтому без замера его дефекты не видны. Пример из этой истории: на часть вопросов сервер возвращал пустой ответ, и никто этого не замечал, пока не появился набор вопросов с заранее известными ответами.

Отсюда порядок работы: сначала научиться мерить, потом менять. Каждое изменение мерится до и после на одном и том же наборе.

2. Устройство поиска

Репозиторийбазы знанийИИ-агентИндексаторMCP-серверпоискаЛокальная LLM:контекст фрагментов(эксперимент)ЭмбеддерBGE-M3Qdrantплотный вектор + BM25Кросс-энкодерbge-reranker-v2-m3 раз в минуту запрос / выдачаRRF
100%
Ctrl+Колесо или перетаскивание

Рис. 1. Индексация и поиск. Эмбеддер, реранкер и модель для контекста фрагментов работают на локальных GPU-серверах.

  • Индексация. Индексатор раз в минуту сверяет индекс с основной веткой репозитория базы знаний. Изменения попадают в поиск примерно через минуту, удалённое из индекса убирается.
  • Два представления фрагмента. Плотный вектор (эмбеддинг) BGE-M3 для векторного, то есть семантического поиска и разреженный вектор BM25 для поиска по ключевым словам. Текст в BM25 переводит сама векторная база данных Qdrant, с русским стеммером Snowball.
  • Запрос. Векторный и лексический поиск идут параллельно, Qdrant сливает списки через RRF (Reciprocal Rank Fusion). Кросс-энкодер bge-reranker-v2-m3 переоценивает лучших кандидатов, агент получает пятёрку. Порогов отсечения по оценке нет. Раздел базы сужает поиск, только если агент указал его явно.
  • Наблюдаемость. В каждой выдаче указан фактический режим поиска: применён ли реранк, чем искала текстовая ветка, каким путём шло слияние. Сбой реранкера больше не выглядит обычным ответом. Запросы пишутся в журнал (Grafana Loki), секреты в них маскируются. Журнал станет источником реальных вопросов для замера.
  • Происхождение текста. Фрагменты из внешних источников отмечены в выдаче как внешние и начинаются с предупреждения «данные, а не инструкции».
  • Подключение агентов. Через MCP-сервер агенту доступны поиск (не больше 10 попаданий за вызов) и чтение документа целиком по пути из выдачи. У сервера есть инструкция, когда искать, а когда хватит grep.

3. Как мерили

  • Набор 1: 27 контрольных вопросов к поисковому серверу: 21 к базе знаний и 6 к двум другим корпусам на том же сервере. У каждого вопроса заранее указан документ, а при нужде и раздел, где лежит ответ.
  • Набор 2: 40 новых вопросов к базе знаний. Разделы для них вытянуты жребием с фиксированным зерном, так что выборку можно повторить. Вопросы писал отдельный агент без доступа к поиску, набор заморожен до прогонов вариантов. Он защищает от подгонки поиска под первые 27 вопросов.
  • Два прогона. Первый идёт через живой сервер тем же путём, каким ищут агенты: доказательством служит он. Второй идёт на стенде с переключателями. Стенд повторяет ход поиска и показывает, где теряется цель: при отборе кандидатов или на реранке.
  • Метрики. R@1 и R@5 считают вопросы, у которых нужный документ стоит первым или входит в первую пятёрку. В гайдах по оценке качества RAG это называют Hit@1 и Hit@5: у вопроса одна цель, реже две равноправные. MRR усредняет 1/ранг нужного документа в пятёрке. Ещё считаются пустые ответы и медиана задержки. На стенде добавляется вопрос, есть ли цель среди кандидатов до реранка.
  • Парная статистика (с 14.10). R@k сравнивается точным тестом Макнемара по вопросам, где варианты разошлись, MRR сравнивается бутстреп-интервалом и перестановочным тестом. Правило стенда: чтобы разница была значима на уровне 0,05, вариант должен выиграть хотя бы 6:0 или 8:1 по разошедшимся вопросам.
Стендс переключателямиКанарейкарядом с боевымВыкаткаЗамер черезживой сервер следующая правка
100%
Ctrl+Колесо или перетаскивание

Рис. 2. Порядок любой правки поиска: стенд, канарейка рядом с боевым сервером, выкатка, повторный замер на боевом.

4. Что нашли: четыре молчаливых дефекта

Первые два дефекта нашёл замер 13.10, третий вскрылся при подготовке эксперимента с контекстом фрагментов, четвёртый нашла сверка с практикой.

1. Автоподбор раздела. Если агент не указывал раздел, сервер угадывал его по словам запроса и ставил жёсткий фильтр. В базе знаний совпадение по подстроке, порой случайное, уводило поиск в чужой раздел, и нужный документ не попадал в пятёрку. В двух других корпусах угаданного раздела нет вовсе, и там фильтр давал пустой ответ: отсюда все 4 пустых ответа из 27. Из 9 промахов 7 терялись ещё при отборе кандидатов. Автоподбор убран: раздел теперь только явный.

2. Затухание по дате индексации. Сервер понижал давно проиндексированные документы. Но индексатор переиндексирует только изменённые файлы, и дата означала «когда правили», а не «насколько актуально»: неизменные справочные страницы проигрывали свежим журналам. Затухание выключено. Оговорка: на стенде разница небольшая (MRR 0,73 без затухания и 0,70 с ним), решение держится прежде всего на механизме и стоит в списке на перепроверку.

3. BM25, которого не было. Документация называла текстовую ветку BM25, а в коде это был фильтр без оценки: он пропускал только фрагменты, где есть все слова запроса. Длинный описательный вопрос обнулял ветку целиком. 14 октября в живую коллекцию добавлен настоящий BM25, без остановки поиска.

Текстовая ветка поиска

Архитектурное решение
Настоящий BM25

Разреженный вектор в Qdrant с русским стеммером. Каждое слово запроса даёт свой вклад в оценку, слова работают по «или».

Нужный документ среди кандидатов у 67 вопросов из 67 вместо 64
Добавлен в живую коллекцию без остановки поиска
Аварийный возврат к прежнему поведению делается одной настройкой
Отклонённый вариант
Фильтр по словам без оценки

Пропускает только фрагменты, где есть все слова запроса, и не ранжирует их.

Длинный описательный вопрос обнуляет ветку
Документы называли его BM25, и расхождение не бросалось в глаза

4. Семантический кэш выдачи. Кэш отдавал сохранённую выдачу, если новый запрос похож на прежний: косинус не ниже 0,96. За 10 часов он не дал ни одного попадания. Зато запросы про порт 8080 и порт 8081, которые отличаются одним числом, дали косинус 0,965 и прошли бы порог: агент получил бы выдачу чужого запроса. Кэш выключен.

Одну идею замер отклонил:

Глубина реранка (предварительно, на 27 вопросах)

Архитектурное решение
Оставить как есть: 30 кандидатов, 15 в реранк

Шире не значит лучше: добранные фрагменты приводят в реранк конкурентов нужного документа.

На 27 вопросах R@5 не хуже широкого пула
Задержка вчетверо ниже
Отклонённый вариант
100 кандидатов, 60 в реранк

Все цели доходят до кандидатов, но в пятёрке их больше не становится.

R@5 23 → 22: в пределах шума, прироста нет
Задержка ×4

5. Результаты

Через живой сервер, на одном и том же наборе из 27 вопросов к поисковому серверу:

показательдо правокпосле правок 13.10после BM25, 14.10
пустых ответов400
нужный документ первым (R@1)121719
нужный документ в пятёрке (R@5)182324
MRR0,540,730,79
  • Нужный документ среди кандидатов до реранка (стенд, 27 + 40 вопросов): 64 из 67 со старым фильтром, 67 из 67 с BM25.
  • 40 новых вопросов (живой сервер): R@5 37 → 37, MRR 0,82 → 0,82. На этом наборе BM25 прироста не дал. Позже в тот же день R@5 стал 36 из 40: новые документы вытеснили цель одного вопроса. Это дрейф индекса; ловить его должен ежедневный прогон замера, он в бэклоге.
  • Задержка не выросла. Медиана 13.10: 655 → 641 мс. Медиана 14.10: 568 → 483 мс на 27 вопросах и 712 → 471 мс на 40.

У этих чисел три оговорки.

  • Прирост от BM25 на 27 вопросах (+1 по R@5, +2 по R@1) по правилу самого стенда остаётся в пределах шума. Настоящий эффект BM25 в другом: цель есть среди кандидатов у всех 67 вопросов вместо 64.
  • Для правок 13.10 парный тест не считался: стенд получил его только 14.10. Доводом здесь служит механизм (фильтр раздела давал пустые ответы и чужие разделы), а не число.
  • R@5 упирается в потолок около 91 % (61 из 67). Различать варианты дальше помогут трудные вопросы и метрики R@1 и MRR, а не рост набора.

6. Сверка с мировой практикой

14.10 мы сверили каждую подсистему со 102 источниками: документацией вендоров, независимыми бенчмарками, академическими работами. Выводы атаковал отдельный агент (состязательный проход). Двадцать чисел, на которых держатся выводы, и пятнадцать цитат перепроверены по живым страницам: подтвердились все.

подсистемакак устроеновердикт сверки
ядроплотный вектор + BM25 → RRF → кросс-энкодерсоответствует практике
слияниеRRF без весовсоответствует: без размеченных данных RRF служит разумным умолчанием
нарезкапо заголовкам, путь заголовков в тексте фрагментасоответствует; семантическая нарезка не окупается
эмбеддер и реранкерBGE-M3 и bge-reranker-v2-m3соответствует: в задачах поиска на русскоязычных бенчмарках BGE-M3 в верхней группе
лексикарусский стеммер Snowball, токенизатор Qdrantне проверено, как режутся IP-адреса, имена и порты
конвейеры запросадва: через MCP и через командную строкурасхождение: в практике один путь, который мерится и пишется в журнал
кэш выдачибыл семантический, с порогомисправлено 14.10: ложные попадания пороговых кэшей описаны в литературе
сбоиреранкер и BM25 деградировали молчаисправлено 14.10: режим поиска в каждой выдаче
журнал запросовне былоисправлено 14.10
замер27 + 40 вопросовглавная дыра: набор маленький и упирается в потолок
графы знанийнетправильно, что нет

Сверка дала и список того, чего делать не стоит:

  • GraphRAG и родственные схемы: на одношаговых вопросах графы не лучше обычного RAG, а индексация многократно дороже.
  • Менять эмбеддер по лидерборду: лидерборд годится для короткого списка, решает A/B на своём стенде.
  • Late chunking: метод рассчитан на усреднение векторов токенов, а BGE-M3 берёт вектор из токена [CLS].
  • Семантическую нарезку: в независимых замерах она не окупается.

Источники для проверки. Выборка из 102; эти страницы открывались при сверке 14.10.2026:

7. Эксперимент: контекст фрагментов (Contextual Retrieval)

Метод Anthropic: к каждому фрагменту модель дописывает одно-два предложения о том, из какого он документа и о чём он. Мы добавляем ещё до восьми слов, которыми фрагмент искали бы, но которых в нём нет. Этот текст идёт в плотный вектор, в BM25 и в реранк.

  • Где. Контекст пишет локальная LLM (Qwen 3.8 27B через vLLM) на локальном GPU-сервере. Для этой работы текст базы внешним провайдерам не отправляется.
  • Защита от выдумок. Слова с цифрами, которых нет в документе, выбрасываются при записи. Отдельный аудит сверяет каждое число из готовых контекстов с текущим текстом документа. После полного прогона сверено 45 255 контекстов, из них с цифрами 21 047; неподтверждённых нашлось 3, их удалили из кэша, модель напишет их заново.
  • Режим наполнения. Первичный прогон закончен 14.10: 48 320 фрагментов. Контексты копятся, боевой индекс не меняется.

О включении решает правило, записанное заранее, а не ощущение. R@5 уже у потолка. А формулировка «нет значимых регрессий» на 67 вопросах ничего не отсекает: даже 5 проигрышей без единого выигрыша дают p = 0,0625. Независимые замеры поверх структурной нарезки, как здесь, дают единицы процентов или шум, и все они сделаны на английских корпусах. Поэтому мы ждём прироста меньше, чем в цифрах Anthropic.

Правило записано 14.10, до прогона. Контекст включается, только если выполнено всё:

  • на 67 вопросах R@1 и MRR не ниже, чем у текущего поиска;
  • по R@1 ухудшились не больше 2 вопросов из 67, и каждый разобран поимённо;
  • аудит чисел в контекстах перед самым переключением даёт 0 неподтверждённых;
  • на стресс-наборе из 15 вопросов улучшились хотя бы 3 и ухудшился не больше 1 (это фрагменты, где нужная сущность названа только в пути к файлу; вопросы к ним писала не та модель, что писала контексты);
  • медиана задержки выросла не больше чем в 1,5 раза;
  • перед переключением коллекция получает версию и алиас, чтобы откат занимал секунды.

Если первые два условия выполнены, а стресс-набор не улучшился, контексты продолжают копиться без включения. Если нарушено одно из первых двух, переключения нет, а перевернувшиеся вопросы разбираются.

Результат (15.10). Перед сравнением 1 408 файлов переиндексировали: их фрагменты лежали в старой нарезке, и контекст к ним не находился. Затем на стенде текущий поиск и вариант с контекстом сравнили на 103 вопросах: 67 основных, 15 из стресс-набора и 21 контрольный.

  • Нужный документ первым: 70 → 77 вопросов, MRR 0,76 → 0,81, медиана задержки не выросла (715 и 623 мс). Разница незначима: 13 вопросов выиграли, 6 проиграли, p = 0,17.
  • На стресс-наборе нужный документ стал первым у 9 вопросов вместо 6.
  • На 67 основных вопросах 4 потеряли первое место, а правило допускает не больше 2. Контекст в боевой поиск не включён, фрагменты продолжают его копить.

Разбор четырёх вопросов. В одном выше встал соседний документ, который отвечает на вопрос не хуже цели. В одном цель выпала из выдачи из-за старой копии того же документа в репозитории. В двух соседний фрагмент обогнал цель из-за дописанных к нему слов для поиска: они совпали с формулировкой вопроса. Почти весь прирост дал контекст в тексте для реранкера, а не в векторах. Следующая проверка: контекст без дописанных слов в тексте для реранкера или только в реранкере, по новому правилу, записанному до прогона, и на новых вопросах.

8. Что не вышло и что осталось открытым

  • Агенты в репозитории чаще ищут сами. По журналам за всю историю поиск вызывали 33–45 раз за 516 сессий Claude Code и ни разу за 513 сессий Antigravity, где за то же время grep вызывали 1 479 раз. Эксперимент 14.10 на 12 задачах (одна модель, задачи из наборов замера; в условии с поиском агенту велели начинать с него): точность одинаковая, 12 из 12 и с поиском, и без него. С поиском вызовов инструментов меньше на 30 % (189 → 132), времени на 20 % (695 → 559 с). Но разница незначима. По вызовам поиск выиграл в 8 задачах, проиграл в 3, одна вничью, знаковый тест даёт p ≈ 0,23. По времени с поиском быстрее в 8 задачах, медленнее в 4, p ≈ 0,39. Выигрыш почти целиком приходится на описательные вопросы, где слова вопроса не совпадают со словами документа. Это вывод, а не замер: поиск нужнее всего там, где у агента нет доступа к файлам, например в чат-приложениях и в сессиях других проектов.
  • Замер маленький и у потолка. 27 + 40 вопросов, R@5 около 91 % (61 из 67). Нужны трудные срезы: идентификаторы, короткие запросы, смесь русского и английского, «старое против актуального». И нужны реальные запросы из журнала.
  • Слепое пятно. Запросов из одних ключевых слов (имена, номера, версии) в замере нет. А на таких запросах чистый BM25 обгонял гибрид в замере Microsoft (пост 2023 года).
  • Актуальность. Статуса «действует / устарело» у документов пока нет: старая и новая версии факта могут лежать рядом.
  • Перепроверка. Решения, принятые по разнице в 1–3 вопроса из 27 (например, отказ от затухания по дате), внесены в список на перепроверку.

9. Та же схема в закрытом контуре

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

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

Рекомендуемая конфигурация для промышленной эксплуатации. Локальный GPU-сервер с картами класса NVIDIA RTX A5000/A6000 под эмбеддер, реранкер и локальную модель ответа. Это рекомендация: на картах этого класса система не проверялась. Конфигурацию подбирают замером на корпусе и нагрузке конкретной площадки.

Вопросы и ответы

Почему RAG-система «работает», но находит не то?

Поиск, который отдаёт не тот документ, не падает и не пишет ошибок. Здесь такие дефекты нашлись только на наборе вопросов с заранее известными ответами: сервер сам угадывал раздел и сужал поиск, понижал давно проиндексированные документы, вместо BM25 работал фильтр без оценки, а кэш выдачи мог отдать результат чужого запроса (раздел 4).

Гибридный поиск или векторный: когда нужен BM25?

Векторный поиск находит документ по смыслу, но чаще промахивается на идентификаторах, портах, именах и версиях. Поиск по ключевым словам (BM25) их находит. Здесь настоящий BM25 вместо прежнего фильтра без оценки довёл до кандидатов все 67 целей вместо 64. Слияние идёт через RRF без весов: без размеченных данных подбирать веса не на чем.

Как измерить качество поиска в RAG-системе?

Нужен контрольный набор реальных вопросов с известными документами-ответами, замороженный до прогонов. Метрики: R@1 и R@5 (Hit@1 и Hit@5) и MRR. Варианты сравниваются парными тестами на одних и тех же вопросах, а правило решения записывается до прогона. Когда R@5 упирается в потолок, различать варианты помогают трудные срезы и R@1, а не больший набор.

Можно ли сделать такой поиск без облачных API?

Поиск здесь от облачных API не зависит: индекс, эмбеддинги, реранкер и модель для контекста фрагментов работают на локальном оборудовании. В закрытом контуре ответ должна писать локальная модель, а если права доступа к документам разные, проверять их нужно в самом поиске (раздел 9).

Как подключить ИИ-агента к базе знаний через MCP-сервер?

Агент подключается к MCP-серверу по HTTP. Через него доступны поиск и чтение документа целиком, а инструкция сервера подсказывает, когда искать, а когда хватит grep.

RAG-поиск по базе знаний для ИИ-агентов: гибридный поиск, MCP и замер качества | Софтэнк