Как измерить долю ошибок LLM в структурированном выводе

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

Схема проверки ответа LLM: разбор JSON, валидация по JSON Schema и оценка значений полей
Схема проверки ответа LLM: разбор JSON, валидация по JSON Schema и оценка значений полей
Fırtına Haber.png | by Fırtına Haber | wikimedia_commons | CC BY-SA 4.0

Структурированный вывод LLM часто проверяют одним условием: удалось ли разобрать ответ как JSON. Этого недостаточно. Корректно разобранный объект может не соответствовать схеме, а объект, прошедший формальную валидацию, — содержать неверную дату, перепутанные поля или выдуманное значение.

Чтобы измерить ошибки LLM в структурированном выводе, разделите проверку на три уровня: синтаксис, схема и смысл. Такой тест покажет, на каком этапе система ошибается и что именно нужно исправлять — формат запроса, ограничение генерации или последующую бизнес-валидацию.

Что считать ошибкой структурированного вывода

Сначала зафиксируйте контракт. Например, модель должна извлечь из обращения клиента дату, сумму и категорию запроса. Контракт определяет, какие ключи обязательны, какие типы данных допустимы и что делать, если значение неизвестно.

Удобно различать три класса ошибок:

Синтаксическая: ответ не разбирается как JSON.

Структурная: JSON разбирается, но нарушает схему — отсутствует обязательное поле, неверен тип или не соблюдено ограничение.
3. Семантическая: форма подходит, но значение не соответствует исходным данным или правилам задачи.

Это не универсальная таксономия, а практичная схема диагностики. Она не позволяет свести все сбои к единому показателю «точности JSON». Отдельно учитывайте ошибки инфраструктуры: тайм-аут, отказ модели, пустой ответ и ошибку API. Они не являются ошибками JSON, но влияют на долю успешных обработок.

Как измерить ошибки LLM в структурированном выводе

Соберите тестовый набор из реальных или обезличенных входов. Включите обычные случаи, пропуски данных, неоднозначные формулировки, необычные форматы и примеры, где нужное значение отсутствует. Для каждого входа подготовьте ожидаемый результат или правила, по которым его можно проверить.

Например, для извлечения суммы можно проверять точное совпадение с размеченным значением. Для категории — соответствие разрешенному списку. Для даты — не только формат, но и совпадение с датой в исходном сообщении.

Обозначим:

  • \(N\) — число запросов в тесте;
  • \(P\) — ответы, успешно разобранные как JSON;
  • \(S\) — разобранные ответы, прошедшие проверку схемы;
  • \(M\) — ответы, прошедшие смысловые проверки.

Тогда полезны три доли:

  • Синтаксическая успешность: \(P/N\).
  • Структурная успешность: \(S/P\) — доля валидных по схеме среди разобранных ответов.
  • Сквозная успешность: \(M/N\) — доля ответов, прошедших все нужные проверки.

Сквозной показатель важен для сравнения систем, но без разложения по этапам он плохо объясняет причину сбоя. Публикуйте и числитель, и знаменатель: результат 90% на 20 примерах намного менее устойчив, чем тот же результат на 2 000 примерах.

Задайте схему, которую можно проверить

JSON Schema позволяет описать структуру объекта и ограничения на его поля. Например, минимальный контракт для извлечения обращения может выглядеть так:

json
{
«type»: «object»,
«properties»: {
«category»: {
«type»: «string»,
«enum»: [«billing», «technical», «other»]
},
«amount»: {
«type»: [«number», «null»]
},
«date»: {
«type»: [«string», «null»],
«format»: «date»
}
},
«required»: [«category», «amount», «date»],
«additionalProperties»: false
}

`required` задает обязательные ключи, а `additionalProperties: false` запрещает незаявленные поля. Перечисление категорий ограничивает допустимые значения. Возможность `null` помогает представить отсутствие данных явно, а не подменять его догадкой.

При этом JSON Schema сама по себе не гарантирует, что дата верна относительно сообщения. Даже проверка `format: date` зависит от используемого валидатора и его настроек. Смысловые условия нужно реализовать отдельно.

