← Production RAG: retrieval systems that do not hallucinate
03 / 07 · 50 min

Query rewriting and multi-hop retrieval

Rewrite ambiguous questions, decompose multi-hop requests, and route queries without multiplying latency and cost without bounds.

Lesson content is currently available in Russian.

What you will learn

  • →Разворачивать контекстно-зависимые и разговорные запросы в самодостаточные поисковые.
  • →Применять decomposition, HyDE и step-back prompting и знать, когда каждая техника оправдана.
  • →Строить multi-hop retrieval, где результат первого поиска порождает второй запрос.

Запрос пользователя — не запрос к индексу

Retrieval ищет то, что спросили, а пользователи не формулируют запросы под индекс. Четыре типовых разрыва. Разговорная зависимость: «а в про-тарифе?» — без предыдущей реплики диалога запрос бессмыслен; перед поиском его нужно развернуть в «какие лимиты хранилища в про-тарифе» (contextualization / condensation — обязательный шаг любого RAG-чата). Множественность интентов: «сравни X и Y по цене и производительности» — это четыре поиска, а не один. Разрыв лексики: пользователь говорит «слетела оплата», документация — «ошибка обработки платежа». Ошибочные предпосылки: «почему функция Z удалена?» — а она переименована.

Поэтому зрелые системы вставляют между пользователем и индексом слой query understanding: дешёвая LLM переписывает, разворачивает и размножает запрос. Базовая техника — multi-query: сгенерировать 3–5 переформулировок («как вернуть деньги» → «политика возврата средств», «refund policy», «отмена платежа и компенсация»), выполнить все поиски параллельно и слить результаты через уже знакомый RRF. Это дёшево (один вызов маленькой модели, поиски параллельны) и стабильно поднимает recall на запросах с нестандартной лексикой.

Важный продакшен-нюанс: rewriting добавляет латентность и точку отказа, поэтому его делают на быстрой модели (gpt-4o-mini, haiku-класс) со strict-таймаутом и fallback’ом на исходный запрос — если переписывание упало или заняло больше 200–400 мс, ищем по оригиналу.

Decomposition, HyDE и step-back

Три техники посильнее multi-query, каждая под свой класс запросов:

  • Decomposition — сложный вопрос разбивается на подвопросы, каждый ищется отдельно, ответ собирается из всех результатов. «Чем тариф Pro отличается от Enterprise по SLA и цене?» → «SLA тарифа Pro», «SLA тарифа Enterprise», «цена Pro», «цена Enterprise». Обязательна для сравнительных и агрегирующих вопросов.
  • HyDE (Hypothetical Document Embeddings) — LLM пишет гипотетический ответ на вопрос, и эмбеддится этот ответ, а не вопрос. Идея: вопрос и документ находятся в разных «регионах» векторного пространства, а выдуманный ответ по форме и лексике ближе к реальному документу. Факты в гипотезе могут быть неверными — важна форма. Работает на коротких вопросах к длинным документам; на запросах с точными ID бесполезен и даже вреден.
  • Step-back prompting — по конкретному вопросу генерируется более общий: «упадёт ли скидка при даунгрейде с годового Pro?» → «как работают скидки при смене тарифа». Общий запрос достаёт раздел с правилами, конкретный — исключения; ищут по обоим.
SUBQ_PROMPT = (
    'Разбей вопрос на 2-4 самостоятельных поисковых подвопроса, '
    'по одному на строку. Вопрос: {q}'
)

async def decompose_and_search(q: str) -> list[Doc]:
    subqs = (await fast_llm.generate(SUBQ_PROMPT.format(q=q))).splitlines()
    results = await asyncio.gather(*[hybrid_search(s, limit=20) for s in subqs])
    return rrf(results)[:30]

Правило выбора: multi-query — дефолт для всех; decomposition — если в вопросе союз «и»/«или»/«сравни»; HyDE — если eval показывает разрыв лексики вопрос↔документ; step-back — для вопросов об исключениях из правил.

Multi-hop: когда ответа нет ни в одном чанке

Некоторые вопросы принципиально не решаются одним поиском: «кто руководит отделом, в котором работает автор документа X?» — сначала нужно найти документ X и извлечь автора, потом найти его отдел, потом руководителя. Это multi-hop retrieval: результат первого хопа становится параметром запроса второго.

