Проверка фактов по источнику: как собрать набор тестов
Проверка фактов по источнику помогает собрать тесты, где ответ сверяют с документом, таблицей или БД. Разберем схему набора, частые ошибки и чек-лист.

Почему проверка на глаз не работает
Аккуратный ответ легко принять за правильный. Модель пишет уверенно, держит хороший тон, повторяет слова из документа - и все равно ошибается в одной дате, сумме, ставке или сроке. Этого уже достаточно, чтобы смысл сломался.
Особенно заметно это в задачах, где ответ обязан опираться на источник, а не на "здравый смысл" модели. Если в регламенте указан срок 14 дней, а ответ говорит 10, хороший стиль ничего не меняет. Для банка, телеком-команды или сервиса поддержки это уже риск.
Модель часто читает источник не так, как человек. Она узнает знакомый шаблон и достраивает формулировку по памяти. Поэтому ручная проверка быстро начинает расходиться. Один ревьюер смотрит на общий смысл и ставит "верно". Другой замечает неверный лимит, пропущенное условие или измененный порядок шагов и ставит "неверно".
Хуже всего тихие ошибки. Ответ выглядит спокойно и убедительно, почти совпадает с источником, но подменяет одну деталь. Такие промахи команда пропускает чаще всего.
Обычно ручной просмотр ломается в четырех местах:
- стиль ответа влияет на оценку сильнее, чем факт
- ревьюеры по-разному понимают "почти правильно"
- мелкие расхождения замечают не сразу
- одну и ту же ошибку сегодня засчитывают, а завтра отклоняют
Если у команды нет жесткого эталона, качество кажется выше, чем есть. В оценке RAG это видно особенно хорошо: поиск нашел нужный фрагмент, ответ звучит разумно, значит все будто бы в порядке. На деле модель могла опереться на догадку, а не на документ.
Факты нужно проверять по правилу совпадения с источником. Иначе вы измеряете впечатление от ответа, а не его точность.
Что считать правильным ответом
Правильный ответ почти никогда не равен "похоже на правду". Для проверки по источнику подходит только то, что можно привязать к конкретному месту в документе или к полю в базе данных. Если такой привязки нет, вы снова оцениваете впечатление.
Сначала разбейте вопрос на короткие утверждения. Один вопрос часто содержит два или три независимых факта, и модель может угадать один, но ошибиться в другом. Проверять нужно каждый факт отдельно.
Простой пример с AI Router: вопрос "Сколько open-weight моделей платформа хостит на собственной GPU-инфраструктуре и в какой валюте идет B2B-инвойсинг?" содержит две отдельные проверки. Первая - число моделей. Вторая - валюта инвойсинга. Ответ "много моделей, счета выставляют в местной валюте" звучит уверенно, но тест он не проходит. В источнике сказано: "20+ open-weight моделей" и "ежемесячно в тенге".
Для каждого факта заранее укажите источник истины. Это может быть поле БД, строка в таблице, абзац в регламенте или конкретный фрагмент FAQ. Чем точнее эта привязка, тем меньше споров при проверке.
Отдельно задайте форму ответа. Без этого один и тот же факт начнут сравнивать по-разному. Обычно достаточно нескольких простых типов:
- число: 500+, 68, 20+
- дата: 2025-04-01 или 01.04.2025
- список: названия провайдеров, тарифов, документов
- точная фраза: формулировка, где нельзя менять слова
Точная строка нужна не всегда. Если вопрос про код продукта, номер договора, ставку, юридическую формулировку или содержимое поля БД, лучше требовать полное совпадение. Если вопрос про обычный факт из текста, часто хватает нормализации: убрать лишние пробелы, привести дату к одному формату, не учитывать регистр, считать "20 +" и "20+" одним значением.
Отдельный источник споров - списки. Решите заранее, важен ли порядок, можно ли принять частичный ответ и что делать с лишними элементами. Если в эталоне три провайдера, а модель назвала два верно и добавила один лишний, это уже не полный матч.
Если вы не можете записать эталон в виде "факт -> источник -> формат -> правило сравнения", тест еще сырой.
Откуда брать эталон
Эталон нельзя брать из памяти команды или из "примерно актуальной" версии источника. Он должен быть привязан к конкретному состоянию данных в день теста. Если вопрос относится к документу, сохраните точную версию файла. Если к таблице или справочнику, сделайте снимок базы на ту же дату.
Иначе тест быстро превращается в спор. Модель ответила по старой редакции регламента, а проверяющий смотрит уже в новый PDF. Или ответ про лимит верный для вчерашней таблицы, но утром цифру поменяли, и тест внезапно стал ложно плохим.
Есть данные, которые живут слишком недолго. Их лучше убрать из общего набора или вынести в отдельные тесты с коротким сроком жизни. Обычно это:
- остатки на складе
- текущий баланс
- статус заявки
- цена на сегодня
- дата последнего входа
Если оставить такие поля в общем наборе, вы будете чинить не модель, а сам тест. Для регулярной оценки лучше брать то, что меняется редко: условия договора, тарифные правила на дату публикации, статусы из фиксированного архива, справочники с версией.
Рядом с каждым вопросом храните не только правильный ответ, но и указатель на источник. Для записи из БД это id строки, имя таблицы и дата снимка. Для документа - имя файла, версия, страница, абзац, таблица или диапазон строк, если вы извлекали текст автоматически.
Такая привязка заметно экономит время. Когда тест падает, команда сразу видит, где искать правильный факт. Не нужно заново читать весь документ или гадать, из какой таблицы взяли цифру.
Смешивать нормы из документа и живые данные из базы можно только с явной меткой времени у обеих частей. Иначе ответ формально выглядит разумно, но проверить его нельзя. Например, правило расчета комиссии взяли из регламента за март, а ставку подтянули из таблицы за июнь. Такой эталон уже спорный.
В продакшен-системах это особенно заметно в RAG. Если компания проверяет ответы по внутренним инструкциям и данным клиентов через единый LLM-шлюз вроде AI Router, спор чаще возникает не из-за модели, а из-за разных версий источника. Сначала фиксируют источник, потом оценивают ответ.
Как собрать автоматический набор тестов
Автоматический набор тестов не начинают с придуманных вопросов. Его собирают из того, что люди уже спрашивают в работе: из логов чата, тикетов поддержки, внутренних регламентов, базы знаний и типовых запросов сотрудников. Тогда набор проверяет не абстрактную "умность", а реальные места, где система ошибается.
Для проверки по источнику подходят не все вопросы. Сначала отберите те, где в документе или в БД есть один ясный ответ. Если вопрос звучит как "что лучше" или "почему компания выбрала этот процесс", автоматическая сверка быстро развалится. А вот "какой срок обработки заявки", "какой тариф действует для сегмента B" или "какой статус у заказа 18452" подходят хорошо.
Удобно хранить каждый тест в одном шаблоне:
- вопрос
- эталонный ответ
- источник, где этот ответ найден
- правило сверки
Правило сверки лучше писать сразу. Для одного вопроса достаточно точного совпадения числа или даты. Для другого нужно проверять только обязательные поля: сумму, валюту, лимит, статус. Если в документе банка указано "до 5 рабочих дней", ответ "5 дней" уже нельзя считать полностью верным.
Добавляйте не только простые случаи. Самые полезные тесты обычно неприятные: похожие названия продуктов, сноски мелким текстом, исключения для отдельных филиалов, условия "кроме", "если", "при наличии". Именно они показывают, читает ли система источник или просто угадывает по общему смыслу.
Небольшой пример: в регламенте есть два похожих тарифа, а различие спрятано в примечании под таблицей. Если модель отвечает по памяти, она почти наверняка их перепутает. Один такой тест часто полезнее десятка обычных.
Когда база тестов вырастет, разделите ее по скорости и риску:
- быстрый набор для каждого изменения промпта или ретривера
- регрессионный набор для ежедневного прогона
- предрелизный набор для самых трудных и дорогих сценариев
Быстрый набор может включать 20-30 вопросов и занимать пару минут. Регрессионный уже шире и ловит старые ошибки, которые любят возвращаться. Предрелизный набор нужен перед выкладкой, когда цена промаха выше: в банке, телекоме, медицине или в любом сервисе, где ответ должен совпасть с документом, а не просто звучать правдоподобно.
Как сверять ответ с документом или БД
Сверка должна проверять не "похоже на правду", а "совпадает с источником". Для этого ответ и эталон сначала приводят к одному виду, а потом сравнивают по правилам, которые легко повторить в коде.
Начните с нормализации. Уберите лишние пробелы, приведите текст к одному регистру, даты переведите в единый формат, валюты - к одному обозначению. Иначе система начнет спорить с мелочами: "15.01.2025" и "2025-01-15" значат одно и то же, но строковое сравнение сочтет их разными.
С числами лучше быть строже. Сравнивайте не только само число, но и единицу измерения. "12" и "12 месяцев" - не одно и то же. "500" без уточнения тоже опасно: это может быть 500 запросов, 500 тенге или 500 моделей. Если бот отвечает про тариф, лимит или срок, единица должна стоять рядом с числом.
Ссылка на источник обязательна
Ответ без опоры на конкретное место в источнике лучше не считать проверенным. Для документа это может быть номер строки, абзаца или раздела. Для БД - идентификатор записи, первичный ключ или комбинация полей, по которой запись нашли.
Если внутренний помощник пишет, что в AI Router инвойсинг идет ежемесячно в тенге, тест должен проверить не только саму фразу, но и ссылку на нужный фрагмент документа или ID записи в справочнике тарифов. Тогда видно, откуда взялся факт.
Частичное совпадение и лишние факты
Полезно хранить как минимум четыре статуса:
- полное совпадение
- частичное совпадение
- промах
- лишние факты вне источника
Частичное совпадение важно отделять от промаха. Ответ "хранение данных внутри страны" может быть верным по смыслу, но неполным, если в документе еще есть аудит-логи, маскирование PII и rate-limits. Это не ноль баллов, но и не полный зачет.
Отдельно ловите добавленные факты. Модель часто пишет уверенно и добавляет детали, которых нет в документе или записи БД. Если источник говорит только "ежемесячно в тенге", а ответ добавляет "с оплатой до 5 числа", это ошибка, даже если первая часть верна.
Хорошая сверка обычно смотрит сразу на три вещи: верен ли факт, есть ли ссылка на источник и не придумал ли ответ ничего сверху.
Пример на одном сценарии
Представьте чат поддержки банка. Клиент спрашивает: "Сколько у меня осталось бесплатного лимита на переводы в этом месяце и по какому правилу он считается?" Ответ должен быть коротким: одно число и одно правило, без пересказа всего тарифа.
Данные лежат в двух местах. PDF с тарифом говорит: по пакету "Smart" переводы без комиссии доступны до 300 000 тенге в месяц, после этого комиссия 1%. База данных хранит текущее состояние клиента: за месяц он уже использовал 120 000 тенге.
Как выглядит один тест
В карточке теста обычно хватает четырех полей:
- вопрос: "Сколько у меня осталось бесплатного лимита на переводы и какое правило действует?"
- фрагмент документа: "Без комиссии доступны переводы до 300 000 тенге в месяц. После превышения лимита комиссия 1%"
- снимок из БД: monthly_limit = 300000, used_amount = 120000
- ожидаемый ответ: "180 000 тенге. Без комиссии можно переводить, пока общая сумма за месяц не превысит 300 000 тенге"
Число 180 000 не берут из документа. Его считают по данным клиента. Правило, наоборот, нельзя достраивать по логике модели. Его нужно взять из PDF и сверить с формулировкой или с нормализованным шаблоном.
Такой тест падает в двух типовых случаях. Первый: модель путает текущий остаток с полным лимитом и отвечает "300 000 тенге". Второй: она добавляет лишнее условие, которого нет в источнике, например "только для переводов внутри банка" или "только после верификации".
Хорошая проверка не спорит с общим впечатлением от ответа. Она задает два простых вопроса: совпало ли число с расчетом из БД и есть ли в тексте только то правило, которое есть в документе. Если хотя бы один пункт не сошелся, кейс считается проваленным, даже если ответ звучит убедительно.
Такой подход быстро показывает, где ломается система: в извлечении факта из PDF, в запросе к БД или в финальной сборке ответа. Для оценки RAG это намного честнее, чем читать ответы вручную и ставить им "похоже верно".
Где чаще всего ошибаются
Многие ломают тесты еще на этапе подбора вопросов. Берут вопрос, на который источник отвечает с оговоркой, в двух местах сразу или вообще не дает одного точного вывода. Потом модель выбирает один допустимый вариант, а тест считает это ошибкой. Для проверки по источнику такие кейсы лучше убирать сразу.
Часто путают и сам эталон. Если записать правильный ответ длинным абзацем, команда быстро скатится в спор о словах. Модель может верно назвать сумму, дату или статус, но тест упадет из-за другой формулировки. Намного лучше хранить эталон как короткое значение, пару допустимых вариантов или набор полей, которые можно сравнить без догадок.
С базой данных есть своя ловушка. Правило уже поменяли, лимит обновили, статус переименовали, а тесты все еще смотрят в старый снимок БД. После этого любой отчет теряет смысл: вы уже не понимаете, ответ неверный или устарел эталон. Если сверка идет по данным, снимок нужно версионировать и обновлять по понятному правилу.
Еще одна частая ошибка - складывать в одну корзину сбой поиска и сбой генерации. Это разные поломки. Если система не достала нужный документ, чинить надо поиск, фильтры или индекс. Если документ найден, но модель перепутала число или придумала лишнюю деталь, проблема уже в генерации, промпте или самой модели.
Отдельная тема - нормализация. Без нее тесты начинают ругаться на мелочи, которые не меняют смысл. Обычно стоит заранее привести к одному виду:
- названия компаний и продуктов
- форматы дат
- валюты и числа
- коды, статусы и сокращения
Простой пример: в БД тариф хранится как PRO_UNL, в документе он назван Pro Unlimited, а в ответе модель пишет "Безлимит Pro". Если вы не свели эти варианты к одному канону, тест отметит ошибку там, где ее нет.
Такие промахи часто встречаются в командах, которые уже сравнивают несколько моделей и считают качество по батчам. Если убрать двусмысленные вопросы, коротко описать эталон, следить за снимком БД и разделить типы ошибок, отчет по тестам станет заметно честнее. Тогда видно, что именно сломалось: поиск, генерация или сами правила сверки.
Быстрый чек-лист перед запуском
Перед первым прогоном полезно остановиться на пяти вещах. Если хотя бы одна из них расплывчата, тесты быстро начнут спорить с людьми, а не ловить ошибки модели.
- Для каждого вопроса выберите один источник истины: конкретный документ, версию таблицы или SQL-выборку. Если ответ можно оправдать двумя разными местами, тест будет спорным.
- Запишите эталон в одной короткой формулировке. Эталон не должен быть "хорошим ответом для клиента". Он должен проверять факт: дату, сумму, статус, имя поля или точную цитату.
- Рядом с тестом сохраните правило сверки. Где-то нужна точная строка, где-то совпадение числа в нужной валюте, где-то можно принять один из двух вариантов формулировки.
- Добавьте не только простые примеры. Нужны и пограничные случаи: пустое поле, старая версия документа, похожие названия, две почти одинаковые записи в базе.
- Зафиксируйте версию данных. Команда должна уметь повторить прогон через неделю и получить тот же результат на том же наборе документов и той же выгрузке.
Чаще всего ломается второй пункт. Люди пишут эталон слишком широко, и модель проходит тест за счет гладкого текста. Если вопрос звучит "какой срок оплаты по договору?", эталон лучше записать как "10 банковских дней" и отдельно указать, что лишние детали не нужны. Тогда проверка по документу оценивает факт, а не стиль.
К каждому тесту полезно добавить короткий комментарий. Одной строки хватает: "сверяем только значение лимита из таблицы tariffs_2025_02" или "принимаем ответ, если модель вернула точное название статуса из пункта 4.2". Это экономит часы, когда команда спорит, кто прав: модель, проверка или сам тест.
Если вы запускаете оценку RAG на внутренних регламентах или на данных из БД, такой чек-лист часто важнее сложной метрики. Он сразу показывает, где модель путает источник, где ретривер приносит не тот фрагмент, а где ошибка сидит в самих тестах.
Что делать дальше в продакшене
Один удачный прогон ничего не значит. В рабочей системе ответы плывут после мелких правок: поменяли промпт, обновили ретривер, добавили фильтр по метаданным, переключили модель - и вчерашний правильный ответ уже не проходит сверку с источником.
Поэтому тесты нужно встроить в обычный цикл релизов. Любое изменение, которое влияет на ответ, должно запускать один и тот же набор проверок. Иначе команда узнает о поломке только после жалобы пользователя.
Общий балл полезен, но он слишком грубый. Если система набрала 82%, этого мало для решения. Нужно видеть, где именно она ошиблась: взяла не ту строку из БД, пропустила число, перепутала единицы, придумала значение или склеила два источника в один ответ.
Такой разбор быстро показывает, что чинить первым. Иногда проблема в модели. Но часто причина скучнее: плохой chunking, лишний SQL join, неверный post-processing или слишком мягкое правило сравнения.
Удобно держать три отдельных набора тестов:
- для ответов только по документам
- для ответов только по SQL и другим данным из БД
- для смешанных сценариев, где модель читает и документ, и таблицу
Смешивать их в один набор обычно вредно. Средний балл скрывает поломки. По документам система может работать хорошо, а на SQL стабильно путать фильтры и даты.
Если команда сравнивает много моделей и провайдеров, стенд лучше не усложнять. Проще гонять один и тот же набор через единый OpenAI-совместимый шлюз. В случае AI Router достаточно сменить base_url на api.airouter.kz и дальше прогонять те же SDK, код и промпты через разные модели без отдельной обвязки под каждого провайдера. Это экономит время и делает сравнение чище.
Еще один практичный шаг - хранить историю прогонов. Тогда видно не только текущий результат, но и момент, когда система начала ошибаться на конкретном факте. Такой журнал полезнее красивого дашборда: он помогает быстро связать деградацию с коммитом, новой моделью или изменением схемы данных.
Если набор тестов живет рядом с кодом, запускается после каждого изменения и показывает тип ошибки, команда перестает спорить по ощущениям. Сразу видно, какой ответ совпал с документом или БД, а какой нет.
Часто задаваемые вопросы
Почему ручная проверка на глаз часто ошибается?
Потому что уверенный и аккуратный текст легко маскирует ошибку в дате, сумме, лимите или сроке. Ревьюеры еще и смотрят по-разному: один засчитает общий смысл, другой заметит подмену детали и отклонит ответ.
Что считать правильным ответом в тесте по источнику?
Правильным считайте только тот факт, который можно привязать к конкретному месту в документе или к полю в базе. Если вы не можете показать источник и правило сравнения, вы проверяете впечатление, а не точность.
Как проверять вопрос, если в нем несколько фактов?
Разбейте вопрос на отдельные утверждения и проверяйте каждое по своему правилу. Если в вопросе есть число и валюта, модель может угадать одно и ошибиться во втором, поэтому общий зачет тут вреден.
Откуда брать эталонный ответ, если данные меняются?
Берите эталон из зафиксированной версии данных на день теста: из конкретного файла, снимка таблицы или выгрузки. Иначе сегодня ответ пройдет, а завтра тот же кейс упадет только потому, что источник уже изменился.
Какие вопросы лучше не включать в автоматический набор?
Не автоматизируйте вопросы без одного ясного ответа. Мнения, объяснения в духе «почему так решили» и живые поля вроде текущего баланса или цены на сегодня лучше вынести в отдельные короткоживущие тесты или убрать из общего набора.
Что нужно хранить в одном тест-кейсе?
В одном тесте держите сам вопрос, короткий эталон, точный указатель на источник и правило сверки. Этого хватает, чтобы команда быстро поняла, где лежит правильный факт и почему тест засчитал или отклонил ответ.
Нужно ли нормализовать ответ перед сверкой?
Да, без нормализации тесты начнут спорить с пробелами, регистром и форматами дат. Сначала приведите ответ и эталон к одному виду, а числа всегда сверяйте вместе с единицей измерения и валютой.
Что делать с частично верным ответом и лишними деталями?
Сразу разделяйте полное совпадение, частичное совпадение и промах. Если ответ назвал верный факт, но добавил деталь, которой нет в документе, это уже ошибка, даже когда первая часть звучит верно.
Как понять, что сломался поиск, а не сама модель?
Смотрите на путь до ответа. Если система не нашла нужный фрагмент или запись, чините поиск, фильтры или индекс; если источник найден, а число или правило перепутаны, проблема уже в генерации, промпте или модели.
Когда такие тесты нужно запускать в продакшене?
Запускайте один и тот же набор после каждого изменения, которое влияет на ответ: промпта, ретривера, модели, постобработки или SQL. Полезно хранить историю прогонов, чтобы сразу видеть, когда и после какого изменения система начала ошибаться.