Structured Outputs в OpenAI-compatible API: руководство по JSON Schema
Практическое руководство по строгой JSON Schema в Responses и Chat Completions, валидации, обработке ошибок и проверке маршрутов.
Structured Outputs позволяет запросить JSON, который соответствует заданной схеме, а не просто попросить модель «вернуть JSON». В OpenAI-compatible API одна JSON Schema может использоваться через Responses и Chat Completions, но поля wrapper различаются, а поддержку необходимо проверять на конкретной модели и маршруте.
Безопасный production-подход состоит из трёх слоёв: использовать strict, когда модель его поддерживает, повторно парсить и валидировать результат в приложении, затем применять детерминированные бизнес-правила. Соответствие Schema устраняет форматные ошибки, но не доказывает фактическую или семантическую корректность.
Structured Outputs и JSON mode — не одно и то же
| Метод | Валидный JSON | Обязательное соответствие Schema | Типичное применение |
|---|---|---|---|
| Только Prompt | Не гарантируется | Нет | Прототип с допустимыми ошибками parsing |
| JSON mode | При поддержке и полном завершении | Нет | Гибкий JSON, форму которого проверяет приложение |
Structured Outputs с strict: true |
С обработкой completion и refusal | Да, в поддерживаемом subset | Типизированное извлечение и workflows |
Руководство OpenAI Structured Outputs рекомендует этот режим вместо JSON mode, если его поддерживают модель и endpoint. Structured response format нужен для предсказуемого объекта ответа; Function Calling — для запроса действия у приложения. Схемы могут быть похожи, но Call IDs и сообщения результата относятся к другому контракту.
Сначала определить Schema, затем выбрать endpoint
Для классификации обращения нужны четыре поля:
{
"type": "object",
"properties": {
"category": { "type": "string", "enum": ["billing", "technical", "account"] },
"priority": { "type": "integer", "minimum": 1, "maximum": 3 },
"requires_human": { "type": "boolean" },
"summary": { "type": "string" }
},
"required": ["category", "priority", "requires_human", "summary"],
"additionalProperties": false
}
Required Properties и additionalProperties: false создают стабильный объект. Это не делает переносимыми все keywords JSON Schema. OpenAI документирует поддерживаемый subset; другой поставщик может реализовать иной subset или не иметь Strict Schema Mode.
Отправить Schema через Responses API
Responses размещает Schema в text.format:
{
"model": "<MODEL_WITH_VERIFIED_STRUCTURED_OUTPUT_SUPPORT>",
"input": "The customer was charged twice and wants a refund.",
"text": {
"format": {
"type": "json_schema",
"name": "support_ticket",
"schema": {
"type": "object",
"properties": {
"category": { "type": "string", "enum": ["billing", "technical", "account"] },
"priority": { "type": "integer", "minimum": 1, "maximum": 3 },
"requires_human": { "type": "boolean" },
"summary": { "type": "string" }
},
"required": ["category", "priority", "requires_human", "summary"],
"additionalProperties": false
},
"strict": true
}
}
}
Body отправляется на POST https://modelflare.dev/v1/responses с Authorization: Bearer <YOUR_API_KEY>. Результат находится в типизированных Output Items. SDK может дать Helper, но HTTP-клиент должен найти завершённый текст и выполнить JSON parsing, не предполагая top-level объекта.
Отправить ту же Schema через Chat Completions
Chat Completions использует response_format.json_schema:
{
"model": "<MODEL_WITH_VERIFIED_STRUCTURED_OUTPUT_SUPPORT>",
"messages": [
{ "role": "user", "content": "The customer was charged twice and wants a refund." }
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "support_ticket",
"schema": {
"type": "object",
"properties": {
"category": { "type": "string", "enum": ["billing", "technical", "account"] },
"priority": { "type": "integer", "minimum": 1, "maximum": 3 },
"requires_human": { "type": "boolean" },
"summary": { "type": "string" }
},
"required": ["category", "priority", "requires_human", "summary"],
"additionalProperties": false
},
"strict": true
}
}
}
JSON обычно возвращается строкой в choices[0].message.content и требует parsing.
| Назначение | Responses API | Chat Completions |
|---|---|---|
| Container | text.format |
response_format.json_schema |
| Type | text.format.type |
response_format.type |
| Name | text.format.name |
response_format.json_schema.name |
| Schema | text.format.schema |
response_format.json_schema.schema |
| Strict | text.format.strict |
response_format.json_schema.strict |
| Result | Output Items / Helper | choices[0].message.content |
Modelflare конвертирует оба wrapper на поддерживаемых OpenAI/Codex-маршрутах, но конверсия не создаёт функцию в upstream-модели без её поддержки. Для Raw Chat Completions Pass-through контракт поставщика остаётся Source of Truth.
Проверять структуру и значение отдельно
{
"category": "billing",
"priority": 2,
"requires_human": true,
"summary": "Customer reports a duplicate charge and requests a refund."
}
Структурная валидация
JSON Schema validator проверяет Required Properties, отсутствие неожиданных полей, типы, Enum и диапазоны. Даже при Strict Adherence это защищает от неподдерживаемого маршрута, ошибки интеграции, truncation и будущих изменений контракта.
Семантическая и бизнес-валидация
Schema не знает, было ли двойное списание, верен ли priority: 2 и кто может одобрить возврат. Объект модели — предлагаемая классификация. Его нужно сверять с авторитетными данными, а права и финансовые лимиты применять в детерминированном коде. Валидный JSON не должен обходить authorization, billing или security boundary.
Обрабатывать неполный и исключительный вывод
Refusals
До JSON parsing проверяйте Status и Refusal Representation. Отказ по безопасности — не ошибка Schema.
Truncation и Output Limits
Если генерация остановилась раньше закрытия объекта, Schema не восстановит остаток. Проверяйте Completion Status и Stop Reason и задавайте достаточный, но ограниченный Output Limit.
Неподдерживаемые keywords
Начинайте с Objects, Arrays, Primitive Types, Enums, Required и явных Additional Properties. References, recursion, сложные Unions и расширенные validators используйте после проверки документации поставщика.
Задержка первой Schema
Некоторые поставщики предварительно обрабатывают и кэшируют новую Schema. Переиспользуйте стабильные версионированные схемы и измеряйте First Use отдельно от Steady State.
Несовместимость модели или маршрута
Endpoint может принимать обычный Chat Completions, но отклонять json_schema или игнорировать strict. Ответ 200 с JSON не подтверждает Strict Adherence. Нужны негативные и граничные тесты.
Выполнить матрицу совместимости
| Тест | Сохраняемые данные |
|---|---|
| Минимальный Required Object | Status, модель, маршрут, Parsed Object, результат валидации |
| Каждый Enum | Создание и parsing всех значений |
| Недостающая информация | Ограниченное значение или запрос уточнения |
| Safety Input | Формат refusal и обработка приложением |
| Низкий Output Limit | Completion Status и truncation |
| Неподдерживаемая функция | Явная ошибка или молчаливое игнорирование |
| Responses и Chat Wrapper | Эквивалентные объекты приложения |
| Стабильная Schema | First Use и Steady State |
Schema, Prompt, Region, Streaming Mode и Output Limit должны оставаться одинаковыми. Записывайте дату теста.
Выбирать endpoint после проверки workflow
Responses подходит для Typed Output Items, Responses Streaming Events и более широкого Tool Workflow. Chat Completions — для стабильной Message-based Integration, если маршрут поддерживает response_format. См. Responses API и Chat Completions.
Независимо от выбора:
- храните одну версионированную Source of Truth для JSON Schema;
- меняйте wrapper, но не смысл;
- валидируйте завершённый ответ;
- затем применяйте детерминированные бизнес-правила;
- отдельно наблюдайте parsing, refusal, truncation и compatibility failures.
Для миграции используйте OpenAI-Compatible API Guide, для неполных потоков — AI API Streaming Guide. Проверьте Models & Pricing и тестируйте Structured Outputs на точном маршруте API Key.
Structured Outputs делает модельный ответ более надёжным интерфейсом, но не заменяет проверку фактов, authorization или billing logic. «OpenAI-compatible» остаётся утверждением, которое нужно проверять для каждой функции.