Многошаговый RAG: как найти ответ через связанные документы

«Какой регламент действует для продукта, который обслуживает наш подрядчик?» В вопросе нет названия продукта. Оно хранится в карточке договора, а сам регламент — в другой базе. Один поиск по исходной фразе часто находит тексты о подрядчиках и теряет нужную связь. Здесь полезно построить два зависимых шага поиска.

Превратите вопрос в цепочку фактов

Сначала нужно установить продукт по договору подрядчика. Затем найти регламент именно этого продукта. Промежуточный результат — проверяемая связь с ID, а не свободный пересказ вроде «кажется, речь о доставке». Сохраните contract_id, product_id и ссылку на место, где указана связь. Это даст второму шагу точную область поиска.

План можно описать заранее: определить подрядчика → получить его действующие договоры → извлечь продукты → найти регламенты. Модель полезна для понимания свободной формулировки и чтения найденных документов. Порядок выполнения и условия остановки лучше выразить в состоянии приложения: текущий шаг, найденные сущности, источники и нерешённые вопросы.

Не превращайте предположение в ключ поиска

Если подрядчик обслуживает два продукта, система должна сохранить оба, а не выбрать первый. Для вопроса об одном продукте задайте уточнение. Для вопроса обо всех выполните поиск по каждому product_id. Эта развилка определяется пользовательской задачей. Сам факт двух результатов не является ошибкой.

Отдельно храните «не найден подрядчик», «не найдена связь» и «не найден регламент». У этих исходов разные исправления. В первом случае помогает уточнение названия, во втором — дополнение договорной базы, в третьем — проверка источника регламентов. Общий ответ «нет информации» скрывает, где именно оборвалась цепочка.

Проверьте сборку цепочки до подключения модели

Ниже два инструмента чтения получают связи подрядчика и регламенты продукта. Они работают на локальных фикстурах; в приложении их тела заменяют разрешённые запросы к источникам. Между шагами сохраняются ID и trace. Если подрядчик связан с несколькими продуктами, функция останавливается с вопросом, вместо того чтобы выбрать первый. Проверены успешная цепочка, неоднозначность и отсутствие регламента.

contracts = [{"id": "D-17", "vendor": "V-4", "product": "P-2"},
             {"id": "D-18", "vendor": "V-5", "product": "P-2"},
             {"id": "D-19", "vendor": "V-5", "product": "P-3"},
             {"id": "D-20", "vendor": "V-6", "product": "P-4"}]
rules = [{"id": "R-8", "product": "P-2", "text": "Приём до 18:00"}]
def find_contracts(vendor):
    return [dict(c) for c in contracts if c["vendor"] == vendor]
def find_rules(product):
    return [dict(r) for r in rules if r["product"] == product]
def answer(vendor):
    found = find_contracts(vendor)
    trace = [{"step": "contracts", "ids": [c["id"] for c in found]}]
    products = sorted({c["product"] for c in found})
    if len(products) != 1:
        return {"state": "clarify" if products else "no_link", "products": products, "trace": trace}
    selected = find_rules(products[0])
    trace.append({"step": "rules", "product": products[0], "ids": [r["id"] for r in selected]})
    return {"state": "answered" if selected else "no_rule", "sources": selected, "trace": trace}
assert answer("V-4")["state"] == "answered"
assert answer("V-5")["state"] == "clarify"
assert answer("V-6")["state"] == "no_rule"
print(answer("V-4"))

Как оценивать такой RAG

Приёмочная задача должна содержать вопрос, допустимые промежуточные связи и достаточные источники итогового ответа. Если оценивать только конечную фразу, случайно угаданный регламент будет выглядеть успехом. Проверьте хотя бы один случай, где два подрядчика имеют похожие названия, и случай, где старый договор указывает прежний продукт.

Записывайте поиск каждого шага отдельно. Когда регламент не найден, инженер должен увидеть, какой product_id получил второй запрос и откуда он взялся. Ранжирование может работать хорошо, а ошибка окажется в первой связи. Повторный поиск регламента по ошибочному продукту только увеличит расходы.

Пользователю показывайте короткую понятную цепочку: «По договору D-17 подрядчик обслуживает продукт P-2; для P-2 найден регламент R-8». По ссылкам можно открыть оба основания. Если для части продуктов результат отсутствует, перечислите их отдельно и не выдавайте частичный ответ за полный.

Начинайте с одной устойчивой цепочки, которая регулярно нужна сотрудникам. После её проверки можно добавить другие связи: оборудование → модель → инструкция или клиент → тариф → условия обслуживания. Для каждой новой связи потребуется собственный источник истины и правило обработки неоднозначности.

Продолжить проект на практике

Для такого проекта важно научиться получать нужный источник на каждом шаге и измерять потери поиска. В платном курсе Vector DB & RAG Developer вы построите поиск с фильтрами и RAG с цитатами на своих документах, разберёте Recall@K и nDCG. Это основа для проверки каждого звена цепочки, прежде чем доверять итоговому ответу.

Откройте программу курса и выберите следующий этап своего проекта.

По теме: Гибридный поиск для RAG: BM25, векторы и расчёт RRF.

Продолжить на практике

  • Vector DB & RAG Developer ↗

    Практический курс по векторным базам данных и семантическому поиску. Вы развернёте Qdrant, Weaviate и FAISS, научитесь индексировать миллионы эмбеддингов, строить k-NN-поиск с фильтрами, объединять его с BM-25 и создавать RAG-ботов, которые отвечают на вопросы на основе ваших данных.