Как проверить JSON-ответ LLM по схеме и смыслу на реальных данных

JSON от языковой модели может пройти проверку синтаксиса и всё равно содержать неверные или противоречивые данные. Разбираем проверку схемы, бизнес-правил и качества извлечения на небольшом воспроизводимом тесте.

Схема проверки JSON-ответа LLM: разбор, валидация по JSON Schema и проверка бизнес-правил
Схема проверки JSON-ответа LLM: разбор, валидация по JSON Schema и проверка бизнес-правил
Visit of Ursula von der Leyen, President of the European Commission, to India (P-065755-00-37).jpg | by Europäische Kommission — Audiovisueller Dienst, CE — Service audiovisuel, EC — Audiovisual Service, Dati Bendo | wikimedia_commons | CC BY 4.0

Как проверить 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 к автоматизации

Перед передачей результата в следующий компонент убедитесь, что тесты проверяют не только красивый пример, но и пустые, противоречивые и неполные входы. Отдельно проверьте, как приложение обрабатывает отказ модели, ошибку разбора, лишние поля и значение, которое не подтверждается источником.

Минимальный полезный контур — схема с запрещёнными лишними ключами, валидатор в коде, набор примеров с эталоном и тесты бизнес-правил. Сохраните исходный текст, версию схемы и диагностический результат в рамках принятой политики данных. Если меняются модель или инструкция, повторите тот же прогон и сравните оба уровня качества — структуру и смысл.

Источники