
Как проверить JSON-ответ LLM по схеме и смыслу на реальных данных
Проверка JSON-ответа LLM начинается не с вопроса «можно ли распарсить строку?», а с трёх отдельных проверок: корректен ли синтаксис, соответствует ли объект ожидаемой структуре и верны ли извлечённые значения. Если ограничиться первой, модель может вернуть формально допустимый JSON с неправильной датой, выдуманным идентификатором или противоречием между полями.
Ниже — практический способ проверить обработчик заявок. Он подходит для извлечения данных из писем, классификации обращений и преобразования текста в записи для API. Пример использует Python, JSON Schema и набор тестовых примеров; его можно адаптировать к Pydantic и другому валидатору.
JSON Schema отсекает неверную структуру, но не проверяет факты
JSON Schema описывает допустимую форму объекта: названия полей, типы, обязательность и некоторые ограничения. Например, схема может запретить строку вместо числа или лишнее поле. Стандарт не устанавливает, правдива ли дата в ответе модели и следует ли значение из исходного письма.
Документация JSON Schema предлагает задавать ограничения для свойств и обязательных полей. Для небольшого контракта можно начать с такого описания:
json
{
«type»: «object»,
«properties»: {
«ticket_id»: {«type»: «string»},
«priority»: {
«type»: «string»,
«enum»: [«low», «normal», «high»]
},
«due_date»: {
«type»: [«string», «null»],
«format»: «date»
}
},
«required»: [«ticket_id», «priority», «due_date»],
«additionalProperties»: false
}
`required` требует наличия перечисленных ключей, но не означает, что значение каждого поля содержательно верно. `enum` ограничивает приоритет заданным набором. `additionalProperties: false` запрещает дополнительные свойства на том уровне объекта, где оно задано.
Формат `date` тоже требует внимания: поддержку проверки форматов определяет конкретный валидатор и его конфигурация. Если дата важна для последующих действий, отдельно проверьте её парсинг и допустимый диапазон. Нельзя считать, что одно описание формата автоматически обеспечивает одинаковую проверку во всех библиотеках.
Разделите тест на три уровня
Удобнее хранить проверки слоями — тогда сбой показывает, что именно пошло не так.
Синтаксис: ответ разбирается как JSON, без обрамляющего Markdown и постороннего текста.
2. Структура: объект проходит проверку схемы.
3. Смысл: значения согласуются с исходным запросом, бизнес-правилами и эталонной разметкой.
Для синтаксиса в Python подойдёт стандартный модуль `json`. Ошибка разбора должна считаться отдельным результатом теста, а не исправляться молча. Автоматически удалять кавычки, обрезать текст до первой фигурной скобки или подменять отсутствующие поля значениями по умолчанию опасно: такой «ремонт» может скрыть систематический дефект вывода.
Для структурной проверки можно использовать пакет `jsonschema` либо модель Pydantic. В Pydantic метод `model_validate_json` принимает JSON и проверяет его по объявленной модели. Это удобно для Python-приложения, однако контракт для внешних систем всё равно стоит сверять с тем, что они ожидают.
Соберите компактный набор примеров с эталоном
Тестовый набор должен отражать не только обычные случаи. Добавьте неоднозначные формулировки, отсутствующие сведения, противоречивые даты и значения, которые нельзя выводить из текста.
Например, для извлечения даты срока можно подготовить такие записи:
python
cases = [
{
«text»: «Пожалуйста, ответьте до 2026-05-14.»,
«expected»: «2026-05-14»
},
{
«text»: «Срок ответа не указан.»,
«expected»: None
},
{
«text»: «Отправили письмо 2026-05-10; срок ответа не указан.»,
«expected»: None
}
]
Эталон здесь выражает поведение продукта: если срок не указан, модель должна вернуть `null`, а не угадать дату из контекста. Для другого приложения правила могут быть иными, но их нужно зафиксировать до сравнения моделей.
Храните примеры вместе с версией инструкции, схемы и вызываемой модели. Иначе изменение результата трудно связать с конкретной причиной: обновлением схемы, новым промптом или новой версией модели. Не включайте в тестовый набор реальные персональные данные без необходимости; используйте обезличенные или синтетические примеры, если они сохраняют нужную сложность.
Проверьте контракт в коде до подключения к приложению
Минимальный тест может одновременно проверять разбор JSON, схему и один смысловой инвариант. Ниже функция для сопоставления даты с эталоном; генерацию ответа она намеренно не выполняет.
python
import json
from jsonschema import Draft202012Validator
schema = {
«type»: «object»,
«properties»: {
«ticket_id»: {«type»: «string»},
«priority»: {
«type»: «string»,
«enum»: [«low», «normal», «high»]
},
«due_date»: {
«type»: [«string», «null»],
«format»: «date»
}
},
«required»: [«ticket_id», «priority», «due_date»],
«additionalProperties»: False
}
validator = Draft202012Validator(schema)
def check_response(raw: str, expected_due_date: str | None):
obj = json.loads(raw)
errors = list(validator.iter_errors(obj))
if errors:
return False, [error.message for error in errors]
if obj[«due_date»] != expected_due_date:
return False, [
f»ожидалось {expected_due_date!r}, «
f»получено {obj[‘due_date’]!r}»
]
return True, []
Это учебный пример, не готовый production-обработчик. В частности, следует отдельно обрабатывать исключение `json.JSONDecodeError`, включить поддержку проверки форматов в используемом валидаторе и добавить содержательные проверки для идентификатора и приоритета. Для настоящей заявки эталонную дату нужно сопоставлять с исходным текстом или разметкой, а не передавать в проверку вручную.
Тесты на нескольких случаях легко параметризовать через `pytest`. Если отдельный тест падает на конкретном примере, сохраняйте сырой ответ и диагностические ошибки. Не записывайте в логи чувствительные тексты без правил доступа и хранения.
Проверяйте бизнес-правила отдельно от схемы
Некоторые условия удобно описать схемой, другие относятся к логике приложения. Например:
- дата окончания не должна быть раньше даты начала;
- идентификатор должен существовать в вашей базе;
- приоритет `high` требует заполненного объяснения;
- поле `due_date` должно оставаться `null`, если в источнике нет срока;
- сумма позиций должна совпадать с заявленным итогом.
Такие проверки не сводятся к валидности JSON. Их можно оформить функциями после структурной валидации и покрыть отдельными тестами. Важно различать правила, основанные на проверяемых данных, и эвристики. Проверка «объяснение не пустое» не доказывает, что объяснение верно.
Для задач извлечения полезно сравнивать нормализованные значения с эталоном. Для классификации — считать качество по каждому классу, а не только общую долю правильных ответов: редкая категория может быть полностью пропущена, хотя средняя метрика выглядит приемлемо. Для свободного текста в поле `reason` точное сравнение строк часто малоинформативно; задайте проверяемые требования либо проведите отдельную ручную оценку на выборке.
Структурированный вывод модели не заменяет независимую проверку
Некоторые API предоставляют режимы структурированного вывода и принимают схему, ограничивая форму ответа. Это может уменьшить число ошибок формата, но не гарантирует правильность извлечения или соответствие ответу вашему источнику. Условия и ограничения такого режима зависят от конкретного API и модели — сверяйтесь с актуальной документацией поставщика.
Для воспроизводимого сравнения разделяйте две метрики:
- доля структурно корректных ответов — сколько результатов проходят разбор и схему;
- доля семантически корректных ответов — сколько результатов совпадают с эталоном или выполняют заранее определённые правила.
Если первая высокая, а вторая низкая, смена валидатора не решит проблему: нужно проверять инструкцию, примеры, неоднозначность входных данных и поведение модели. Если ломается структура, изучите формат ответа, схему и обработку отказов.
Прогоняйте одни и те же случаи после каждого изменения
Сравнивайте версии на одном и том же наборе, сохраняя исходные входы и конфигурацию. Для каждой версии полезно записывать:
- долю ошибок разбора JSON;
- долю нарушений схемы;
- ошибки каждого бизнес-правила;
- совпадение значений с эталоном;
- задержку и стоимость, если они значимы для сценария.
Отдельный «сложный» набор можно держать для редких случаев, но не подменяйте им обычный тест: важно видеть результаты и на типичных запросах. Документация `pytest` описывает параметризацию тестов, позволяющую выполнить одну проверку на нескольких входах. Для результатов модели добавьте фиксацию версии и параметров запроса: генерация может быть вариативной, а изменение настройки влияет на сравнение.
Порог приёмки выбирайте по последствиям ошибки. Некорректная категория в черновой сортировке и неверная дата в автоматической отправке — не одинаковый риск. Если значение запускает действие, предусмотрите отказ от автоматического выполнения при провале проверки и путь ручного подтверждения.
Что проверить перед подключением JSON к автоматизации
Перед передачей результата в следующий компонент убедитесь, что тесты проверяют не только красивый пример, но и пустые, противоречивые и неполные входы. Отдельно проверьте, как приложение обрабатывает отказ модели, ошибку разбора, лишние поля и значение, которое не подтверждается источником.
Минимальный полезный контур — схема с запрещёнными лишними ключами, валидатор в коде, набор примеров с эталоном и тесты бизнес-правил. Сохраните исходный текст, версию схемы и диагностический результат в рамках принятой политики данных. Если меняются модель или инструкция, повторите тот же прогон и сравните оба уровня качества — структуру и смысл.









