Содержание
- RAG — это конвейер, а не модель
- Не только векторный поиск
- Чанкинг и грязные документы
- Reranking: воронка точности
- Предобработка: метаданные, версии, дедупликация
- Где хранить векторы
- Agentic RAG и GraphRAG: когда они оправданы
- Узкое место смещается в reasoning
- Метрики: чем мерить качество
- Итог
- FAQ
- Источники
Модель знает, что написано в открытом интернете, и ничего не знает про ваш регламент, вашу базу тикетов и ваши договоры. Спросите её про сроки согласования закупки, и ответит она красиво и мимо.
Разрыв закрывают самым прямым способом. Прежде чем задать вопрос модели, по своим документам ищут подходящие куски текста и кладут их прямо в запрос. Приём называется RAG (Retrieval-Augmented Generation, генерация с дополнением через поиск), и весь он умещается в четыре стрелки: запрос → поиск в базе → найденное подмешивается в контекст → генерация.
Простота обманчива. Посмотрим, как этот конвейер устроен внутри, чем его чинят на сканах и таблицах и почему «улучшать поиск» перестаёт помогать ровно там, где вопрос становится многошаговым.
Дневник курса, урок 3. Пригодится разобранное в соседних постах: анатомия агента (урок 1) — почему база знаний и память агента живут в разных хранилищах; цикл агента на графе состояний (урок 6) — та петля, которую в Agentic RAG надстраивают над поиском. Пост читается отдельно: все термины вводятся заново.
1. RAG — это конвейер, а не модель
Демо, собранное за вечер, обычно работает. Подключили модель к папке с регламентами, задали три вопроса, получили три разумных ответа. Неприятности начинаются на четвёртом. Сотрудник спрашивает, сколько дней отпуска положено на удалёнке, и получает уверенный, связный, аккуратно оформленный неверный ответ. В логе видно, откуда он взялся. В контекст попали три фрагмента про командировки и ни одного про отпуск.
Модель здесь не ошиблась в привычном смысле слова. Ей дали текст и вопрос, и она добросовестно ответила по тексту. Отличать «ответ в контексте есть» от «ответа в контексте нет» — отдельный навык, и полагаться на него не стоит. По умолчанию модель исходит из того, что переданные ей куски к делу относятся, иначе бы их никто не передавал. Отсюда и правило, которое в RAG повторяют чаще любого другого. Качество retrieval (поиска) критичнее качества LLM, потому что мусор на входе даёт мусор на выходе. Флагманская модель на плохом поиске выдаёт ровно то же, что и слабая, только дороже и убедительнее.
Поэтому RAG разбирают не как «умную модель», а как конвейер из двух слоёв — офлайнового и онлайнового. И есть у него третья часть, которой в демо не видно вовсе: обратная связь, замыкающая генерацию обратно на индекс.
┌─▶ INDEXING → docs → chunking → embeddings → vector store
│ offline, тяжёлый, один раз
│ │
│ ▼
│ QUERY → запрос пользователя (online, на каждый запрос)
│ │
│ ▼
│ SEARCH → hybrid search — recall (полнота): 12–20 кандидатов
│ │
│ ▼
│ RERANK → reranking — precision (точность): 6–10 чанков
│ │
│ ▼
│ GENERATE → inject в контекст → генерация с цитатами
│ │
└──────┴── лайк/дизлайк, замер качества → правка индекса и базы знанийЗамыкающая стрелка на схеме — не украшение. Демо живёт один вечер, и корпус в нём зафиксирован. Прод живёт годами, корпус в нём правят, а следом за корпусом уезжают и запросы пользователей. Что именно течёт по этой стрелке и как её замкнуть, разбираем в разделе про метрики.
Верхняя половина схемы и нижняя живут по разным правилам, и это разделение определяет всю эксплуатацию. Индексация — дорогая разовая операция. Даже короткий документ нужно прогнать через нарезку и embedding-модель (модель векторного представления), а это заметно тяжелее одного поискового запроса. Поиск, наоборот, лёгкий, и гоняют его на каждое обращение пользователя. Отсюда и режим работы. Индекс строят один раз на мощном узле, сохраняют на диск и переиспользуют, а инференс крутят хоть на CPU.
Если нужна грубая аналогия, база знаний тут учебник, retrieval работает оглавлением, которое находит нужную страницу, а генерация играет роль ученика, который читает и формулирует. Качество ответа упирается в качество оглавления, а не в эрудицию ученика. Сколько бы ученик ни знал, отвечать он будет по той странице, которую ему открыли. Правда, работает эта аналогия ровно до тех пор, пока ответ лежит на одной странице.
2. Не только векторный поиск
Чистого векторного поиска в 2026 уже недостаточно, и убеждаются в этом обычно на однотипных жалобах от поддержки. Пользователь спрашивает, что делать с ошибкой GO-124, а в контекст приезжают куски про соседние коды ошибок и общие слова про сбои выгрузки. На вопросах вроде «как оформить отгул» тот же поиск работает прекрасно.
Отчего такая избирательность? Дело в том, как устроен эмбеддинг. Текст сжимается в одну точку пространства смыслов, и близость там меряется смысловая, а не символьная. «GO-124» и «GO-142» для модели, обученной на значениях, почти неразличимы, а редкий токен вносит в усреднённый вектор чанка мизерный вклад. Лексический BM25 работает наоборот. Он считает совпадения слов с поправкой на их редкость, поэтому точный код находит мгновенно, зато на «отгуле» не дотянется до документа, где то же самое названо заявлением на отпуск за свой счёт.
Слепые зоны у двух методов не пересекаются, и отсюда напрашивается очевидный ход. Hybrid search (гибридный поиск) запускает оба поиска и сливает результаты через RRF (Reciprocal Rank Fusion, слияние по обратному рангу).
RRF(d) = 1/(k + rank_bm25(d)) + 1/(k + rank_vector(d)), k ≈ 60
Документ, высоко стоящий в обоих списках, получает максимальный итоговый ранг. Обратите внимание, что формула складывает не баллы, а позиции. У косинусной близости и у BM25 шкалы несопоставимы, и сумма баллов зависела бы от того, чьи числа в этот раз оказались крупнее. Ранги такой беды не знают. Константа k (магическое число 60) сглаживает вклад низких позиций, иначе документ с первого места в одном списке перевешивал бы всё остальное. На recall-этапе берут топ-50 от BM25 и топ-50 от вектора, а RRF сводит их в топ-20.
Собирать гибрид обычно пробуют на том, что уже развёрнуто, и тут ждёт нюанс. PostgreSQL full-text search (tsvector/tsquery) даёт лексический поиск, но он не равноценен полноценному вероятностному BM25 из dedicated-систем. Для честного production-hybrid чаще берут отдельный слой (Qdrant с нативным sparse/BM25, OpenSearch).
Отдельные приёмы работают не с индексом, а с самим запросом. Они выручают, когда пользователь пишет коротко или неоднозначно и искать по такой формулировке попросту нечем.
- Query expansion — запрос расширяют синонимами, чтобы лексическая часть поиска зацепилась хоть за что-то.
- HyDE (Hypothetical Document Embeddings) — LLM генерирует «гипотетический ответ», и поиск идёт уже по нему, а не по голому вопросу. Расчёт на то, что придуманный ответ по форме и лексике ближе к искомому документу, чем вопрос. Близкий вариант — Query2doc.
- Parent-document retrieval (small-to-large) — ищем по мелким чанкам (точность retrieval), а в LLM отдаём родительский крупный документ (полнота контекста). Прибавка порядка 15–25% идёт по Context Precision — доле действительно относящихся к вопросу фрагментов среди тех, что уехали в контекст модели.
- Multi-hop (многошаговый поиск по цепочке документов) — связать факты из разных документов: туда обычный топ-K плохо дотягивается, это уже территория Agentic RAG и GraphRAG.
Два пункта из этого списка стоит брать с оговоркой, и оговорка невесёлая. В прод-экспериментах на русскоязычной базе знаний контактного центра прироста не дали ни переписывание запроса, ни HyDE.
Расширение синонимами и переписывание запроса — это разные вещи, хотя их часто зовут одним словом. Синонимы добавляются к исходной формулировке и дают по замерам +5–6% recall. Переписывание отдаёт вопрос модели целиком, а модель дописывает в него то, чего пользователь не спрашивал: галлюцинация на входе поиска работает ровно как галлюцинация на выходе, только заметить её труднее. HyDE спотыкается о другое. Придуманный ответ модель строит по своим общим представлениям о предмете, а в узком домене со своей терминологией и своими номерами регламентов этих представлений просто нет — вместо приближения к искомому документу в поиск уезжает правдоподобный шум. Рядом с ними в том же разборе оказался RAPTOR (иерархическая кластеризация чанков с суммаризацией каждого уровня): здесь дело не в качестве, а в том, что индексация перестаёт масштабироваться на большом корпусе.
Вывод отсюда мягче, чем «выбросить»: просто не закладывайте их в план как гарантированную прибавку. Все три остаются кандидатами на эксперимент, и решает его результат на вашем корпусе, а не строчка в обзоре техник.
Связка Hybrid Search + Reranking — это и есть минимальный продакшен-стандарт 2026. Освоив её, вы уже впереди большинства работающих внедрений.
3. Чанкинг и грязные документы
Чанкинг — первая точка, где ломается RAG, и ломается он тихо. Граница чанка определяет, окажется ли нужный факт целиком в одном фрагменте или расколется пополам, а половина факта не находится ни по одному разумному запросу. Эмбеддинг чанка усредняет то, что в него попало, и обрубок про «условия действуют до конца квартала» без указания, о каких условиях речь, семантически ни к чему не близок.
Классический провал того же рода — анафоры. Чанк «Его цена 500 руб» не знает, что «его» относится к товару X из соседнего фрагмента. Для человека, читающего документ подряд, местоимение прозрачно; для поиска, который видит только вырезанный кусок, это текст без субъекта.
Наивный fixed-чанкинг 512/128 — не универсальный дефолт. Нотация читается так. 512 токенов на чанк, 128 токенов перекрытия с соседним, чтобы факт, попавший на стык, целиком уместился хотя бы в один фрагмент. Режет такая нарезка по счётчику токенов, поэтому рвёт предложения и отделяет вопрос от ответа. Диагностика oversized-чанкинга проста. Возьмите 15 запросов из production-логов и измерьте плотность релевантных предложений в топовом чанке. Ниже 25% — нарезка слишком широкая, и модель ищет иголку в стоге собственного контекста. Рабочие ориентиры размеров ниже, и это не закон, а старт.
| Тип контента | Размер чанка | Overlap |
|---|---|---|
| Проза | 500–800 | 10–15% |
| Код | 200–400 | structure-aware (AST) |
| Регуляторика | 800–1200 | + parent-heading |
| Таблицы | row-by-row | + инъекция заголовков колонок |
Логика таблицы одна. Резать надо по границам смысла, а они у разных типов контента разные. У прозы такой границей служит абзац, у кода функция, у регуляторики целое определение вместе с номером и заголовком раздела, а у таблицы строка, бессмысленная без шапки.
Отдельная боль — грязные документы. Сканы, фото, многоколоночные PDF, таблицы с объединёнными ячейками. Голый текстовый экстрактор читает страницу как поток символов, поэтому две колонки склеиваются в чередующиеся обрывки строк, а таблица превращается в последовательность чисел без привязки к заголовкам. Чанк теряет смысл ещё до нарезки. Здесь нужен layout-aware препроцессинг: Docling (локально, парсит таблицы и формулы, держит таблицу в изолированном чанке), LlamaParse (облако, чистый иерархический Markdown), Reducto (вложенные заголовки, merged cells, multi-page таблицы), DocStrange (OCR по низкокачественным сканам).
Одна деталь на выходе парсера решает больше, чем кажется: таблицы кладите в чанк как JSON, а не как Markdown. Markdown-таблица держится на выравнивании пайпами, и связь ячейки с заголовком колонки в ней позиционная. Стоит парсеру потерять одну ячейку или чанкеру разрезать таблицу поперёк, и вся сетка съезжает на колонку влево — числа остаются, а значат уже другое. В JSON заголовок стоит рядом со значением в каждой записи, и разрезать эту связь нечем.
А можно ли починить анафоры, не трогая нарезку? Можно, на уровне самого эмбеддинга. Путей три, и третий снимает компромисс первых двух.
- Late Chunking (Jina, 2024) — весь документ сначала прогоняется через embedding-модель (self-attention видит всё), потом режется на чанки со span pooling. Порядок операций перевёрнут относительно наивной схемы, и каждый токен уже «знает» глобальный контекст, без доп. LLM-вызовов. Ограничение — контекстное окно модели. Документ, который в него не помещается, обработать так не выйдет.
- Contextual Retrieval (Anthropic, 2024) — LLM дописывает к каждому чанку краткий контекст документа перед эмбеддингом. Решает анафоры лучше всех, но дорого: LLM-вызов на каждый чанк. «Для VIP-документов».
- voyage-context-3 — нативные contextualized-эмбеддинги в один проход, без ручной аугментации и без лимита окна. Замер идёт по качеству поиска на наборе тестовых запросов. Когда ищут по отдельным чанкам, метод обходит late chunking на 23.66% и contextual retrieval на 6.76%; когда ранжируют документы целиком — на 20.54% и 2.40%. Проценты здесь относительные, то есть прибавка к результату соперника, а не пункты метрики. Это снимает trade-off «дорогой Contextual против ограниченного окна Late Chunking».
4. Reranking: воронка точности
Векторный поиск (bi-encoder) кодирует вопрос и документ независимо и сравнивает косинусом. Отсюда его скорость. Вектор документа посчитан один раз при индексации и лежит готовым, на запрос остаётся сравнить числа. Отсюда же и его грубость. Документ сжали в вектор до того, как узнали вопрос, и что именно спросят, при сжатии учесть было нельзя.
Cross-encoder подаёт пару «вопрос + документ» в модель вместе, и внимание сопоставляет слова напрямую, поэтому он ранжирует точнее. Плата ровно та, которой можно было ожидать. Ничего не посчитаешь заранее, ведь каждая пара требует полного прохода модели. Гонять его по всей базе нельзя, ведь прогон по корпусу в миллион документов занимает десятки минут, а при неудачном железе и около часа на один запрос.
Как совместить точность одного с дешевизной другого? Поставить cross-encoder сужающейся воронкой, только к уже отфильтрованному топу.
hybrid search ──▶ over-retrieve: 12–20 кандидатов
cross-encoder ──▶ rerank down: 6–10
финал ──▶ в промпт LLMЭто прямой контраст с дефолтным top-3. Сначала набрать с запасом, потом точно отсеять. Дешёвый этап отвечает за полноту и имеет право ошибаться в порядке, дорогой наводит порядок на двух десятках кандидатов вместо миллиона.
Reranking обычно поднимает NDCG@10 на 5–15 пунктов, а на лексически сложных наборах и больше. NDCG@10 (Normalized Discounted Cumulative Gain) оценивает качество порядка в первой десятке результатов. Релевантный документ на второй позиции приносит больше, чем он же на девятой, а итог нормируется на идеальную выдачу, поэтому шкала укладывается в диапазон от нуля до единицы. Ближе к нижней границе прибавка оказывается там, где базовый поиск уже силён. Окупается и такая, потому что reranking — самый дешёвый по усилиям шаг конвейера. Он и входит в production-baseline вместе с hybrid search. По скорости ориентируются на P95 ниже 3 секунд и TTFT (время до первого токена) ниже 500 мс.
Пройдём по конвейеру тем самым вопросом про ошибку GO-124. Лексическая ветка находит четыре чанка, где код упомянут дословно, в том числе строку таблицы кодов и абзац из инструкции поддержки. Векторная ветка кода не замечает вовсе, зато приносит раздел про сбои выгрузки отчётов, где симптом описан словами и никакого номера нет. RRF ставит наверх то, что попало в оба списка, и отдаёт шестнадцать кандидатов. Cross-encoder прогоняет шестнадцать пар «вопрос + чанк», видит, что абзац из инструкции отвечает на вопрос прямо, а строка таблицы только называет код, и оставляет восемь чанков в нужном порядке. В промпт уходят они, а не первые три по косинусу.
from sentence_transformers import CrossEncoder
reranker = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
def rerank(question, candidates, k=8):
pairs = [(question, c.page_content) for c in candidates] # пара вместе
scores = reranker.predict(pairs) # точный балл
ranked = sorted(zip(scores, candidates), key=lambda x: x[0], reverse=True)
return [c for _, c in ranked][:k]
Из мультиязычных reranker'ов текущим ориентиром служит jina-reranker-v3 (0.6B, listwise, BEIR 61.94 nDCG@10, покрывает русский). Правда, конкретные SOTA-модели устаревают быстро, так что на эту цифру не молиться.
С этого места и начинается собственно внедрение. Гибридный поиск плюс переранжирование закрывают около 80% сценариев, и всё, что дальше, надстраивается над ними по одной технике за раз. Порядок такой:
1. BASELINE гибридный поиск, рекурсивный чанкинг, один реранкер
гейт: Hit Rate, NDCG
2. OPTIMIZATION многоступенчатое переранжирование, parent-document,
расширение запроса
гейт: + Faithfulness, Answer Relevancy
3. ADVANCED contextual retrieval, сжатие контекста, late chunking
гейт: + Noise Robustness
4. ENTERPRISE Agentic RAG / GraphRAG, multi-hop,
замкнутый цикл обратной связи
гейт: + User SatisfactionСлово «гейт» здесь буквальное: к следующей фазе не переходят, пока метрики текущей не взяты. Правило выглядит бюрократическим, а спасает от вполне конкретной беды. Техники третьей и четвёртой фазы дороги, и каждая меняет сразу несколько мест конвейера. Если запустить их поверх непонятного baseline, разбираться потом придётся не с одним изменением, а со всеми сразу — и первый же провал по качеству будет нечем локализовать.
5. Предобработка: метаданные, версии, дедупликация
Поиск полгода в проде, качество заметно просело, и никто ничего не менял. Последняя часть фразы и есть диагноз. Корпус меняется сам. Документы правят и удаляют, копии расползаются по дискам, а эмбеддинги остаются теми, что посчитали при запуске. Нам понадобится несколько вещей, без которых прод тихо деградирует.
- Метаданные на каждом чанке:
doc_id, заголовок секции,url/путь, timestamp, версия embedding-модели (детект дрейфа) и content hash. Стоят они копейки на индексации и окупаются в первый же разбор жалобы: безdoc_idи пути вы не ответите даже на вопрос, откуда взялся показанный пользователю фрагмент. - Дедупликация в два слоя: идентичные файлы отсекаются SHA-256 по каноническому содержимому; near-duplicate ловят через MinHash + Jaccard и LSH-джойны (Locality-Sensitive Hashing) и выпиливают. Второй слой важнее первого: пять почти одинаковых редакций одного регламента забивают топ выдачи собой, вытесняя оттуда всё остальное, и модель получает пять раз один и тот же абзац вместо пяти разных.
- Жизненный цикл (lifecycle) / каскадные удаления: векторные БД не делают нативный update. Когда документ изменён или удалён, нужен Document Registry (
doc_id → chunk_ids), чтобы найти и зачистить устаревшие чанки и эмбеддинги, иначе в выдаче всплывают мёртвые версии — и это худший тип ошибки, потому что отменённый регламент выглядит в ответе точно так же убедительно, как действующий. - Embedding drift: устаревшие векторы тихо теряют за год 10–20 пунктов метрик поиска — тех самых Hit Rate и NDCG. Двигается тут не индекс, а всё вокруг него: в корпус приезжает лексика, которой год назад не было (новые продукты, коды, названия команд), запросы пользователей идут за корпусом, а часть векторов посчитана предыдущей версией модели, снятой вендором с обслуживания. Лечение — держать версию модели в метаданных чанка и переиндексировать, когда доля старых векторов растёт.
- Faithfulness floor: у привязки ответа к источникам стоит держать не один порог, а шкалу. Цель на проде — около 90%; ниже 0,85 (планка RAGAS) уходит предупреждение; ниже 0,80 — critical, релиз останавливается; 70% — абсолютный минимум, под которым систему нельзя показывать пользователю вообще. Шкала нужна затем, чтобы деградация была видна до того, как станет заметна снаружи.
Смена модели эмбеддингов — это, по сути, миграция схемы БД. Каждая модель раскладывает смыслы по своим осям, и координаты одного пространства в другом не значат ничего. Старые векторы геометрически несовместимы с новыми запросами, так что нужна полная переиндексация. Делают её zero-downtime через alias: строят теневой индекс новой моделью, валидируют на бенчмарк-наборе, атомарно переключают alias, старый держат для отката.
6. Где хранить векторы
После чанкинга и эмбеддинга векторы надо где-то держать и быстро искать ближайших соседей. Точный перебор здесь не годится. Миллион векторов — это миллион скалярных произведений на каждый запрос, поэтому ищут приближённо (ANN), торгуя долей пропущенных соседей за скорость. Выбираем хранилище по масштабу, скорости и потребности в фильтрации.
| БД | Масштаб | Сильная сторона |
|---|---|---|
| FAISS / Chroma | in-memory | Прототип, минимум настройки, persist на диск |
| pgvector (Postgres) | комфортно до ~10M, деградация за 10–20M | Развёрнут везде; embeddings рядом с relational data |
| Qdrant | >100M, единицы мс* | Лучшая фильтрация по метаданным, нативный BM25 |
| Weaviate | — | Популярный универсал |
| Milvus | >100M, K8s | Лучшее масштабирование, RRF из коробки |
| Redis | — | Низкая latency, vector-поиск поверх кэша |
Выбирают обычно по второй колонке, а решает чаще третья. В живом приложении поиск почти никогда не идёт по всему корпусу. Нужны документы этого клиента, этой версии продукта, доступные этой роли. Фильтр по метаданным поверх приближённого поиска — операция нетривиальная, и на ней хранилища расходятся сильнее, чем на голой задержке.
Про потолок pgvector важно без догматизма. Малые и средние корпуса он тянет уверенно, особенно когда векторы — атрибут relational-строки и не нужна ни селективная фильтрация, ни hybrid. Комфортный ориентир 2026 года — до ~10M векторов; деградация начинается за 10–20M, а реальным ограничением оказывается не число векторов, а память под HNSW-индекс.
В обзорах при этом гуляет куда более щедрая оценка потолка, 50 миллионов векторов. Этот порог не брать как дефолт без своего бенчмарка. Строгие числа (включая «единицы мс» задержки Qdrant, отмеченные *) идут от вендора, а в независимых замерах на 10M задержка ближе к 20+ мс. Когда нужна filterable HNSW, sparse/BM25 или multivector, переходят на dedicated store. Есть и отдельная ловушка. ColBERT эмбеддит каждый токен, и 100k документов превращаются в 10M+ векторов: корпус, который выглядел крошечным, внезапно упирается в верхнюю границу комфортной зоны.
7. Agentic RAG и GraphRAG: когда они оправданы
Линейный RAG делает один проход и на вопросах вида «что написано в документе про X» работает отлично. На вопросе «как связаны X и Y, если они упомянуты в разных документах» он выдаёт пустоту, и не потому, что поиск слабый. Промежуточное звено цепочки не похоже на исходный вопрос. Похоже оно на промежуточный ответ, которого у нас ещё нет, и искать по сходству с вопросом тут просто нечего.
Две архитектуры из Enterprise-фазы надстраиваются над линейным RAG именно ради таких случаев. Обе дорогие, и включать их «по умолчанию» — ошибка.
Agentic RAG ставит над поиском агентный цикл: query planner декомпозирует запрос, retrieval agent выбирает источник (vector / SQL / API), reflection через LLM-as-Judge проверяет полноту, и при нехватке данных запрос переписывается и поиск повторяется. Один проход превращается в несколько, и каждый следующий формулируется по результатам предыдущего, что и снимает single-shot-ограничение на multi-step и cross-system задачах.
Однако за автономию приходится платить, и платит ровно та часть, которая даёт выигрыш. Решение «данных достаточно» принимает вероятностная модель, а она склонна останавливаться рано. На верификации фактов (FEVER) у Agentic RAG recall падает до ~49%, потому что агент решает, что искать больше нечего, и прекращает поиск. Детерминированный Enhanced RAG на тех же данных держит ~84%. Под этим названием понимают конвейер с фиксированной программой шагов — расширить запрос, поискать, переформулировать, поискать ещё раз. Программа отрабатывает всегда до конца, и решения «хватит» модель в ней не принимает вовсе.
Тут уместно вернуться к границе из первого урока. Там выбор между агентом и жёстко прописанной цепочкой шагов упирался в вопрос, можно ли проверить результат автоматически. Здесь у того же выбора появляется цена в цифрах — 49% против 84% на задаче, где проверять как раз есть чем. Платит за автономию полнота поиска.
Поэтому Agentic RAG не берут под жёсткий SLA, ограниченный бюджет или требование воспроизводимости (финансы, медицина). Цикл, который сам решает, сколько ему шагов, невозможно ни уложить в бюджет, ни повторить дважды одинаково.
GraphRAG заходит с другой стороны и меняет саму единицу хранения. Вместо изолированных чанков строится граф знаний: LLM извлекает из документов сущности и связи между ними, и ответ собирается обходом по рёбрам, а не подбором похожего текста. Связь «X работал в компании Y», найденная в одном документе, и связь «Y основана в городе Z» из другого становятся двумя рёбрами одного графа, и путь между ними существует физически. Незаменим для global / multi-hop вопросов («какие основные темы во всём архиве?», «как связаны X и Y из разных документов?»), где топ-K похожих чанков бессилен.
Полноту охвата темы (comprehensiveness) на global-вопросах меряют попарным сравнением. LLM-судье показывают два ответа на один вопрос — от графового пайплайна и от обычного векторного — и спрашивают, какой полнее. Графовый выигрывает в 72–83% пар. Расплатой становится отдельная база поверх векторной, а это стоимость памяти и индексации примерно ×2, и сам граф тоже строит LLM. Семейство расширяется (NodeRAG, RAPTOR, TreeRAG), доступно коробочно в RAGFlow и Dify.
На слове «коробочно» стоит задержаться, особенно если корпус русскоязычный. Развернуть RAGFlow или Dify — это часы против недель ручной сборки, и внутри уже лежат и гибридный поиск, и переранжирование, и графовые режимы. Плата видна в замере качества поиска: у коробочных платформ MRR обычно держится в диапазоне 0.50–0.70, у ручной сборки — 0.70–0.90 и выше. Часть этого разрыва наша персонально. Парсеры, эмбеддинг-модели и реранкеры в коробках подобраны под латиницу, на кириллице каждое звено отрабатывает чуть хуже, а звеньев в конвейере пять — и потери перемножаются. Так что коробка хорошо отвечает на вопрос «где у нас на самом деле болит» и плохо — на «как выжать максимум».
Для advanced-сборки этих пайплайнов удобен LlamaIndex, где QueryFusionRetriever оркестрирует семантико-лексический hybrid (dense + sparse BM25) из коробки, а поверх ложатся reranking и агентные flow.
8. Узкое место смещается в reasoning
Тезис «качество упирается в retrieval» верен, пока запрос простой. На multi-hop он перестаёт быть полным, и заметно это по характерной картине. Вы усиливаете поиск, Hit Rate растёт — то есть нужный документ всё чаще попадает в топ выдачи, — а доля верных ответов стоит на месте. Свежие замеры на Graph-RAG показывают разрыв напрямую. Покрытие контекста (gold-ответ где-то в найденном тексте) достигает 77–91%, а итоговая точность ответа держится всего между 35% и 78%. То есть нужное уже найдено, но модель не может его связать.
retrieval coverage █████████████████░░░ 77–91% (нашли)
response accuracy ███████████░░░░░░░░░ 35–78% (ответили)
└─ разрыв = reasoning failureРазбор ошибок подтверждает диагноз. На reasoning failures приходится 73–84% случаев, а не на промахи поиска. Сильный поиск не гарантирует сильного ответа. И это объяснимо, если посмотреть, о чём мы просим модель на таком вопросе. В контексте лежат пять-шесть фрагментов, два из них — звенья цепочки, остальные похожи на них по теме и служат дистракторами. Собрать из этого вывод в два-четыре шага модель должна сама, без подсказки, какие именно фрагменты стыкуются.
Лечение здесь — не «ещё лучше искать», а структурировать рассуждение. Structured prompting (например, SPARQL chain-of-thought на графовых пайплайнах, где модель сначала выписывает шаги как запрос к графу и только потом отвечает) даёт +7.6 п.п. на 2WikiMultiHopQA. Свежий приём ConRAG согласует relational / entity / textual сигналы в едином ранжировании и прибавляет до 10.2 пункта Accuracy на MuSiQue, самом сложном из multi-hop бенчмарков (2–4 шага рассуждения, похожие дистракторы).
Отсюда практический вывод. Прежде чем усложнять retrieval, померим, где именно ломается цепочка — в поиске или в рассуждении. Иначе легко полгода полировать поиск, который и так приносит нужное. Для такого замера появились process-level инструменты: RAGCap-Bench оценивает планирование, извлечение свидетельств, grounded inference (вывод с опорой на найденные источники) и устойчивость к шуму по отдельности; AgenticRAGTracer делает hop-aware трассировку и показывает, на каком конкретно хопе агент «схлопнулся» или «переусердствовал». Одной цифре итоговой точности верить не стоит, ведь она прячет, на каком этапе утекло качество.
9. Метрики: чем мерить качество
Разрыв между coverage и accuracy — частный случай общего правила. Одна итоговая цифра прячет место утечки. Поэтому качество RAG меряют не одним числом, а дашбордом из ~12 показателей, разбитых по группам, и каждая группа отвечает на свой вопрос: нашли ли (Retrieval), хорошо ли ответили (Generation), как сработал конвейер целиком (End-to-End). Если итоговая точность просела, именно разбивка по группам показывает, где именно течёт, в поиске, в рассуждении или в склейке контекста.
С чего начинать? С Hit Rate. Если документы не находятся, остальные метрики бессмысленны, потому что улучшать генерацию поверх пустого контекста нечего. Вторая по приоритету — Faithfulness. Галлюцинации остаются главной проблемой RAG в проде, и ловит их именно она.
| Метрика | Группа | Цель | Что измеряет |
|---|---|---|---|
| Hit Rate@K | Retrieval | ≥ 0.85 | recall: попал ли релевантный документ в топ-K |
| MRR | Retrieval | ≥ 0.75 | позиция первого релевантного |
| NDCG@K | Retrieval | ≥ 0.70 | качество ранжирования с учётом позиции |
| Faithfulness | Generation | ≥ 0.85 | доля утверждений ответа, подтверждённых контекстом (галлюцинации) |
| Answer Relevancy | Generation | ≥ 0.80 | соответствие ответа вопросу |
| Answer Correctness | Generation | ≥ 0.75 | F1 + семантическое сходство с эталоном |
| Toxicity | Generation | ≤ 0.05 | безопасность ответа |
| Context Precision | End-to-End | ≥ 0.70 | доля релевантных чанков в контексте |
| Context Recall | End-to-End | ≥ 0.80 | покрытие всех нужных документов |
| Context F1 | End-to-End | ≥ 0.75 | баланс Precision и Recall |
| Noise Robustness | End-to-End | ≥ 0.90 | устойчивость к нерелевантным чанкам в контексте |
| Latency P95 / TTFT | Операц. | < 3 c / < 500 мс | время ответа end-to-end и до первого токена |
Пороги — ориентир, а не закон, и для русскоязычного домена реалистичные планки стоит откалибровать на своём наборе. Faithfulness в таблице стоит по планке RAGAS (≥0.85), средней ступени уже описанной шкалы: цель ~0,90, предупреждение ниже 0,85, critical ниже 0,80, абсолютный минимум 0,70.
Считать всё это руками не нужно. RAGAS — open-source стандарт де-факто. LLM-as-Judge оценивает Faithfulness, Answer Relevancy, Context Precision/Recall без ground truth, плюс автогенерирует синтетический eval-датасет из ваших документов (from ragas import evaluate). Работа без эталонов тут принципиальна. Размеченный набор вопросов по вашему корпусу никто не подарит, а собирать его руками дорого, и на этом попытки мерить качество обычно заканчиваются. Рядом стоят DeepEval (pytest-стиль для CI/CD), RAGChecker (Amazon, claim-level диагностика) и TruLens (RAG Triad). Offline-прогон на GPT-4o-судье обходится примерно в $0.05–0.20 за запрос, и цену можно сбить локальной моделью.
Метрики не считают разово. Их встраивают в замкнутый цикл, и замер идёт на каждой фазе внедрения, а к следующей технике не переходим, пока текущие пороги не взяты.
ЛОГИРОВАНИЕ → query, retrieved_chunks, answer, latency, feedback
│
▼
ОЦЕНКА → online (без GT): cosine, Faithfulness
offline: тест-датасет 50–200 вопросов
│
▼
МОНИТОРИНГ → дашборд + алерты при падении ниже порога
│
▼
УЛУЧШЕНИЕ → анализ провалов, A/B на одном параметре, реиндекс/промпт
│
└──▶ повторный замер → цикл с начала- Логирование — на каждый запрос сохраняют
query,retrieved_chunks,answer,latencyиfeedback(лайк/дизлайк, копирование ответа как implicit-сигнал). Retrieved-чанки пишут целиком: без них разбор жалобы упирается в вопрос «а что модель вообще видела», и ответить на него задним числом уже нечем. - Оценка — online без ground truth (cosine similarity, Faithfulness через LLM-as-Judge, Toxicity-классификатор на каждый ответ) и offline на тест-датасете 50–200 вопросов с эталонами, прогоняемом при каждой смене модели, промпта или индекса.
- Мониторинг — дашборд со скользящими средними и алерты: Faithfulness < 0.80 → critical, Hit Rate < 0.75 → warning, Latency P95 > 5 c → warning.
- Улучшение — классифицировать провал (retrieval / ranking / generation / missing knowledge), приоритизировать по частоте и бизнес-импакту, прогнать A/B, поменяв ровно один параметр, и сравнить все метрики до/после. Один параметр за прогон — не занудство: чанкинг, эмбеддинги и промпт влияют друг на друга, и при двух правках разом вы узнаете только то, что стало лучше или хуже, но не благодаря чему.
Четвёртый тип провала в этой классификации конвейером не лечится вовсе. missing knowledge означает, что нужного документа в базе нет, и сколько ни правь нарезку с эмбеддингами, находить нечего. Долю таких запросов меряют отдельно — метрикой KB Coverage, то есть долей обращений, закрытых базой знаний. Ориентир: выше 90% — норма, 75–90% — предупреждение, ниже 75% — critical.
Счётчик этот адресован авторам документации, а не инженерам, и работа по нему расписывается в пять шагов. Собрать запросы с низким Hit Rate. Скластеризовать их по темам. Увидеть, каких тем в базе нет вообще. Написать или обновить документы вместе с доменным экспертом. Переиндексировать и сравнить метрики до и после на том же тестовом наборе. Заодно это ответ на вопрос, что течёт по замыкающей стрелке из схемы в начале поста: генерация → оценки и жалобы → правка базы знаний → индексация. Цикл замкнулся, и без последнего звена RAG остаётся конвейером, который умеет только деградировать.
Дальше петля выходит за границы RAG и становится обычной эксплуатацией сервиса. Что писать в трейс каждого шага и как из накопленных трейсов вырастает набор автоматических проверок, разбирается в посте про мониторинг агента (урок 7). Отдельная задача — сшить трассировку так, чтобы она пережила несколько сервисов и версий; про это пост про наблюдаемость AI-сервисов. А превращение замера в гейт, который не пускает просевшую версию дальше стенда, разбирается в посте про релизы LLM-сервисов.
Без этого цикла RAG не отличить от демо. Цифры показывают текущее качество и заодно место, где оно утекает между фазами конвейера.
Итог
- Качество ответа упирается в retrieval — но только пока запрос простой: мусор на входе = мусор на выходе, и флагманская модель этого не спасёт.
- Минимальный продакшен-стандарт 2026 — Hybrid Search + Reranking: он самый дешёвый по усилиям и закрывает большинство сценариев. Всё остальное (Agentic RAG, GraphRAG, contextualized-эмбеддинги) — надстройки под конкретную боль, не дефолт.
- Внедряют это по фазам — baseline → optimization → advanced → enterprise, — и к следующей не переходят, пока метрики текущей не взяты. Иначе локализовать провал будет нечем.
- Переписывание запроса, HyDE и RAPTOR в прод-замерах на русскоязычном корпусе прироста не дали. Расширение синонимами дало (+5–6% recall). Разница между этими приёмами гораздо больше, чем между их названиями в обзорах.
- На multi-hop узкое место смещается из поиска в reasoning: нужное уже найдено (coverage 77–91%), но модель не связывает факты (accuracy 35–78%). Лечат не «ещё лучше искать», а структурированием рассуждения.
- Мерить надо не одной цифрой, а дашбордом по группам (Retrieval / Generation / End-to-End), начиная с Hit Rate и Faithfulness, и встраивать замер в замкнутый цикл логирование → оценка → мониторинг → улучшение.
Если сводить всё к одной фразе, она такая: конвейер RAG замкнут в цикл. Прямая часть — от документа до ответа — собирается за неделю и на демо выглядит законченной. Обратная — от дизлайка через замер обратно к нарезке, эмбеддингам и самой базе знаний — не выглядит никак, потому что видна только на дистанции в месяцы. Ровно поэтому и фазы внедрения, и пороги метрик, и KB Coverage стоят в одном ряду: все они про то, чтобы обратная стрелка существовала.
FAQ
Чем hybrid search лучше чисто векторного?
У каждого метода своя слепая зона. Вектор «размазывает» точные ID, коды и имена собственные («ошибка GO-124», «договор №123»), а лексический BM25 пропускает синонимы и смысл. Hybrid берёт топ от обоих и сливает ранги через RRF (Reciprocal Rank Fusion), так что документ, высоко стоящий в обоих списках, получает максимальный итоговый ранг. Это и есть минимальный продакшен-baseline вместе с reranking.
Когда нужен GraphRAG, а когда он лишний?
GraphRAG оправдан на global и multi-hop вопросах вроде «какие основные темы во всём архиве?» и «как связаны X и Y из разных документов?», где топ-K похожих чанков бессилен. По полноте охвата темы графовый ответ выигрывает у обычного векторного в 72–83% попарных сравнений, судит LLM. Расплатой становится отдельная база знаний поверх векторной, а память и индексация дорожают примерно вдвое. Для линейного «найди факт в документе» это избыточно, и включать его по умолчанию будет ошибкой.
Какие метрики мерить у RAG?
Не одну итоговую цифру, а дашборд по трём группам: Retrieval (Hit Rate@K, MRR, NDCG), Generation (Faithfulness, Answer Relevancy, Toxicity) и End-to-End (Context Precision/Recall, Noise Robustness) плюс операционные Latency P95 / TTFT. Начинать с Hit Rate, потому что при ненаходимых документах остальное бессмысленно. Второй по приоритету идёт Faithfulness, она ловит галлюцинации. Считать удобно через RAGAS без ground truth.
pgvector или Qdrant?
По масштабу и потребности в фильтрации. pgvector хорош, когда векторы служат атрибутом relational-строки и не нужны ни селективная фильтрация, ни hybrid. Комфортный ориентир тут до ~10M векторов, дальше начинается деградация. Когда нужны filterable HNSW, нативный sparse/BM25 или multivector, либо корпус крупнее, переходят на dedicated store вроде Qdrant. Гуляющий по обзорам потолок в 50 миллионов векторов не стоит брать как дефолт без собственного бенчмарка, ведь реальным ограничением оказывается память под HNSW-индекс, а не число векторов.
Что делать с таблицами и сканами?
Голый текстовый экстрактор разрушает структуру таблицы, и чанк теряет смысл, поэтому нужен layout-aware препроцессинг вроде Docling (локально, парсит таблицы и формулы, держит таблицу в изолированном чанке), LlamaParse (облако, иерархический Markdown), Reducto (merged cells, multi-page таблицы), DocStrange (OCR по низкокачественным сканам). Таблицы режут row-by-row с инъекцией заголовков колонок, а структурные заголовки секций добавляют в текст чанка до эмбеддинга.
В каком порядке внедрять RAG-техники?
По четырём фазам, с замером на каждой. Baseline — гибридный поиск, рекурсивный чанкинг и один реранкер, гейт по Hit Rate и NDCG; эта связка закрывает около 80% сценариев. Optimization — многоступенчатое переранжирование, parent-document и расширение запроса, добавляется гейт по Faithfulness и Answer Relevancy. Advanced — contextual retrieval, сжатие контекста, late chunking, плюс Noise Robustness. Enterprise — Agentic RAG или GraphRAG, multi-hop и замкнутый цикл обратной связи. К следующей фазе не переходят, пока пороги текущей не взяты.
Работают ли HyDE и переписывание запроса на практике?
По прод-замерам на русскоязычном корпусе — нет. Переписывание отдаёт вопрос модели целиком, и модель дописывает в него то, чего пользователь не спрашивал, так что поиск после этого становится хуже. HyDE строит «гипотетический ответ» по общим представлениям модели о предмете, а в узком домене со своей терминологией эти представления мимо, и в поиск уходит правдоподобный шум. Расширение синонимами — другой приём, и он как раз работает: +5–6% recall. RAPTOR упирается не в качество, а в масштабирование индексации.
Когда брать Agentic RAG?
Когда задача multi-step или cross-system и нужно декомпозировать запрос, выбирать источник и при нехватке данных переписывать запрос и искать снова. Но за автономию приходится платить. На верификации фактов (FEVER) recall у Agentic RAG падает до ~49% против ~84% у детерминированного pipeline, потому что агент рано решает, что данных достаточно. Поэтому его не берут под жёсткий SLA, ограниченный бюджет или требование воспроизводимости (финансы, медицина).
Источники
- The Reasoning Bottleneck in Graph-RAG (arXiv 2603.14045) — на multi-hop узкое место не retrieval, а reasoning: coverage 77–91% при accuracy 35–78%, 73–84% ошибок — reasoning failures.
- voyage-context-3 — contextualized chunk embeddings (Voyage AI) — третий путь между Late Chunking и Contextual Retrieval: native contextualized-эмбеддинги в один проход.
- ConRAG — Consensus-Driven Multi-View Retrieval (arXiv 2605.28093) — согласование relational/entity/textual сигналов в едином ранжировании, до +10.2 Accuracy на MuSiQue.
- RAGCap-Bench (arXiv 2510.13910) — process-level eval для Agentic RAG: планирование, извлечение свидетельств, grounded inference, устойчивость к шуму по отдельности.
- AgenticRAGTracer (arXiv 2602.19127) — hop-aware трассировка: на каком конкретно хопе цепочка «схлопнулась» или «переусердствовала».
- Build an unstructured data pipeline for RAG (Databricks) — двухслойная дедупликация (SHA-256 + MinHash/LSH) и каскадные удаления устаревших чанков.
- RAG Anti-Patterns: 7 Failure Modes (Digital Applied) — антипаттерны прод-RAG с измеримыми порогами: oversized chunking, shallow retrieval, embedding drift, faithfulness floor.
Числовые ориентиры из текста — пороги метрик, бенчмарки векторных БД и проценты приростов — зависят от корпуса и профиля нагрузки; на своих данных и своей нагрузке они будут другими, калибруйте на собственном наборе.