Гибридный поиск из нескольких независимых «ног» с переранжированием и графом знаний поднял nDCG@10 на нашем эталонном наборе с 0.756 до 0.813, а долю найденных ключевых документов в топ-10 — с 0.647 до 0.755. Ни один приём по отдельности такого не даёт: выигрыш возникает там, где ноги закрывают слепые зоны друг друга.

Если коротко — для бизнеса

  • Ассистент отвечает почти в 6 раз быстрее: медианное время поиска упало с 7.7 до 1.3 с.
  • В выдаче чаще то, что нужно: в топ-10 теперь попадает в среднем 75% ключевых документов запроса против 44% на старте, а сама выдача выросла с 6 до 9.4 документа из 10.
  • Главный урок: волшебной технологии нет. Результат дала связка нескольких дешёвых способов поиска и одного дорогого «судьи», и мерить их нужно только вместе.

О цикле «RAG в проде»

Четыре статьи о том, как за две-три недели октября 2026 года мы перестроили поиск корпоративного RAG-ассистента. «Мы» — это автор и AI-агенты, которым он делегировал часть исследований и правок. Компания и системы обезличены, цифры настоящие.

  1. Гибридный поиск: как «ноги» усиливают друг друга ← вы здесь
  2. Индексы под гибридный поиск в PostgreSQL
  3. Структурный чанкинг и контекстные эмбеддинги
  4. Граф знаний в RAG
Термины и метрики за минуту
  • RAG (Retrieval-Augmented Generation) — ассистент сначала ищет фрагменты документов, затем языковая модель отвечает по ним [1].
  • Чанк — фрагмент документа, единица поиска и контекста модели.
  • «Нога» поиска — независимый способ набрать кандидатов: по смыслу (векторы), по словам (лексика), по связям (граф).
  • Реранкер — модель, которая оценивает пару «запрос — документ» точнее поисковых индексов, но дороже. Поэтому ей дают уже отобранный пул.
  • nDCG@10 — качество порядка в первой десятке с учётом степени релевантности: 1.0 — идеальный порядок.
  • key-doc R@10 (g3) — доля ключевых документов запроса (оценка 3 по шкале 0–3), попавших в топ-10.
  • nR@10 — полнота в топ-10, нормированная на min(10, число релевантных документов).
  • Success@5 (g3) — доля запросов, у которых хотя бы один ключевой документ есть в топ-5.
  • MRR@10 — насколько высоко стоит первый правильный документ (среднее 1/ранг).
  • п.п. — процентные пункты: рост с 0.647 до 0.755 — это +10.8 п.п.
  • 95%-й бутстреп-интервал [a; b] — диапазон, в котором с 95%-й уверенностью лежит эффект. Если он не включает ноль, эффект статистически значим.
  • LLM-судья — модель, которая размечает релевантность пар «запрос — документ» по шкале 0–3 (по мотивам UMBRELA [11]).
  • p50 / p95 — медиана и 95-й перцентиль задержки.

Постановка

Кому особенно полезен раздел:Бизнес и продуктАрхитекторы

Задача — поиск для RAG-ассистента по корпоративной базе знаний на русском языке. Корпус неоднородный:

  • две вики-платформы, около 38 тысяч страниц и статей: регламенты, стандарты, технические описания, таблицы;
  • реестр мастер-данных, около 600 тысяч коротких карточек: договоры, справочники, коды;
  • запросы пользователей — от естественного вопроса до «каши» из ключевых слов и кодов.

Один векторный поиск на таком корпусе ведёт себя предсказуемо плохо. Он хорошо понимает перефразировки, но путает похожие регламенты соседних подразделений, не различает коды и артикулы и тонет в 600 тысячах однотипных карточек реестра.

Качество мы меряем на эталонном наборе из 111 запросов пяти классов: точечный поиск документа, короткий запрос, «каша» из ключевых слов, строка таблицы, многошаговый запрос через сущности. Релевантность размечает LLM-судья [11]; кандидатов для разметки собирают со всех сравниваемых прогонов (пулинг), а разницу между прогонами мы оцениваем бутстрепом по запросам. Определения метрик — в блоке «Термины и метрики» выше.

