Практика · RAG и оценка качества
Проверка целостности цитат в RAG: действительно ли источник подтверждает утверждение модели
Ссылка рядом с ответом доказывает только то, что система выбрала некоторый источник. Она не доказывает, что процитированный фрагмент содержит заявленный факт. Надёжная проверка начинается не с документа целиком, а с пары «атомарное утверждение — точный фрагмент источника».
Что именно нужно проверять
В системе RAG модель получает найденные фрагменты и формирует ответ с атрибуцией. Ошибка целостности возникает, когда ссылка существует, но источник не подтверждает относящееся к ней утверждение.
Различайте четыре независимых свойства:
- Корректность ссылки: идентификатор ведёт к реально переданному модели фрагменту.
- Полнота атрибуции: каждое проверяемое утверждение имеет хотя бы одну ссылку.
- Подтверждаемость: фрагмент логически поддерживает всё утверждение, а не только совпадает по теме.
- Качество источника: документ актуален, авторитетен и применим к контексту вопроса.
Шаг 1. Зафиксируйте проверяемый контракт ответа
Аудит становится воспроизводимым, если генератор возвращает не свободный текст со сносками, а структурированные утверждения. Минимальный контракт:
{
"answer": "Текст ответа для пользователя.",
"claims": [
{
"claim_id": "c1",
"text": "Одно самостоятельно проверяемое утверждение.",
"citation_ids": ["s1"]
}
],
"citations": [
{
"citation_id": "s1",
"document_id": "doc-17",
"chunk_id": "doc-17:4",
"quote": "Точный фрагмент, переданный генератору."
}
]
}
Не разрешайте генератору создавать document_id, chunk_id
или цитату самостоятельно. Приложение должно выбирать идентификаторы из
разрешённого набора, а текст фрагмента — извлекать из хранилища по идентификатору.
Так проверка не зависит от того, насколько аккуратно модель переписала цитату.
Одно утверждение должно содержать одну проверяемую мысль. Фразу «режим появился в версии 4, включён по умолчанию и ускоряет обработку на 30%» следует разделить на три утверждения: один источник может подтверждать лишь часть исходной фразы.
Шаг 2. Соберите воспроизводимый набор проверок
Сохраните вопрос, итоговый ответ, атомарные утверждения, идентификаторы найденных
фрагментов и точный текст каждого фрагмента. Добавьте ожидаемую ручную метку:
supported, contradicted или insufficient.
Ниже — синтетический пример формата, а не описание реального продукта:
{"case_id":"ex-001","claim":"Сервис хранит журналы 30 дней.","citation":"Журналы хранятся не более 30 календарных дней.","label":"supported"}
{"case_id":"ex-002","claim":"Сервис хранит журналы бессрочно.","citation":"Журналы хранятся не более 30 календарных дней.","label":"contradicted"}
{"case_id":"ex-003","claim":"Журналы удаляются автоматически каждый час.","citation":"Журналы хранятся не более 30 календарных дней.","label":"insufficient"}
Включите трудные случаи: отрицания, числа с единицами измерения, даты, диапазоны, исключения, условия, местоимения, одинаковые названия разных сущностей и утверждения, для которых нужны два фрагмента одновременно.
Шаг 3. Сначала выполните детерминированные проверки
До обращения к модели-проверяющему отбрасывайте технически некорректные ответы. Эти проверки дешёвы, понятны и не требуют вероятностных порогов.
- Каждый
citation_idсуществует и уникален. - Каждая ссылка указывает на фрагмент, который действительно входил в контекст генерации.
- Сохранённая цитата дословно совпадает с текстом фрагмента либо является его точной подстрокой.
- У каждого проверяемого утверждения есть ссылка.
- Числа, даты и единицы из утверждения встречаются в источнике или явно выводятся из нескольких процитированных фрагментов.
- Размер фрагмента не превышает установленный предел: ссылка на целую главу плохо локализует доказательство.
Безопасная локальная команда для проверки JSONL-файла без отправки данных наружу:
python -m json.tool < evaluation/citation-cases.jsonl > /dev/null
Для JSONL с несколькими объектами удобнее валидировать каждую строку отдельным скриптом. Пример проверяющей функции:
def validate_links(result, retrieved_chunks):
allowed = {
item["chunk_id"]: item["text"]
for item in retrieved_chunks
}
citations = {
item["citation_id"]: item
for item in result["citations"]
}
errors = []
for citation_id, citation in citations.items():
chunk_id = citation["chunk_id"]
source_text = allowed.get(chunk_id)
if source_text is None:
errors.append((citation_id, "chunk_not_retrieved"))
continue
if citation["quote"] not in source_text:
errors.append((citation_id, "quote_not_in_chunk"))
for claim in result["claims"]:
if not claim["citation_ids"]:
errors.append((claim["claim_id"], "missing_citation"))
for citation_id in claim["citation_ids"]:
if citation_id not in citations:
errors.append((claim["claim_id"], "unknown_citation"))
return errors
Эта функция проверяет происхождение ссылки, но не смысловое соответствие. Успешный результат означает лишь, что цитата не сфабрикована технически.
Шаг 4. Проверьте смысловое подтверждение
Для каждой пары передайте проверяющему только утверждение и связанные с ним фрагменты. Не добавляйте весь ответ: соседние предложения могут незаметно подсказывать желаемый вывод.
Задача: определить, подтверждают ли источники утверждение.
Метки:
- supported: источники достаточны для всего утверждения;
- contradicted: источники явно несовместимы с утверждением;
- insufficient: источники относятся к теме, но доказательства недостаточно.
Правила:
1. Используй только переданные источники.
2. Проверяй числа, даты, отрицания, условия и область действия.
3. Не дополняй доказательство общими знаниями.
4. Если подтверждена лишь часть утверждения, выбери insufficient.
5. Верни JSON без дополнительного текста.
Утверждение:
<claim>...</claim>
Источники:
<sources>
[1] ...
[2] ...
</sources>
Формат:
{
"label": "supported | contradicted | insufficient",
"evidence": [{"source": 1, "start": 0, "end": 42}],
"reason": "Краткое объяснение",
"confidence": 0.0
}
Поля start и end должны указывать на минимальный
доказательный отрезок. После ответа приложения проверьте, что границы допустимы и
выделенный текст действительно принадлежит указанному источнику.
Значение confidence нельзя считать вероятностью без калибровки на
размеченной выборке. Используйте его для сортировки ручной очереди только после
сравнения с человеческими метками.
Шаг 5. Считайте метрики на уровне утверждений
Две базовые метрики отвечают на разные вопросы:
citation_coverage =
claims_with_at_least_one_citation / all_verifiable_claims
citation_support =
supported_claims / claims_with_at_least_one_citation
Отдельно считайте долю contradicted. Не объединяйте её с
insufficient: противоречие опаснее отсутствия достаточного
доказательства и обычно требует отдельной блокировки.
Для ответа целиком полезно строгое правило: ответ проходит проверку, только если все существенные утверждения имеют ссылки и подтверждены. Дополнительно публикуйте мягкую метрику по утверждениям, иначе один сбой скроет улучшения в остальных частях.
Пример конфигурации порогов, которую нужно откалибровать на собственных данных:
{
"policy": {
"block_on_contradiction": true,
"require_citation_for_material_claims": true,
"manual_review_below_confidence": 0.80,
"max_uncited_material_claims": 0
}
}
Число 0.80 здесь является иллюстрацией, а не универсально доказанным
порогом. Выберите рабочее значение по ошибкам на вашей размеченной выборке.
Шаг 6. Добавьте выборочную ручную проверку
Автоматический проверяющий тоже может ошибаться. Ручная выборка должна охватывать не только случайные ответы, но и зоны повышенного риска:
- все автоматические противоречия;
- ответы около рабочего порога;
- утверждения с числами, датами, юридическими или операционными условиями;
- случаи с несколькими источниками;
- небольшую случайную долю автоматически принятых ответов.
Рецензенту показывайте вопрос, одно утверждение, связанные фрагменты и решение проверяющего. Сначала просите поставить собственную метку, затем раскрывайте автоматический результат: это уменьшает якорный эффект.
Короткая инструкция рецензенту:
- Можно ли вывести всё утверждение только из показанных фрагментов?
- Совпадают ли субъект, объект, версия, период, условие и единицы?
- Не превращает ли ответ возможность в гарантию, рекомендацию — в требование, а корреляцию — в причину?
- Если убрать фоновые знания, остаётся ли доказательство достаточным?
Часть выборки независимо размечайте двумя людьми. Разногласия полезны: они выявляют неоднозначные инструкции, слишком крупные утверждения и фрагменты без достаточного контекста.
Как проверить, что контур работает
Перед включением проверки в рабочий поток выполните контролируемую процедуру:
- Зафиксируйте версию набора примеров, правила разметки и конфигурацию проверяющего.
- Прогоните синтетические пары с поддержкой, противоречием и недостатком данных.
- Убедитесь, что подмена
chunk_idвызываетchunk_not_retrieved. - Убедитесь, что изменённая цитата вызывает
quote_not_in_chunk. - Замените в поддерживаемом утверждении число или отрицание и проверьте, что оно перестаёт считаться подтверждённым.
- Сравните автоматические решения с ручной разметкой на отложенной выборке.
- Зафиксируйте ложные принятия отдельно: именно они пропускают неподтверждённые ответы пользователю.
В журнал проверки записывайте case_id, хеш входа, версию правил,
решение, выделенный доказательный отрезок и причину. Не сохраняйте секреты и лишние
персональные данные; при необходимости заменяйте содержимое стабильными
обезличенными идентификаторами.
Успех — это не «проверяющий вернул JSON», а воспроизводимое совпадение с принятой разметкой при приемлемой доле опасных ложных принятий.
Типовые ошибки
- Проверка документа вместо точного фрагмента
- Наличие факта где-то в документе не доказывает, что модель получила его при генерации. Проверяйте именно переданный контекст.
- Одно составное утверждение
- Проверяющий видит частичное совпадение и принимает всю фразу. Разделяйте числа, условия и независимые выводы.
- Использование поиска как судьи
- Высокий retrieval-score показывает близость запросу, но не логическую поддержку ответа.
- Проверяющий видит эталонную метку
- Это создаёт утечку ответа. Метка хранится в оценочном контуре и не включается во вход проверяющего.
- Автоматически созданная «точная цитата»
- Модель может перефразировать или дополнить источник. Цитату извлекает приложение по сохранённым границам.
- Один порог для всех рисков
- Ошибка в справочном описании и ошибка в критичном условии имеют разную цену. Разделяйте политики по типу утверждения и сценарию использования.
- Оценка только успешных ответов
- В выборку должны попадать отказы, ответы без ссылок и случаи, где поиск не нашёл достаточных данных.
Ограничения метода
- Подтверждение цитат не гарантирует истинность источника: документ сам может быть ошибочным, устаревшим или неприменимым.
- Короткий фрагмент может терять определения и исключения, а длинный — облегчать ложное совпадение. Размер контекста приходится настраивать экспериментально.
- Некоторые выводы требуют таблиц, изображений, вычислений или объединения многих фрагментов; текстовая проверка одной пары для них недостаточна.
- Модель-проверяющий подвержена тем же классам ошибок, что и генератор. Желательно разделять их инструкции и регулярно сверять решения с людьми.
-
Ручная разметка не абсолютно объективна. Нужны чёткие правила, класс
insufficientи разбор разногласий.
Минимальный производственный чек-лист
- Ответ разложен на атомарные проверяемые утверждения.
- Ссылки выбираются только из реально полученного контекста.
- Цитаты извлекаются приложением, а не сочиняются генератором.
- Техническая валидность проверяется до смысловой.
- Для каждого утверждения назначается одна из трёх явных меток.
- Противоречия блокируются отдельно от недостаточных доказательств.
- Пороги откалиброваны на собственной размеченной выборке.
- Автоматически принятые ответы регулярно попадают в ручной аудит.
- Версии данных, инструкций и конфигурации сохраняются вместе с результатами.
Для расширения контура оценки перейдите к другим материалам в разделе руководств. Определения терминов, используемых при проектировании RAG-систем, собраны в глоссарии.