Почему «верни только JSON» перестаёт работать в автоматизации
Человек спокойно проигнорирует пояснение перед объектом, исправит запятую и догадается, что строка «не указано» означает отсутствие даты. Программа ожидает строгий контракт. Один лишний code fence, новое имя поля или число с символом валюты ломают следующий шаг.
Структурированный ответ - это результат модели, ограниченный заранее заданной схемой. Он нужен, когда данные идут в CRM, базу, таблицу, API, маршрутизатор или инструмент агента. Для обычного объяснения человеку свободный текст часто удобнее.
JSON, JSON mode и Structured Outputs - три разных уровня
| Подход | Что обещает | Чего не обещает |
|---|---|---|
| Текстовый промпт | Модель постарается следовать примеру | Валидный JSON и постоянные поля |
| JSON mode | Синтаксически корректный JSON при штатном ответе | Соответствие вашей схеме |
| Structured Outputs | Соответствие поддерживаемой JSON Schema | Истинность и безопасность значений |
| Function calling | Структурированные аргументы для вызова | Что действие разрешено выполнять |
OpenAI различает JSON mode и Structured Outputs: строгий режим связывает ответ со схемой. В документации Gemini также разделены structured output для финального формата и function calling для действия. У Anthropic доступны JSON outputs и strict tool use. Названия и поддерживаемые модели меняются, поэтому актуальные условия проверяйте в документации провайдера.
Сначала контракт данных, потом промпт
Начните не с инструкции модели, а с потребителя результата. Какие поля действительно нужны следующему шагу? Какие обязательны? Какие значения допустимы? Что делать, если исходный текст не содержит ответа?
- Назначение объекта и система-получатель.
- Имя и смысл каждого поля.
- Тип, единица измерения и формат.
- Обязательность и допустимость null.
- Закрытый список значений для классификаций.
- Минимальное и максимальное количество элементов.
- Источник доказательства для извлечённого значения.
- Правила, которые нельзя выразить одной схемой.
- Действие при неуверенности или конфликте.
- Номер версии и совместимость.
Чем меньше контракт, тем легче модели и коду соблюдать его. Не включайте поля «на будущее» и не заставляйте модель генерировать данные, которые можно надёжно вычислить после ответа.
Минимальная JSON Schema для извлечения заявки
JSON Schema описывает типы и ограничения JSON-документа. В полном стандарте возможностей много, но API моделей обычно поддерживают только подмножество. Проверяйте конкретные keywords у провайдера до разработки.
{
"type": "object",
"properties": {
"customer_name": {
"type": ["string", "null"],
"description": "Имя ровно как во входе; null, если не указано"
},
"request_type": {
"type": "string",
"enum": ["consultation", "support", "partnership", "other"]
},
"summary": {
"type": "string",
"description": "Краткое описание без добавления новых фактов"
},
"needs_review": {"type": "boolean"}
},
"required": ["customer_name", "request_type", "summary", "needs_review"],
"additionalProperties": false
}В JSON Schema поля из properties не становятся обязательными автоматически: для этого существует required. Значение null также не равно отсутствующему полю - его нужно разрешить в типе явно.
Descriptions - часть инструкции модели
Название amount не объясняет, включает ли сумма налог, какую валюту использовать и можно ли округлять. Description должен снять неоднозначность: источник, единица, правило преобразования и поведение при отсутствии данных.
| Слабое описание | Рабочее описание |
|---|---|
| Дата | Дата начала из договора в формате YYYY-MM-DD; null, если прямо не указана |
| Цена | Числовая итоговая сумма с НДС без символа валюты; не вычислять при конфликте итогов |
| Категория | Одна из enum по главной цели обращения, а не по отдельным словам |
| Резюме | До трёх предложений, только факты из входного текста |
Не помещайте противоречащие правила одновременно в system prompt, пользовательский текст и description. Определите один приоритетный источник контракта.
Required, optional и null: выберите одну семантику
Отсутствующее поле может означать «не запрашивали», «не нашли», «не применимо» или «модель забыла». Для аналитики и интеграций это разные состояния. Во многих strict-реализациях удобнее сделать поле обязательным, но разрешить null, если значение может отсутствовать.
field: null- поле предусмотрено контрактом, данных нет.- Поля нет - контракт допускает его отсутствие или ответ нарушен.
- Пустая строка - строка существует, но пуста; не используйте её как универсальный null.
0иfalse- реальные значения, а не отсутствие данных.
Если причина отсутствия важна, добавьте отдельный enum: missing_reason со значениями вроде not_provided, conflict, not_applicable.
Enum закрывает словарь и упрощает маршрутизацию
Свободная классификация быстро порождает варианты «продажи», «отдел продаж», sales и «коммерческий вопрос». Enum делает допустимый словарь частью контракта и позволяет писать обычный код без fuzzy matching.
Категории должны быть взаимно различимыми и иметь понятный fallback. Если почти всё попадает в other, исправляйте таксономию и примеры. Не просите модель выбирать код, значение которого не описано.
Проверь таксономию для классификатора. Категории: [enum и определения]. Примеры обращений: [обезличенная выборка]. Найди пересечения, отсутствующие классы и случаи, где нужен other или manual_review. Предложи непересекающиеся определения и по два граничных примера на класс. Не меняй бизнес-маршруты без отдельного списка предложений.
Запрет лишних полей защищает контракт от дрейфа
Без ограничения модель может добавить пояснение, confidence или альтернативное поле. Потребитель иногда молча проигнорирует его, а иногда сохранит неожиданные данные. В JSON Schema для контроля дополнительных свойств используется additionalProperties.
Но поддержка и поведение keywords зависят от реализации и композиции схем. Официальная документация JSON Schema предупреждает, что additionalProperties учитывает свойства в той же subschema и требует внимания при расширении через allOf. Для model API сначала проверяйте поддерживаемое подмножество, затем тестируйте схему реальным запросом.
Структурированный ответ не гарантирует правильные данные
Объект может идеально пройти schema validation и содержать выдуманный телефон, неверную сумму или дату не из документа. Схема проверяет форму. Семантическая корректность требует других механизмов.
Четыре слоя валидации в production pipeline
- Транспорт. Получен ли ответ, завершился ли запрос штатно, нет ли timeout или отказа.
- Синтаксис. Разбирается ли JSON стандартным parser без регулярных выражений и ручного удаления fences.
- Схема. Совпадают ли типы, required, enum, массивы и дополнительные поля.
- Бизнес-правила. Существует ли клиент, сходится ли сумма, допустим ли переход статуса, подтверждено ли действие.
Проверка должна возвращать машинно читаемые коды ошибок. Не складывайте все проблемы в строку invalid output: иначе невозможно понять, менять схему, промпт, модель или исходные данные.
Спроектируй валидаторы после JSON Schema для объекта [назначение]. Схема: [schema]. Бизнес-правила: [правила]. Раздели проверки на: локальные для поля, связи между полями, сверку со справочником, разрешения и критические запреты. Для каждой дай стабильный error_code, безопасное сообщение, retryable true/false и маршрут: accept, retry или manual_review.
Refusal, truncation и неполный ответ - нормальные ветки процесса
Даже при строгой схеме запрос может не вернуть ожидаемый объект: провайдер способен отказать по правилам безопасности, запрос может оборваться, исчерпать лимит или завершиться инфраструктурной ошибкой. Разбирайте envelope ответа до обращения к полезной нагрузке.
- Проверить HTTP/API status и идентификатор запроса.
- Проверить причину завершения и наличие refusal.
- Не парсить отсутствующий content как JSON.
- Провести schema validation.
- Провести бизнес-валидацию.
- Классифицировать ошибку как retryable или окончательную.
- Ограничить число повторов и добавить backoff.
- Не повторять необратимое внешнее действие автоматически.
- Сохранить безопасный диагностический контекст.
Повторный запрос должен исправлять причину, а не крутиться по кругу
Повтор полезен при временном сетевом сбое или очевидной исправимой ошибке. Если вход не содержит обязательного факта, десять повторов не создадут достоверное значение. Для schema mismatch передавайте модели короткое описание нарушения, не весь stack trace.
| Сбой | Действие |
|---|---|
| Timeout или временная ошибка API | Ограниченный retry с backoff |
| Нарушена схема | Один исправляющий запрос или другая совместимая модель |
| Нет данных во входе | null, уточнение пользователя или manual review |
| Конфликт бизнес-правил | Не записывать; передать человеку |
| Refusal | Обработать как отдельный результат, не обходить защиту |
Function calling: модель предлагает аргументы, приложение решает
Function calling применяют, когда модель выбирает инструмент и формирует аргументы. Structured output финального ответа и tool call решают связанные, но разные задачи. Даже strict-схема аргументов не даёт модели права выполнить действие.
Приложение обязано проверить аутентификацию, права, область данных, бизнес-ограничения и необходимость подтверждения. Между «создать черновик платежа» и «отправить платёж» должна быть явная граница. Идентификаторы лучше получать из авторитетного справочника, а не просить модель угадывать.
Версионирование схемы без остановки интеграций
Схема - публичный контракт между моделью и кодом. Переименование поля или изменение enum может сломать потребителя так же, как несовместимое изменение API.
- Сохраняйте
schema_versionрядом с результатом. - Добавляйте необязательные поля совместимо, если политика провайдера это позволяет.
- Для breaking change создавайте новую версию и период миграции.
- Проверяйте старые записи новым reader и новые записи старым reader, где это требуется.
- Версионируйте также prompt, модель и валидаторы.
Не смешивайте данные разных схем в одной таблице без явного преобразования и журнала миграции.
Тестовый набор для structured outputs
Проверьте не только happy path. Нужны пустые входы, очень длинные документы, несколько кандидатов на поле, локали чисел и дат, кавычки, переносы, Unicode, prompt injection внутри документа и отсутствие обязательного факта.
- Минимальный валидный объект.
- Все допустимые enum и fallback.
- null для каждого потенциально отсутствующего поля.
- Лишние поля и неверные типы.
- Пустые и максимальные массивы.
- Разные форматы дат, валют и десятичных разделителей.
- Конфликтующие значения в одном источнике.
- Инструкция внутри данных, пытающаяся изменить схему.
- Refusal, timeout и обрезанный ответ.
- Исторические production-ошибки.
Считайте отдельно schema pass rate и semantic accuracy. Высокая доля валидных объектов не компенсирует неверное извлечение.
Наблюдаемость: что сохранять для разбора ошибки
Минимальный журнал содержит request ID, время, версию модели, схемы и промпта, причину завершения, этап сбоя, коды валидаторов, число повторов, задержку и стоимость. Сырые входы и ответы храните только если это разрешено политикой данных; чувствительные поля маскируйте.
Полезная панель разделяет сетевые ошибки, refusals, parse errors, schema errors и business errors. Рост каждой группы ведёт к разным действиям. Добавьте алерт на критические нарушения и внезапный сдвиг распределения enum.
Архитектура безопасного конвейера
Между генерацией и записью всегда должна оставаться программная граница доверия. Модель предлагает данные; приложение допускает их в систему.
Финальный чек-лист перед запуском
- Выбран нативный structured output совместимой модели.
- Схема содержит только нужные потребителю поля.
- Имена и descriptions однозначны.
- Required и null имеют согласованную семантику.
- Классификации закрыты enum с fallback.
- Лишние поля запрещены там, где поддерживается.
- Проверено поддерживаемое провайдером подмножество JSON Schema.
- Envelope и refusal обрабатываются до parsing.
- После schema validation работают бизнес-валидаторы.
- Критические значения связаны с доказательством.
- Retries ограничены и не повторяют внешние действия.
- Запись идемпотентна и защищена правами.
- Есть regression-набор и семантические эталоны.
- Схема, prompt, модель и валидаторы версионируются.
- Логи не раскрывают чувствительные данные.
- Есть очередь manual review и понятный владелец.
Надёжность появляется не потому, что модель напечатала фигурные скобки. Она появляется, когда формат ограничен, смысл проверен, ошибка ожидаема, а опасное действие остаётся под контролем приложения.