Исходные условия

Кому особенно полезен раздел:Бизнес и продуктИнженеры

На старте программы поиск отвечал за 7.7 с (p50) и возвращал в среднем 6 документов из 10, а в топ-10 попадало в среднем 44% ключевых документов запроса. Архитектура ног уже была, но часть из них не работала:

  • Графовая стадия таймаутила в 100% запросов и добавляла около 5 с к каждому ответу, возвращая пустой результат. Причина — план запроса к графовой БД с полным сканом 4.7 млн вершин вместо индекса.
  • Векторная нога теряла кандидатов на фильтре по источнику: из 50 запрошенных строк для одной из вики оставалось 12, для реестра — 0 (подробно — во второй статье).
  • Реранкер (bge-reranker-large) получал пул, глубина которого была привязана к длине выдачи: при ответе из 10 документов на переранжирование попадало слишком мало кандидатов.
  • Гейт релевантности отрезал выдачу в среднем до 6 документов.

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

Поиск до и после программы. 111 запросов, единый набор судейских оценок
МетрикаДо начала работСейчас
nDCG@100.5420.813
key-doc R@10 (g3)0.4390.755
nR@100.4970.824
Success@5 (g3)0.8650.973
MRR@100.9110.979
Документов в выдаче, среднее6.09.4
Сквозная задержка p50, с7.71.3

Между стартом и точкой, от которой считается мост в разделе «Итоговые цифры» (nDCG@10 0.756), лежат три изменения: глубина пула реранкера отвязана от длины выдачи, на вход реранкера добавлен заголовок документа, а сам реранкер заменён на bge-reranker-v2-m3. Задержку сняли в первую очередь исправления графовой стадии и индексов. Переход на Qwen3-реранкер потом добавил к ней около 0.5 с, и итоговые 1.3 с уже включают эту цену.

Архитектура

Кому особенно полезен раздел:АрхитекторыИнженеры

Конвейер строится вокруг одного принципа: дешёвые ноги набирают широкий пул кандидатов, дорогой реранкер расставляет его одной линейкой, а дешёвые множители после него только переставляют похожие.

Конвейер поиска: две ноги и граф сходятся в один общий проход реранкераКонвейер поиска: две ноги и граф сходятся в один общий проход реранкераЗапрос пользователяАнализ запросатип запроса, термины,кодыВекторная ногаHNSW по halfvec(2000),по источникамЛексическая ногаRUM, AND → OR, поисточникамReciprocal Rank Fusionпо спискам каждогоисточникаКросс-энкодер поногамдо 40 документов × 3чанкаГраф знанийсиды: топ-k послеслиянияСлияниесреднее по ногам +графовые кандидатыОбщий проходреранкераQwen3-Reranker-4B, fp8Хвост ранжированияфьюжн → гейт →свежесть → архив ×0.8Топ-10 в контекстмодели
Конвейер поиска: две ноги и граф сходятся в один общий проход реранкера

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

Архитектору

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

Синергия: что ловит каждая нога

Кому особенно полезен раздел:ML и исследователиАрхитекторы

Абляции показывают, что ноги не дублируют, а дополняют друг друга. Уберите векторную — ключевые документы в топ-10 теряются на 13 п.п. Уберите лексическую — проседает полнота глубокого пула.

Замер сделан на ранней версии конвейера (до структурного чанкинга и нового реранкера), выдача 20 документов. В таблице — разница с полным конвейером, в скобках 95%-й бутстреп-интервал.

Абляции ног: разница с полным конвейером
КонфигурацияΔ nDCG@10, п.п.Δ key-doc R@10, п.п.Δ R@50 пула, п.п.
Только лексика−8.6 [−12.2; −5.2]−13.0 [−18.2; −8.3]−10.7 [−14.5; −7.3]
Только векторы−1.1 [−4.8; +2.4]−2.2 [−6.8; +2.2]−5.2 [−9.1; −1.6]
Без графа+0.1 [−0.9; +1.1]−0.1 [−1.4; +1.2]−2.1 [−3.1; −1.2]

