Есть три уровня надёжности:
Верни JSON с полями: intent, priority, requires_human.
Текст: «Списали оплату дважды и не отвечают третий день» Ответ модели
{"intent":"billing_issue","priority":"high","requires_human":true}
JSON нужен не ради красоты, а ради надёжного handoff между моделью и приложением. Чем дороже ошибка downstream, тем меньше стоит надеяться на "примерно такой формат".
JSON Mode и Structured Outputs — не одно и то же.
JSON Mode гарантирует форму JSON.Structured Outputs или responseSchema гарантируют соответствие схеме.Tool calls гарантируют корректные аргументы инструмента, но не всегда заменяют финальный JSON-ответ пользователю.Это различие критично. Очень многие пайплайны ломаются не потому, что JSON невалиден, а потому что:
поля названы не так; типы расходятся; появляются дополнительные свойства; модель заполняет обязательные поля мусором. Сценарий Что обычно выбирать Быстрый прототип json_object или даже plain JSON requestВнутренний скрипт с tolerant parser JSON object mode Запись в БД strict schema Tool invocation tool arguments Multi-agent handoff strict schema или typed tool payload
Практическое правило:
Если downstream tolerant и человек рядом, можно начинать с JSON object. Если downstream strict и без ручного контроля, переходите на schema-first. Если JSON нужен только как параметры действия, чаще лучше tool call, а не отдельный JSON-ответ. Плюсы Удобен для API, workflows, ETL и agentic toolchains Снижает хрупкость парсинга Позволяет валидировать ответ до бизнес-логики Хорошо сочетается с app-side validation Минусы Слишком сложные схемы делают промпт тяжелее Некоторые модели при плохой схеме начинают заполнять поля мусором JSON сам по себе не делает ответ фактически верным Нужна валидация на стороне приложения даже при strict mode Когда downstream tolerant:
internal scripts; быстрый прототип; лёгкий extraction без сложной вложенности. Когда downstream strict:
запись в БД; запуск workflow; tool orchestration; интеграция между агентами; compliance и audit-пайплайны. Когда смысл задачи такой: "выбери параметры действия". Например:
вызвать triage_ticket; отправить schedule_meeting; заполнить crm_update. В этих сценариях модель должна не "описать мир в JSON", а "подготовить валидные аргументы для действия".
Без техники
Ответь строго JSON без комментариев
С техникой
Используй schema-constrained output и всё равно провалидацируй результат в приложении
Даже идеальный JSON может содержать неправильные факты. JSON решает проблему формата, но не проблему истинности.
Другие типовые ошибки:
Делать схему слишком широкой и абстрактной. Разрешать additionalProperties, хотя проект не готов к дрейфу полей. Смешивать user-facing summary и machine payload в одном объекте без необходимости. Не задавать enum там, где вариантов конечное число. Не оставлять retry/repair strategy. Хорошая JSON-схема обычно:
короткая; с минимальным числом обязательных полей; с enum там, где это возможно; без свободных словарей "на всякий случай"; с отдельным полем для uncertainty, если она важна. Плохая схема обычно выглядит так:
20-30 полей; половина полей optional и плохо определена; в каждом поле можно вернуть "что угодно"; названия полей слишком бизнес-специфичны и непонятны модели. Верни JSON с полями: category, priority, requires_human, summary.
Обращение: «Списали оплату дважды, нужен возврат сегодня» Ответ модели
{"category":"billing","priority":"high","requires_human":true,"summary":"Клиент сообщает о двойном списании и просит срочный возврат"}
Верни JSON с полями: segment, intent, urgency, next_owner.
Входящий запрос: «Нам нужен enterprise-план, SSO и договор DPA до конца недели» Ответ модели
{"segment":"enterprise","intent":"sales","urgency":"high","next_owner":"account_executive"}
В обоих примерах JSON полезен не потому, что он красивый, а потому что дальше его можно:
провалидировать; залогировать; отправить в следующий сервис; использовать как часть automation chain. from openai import OpenAI
client = OpenAI ()
response = client . responses . create (
model = " gpt-5-mini " ,
text ={
" format " : {
" type " : " json_schema " ,
" name " : " ticket_triage " ,
" schema " : {
" type " : " object " ,
" properties " : {
" category " : { " type " : " string " },
" priority " : { " type " : " string " },
" requires_human " : { " type " : " boolean " }
},
" required " : [ " category " , " priority " , " requires_human " ],
" additionalProperties " : False
},
" strict " : True
}
},
input = " Определи triage для обращения: «Списали оплату дважды» " ,
)
print ( response . output_text )
tools = [
{
" type " : " function " ,
" name " : " triage_ticket " ,
" parameters " : {
" type " : " object " ,
" properties " : {
" category " : { " type " : " string " },
" priority " : { " type " : " string " }
},
" required " : [ " category " , " priority " ],
" additionalProperties " : False
}
}
]
Если задача по смыслу означает "выбери аргументы для действия", tool call обычно естественнее, чем отдельный JSON response.
def validate_output ( data ):
assert data [ " priority " ] in { " low " , " medium " , " high " }
Даже при schema constraints оставляйте:
schema validation; semantic validation; fallback / retry policy; safe defaults. def repair_or_retry ( raw_text : str , validator ):
try :
return validator ( raw_text )
except Exception :
return None
В production почти всегда полезны:
первая попытка со strict schema; быстрая валидация; retry на ту же схему при ошибке; fallback на human review или safe default. def semantic_validate_ticket ( data : dict ) -> dict :
if data [ " category " ] == " billing " and data [ " priority " ] == " low " :
raise ValueError ( " billing issue with refund urgency looks misclassified " )
return data
Это отдельный слой поверх JSON-валидации. Он отвечает уже не за синтаксис, а за бизнес-правдоподобность результата.
Не пытайтесь всё превращать в JSON, если:
результат в основном читает человек; ответ исследовательский и структура ещё плавает; важнее богатое объяснение, чем автоматизация; machine payload можно вынести в отдельный маленький блок. В этих случаях лучше дать модели markdown/memo, а typed payload получить отдельным шагом.