Ответ выглядит убедительно и содержит ссылку. По ссылке действительно открывается документ, но нужного утверждения в нём нет. Это не одна ошибка, а как минимум два возможных сбоя: поисковая часть выбрала неподходящий фрагмент или модель вышла за пределы подходящего источника.
В RAG источники извлекаются из коллекции и передаются модели как контекст. Исследование Anthropic по contextual retrieval отдельно разбирает потерю смысла при разрезании документов и сочетание лексического и семантического поиска. Его результаты относятся к описанным экспериментам и не обещают такой же прирост в любой базе. Anthropic: Contextual Retrieval
Две проверки вместо одной оценки
Для учебного помощника по документации составим двадцать вопросов. У каждого заранее запишем допустимые источники и минимальные факты, необходимые для ответа. Часть вопросов намеренно не имеет ответа в коллекции.
Сначала запускается только поиск. Есть ли нужный документ среди кандидатов? Правильная ли версия? Остался ли смысл в вырезанном фрагменте? Если полезный материал не найден, замена формулировки ответа обычно не исправит причину.
Затем выбранные фрагменты передаются модели. Теперь оценивается, подтверждён ли каждый существенный вывод, нет ли смешения разных версий и правильно ли обработано отсутствие сведений. Эти оценки нельзя бездумно сложить в один средний балл: хороший поиск не компенсирует выдуманное критичное условие.
Сделать небольшой проверочный набор
В учебной коллекции лежат две версии правил экспорта. Старая допускает один формат файла, новая — другой. Вопрос должен указывать на текущую версию, а источник хранить её явный признак. Ещё один вопрос содержит точный технический код; третий формулирует проблему разговорным языком.
Один простой показатель поиска — доля вопросов, для которых нужный источник встретился среди первых k кандидатов. Для случая с одним обязательным источником это можно посчитать так:
expected = {"q1": "doc-a", "q2": "doc-b", "q3": "doc-c"}
found = {"q1": ["doc-a", "doc-x"],
"q2": ["doc-x", "doc-b"],
"q3": ["doc-y", "doc-z"]}
covered = sum(expected[q] in found[q][:2] for q in expected)
assert covered == 2
coverage_at_2 = covered / len(expected)
Это учебная hit-rate по вопросам с одним нужным документом, а не универсальное определение recall для всех поисковых задач. Если ответ требует несколько независимых источников, одного попадания недостаточно.
Фрагмент должен сохранять контекст
Таблица без заголовка, инструкция без имени продукта и исключение без основного правила могут стать ложным свидетельством. Важно сохранять вместе с фрагментом сведения, необходимые для его интерпретации: название документа, раздел, версию и применимость.
При этом добавление контекста не означает бесконечное увеличение размера запроса. Длинная выдача может принести противоречия и лишние документы. Полезнее сравнить несколько понятных конфигураций на одном наборе и посмотреть конкретные ошибки, чем выбирать максимальное k наугад.
Проверить отказ и границы доступа
Для вопроса без ответа хороший результат может быть коротким признанием недостатка сведений и предложением уточнить запрос. Нельзя требовать содержательный ответ любой ценой: тогда система будет вознаграждаться за правдоподобную догадку.
Документы разных условных пользователей нужно разделять до передачи в модель. Фильтрация только готового текста не устраняет утечку закрытого контекста в промежуточной обработке. Дополнительно проверяют кэш: результат одного пользователя не должен становиться общим без учёта правил доступа.
Наконец, фиксируют версии коллекции, поиска и модели. Иначе вчерашний удачный прогон невозможно сопоставить с сегодняшним. Полезная проверка RAG объясняет, какое изменение улучшило поиск и какое — точность ответа, а не ограничивается впечатлением «стало умнее».