RAG and vector search: answering from your own documents
When retrieval beats fine-tuning, how to chunk documents, why hybrid search and reranking matter, and why citations matter more than a pretty answer.
Lesson content is currently available in Russian.
What you will learn
- →Обоснованно выбирать между RAG, fine-tuning и длинным контекстом.
- →Строить retrieval pipeline: chunking, гибридный поиск, reranking, context packing.
- →Измерять качество retrieval отдельно от качества генерации и показывать источники.
Когда RAG нужен, а когда мешает
RAG (retrieval-augmented generation) решает одну задачу: дать модели релевантные факты в момент запроса, вместо того чтобы надеяться на её веса. Сравним три подхода честно:
- RAG выигрывает, когда данные меняются (документация, база знаний, тикеты), когда нужны цитаты и проверяемость, и когда у разных пользователей разные права доступа — фильтровать можно на этапе поиска. Обновление знаний = обновление индекса, минуты, а не недели.
- Fine-tuning учит форму, а не факты: стиль, формат, следование специфичному протоколу. Заучивать fine-tuning-ом свежие факты — дорого, медленно и без возможности процитировать источник. Классическая ошибка — тюнить модель на базе знаний и удивляться галлюцинациям.
- Длинный контекст («просто положим все документы в промпт») работает до определённого объёма, но платите вы за каждый токен в каждом запросе, а качество использования середины длинного контекста заметно проседает. Для корпуса из тысяч документов это не вариант ни по деньгам, ни по качеству.
Когда RAG мешает: если знания стабильны и умещаются в компактный системный промпт (правила сервиса на две страницы) — retrieval добавит латентность, инфраструктуру и новые точки отказа без выигрыша. RAG — это поисковая система плюс генерация, и обе части нужно строить и мерить. Не начинайте с векторной базы; начинайте с вопроса «какие запросы и по каким документам мы должны отвечать и как проверим, что отвечаем правильно».
Chunking и метаданные
Документы режутся на чанки, потому что искать и класть в контекст нужно фрагменты, а не тома. Гранулярность — главный рычаг качества: слишком крупные чанки (2000+ токенов) размывают вектор — в один embedding попадает несколько тем, и поиск теряет точность; слишком мелкие (меньше ~100 токенов) теряют контекст — найденное предложение без окружения бесполезно и модели, и человеку.
Рабочие ориентиры, от которых можно отталкиваться:
- Размер чанка 200–800 токенов, overlap 10–20%, чтобы мысль, разрезанная границей, целиком попала хотя бы в один чанк.
- Structure-aware splitting: резать по заголовкам, спискам и абзацам, а не по числу символов. Таблицу не резать никогда — она теряет смысл; таблицы и код лучше индексировать отдельными чанками с текстовым описанием.
- К каждому чанку — заголовок его раздела и документа («Тарифы > Возвраты > Сроки»): это дёшево и заметно улучшает и поиск, и понимание модели.
Метаданные — вторая половина chunking, которую чаще всего забывают: id и версия документа, дата обновления, раздел, язык и — критично — права доступа. ACL-фильтрация обязана происходить на этапе поиска (фильтр по метаданным в запросе к индексу), а не после генерации: если чанк с чужими данными попал в контекст, утечка уже произошла, что бы вы ни вырезали из ответа. Дата обновления нужна для freshness: при конфликте версий документа выигрывать должен свежий, а устаревшие чанки должны уходить из индекса вместе с обновлением источника.
Retrieval pipeline: hybrid, rerank, packing
Продовый пайплайн поиска почти никогда не состоит из одного векторного запроса. Стандартная цепочка выглядит так:
- Гибридный поиск: dense (embeddings) ловит смысловые перефразировки, лексический BM25 ловит точные термины, артикулы и редкие слова, на которых embeddings слепнут. Результаты сливаются, обычно через Reciprocal Rank Fusion (RRF) — простой и устойчивый способ без подбора весов.
- Широкая выборка, узкая выдача: из индекса берут top 30–50 кандидатов, затем reranker (cross-encoder) пересортировывает их, читая пару «запрос + чанк» целиком. Bi-encoder на этапе индексации кодирует запрос и документ независимо — это быстро, но грубо; cross-encoder видит их взаимодействие и намного точнее, но дорог — поэтому его применяют только к кандидатам. В контекст уходит top 5–8 после rerank.
- Context packing: каждый чанк кладётся в промпт с идентификатором и источником, а инструкция требует ссылаться на id при каждом утверждении:
const context = chunks
.map((c, i) => '[' + (i + 1) + '] (' + c.source + ', ' + c.updatedAt + ')\n' + c.text)
.join('\n\n');
// Инструкция: 'Каждое утверждение подкрепляй ссылкой [n]. Нет опоры — так и скажи.'
Цитаты — это не украшение UI, а механизм проверяемости: они позволяют пользователю (и вашим evals) проверить каждое утверждение, а вам — отладить, какой чанк привёл к какому выводу. Ответ без цитат в RAG-системе должен считаться подозрительным по умолчанию.
Failure modes и метрики retrieval
Главная опасность RAG — ошибки выглядят убедительно. Модель, получившая нерелевантные чанки, не скажет «поиск не сработал» — она сгенерирует гладкий ответ из того, что есть, плюс собственные веса. Поэтому качество retrieval и качество генерации измеряются раздельно:
- Retrieval-метрики считаются на размеченном наборе пар «вопрос → релевантные чанки»: recall@k (какая доля нужных чанков попала в top-k), MRR (как высоко стоит первый релевантный). Если recall@10 низкий — никакой промпт генерацию не спасёт: чинить надо chunking, гибрид, reranker.
- Faithfulness генерации: доля утверждений ответа, реально подкреплённых переданными чанками. Проверяется LLM-судьёй по паре «ответ + контекст» и выборочной ручной разметкой.
Типовые режимы отказа, которые нужно ловить отдельными счётчиками:
- Пустой retrieval: релевантного нет, но модель всё равно отвечает — из весов. Лечится порогом релевантности и явным путём «данных недостаточно» как first-class ответом, который показывается пользователю честно, а не маскируется.
- Устаревший чанк: документ обновили, старая версия осталась в индексе и выигрывает по близости. Лечится дисциплиной переиндексации и фильтром по версии.
- «Почти правильный» ответ: чанк про смежную тему, ответ правдоподобен, но неверен. Самый опасный режим — ловится только evals и цитатами, по которым видно, что источник не о том.
Практический индикатор здоровья системы: доля ответов без единой цитаты и доля ответов «данных недостаточно». Резкое движение любой из них — повод смотреть traces, не дожидаясь жалоб.
Practice
Knowledge assistant
Соберите мини-ассистента по 20 документам: structure-aware chunking с метаданными, гибридный поиск (BM25 + embeddings, слияние через RRF), reranking, ответ с цитатами [n] и кнопка «показать найденные фрагменты». Разметьте 15 вопросов с ожидаемыми чанками и посчитайте recall@5 до и после включения reranker.
Deliverable
Работающий поиск с цитатами, список чанков, попавших в ответ, и табличка recall@5: гибрид без rerank против гибрида с rerank.
Checkpoint
Как понять, что retrieval провалился, даже если ответ звучит убедительно?
- ✓ Есть explainability через источники и цитаты, по которым можно проверить каждое утверждение.
- ✓ Retrieval-качество измеряется отдельно (recall@k / MRR на размеченных парах), а не только «нравится ли ответ».
- ✓ Путь «данных недостаточно» существует, показывается пользователю честно и отслеживается метрикой.
Check yourself
Sources