Наивный RAG на таких вопросах галлюцинирует особенно охотно: retrieval приносит чанки, похожие на вопрос целиком, но не содержащие цепочку фактов, и модель «достраивает» недостающие звенья. Классические датасеты этого класса — HotpotQA и MuSiQue (2–4 хопа); на них single-shot retrieval теряет десятки пунктов точности против итеративных схем.

Базовая реализация — управляемый цикл: (1) поиск по исходному вопросу; (2) LLM смотрит на найденное и отвечает на вопрос «достаточно ли фактов для ответа? если нет — какой следующий поисковый запрос?»; (3) новый поиск, накопление контекста; (4) стоп по условию «достаточно» или по лимиту хопов (обычно 3–4, иначе латентность и стоимость уходят в небо). Схема известна в литературе как self-ask / IRCoT (interleaved retrieval + chain-of-thought).

Два инженерных предохранителя обязательны. Бюджет: каждый хоп — это вызов LLM плюс поиск, поэтому multi-hop включается классификатором сложности запроса, а не для всех подряд. Накопление контекста: промежуточные находки суммируются в компактные факты («автор X — Иванова, отдел — аналитика»), а не конкатенируются полными чанками, иначе к третьему хопу контекст распухает и качество рассуждения падает.

Роутинг запросов: не всё — retrieval

Продакшен-система обслуживает поток разнородных запросов, и прогонять все через полный конвейер «rewrite → hybrid → rerank → multi-hop» расточительно. Перед конвейером ставят router — быструю LLM или классификатор, который решает: (a) простой фактический вопрос — прямой гибридный поиск без переписывания; (b) сравнение/агрегация — decomposition; (c) цепочка фактов — multi-hop; (d) чит-чат или вопрос вне базы знаний — вообще без retrieval, чтобы не подмешивать нерелевантный контекст (частая причина галлюцинаций: модель чувствует себя обязанной использовать приложенные чанки); (e) запрос к структурированным данным — SQL/API вместо векторного поиска.

Роутер — это одна классификация на дешёвой модели (20–50 мс, доли цента), которая экономит секунды и центы на каждом сложном маршруте и заметно чистит качество простых. Ошибки роутера дешевле недо-инженерии: неверно направленный в полный конвейер простой запрос просто медленнее; сложный запрос, ушедший по простому пути, ловится eval’ом и правкой правил роутинга.

Практический минимум для внедрения из этого урока: (1) condensation разговорных запросов — всегда; (2) multi-query с RRF — дефолт; (3) роутер на три маршрута: no-retrieval / simple / complex; (4) decomposition и multi-hop — по показаниям eval на вашем распределении запросов. Каждый слой добавляйте по одному и замеряйте recall и латентность до/после — сложность конвейера сама по себе не ценность.

Practice

Слой query understanding

Соберите 15 «плохих» запросов трёх типов: 5 контекстно-зависимых из диалога, 5 сравнительных, 5 с лексикой, не совпадающей с документами. Реализуйте condensation + multi-query(3) с RRF-слиянием и decomposition для сравнительных. Замерьте recall@10 до и после для каждого типа.

Deliverable

Скрипт со слоем rewriting (fallback на исходный запрос по таймауту) и таблица recall@10 «до/после» по трём типам запросов с выводом, какая техника дала наибольший прирост.

Checkpoint

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

  • ✓ Назван шаг condensation: разворачивание анафоры «это»/«прошлой версии» в самодостаточный запрос по истории диалога.
  • ✓ Показана decomposition сравнительного вопроса на подзапросы по каждой версии и сущности со слиянием через RRF.
  • ✓ Упомянуты предохранители: быстрая модель для rewriting, таймаут с fallback, лимит хопов и роутер, решающий, нужен ли сложный маршрут.

Check yourself

Что эмбеддится при использовании HyDE?

01Что эмбеддится при использовании HyDE?

Для какого вопроса обязательна decomposition?

02Для какого вопроса обязательна decomposition?

Чем multi-hop retrieval отличается от multi-query?

03Чем multi-hop retrieval отличается от multi-query?

Зачем роутер направляет часть запросов в маршрут «без retrieval»?

04Зачем роутер направляет часть запросов в маршрут «без retrieval»?

Какой предохранитель обязателен для слоя query rewriting в проде?

05Какой предохранитель обязателен для слоя query rewriting в проде?

Sources

Query rewriting and multi-hop retrieval · Production RAG: retrieval systems that do not hallucinate · VORCL