База знаний · Продвинутый уровень
Когда RAG должен отказаться от ответа: калибровка порога достаточности источников
Модель способна составить правдоподобный ответ даже тогда, когда найденные документы не содержат требуемого факта. Надёжный RAG должен оценивать не только похожесть фрагментов на вопрос, но и то, действительно ли они подтверждают каждое существенное утверждение ответа.
Почему релевантный фрагмент ещё не даёт права отвечать
RAG объединяет поиск по базе знаний и генерацию ответа. Типичная ошибка возникает между этими стадиями: поисковик возвращает текст по нужной теме, а генератор принимает тематическую близость за доказательство конкретного факта.
Например, пользователь спрашивает: «Каков срок хранения резервных копий проекта X?». Поиск находит документ о резервном копировании проекта X, но в нём нет срока хранения. Если модель отвечает «30 дней», она не извлекает факт, а заполняет пробел вероятным значением. Высокая уверенность формулировки ничего не говорит о наличии доказательства.
Поэтому решение об ответе удобно разделить на два шлюза:
- Достаточность поиска: получены ли содержательные, непротиворечивые и достаточно близкие к вопросу фрагменты?
- Подтверждаемость ответа: покрывает ли источник каждое проверяемое утверждение, которое система собирается сообщить?
Отказ должен срабатывать, если закрыт хотя бы один шлюз. Один порог similarity score эту задачу не решает.
Шаг 1. Зафиксируйте контракт отказа
До выбора чисел определите допустимые выходы системы. Практичный контракт содержит три состояния:
answer- Источники достаточны, существенные утверждения подтверждены.
partial- Подтверждена только часть запроса; система отвечает лишь на неё и явно обозначает границу.
abstain- Данных недостаточно, источники конфликтуют или ключевое утверждение не подтверждается.
Текст отказа не должен превращаться в замаскированную догадку. Подходящий шаблон:
В найденных материалах нет достаточного подтверждения для ответа на этот вопрос. Уточните объект или период либо добавьте документ, содержащий требуемый факт.
Не добавляйте после этой фразы «вероятно», «обычно» или значение из общих знаний модели.
Шаг 2. Соберите независимые сигналы
Не смешивайте все наблюдения в один непрозрачный score. Сохраните отдельные признаки, чтобы отказ можно было объяснить и отладить:
| Сигнал | Что измеряет | Опасный случай |
|---|---|---|
top_score |
Оценку лучшего результата поисковиком | Низкая близость даже у первого фрагмента |
score_margin |
Разницу между релевантными и фоновыми результатами | Все кандидаты почти одинаково слабы |
query_coverage |
Покрытие ограничений вопроса: объект, период, версия, атрибут | Документ о нужной теме, но не о нужной версии |
source_agreement |
Согласованность независимых фрагментов | Два действующих документа дают разные значения |
claim_support |
Долю существенных утверждений с прямой опорой на источник | Цитата подтверждает тему, но не заявленный факт |
critical_support |
Подтверждение ключевого утверждения | Второстепенные детали покрыты, основной ответ — нет |
Важно: шкалы retrieval score зависят от поисковика, модели эмбеддингов, индекса и способа нормализации. Число 0.8 не имеет универсального смысла и не должно переноситься между системами без повторной калибровки.
Шаг 3. Подготовьте набор для калибровки
Нужен размеченный набор запросов, отделённый от данных, на которых подбирались промпты. Включите как минимум четыре группы:
- ответ явно присутствует в одном или нескольких фрагментах;
- документы тематически близки, но требуемого факта нет;
- ответ присутствует частично;
- источники противоречат друг другу или относятся к разным версиям и периодам.
Для каждого примера храните ожидаемое решение и перечень утверждений, которые разрешено сделать. Ниже приведён синтетический формат записи; значения служат только примером структуры, а не готовым датасетом:
{
"id": "example-0042",
"question": "Каков срок хранения резервных копий проекта X?",
"expected_decision": "abstain",
"allowed_claims": [],
"reason": "retrieved_chunks_mention_backup_but_not_retention",
"risk": "high"
}
Размечайте именно наличие доказательства, а не правдоподобие ответа. Если эксперт знает факт, но его нет в доступных системе источниках, правильное решение для RAG — отказ.
Шаг 4. Разделите генерацию и проверку утверждений
Надёжная схема сначала строит черновик только из контекста, затем выделяет атомарные проверяемые утверждения и сопоставляет каждое из них с фрагментами. Проверка должна видеть идентификаторы и точный текст источников, а не пересказ генератора.
retrieved = retrieve(question, top_k=8)
ranked = rerank(question, retrieved)
retrieval_gate = assess_retrieval(question, ranked)
if not retrieval_gate.passed:
return abstain(retrieval_gate.reason)
draft = generate_grounded_draft(question, ranked)
claims = extract_atomic_claims(draft)
support = verify_claims(claims, ranked)
decision = decide(retrieval_gate, support)
if decision == "abstain":
return abstain(support.reason)
if decision == "partial":
return render_only_supported_claims(support)
return render_answer_with_citations(support)
Атомарность важна. Фраза «Политика действует с марта и хранит копии 30 дней» содержит два утверждения. Один источник может подтверждать дату вступления в силу, но не срок хранения.
Для каждого утверждения проверяющий компонент должен вернуть структурированный результат:
{
"claim": "Резервные копии хранятся 30 дней.",
"importance": "critical",
"verdict": "unsupported",
"evidence_chunk_ids": [],
"entailment_score": 0.18
}
entailment_score в этом примере — внутренний сигнал выбранного проверяющего компонента. Его нельзя трактовать как вероятность истинности без отдельной проверки калибровки.
Шаг 5. Настройте явные правила решения
Начните с интерпретируемых правил, а не с единой взвешенной суммы. Так слабое подтверждение критического факта не сможет компенсироваться высоким score второстепенных фрагментов.
abstention:
retrieval:
min_top_score: 0.62
min_query_coverage: 0.80
require_version_match: true
evidence:
min_supported_claim_ratio: 0.90
min_critical_claim_support: 0.78
reject_on_critical_contradiction: true
allow_partial_answer: true
response:
include_reason_code: true
expose_internal_scores: false
Все числа выше — только стартовый пример конфигурации. Подставьте пороги, полученные на собственном калибровочном наборе.
Логика решения может выглядеть так:
if top_score < T_RETRIEVAL:
abstain("weak_retrieval")
elif query_coverage < T_COVERAGE:
abstain("missing_query_constraints")
elif critical_contradiction:
abstain("conflicting_sources")
elif critical_support < T_CRITICAL:
abstain("critical_claim_unsupported")
elif supported_claim_ratio < T_CLAIMS:
partial_or_abstain()
else:
answer()
Версионность и дата действия документа лучше проверять отдельным правилом. Семантически близкий, но устаревший регламент часто получает высокий retrieval score.
Шаг 6. Калибруйте пороги по стоимости ошибок
Для каждого кандидата порога прогоните весь набор без изменения индекса, промпта и модели. Считайте по меньшей мере:
- unsupported answer rate — долю выданных ответов, содержащих неподтверждённое ключевое утверждение;
- false abstention rate — долю отказов при наличии достаточного доказательства;
- answer coverage — долю запросов, на которые система ответила полностью или частично;
- selective accuracy — точность среди случаев, в которых система решила отвечать.
Подбирайте порог не по максимальной общей accuracy, а по допустимой стоимости неподтверждённого ответа. Для высокорисковых фактов — сумм, разрешений, сроков, версий и обязательств — цена ложного ответа обычно выше цены отказа. Это продуктовая политика, которую следует зафиксировать явно.
Безопасный локальный запуск оценочного сценария может выглядеть так:
python -m evaluation.abstention \
--config configs/abstention.yaml \
--dataset data/abstention-calibration.jsonl \
--output reports/abstention-calibration.json
Команда является примером интерфейса проекта: она не предполагает наличие указанных файлов или модуля. Оценочный сценарий не должен отправлять данные наружу, изменять индекс или использовать секреты из командной строки.
Проверка результата
После калибровки заморозьте пороги и проверьте их на отдельной выборке. Не используйте для итоговой оценки те же запросы, по которым выбирались значения.
1. Проверьте негативные пары
Замените правильный фрагмент на документ по той же теме, в котором отсутствует нужный атрибут. Ожидаемый результат — abstain, даже если тематическая близость остаётся высокой.
2. Проверьте минимальные изменения вопроса
Меняйте только версию, дату, регион или объект. Система не должна использовать доказательство для проекта X как подтверждение факта о проекте Y.
3. Проверьте противоречия
Подайте два фрагмента с несовместимыми значениями и одинаковым статусом актуальности. Без правила приоритета ожидается отказ с причиной conflicting_sources.
4. Проверьте удаление доказательства
Сначала получите обоснованный ответ, затем удалите единственный подтверждающий фрагмент из входного контекста. Решение должно измениться с answer на abstain или partial. Если текст ответа остаётся прежним, генератор опирается не только на переданные источники.
5. Проверьте трассировку
В журнале решения должны сохраняться версия индекса, идентификаторы фрагментов, значения сигналов, сработавшее правило и итоговое состояние. Не записывайте секреты и полный пользовательский контент, если для диагностики достаточно идентификаторов или безопасных выдержек.
Минимальный критерий приёмки формулируется заранее. Например: «На итоговой выборке ни один ответ с неподтверждённым критическим утверждением не проходит шлюз». Это пример строгого критерия, а не заявление о достижимом результате любой системы.
Промпт — ограничитель, но не механизм контроля
Инструкция генератору полезна, однако её недостаточно без программного шлюза. Базовая формулировка может быть такой:
Отвечай только на основании переданных фрагментов.
Для каждого проверяемого утверждения укажи идентификатор фрагмента.
Если фрагменты не подтверждают ключевой факт прямо, верни:
{"decision":"abstain","reason":"insufficient_evidence"}.
Не используй фоновые знания для заполнения отсутствующих значений.
Проверяйте структурированный ответ схемой и принимайте итоговое решение вне модели. Даже хороший промпт не гарантирует, что модель не сформулирует догадку или не поставит ссылку на фрагмент, который фактически её не подтверждает.
Типовые ошибки
Один глобальный similarity threshold
Высокая похожесть подтверждает близость тем, но не логическое следование факта. Дополняйте retrieval gate проверкой утверждений.
Порог выбран на удобных положительных примерах
Без тематически близких негативных примеров система выглядит точнее, чем есть. Основную ценность дают случаи, где нужного факта нет.
Среднее скрывает провал критического утверждения
Пять подтверждённых второстепенных деталей не компенсируют неподтверждённую сумму или дату. Для критических утверждений используйте отдельный обязательный порог.
Модель сама оценивает свою уверенность
Самооценка может быть дополнительным сигналом, но не заменяет сопоставление с источником. Уверенность в формулировке и наличие доказательства — разные свойства.
Цитата считается доказательством автоматически
Наличие ссылки рядом с предложением не означает, что фрагмент подтверждает его. Проверяйте entailment на уровне атомарного утверждения.
Частичный ответ незаметно становится полным
Если подтверждена только часть запроса, удалите неподтверждённые утверждения до рендеринга и явно перечислите, какой информации не хватает.
Порог не пересматривается после изменения системы
Новая модель эмбеддингов, разбиение документов, reranker, индекс или промпт меняют распределение сигналов. После такого изменения нужна повторная калибровка.
Ограничения подхода
- Проверяющая модель тоже ошибается и может принять перефразирование за доказательство отсутствующего факта.
- Слабая разметка калибровочного набора закрепляет неверную границу между ответом и отказом.
- Источники могут быть внутренне неверными, устаревшими или неполными; подтверждаемость не равна истинности.
- Для сложного вывода из нескольких документов атомарное сопоставление может быть недостаточным: потребуется явная цепочка промежуточных выводов.
- Строгий порог снижает число опасных ответов, но увеличивает число отказов и может ухудшить пользовательский опыт.
- Метрики на статической выборке не заменяют наблюдение за изменением запросов и корпуса после запуска.
Отказ — не доказательство безопасности, а управляемое поведение при недостатке свидетельств. Для критических сценариев добавляйте правила актуальности, права доступа, ручную проверку и предметные ограничения.
Что контролировать после запуска
Собирайте распределения сигналов по версиям системы, долю причин отказа и выборочную экспертную оценку ответов. Следите не только за общим процентом отказов: стабильная средняя величина может скрывать провал на отдельном типе вопросов.
Полезные срезы — версия документа, язык, длина запроса, тип требуемого факта, источник данных и уровень риска. Алерт должен срабатывать при сдвиге распределения retrieval score, росте critical_claim_unsupported и резком изменении answer coverage.
Итоговый чек-лист
- Определены состояния
answer,partialиabstain. - Качество поиска и подтверждаемость утверждений оцениваются раздельно.
- Ключевые утверждения имеют обязательный индивидуальный порог.
- В наборе есть тематически близкие примеры без требуемого факта.
- Пороги выбраны по собственной калибровочной выборке и стоимости ошибок.
- Итоговая проверка выполнена на отдельном наборе.
- Удаление доказательства действительно переключает систему в отказ.
- Решение трассируется без записи секретов и лишних пользовательских данных.
- Изменение поиска, индекса или модели запускает повторную калибровку.
Главный принцип прост: система получает право отвечать не тогда, когда нашла текст на похожую тему, а тогда, когда может показать достаточное подтверждение каждого существенного утверждения.