Что за этим стоит:

  • Векторная нога ловит семантику и перефразировки [2]: «как оформить отпуск» ↔ «порядок предоставления ежегодного оплачиваемого отпуска». На ней держится топ-10.
  • Лексическая нога ловит коды, артикулы, номера стандартов, редкие термины и строки таблиц. Эмбеддинг видит «ABC-1234» и «ABC-1235» почти одинаково, а индекс по словам — нет. К тому же лексика устойчивее векторов вне домена, на котором обучали эмбеддинги [3]. Она наполняет пул кандидатов, из которого реранкер потом достаёт нужное.
  • Граф знаний на топ-10 не влияет, но даёт +2 п.п. полноты глубокого пула. Это документы, связанные с выдачей общей сущностью (системой, процессом, стандартом), но не похожие на запрос ни по словам, ни по смыслу. Для RAG важнее другое: граф кладёт в контекст модели связи между сущностями [9], а это метриками ранжирования не измеряется. Подробно — в четвёртой статье.

ML-исследователю

Обратите внимание на последнюю колонку. У строк «Только векторы» и «Без графа» интервалы для nDCG@10 включают ноль, а для R@50 пула — нет. Если оценивать ногу только по топ-10, её вклад не виден: он проявляется через пул, из которого реранкер выбирает финальный порядок.

Почему списки — по источникам

Кому особенно полезен раздел:АрхитекторыИнженеры

Каждая нога ищет отдельно по каждому источнику и отдаёт свой список в Reciprocal Rank Fusion [4]. Без этого реестр из 600 тысяч однотипных карточек статистически вытесняет из общего топа вики-страницы. Со списками по источникам лучший документ каждого источника получает ранг 1 в своём списке, и RRF даёт ему равные шансы попасть к реранкеру.

RRF удобен ещё и тем, что игнорирует скоры и смотрит только на ранги. У нас k = 60 — классическое значение из исходной работы:

RRF(d)=∑i∈списки1k+ranki(d)\mathrm{RRF}(d) = \sum_{i \in \text{списки}} \frac{1}{k + \mathrm{rank}_i(d)}

Косинусная близость векторов и ts_rank лексики живут в несравнимых шкалах, а ранги сравнимы всегда.

Почему реранк — по ногам, а потом общий

Кому особенно полезен раздел:ИнженерыML и исследователи

Кросс-энкодер [5] сначала оценивает кандидатов каждой ноги отдельно (до 40 документов × 3 чанка), затем оценки усредняются при слиянии, а после добавления графовых кандидатов идёт один общий проход. Повторные пары не пересчитываются: в рамках запроса они мемоизируются, между запросами — кэшируются. В итоге графовые чанки не получают незаслуженного преимущества из-за своих исходных скоров, а финальный скор всех кандидатов измерен одной линейкой.

Резать глубину реранка ради скорости нельзя. При 30 документах × 2 чанка мы теряли весь выигрыш нового реранкера, а при 25 × 1 качество упало ниже старого (−4.6 п.п. nDCG@10). Лексическая нога полезна именно через глубокий пул.

Реранкер: от классификатора к декодеру

Кому особенно полезен раздел:ML и исследователиИнженеры

Замена bge-reranker-v2-m3 (568M параметров, классический кросс-энкодер [7]) на Qwen3-Reranker-4B [8] дала +2.4 п.п. nDCG@10 и +3.3 п.п. key-doc R@10, оба прироста статистически значимы. Цена — около 0.5 с на запрос, и почти вся инженерная работа ушла на то, чтобы её снизить.

Qwen3-Reranker — это декодер. Он получает промпт «инструкция + запрос + документ» и должен ответить yes или no. Релевантность — нормированная вероятность первого токена (та же идея, что у ранжирования через токен «true/false» [6]):

