RAG II: стратегии поиска, роутинг запросов и Text-to-SQL
В прошлой статье мы построили индексацию для RAG: подготовили документы, нарезали на чанки, посчитали эмбеддинги и записали их в векторное хранилище. Теперь переходим ко второй части: как правильно искать и как разговаривать с данными в онлайне.
Эта статья продолжает линию курса:
из первой статьи вы берёте базовые кирпичики LangChain (промпт → модель → парсер, LCEL)
из второй статьи вы берёте готовый индекс (чанки, метаданные, векторная база)
в этой статье вы собираете retrieval-пайплайны, которые дают качество в реальных сценариях!Общая карта того, где именно применяются стратегии retrieval
Базовый RAG-пайплайн: Retrieve-Augment-Generate
Минимальная рабочая схема RAG выглядит так:
вы получаете вопрос пользователя
находите релевантные фрагменты (чанки) через retrieval
добавляете эти фрагменты в промпт как контекст
LLM генерирует ответ, опираясь на контекстВ LangChain это обычно выражается как runnable-цепочка.
Даже в этой простой схеме есть места, где чаще всего падает качество:
запрос пользователя слишком короткий, расплывчатый или содержит неявный контекст
ретривер возвращает похожие, но не отвечающие на вопрос чанки
в контексте много шума и дубликатов
вопрос требует точной агрегации данных, а не семантического поискаДальше мы разберём техники, которые адресуют эти проблемы.
Ручки качества retrieval: что настраивают в первую очередь
Прежде чем усложнять архитектуру, убедитесь, что вы используете базовые настройки осознанно.
k (top-k): сколько чанков возвращать
score threshold: порог релевантности, ниже которого документы отбрасываются
фильтры по метаданным: продукт, версия, язык, права доступа
diversity: в выдаче должны быть не только дубликаты одного и того же фрагментаПрактическое правило: если у вас нет метаданных и фильтрации, вы почти всегда будете проигрывать в качестве на корпоративных данных.
Rewrite-Retrieve-Read: переписываем запрос, затем ищем, затем отвечаем
Rewrite-Retrieve-Read — паттерн, который добавляет шаг переписывания запроса перед retrieval.
Зачем переписывать запрос:
пользователь пишет «как это починить», но не уточняет сущность
в вопросе есть лишние детали, которые ухудшают поиск
вопрос сложный и его нужно превратить в короткую «поисковую формулировку»Минимальная схема
LLM получает исходный вопрос
LLM возвращает улучшенную поисковую формулировку
ретривер ищет по улучшенному запросу
LLM отвечает по найденному контекстуПрактический совет: сохраняйте оригинальный вопрос для ответа пользователю, а переписанный используйте только для retrieval. Это уменьшает риск «потери смысла».
Multi-Query Retrieval: несколько переформулировок вместо одной
Если один запрос даёт нестабильную выдачу, помогает стратегия Multi-Query: LLM генерирует несколько вариантов запроса, затем вы ищете по каждому и объединяете результаты.
плюс: растёт recall (шанс найти нужное)
минус: дороже, потому что retrieval вызывается несколько разВ LangChain есть готовый компонент.
Ссылка: MultiQueryRetriever (LangChain how-to)
Когда Multi-Query особенно полезен:
пользователи используют разные термины для одного и того же
документация содержит синонимы, аббревиатуры, внутренние названия
вопросы часто «не в терминах документации»RAG-Fusion: объединяем выдачи разных запросов аккуратно
Если вы делаете Multi-Query, возникает вопрос: как объединять результаты?
RAG-Fusion — популярный подход, в котором вы:
генерируете несколько запросов
получаете несколько списков документов
объединяете их через Reciprocal Rank Fusion (RRF): документ тем выше, чем чаще и чем выше он встречается в ранжированных спискахФормула RRF часто записывается так:
Где:
— конкретный документ (чанк), которому мы считаем итоговый балл
— число списков результатов (обычно равно числу запросов)
— позиция документа в -м списке (1 — самый верх)
— константа сглаживания, чтобы разница между местами не была слишком резкойИнтуиция: документ, который стабильно попадает в топ разных поисковых формулировок, скорее всего действительно релевантен.
Ссылка: Reciprocal rank fusion
HyDE: поиск через гипотетический документ
HyDE (Hypothetical Document Embeddings) — техника, где LLM сначала пишет гипотетический ответ-документ на вопрос, а затем retrieval делается не по исходному вопросу, а по эмбеддингу этого гипотетического текста.
Зачем это может работать:
короткий вопрос плохо «попадает» в эмбеддинг-пространство
гипотетический документ содержит больше терминов, которые совпадают с документациейВажное ограничение: HyDE повышает recall, но может привести и к более уверенным ошибкам, если гипотетический текст «увёл» семантику.
Ссылка: HyDE: Precise Zero-Shot Dense Retrieval without Relevance Labels (arXiv)
Query Routing: выбираем правильный путь для запроса
Когда в проекте появляется несколько источников знаний, один ретривер перестаёт быть достаточным:
векторная база по документации
отдельная база по инцидентам
SQL-база с транзакциями
внешние APIРоутинг запросов — это выбор маршрута: куда идти за данными и какой пайплайн применять.
!Понимание, что «RAG» часто не один, а несколько пайплайнов
Логический роутинг (правила)
Подход, который часто стоит внедрять первым:
правила по ключевым словам и паттернам
явные команды пользователя
роутинг по контексту продукта/экрана (если вы встраиваетесь в UI)Плюсы:
предсказуемость
простая отладкаМинусы:
плохо масштабируется на множество интентовСемантический роутинг (по смыслу)
Идея: вы описываете «навыки» или «домены» короткими текстами, считаете эмбеддинги и выбираете ближайший.
домен A: «вопросы по документации продукта и настройкам»
домен B: «вопросы по данным продаж и агрегатам»
домен C: «инциденты и статус систем»Плюсы:
лучше ловит перефразированияМинусы:
нужно следить за ошибками классификации
часто нужен fallback и логирование решенийПрактичный гибрид
Часто в продакшене делают так:
сначала быстрые правила для явных случаев
затем семантический роутер для «остального»
затем fallback на самый безопасный сценарий (например, RAG по документации с отказом, если контекст не найден)Query Construction и Text-to-Metadata Filter: превращаем текст в фильтры
В реальных базах знаний метаданные так же важны, как и текст:
product, version
department
lang
access_levelЕсли пользователь спрашивает: «как настроить SSO в версии 3.2», правильное поведение — отфильтровать документы по version=3.2 и только потом делать векторный поиск.
SelfQueryRetriever: LLM строит фильтр сама
LangChain поддерживает паттерн, где LLM преобразует естественный язык в:
поисковую строку
структурированный фильтр по метаданнымЭто часто называют Text-to-Metadata Filter.
Ссылка: SelfQueryRetriever (LangChain how-to)
Query Construction: когда вы строите запросы к внешним системам
Иногда retrieval — это не только векторный поиск, но и запрос к API/поисковому движку/каталогу. Тогда вы решаете задачу конструирования запроса под конкретный backend.
Ссылка: Query construction (LangChain how-to)
Практические правила безопасности и качества для Text-to-Metadata Filter:
храните allowlist полей, по которым вообще разрешено фильтровать
валидируйте типы значений (даты, числа, перечисления)
логируйте фильтры, которые построила модель
добавляйте fallback: если фильтр невалиден, используйте поиск без фильтра или задайте уточняющий вопросText-to-SQL: когда данные живут в таблицах
Вопросы вроде «сколько оплат было вчера по тарифу Pro» плохо решаются чистым RAG по текстам. Это задача для SQL.
Text-to-SQL — пайплайн, в котором LLM:
генерирует SQL-запрос
вы выполняете его в базе
отдаёте результат обратно в LLM для финального ответа на человеческом языкеМинимальная архитектура Text-to-SQL
вход: вопрос пользователя
контекст: схема БД (таблицы, поля, связи)
выход: SQL
выполнение SQL
финальный ответ + (желательно) показ SQL пользователю или в логахСсылка: SQL QA tutorial (LangChain)
Пример пайплайна (упрощённо)
Важно: в продакшене почти никогда нельзя «просто выполнить SQL, который придумала модель».
Как сделать Text-to-SQL безопасным
Минимальный набор мер:
read-only доступ для пользователя БД, от имени которого вы выполняете запросы
allowlist таблиц и колонок: модель не должна уметь читать всё
лимиты: LIMIT, ограничения по диапазону дат, таймауты выполнения
валидация SQL: запрет опасных конструкций и проверка, что запрос только SELECT
логирование: сохраняйте вопрос, сгенерированный SQL, время выполнения, ошибкиКогда выбирать Text-to-SQL, а когда RAG
| Сценарий | Лучше RAG | Лучше Text-to-SQL |
|---|---|---|
| “Как настроить SSO?” | да | нет |
| “Какие поля есть в отчёте X?” | да | нет |
| “Сколько оплат было вчера?” | нет | да |
| “Покажи топ-10 клиентов по выручке за квартал” | нет | да |
| “Что означает ошибка E104?” | да | иногда |
Частая продакшн-архитектура: роутер решает, что запрос «про цифры» уходит в Text-to-SQL, а запрос «про инструкции/политику/описание» уходит в RAG по документам.
Минимальный чек-лист качества для RAG II
вы понимаете, какой паттерн retrieval используете и зачем (rewrite, multi-query, fusion, HyDE)
у вас есть фильтрация по метаданным и стратегия, что делать при пустой выдаче
у вас есть роутинг между разными источниками знаний
для табличных фактов вы используете Text-to-SQL, а не пытаетесь «вытащить цифры из текстов»
все решения LLM, влияющие на данные (фильтры, SQL), валидируются и логируютсяЧто дальше по курсу
Следующий большой шаг — перейти от линейных пайплайнов к состояниям и ветвлениям в LangGraph: память, управление историей, и затем агентные архитектуры, где роутинг и планирование становятся частью графа выполнения.