Для Python можно использовать `jsonschema`:

python
import json
from jsonschema import Draft202012Validator

schema = {
«type»: «object»,
«properties»: {
«category»: {
«type»: «string»,
«enum»: [«billing», «technical», «other»]
},
«amount»: {«type»: [«number», «null»]},
«date»: {«type»: [«string», «null»], «format»: «date»}
},
«required»: [«category», «amount», «date»],
«additionalProperties»: False
}

validator = Draft202012Validator(schema)

def check_response(raw):
try:
data = json.loads(raw)
except json.JSONDecodeError as exc:
return {«stage»: «syntax», «error»: str(exc)}

errors = list(validator.iter_errors(data))
if errors:
return {
«stage»: «schema»,
«errors»: [error.message for error in errors]
}

return {«stage»: «passed», «data»: data}

Учитывайте версию диалекта схемы: пример явно использует Draft 2020-12. Также проверьте настройки валидатора, если хотите проверять форматы вроде даты. Библиотека для валидации не заменяет тестирование вашей прикладной логики.

Проверьте смысл, а не только форму

После валидации схемы добавьте проверки, специфичные для задачи. Для извлечения суммы можно сопоставить результат с эталонной разметкой. Для категории — проверить, что решение соответствует описанным правилам. Для даты — сверить значение с источником, а не только с календарным форматом.

Простой пример для набора с эталонными ответами:

python
def semantic_checks(result, expected):
if result[«stage»] != «passed»:
return False

data = result[«data»]
return (
data[«category»] == expected[«category»]
and data[«amount»] == expected[«amount»]
and data[«date»] == expected[«date»]
)

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

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

Добавьте сложные входы и отрицательные тесты

Набор только из простых примеров покажет, как модель ведет себя в идеальных условиях, но не выявит хрупкие места. Добавьте случаи:

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

Для каждого случая заранее решите, какое поведение считается правильным. Если данных недостаточно, ожидаемый ответ может предусматривать `null` или отказ от классификации. Иначе система будет поощрять уверенную подстановку значения вместо корректного сообщения о неопределенности.

Повторите тест на нескольких запусках, если генерация недетерминирована или используется случайность. Храните входы, сырые ответы, версии модели и схемы, параметры запроса и ошибки валидатора. Без этого результат после обновления модели трудно сравнить с предыдущим.

Сравните режим генерации со свободным JSON

Некоторые API предлагают режимы, которые ограничивают ответ структурой или принимают схему напрямую. Например, документация OpenAI описывает Structured Outputs, а Anthropic — structured outputs в Claude. Их возможности и ограничения зависят от конкретного API и поддерживаемого подмножества схемы; проверяйте актуальную документацию перед интеграцией.

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

  • сквозную смысловую успешность;
  • долю отказов и ошибок API;
  • задержку;
  • число повторных запросов;
  • стоимость на успешно обработанный запрос;
  • поведение на неподдерживаемых ограничениях схемы.

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

Разберите ошибки по причинам

Сохраните классификацию неудач: синтаксис, схема, смысл, отказ, инфраструктура. Для структурных ошибок записывайте конкретное нарушение — например, пропущенный ключ или недопустимый тип. Для смысловых — поле и правило, которое не прошло проверку.

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

Не скрывайте повторные попытки за итоговой метрикой. Считайте отдельно долю ответов с первой попытки и долю успешных после повторов. Иначе система с дорогими повторными запросами может выглядеть такой же надежной, как система, которая отвечает корректно сразу.

Когда результат теста нельзя обобщать

Тест оценивает конкретные версии модели, промпта, схемы и параметров на конкретных данных. Он не доказывает качество на новом языке, другой предметной области или свежих типах запросов. Разделяйте наборы настройки и оценки, чтобы не подгонять систему под одни и те же примеры.

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

Перед внедрением проверьте версию диалекта JSON Schema, поддержку нужных ограничений в API, обработку отказов и пустых ответов, а также сохранение сырых результатов для аудита. Затем прогоните тестовый набор после каждой смены модели, схемы или инструкции и сравните не одну общую цифру, а ошибки на каждом уровне.

Источники