s(q,d)=p(yes∣q,d)p(yes∣q,d)+p(no∣q,d)s(q, d) = \frac{p(\text{yes} \mid q, d)}{p(\text{yes} \mid q, d) + p(\text{no} \mid q, d)}

Три практических наблюдения.

  1. Серверный rerank-эндпоинт инференс-движка сжимал скоры в полосу 0.85–0.97. Причина — шаблон чата без пустого блока рассуждения, который модель ожидает перед ответом. Мы отказались от эндпоинта: собираем промпт ровно как в карточке модели, шлём пачку в генерацию с max_new_tokens = 1 и считаем s по logprobs двух токенов. Скоры снова растянулись на весь диапазон [0, 1].
  2. Задержку душил не декодер, а конфигурация памяти. При доле видеопамяти 0.18 веса занимали почти всё, KV-кэш получал 1 698 токенов, и пары шли по 2–3 за раз. При 0.30 кэш вырос до ~60 тысяч токенов. Реальный объём — около 40 тысяч токенов prefill на запрос (~200 пар) при пропускной способности 24–48 тысяч токенов в секунду. Общий префикс «инструкция + запрос» переиспользуется через radix-кэш, так что вычисления тратятся только на тела документов.
  3. FP8 дал скорость без потери качества. Квантизация весов при загрузке ускорила реранк в 2.3 раза при том же nDCG@10 (0.813 против 0.813).
Реранкеры и глубина реранка. «40 × 3» — до 40 документов на ногу и до 3 чанков на документ; все строки на одном индексе и одних судейских оценках. Жирным — конфигурация в проде
КонфигурацияnDCG@10key-doc R@10Реранк p50 / p95, с
bge-reranker-v2-m30.7890.7220.09 / 0.20
Qwen3-Reranker-4B, bf16, 40 × 30.8130.7591.42 / 2.92
Qwen3-Reranker-4B, fp8, 40 × 30.8130.7550.63 / 1.23
Qwen3-Reranker-4B, fp8, 30 × 20.7870.7070.51 / 0.91
Qwen3-Reranker-4B, fp8, 25 × 10.7430.6420.43 / 0.80

Проверьте у себя

  • Если скоры LLM-реранкера сбились в узкую полосу у единицы, сначала сверьте шаблон промпта с карточкой модели, а потом уже трогайте пороги.
  • Посмотрите, сколько токенов KV-кэша остаётся после загрузки весов. Если там единицы тысяч, пары идут по одной-две, и никакая квантизация задержку не спасёт.
  • Держите общий префикс «инструкция + запрос» в начале промпта: тогда radix-кэш считает его один раз на запрос.

Хвост ранжирования после реранкера

Кому особенно полезен раздел:АрхитекторыИнженеры

После реранкера идут четыре дешёвых шага, и их порядок важен: сначала решаем, кто попадает в выдачу, потом — в каком порядке. Модель читает контекст неравномерно, поэтому состав и порядок топа важнее его длины [10].

  1. Фьюжн скоров. Итоговый скор — взвешенная сумма нормированных скоров вектора (0.2), лексики (0.2) и реранкера (0.6). Реранкер доминирует, но точное совпадение кода или термина не теряется из-за одной его ошибки.
  2. Гейт релевантности с полом. Порог отсечки зависит от типа запроса: для «каши» из ключевых слов и коротких запросов он свой. Пол гарантирует минимальную длину выдачи: пустой ответ для RAG хуже слабого. Пороги калибруются под конкретный реранкер: декодер оценивает средне-релевантные документы выше, чем классификатор.
  3. Свежесть. Скор умножается на множитель, который затухает с возрастом документа a — числом дней с его последней правки в источнике — с периодом полураспада 180 дней. Применяется после гейта, поэтому меняет порядок, но не состав выдачи.
  4. Понижение архивных. Архивные статьи остаются находимыми, но их скор умножается на 0.8. Тот же принцип: состав решён гейтом, множитель только переставляет. Актуальная страница при равной релевантности стоит выше своей заменённой версии.
