Почему RAG-системы говорят «см. раздел 7.2» вместо ответа
Пользователь спрашивает: «Какие гарантийные обязательства действуют на серверное оборудование?». Система находит релевантный фрагмент в контракте, но вместо конкретного списка выдаёт: «Гарантийные обязательства описаны в разделе 7.2». Ответ формально корректен, но бесполезен. Это не галлюцинация и не ошибка модели - это архитектурное ограничение наивного RAG.
Стандартный RAG-пайплайн работает с фиксированным окном контекста. Ретривер извлекает top-K чанков, генератор строит ответ только на их основе. Если в чанке обнаруживается перекрёстная ссылка на другой раздел, модель не может «перелистнуть страницу» - содержимое раздела 7.2 физически отсутствует в переданном контексте. Модель честно сообщает о существовании раздела, но не имеет доступа к его данным.
Проблема обостряется в корпоративных документах: техническая документация, юридические контракты, стандарты и регламенты пронизаны перекрёстными ссылками. Раздел с требованиями отсылает к разделу с определениями, пункт о штрафных санкциях ссылается на условия расторжения, описание API указывает на схему аутентификации. Пользователь получает каскад отсылок вместо фактов - и теряет доверие к системе.
Решение требует выхода за пределы однопроходной архитектуры. Нужен механизм, который обнаруживает ссылки в сгенерированном ответе, извлекает содержимое целевых разделов и дополняет ответ фактами. Этот механизм - контур обратной связи с оркестратором и структурированным выводом генерации.
Архитектура с контуром обратной связи: как научить RAG разрешать ссылки
Контур обратной связи добавляет в пайплайн два компонента: структурированный вывод генерации и оркестратор, принимающий решения о дополнительных проходах. Базовая схема не усложняет простые запросы - для них контур отрабатывает за один проход и завершается. Для запросов, требующих разрешения ссылок, запускается второй проход с дополнительным контекстом.
Первый проход: ретривер извлекает чанки по запросу пользователя, генератор строит ответ и возвращает структурированные метаданные - список неразрешённых ссылок и оценку полноты ответа. Оркестратор анализирует метаданные. Если answer_completeness указывает на полноту - ответ возвращается пользователю. Если обнаружены pending_references - оркестратор извлекает содержимое целевых разделов и запускает второй проход генерации с расширенным контекстом.
Этот подход перекликается с техниками из Loop Engineering для уточнения запросов, где один цикл обратной связи с пользователем повышает точность retrieval. Здесь цикл замкнут внутри системы - пользователь не участвует в разрешении ссылок.
Структурированный вывод генерации: pending_references и answer_completeness
Генератор возвращает не строку, а типизированный объект. Два ключевых поля: answer_completeness - булев флаг или шкала (complete, partial, incomplete), и pending_references - массив объектов с описанием каждой неразрешённой ссылки.
Пример вывода для запроса о гарантийных обязательствах:
{
"answer": "Гарантийные обязательства описаны в разделе 7.2.",
"answer_completeness": "incomplete",
"pending_references": [
{
"reference_type": "section",
"target": "7.2",
"description": "Гарантийные обязательства на серверное оборудование"
}
]
}
Добиться такого вывода можно через промпт-инжиниринг с Pydantic-схемой. Модель получает инструкцию: обнаружив отсылку к другому разделу, не придумывать содержание, а зафиксировать ссылку в pending_references и выставить answer_completeness: incomplete. Для маленьких моделей применяется правило декомпозиции из шаблонов контрактов генерации: сложный контракт разбивается на несколько последовательных вызовов с прослойкой на Python.
Оркестратор: логика принятия решений о втором проходе
Оркестратор - детерминированный компонент на Python, не требующий LLM-вызовов. Алгоритм: проверить answer_completeness. Если complete - вернуть ответ. Если incomplete - извлечь массив pending_references, для каждой ссылки определить тип и выбрать метод разрешения, выполнить извлечение содержимого, собрать расширенный контекст и запустить второй проход генерации.
Глубина ограничена одним дополнительным проходом. Если во втором проходе снова обнаруживаются ссылки, они фиксируются, но третий проход не запускается - вместо этого в ответе указывается: «Дополнительная информация доступна в разделах X, Y, Z». Это предотвращает рекурсию и контролирует latency.
Оркестратор также отвечает за приоритизацию: ссылки на разделы того же документа разрешаются в первую очередь, внешние ссылки - опционально, в зависимости от конфигурации системы.
Методы разрешения перекрестных ссылок: от детерминированного поиска до LLM-ассистента
Перекрёстные ссылки в документах делятся на четыре типа: ссылки на разделы («см. раздел 7.2»), ссылки на условные пункты («в соответствии с предыдущим пунктом»), ссылки на определения («как определено в глоссарии») и ссылки на внешние источники («согласно стандарту ISO 27001»). Для каждого типа - свой метод разрешения.
Стратегия выбора метода - дешёвые дефолты. Сначала применяется быстрый детерминированный поиск по структурированным метаданным. Если он не даёт результата, подключается LLM-ассистированный метод. Это минимизирует затраты токенов и latency - 80% ссылок в структурированных документах разрешаются детерминированно.
Детерминированный поиск по реляционным таблицам: toc_df и object_registry
На этапе парсинга документа создаются две структуры: toc_df (table of contents) - таблица содержания с маппингом номеров разделов на заголовки и границы чанков, и object_registry - реестр именованных объектов: таблиц, рисунков, определений, формул с их расположением в документе.
Когда оркестратор получает ссылку типа {"reference_type": "section", "target": "7.2"}, он выполняет поиск по toc_df: находит строку с номером 7.2, получает заголовок раздела и идентификаторы чанков, содержащих этот раздел. Затем извлекает чанки из хранилища и добавляет в контекст второго прохода. Время разрешения - миллисекунды, стоимость - ноль LLM-вызовов.
Для ссылок на определения работает object_registry. Ссылка «как определено в глоссарии, термин 'форс-мажор'» преобразуется в запрос к реестру, который возвращает чанк с определением термина. Точность детерминированного метода для структурированных документов приближается к 100% - номера разделов и термины глоссария однозначны.
LLM-ассистированное разрешение для неоднозначных формулировок
Ссылки типа «как описано выше», «в соответствии с предыдущим пунктом», «с учётом ранее указанных ограничений» не содержат явного идентификатора. Детерминированный поиск бессилен - «выше» может означать предыдущий абзац, предыдущий раздел или весь предыдущий блок документа.
Для таких случаев оркестратор передаёт LLM расширенный контекст: исходный чанк со ссылкой и несколько окружающих чанков. LLM интерпретирует ссылку и возвращает структурированный объект с конкретным целевым разделом или чанком. Затем оркестратор извлекает этот чанк детерминированно и добавляет в контекст.
Стоимость LLM-ассистированного разрешения выше - один дополнительный вызов на каждую неоднозначную ссылку. На практике доля таких ссылок не превышает 15-20% от общего числа, поэтому влияние на общую стоимость системы умеренное. Стратегия дешёвых дефолтов оправдывает себя: быстрые методы покрывают большинство случаев, дорогие - только исключения.
Практический пример: разрешаем ссылки в статье «Attention Is All You Need»
Возьмём запрос: «Как устроен механизм внимания в Transformer и как он связан с позиционным кодированием?». Статья «Attention Is All You Need» описывает механизм внимания в разделе 3, а позиционное кодирование - в разделе 3.5. Стандартный RAG с top-3 ретривалом может извлечь чанки только из одного раздела и выдать ответ с пробелом.
Первый проход: ретривер находит чанк из раздела 3 с описанием Scaled Dot-Product Attention. Генератор строит ответ, но обнаруживает ссылку на позиционное кодирование и возвращает:
{
"answer": "Механизм внимания использует Scaled Dot-Product Attention. Позиционное кодирование описано в разделе 3.5.",
"answer_completeness": "incomplete",
"pending_references": [
{
"reference_type": "section",
"target": "3.5",
"description": "Позиционное кодирование"
}
]
}
Оркестратор обращается к toc_df, находит раздел 3.5, извлекает соответствующий чанк. Запускается второй проход с расширенным контекстом - исходный чанк раздела 3 и чанк раздела 3.5. Генератор строит полный ответ, связывающий механизм внимания с позиционным кодированием через формулу суммирования эмбеддингов.
Ключевая оптимизация - топ-1 ретривал на первом проходе. Поскольку оркестратор доберёт недостающие чанки по ссылкам, нет смысла извлекать top-3 или top-5 на старте. Это сокращает входной контекст первого прохода на 60-70% и ускоряет генерацию. Ленивое извлечение ссылок на этапе парсинга - toc_df и object_registry строятся один раз при индексации документа, а не для каждого запроса - дополнительно снижает нагрузку на хранилище.
Этот подход концептуально близок к последовательному режиму подачи контекста, описанному в детерминированных циклах подачи контекста: начинаем с минимального контекста и добавляем данные только при необходимости.
Ограничения и когда этот подход не сработает
Контур обратной связи добавляет latency. Второй проход генерации означает дополнительный LLM-вызов, что увеличивает время ответа на 50-100%. Для систем с жёсткими требованиями к задержке (менее 2 секунд) это критично. Решение - асинхронная обработка: первый ответ с пометкой «собираю дополнительные данные» возвращается сразу, полный ответ приходит вторым сообщением.
Качество разрешения ссылок зависит от качества парсинга. Если toc_df построен с ошибками или документ не имеет чёткой структуры разделов, детерминированный поиск не сработает. Документы без оглавления, с нестандартной нумерацией или сканированные PDF требуют дополнительной предобработки. В таких случаях доля LLM-ассистированных разрешений возрастает, увеличивая стоимость.
Сложные каскадные ссылки - «см. раздел 7.2, который отсылает к приложению B» - требуют двух дополнительных проходов. Ограничение глубины одним проходом означает, что пользователь получит содержимое раздела 7.2, но ссылка на приложение B останется неразрешённой. Это осознанный компромисс между полнотой и latency.
Внешние ссылки на другие документы в корпоративной базе знаний разрешаются, только если целевой документ проиндексирован в той же системе. Ссылки на внешние веб-ресурсы требуют дополнительного парсинга и проверки доступности - этот сценарий выходит за рамки базового контура и решается через интеграцию с многошаговым семантическим поиском.
Рекомендации по внедрению: начинайте с документов с чёткой структурой разделов и явными перекрёстными ссылками. Реализуйте детерминированный поиск по toc_df как первый этап - он покрывает большинство случаев при минимальных затратах. Добавляйте LLM-ассистированное разрешение только после анализа реальных запросов и типов ссылок в вашей базе документов. Мониторьте долю успешно разрешённых ссылок и latency второго прохода - эти метрики покажут окупаемость контура.