mсвежесть(a)=1+0.15⋅2−a/180m_{\text{свежесть}}(a) = 1 + 0.15 \cdot 2^{-a/180}

Архитектору

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

Итоговые цифры

Кому особенно полезен раздел:Бизнес и продуктML и исследователи

Вместе изменения дали +10.7 п.п. key-doc R@10 [+5.9; +15.7] и +5.7 п.п. nDCG@10 [+3.2; +8.4] при прежнем MRR@10 ≈ 0.97–0.98. Первый правильный документ система находила и раньше, а растёт полнота нужного в топе.

stepkey-doc R@10
Исходная точка0.647
Чанкинг + граф0.065
HNSW m=320.01
Qwen3-Reranker fp80.033
Итог0.755
Мост key-doc R@10 от 0.647 до 0.755: вклад каждого изменения. Бледный столбец — 95%-й интервал включает ноль. 111 запросов, единые судейские оценки, октябрь 2026

Перестройка индекса с классическим реранкером дала всего +1 п.п., а в паре с Qwen3 — значимые +3.9 п.п. (отдельный прогон). Более сильный реранкер умеет пользоваться кандидатами, которых прежний индекс не находил, а слабый не дотягивает их до топа. Это и есть синергия: ступени конвейера нужно мерить вместе.

Выводы

Кому особенно полезен раздел:Бизнес и продуктАрхитекторыИнженерыML и исследователи

  • Ноги ценны разным. Вектор держит топ, лексика и граф наполняют глубокий пул. Оценивать ногу только по nDCG@10 — значит не увидеть её вклад.
  • Списки по источникам в RRF — дешёвая защита от доминирования самого большого источника.
  • LLM-реранкер окупается, если следить за KV-кэшем и квантизацией, а глубину реранка не трогать.
  • Множители после реранкера (свежесть, архив) применяются после гейта и потому безопасны.

Дальше по циклу — индексы в PostgreSQL, на которых стоят обе ноги.

Литература

Кому особенно полезен раздел:ML и исследователи

  1. Lewis P. et al. Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks. arXiv:2005.11401, 2020.
  2. Karpukhin V. et al. Dense Passage Retrieval for Open-Domain Question Answering. arXiv:2004.04906, 2020.
  3. Thakur N. et al. BEIR: A Heterogenous Benchmark for Zero-shot Evaluation of Information Retrieval Models. arXiv:2104.08663, 2021 — почему BM25 остаётся сильным базовым уровнем вне домена обучения.
  4. Cormack G., Clarke C., Buettcher S. Reciprocal Rank Fusion outperforms Condorcet and individual Rank Learning Methods. SIGIR 2009 (без версии на arXiv).
  5. Nogueira R., Cho K. Passage Re-ranking with BERT. arXiv:1901.04085, 2019.
  6. Nogueira R. et al. Document Ranking with a Pretrained Sequence-to-Sequence Model. arXiv:2003.06713, 2020 — ранжирование через вероятность токена «true/false», предшественник схемы yes/no.
  7. Chen J. et al. M3-Embedding: Multi-Linguality, Multi-Functionality, Multi-Granularity Text Embeddings Through Self-Knowledge Distillation. arXiv:2402.03216, 2024 — основа bge-reranker-v2-m3.
  8. Zhang Y. et al. Qwen3 Embedding: Advancing Text Embedding and Reranking Through Foundation Models. arXiv:2506.05176, 2025.
  9. Edge D. et al. From Local to Global: A Graph RAG Approach to Query-Focused Summarization. arXiv:2404.16130, 2024.
  10. Liu N. F. et al. Lost in the Middle: How Language Models Use Long Contexts. arXiv:2307.03172, 2023 — почему порядок и состав топ-10 важнее длины контекста.
  11. Upadhyay S. et al. UMBRELA: UMbrela is the (Open-Source Reproduction of the) Bing RELevance Assessor. arXiv:2406.06519, 2024 — шкала 0–3 и промпт LLM-судьи.

Идентификаторы arXiv сверены с API arXiv 25 октября 2026.