Структурированный вывод
Ответ по именованной JSON-схеме — какой формат принимает API, что значит строгость, как это выглядит на трёх диалектах и почему поддержка проверяется отдельно для каждой модели.
Быстро
Добавьте response_format — и ответ придёт по вашей схеме, а не свободным текстом.
{
"model": "<model>",
"max_tokens": 256,
"messages": [{ "role": "user", "content": "Разбери адрес: Казань, ул. Баумана, 15." }],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "postal_address",
"strict": true,
"schema": {
"type": "object",
"properties": [
{ "name": "city", "type": "string", "description": "Город." },
{ "name": "street", "type": "string", "description": "Улица." },
{ "name": "house", "type": "string", "description": "Номер дома." }
]
}
}
}
}import json
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.kumorouter.com/v1", api_key=os.environ["KUMO_API_KEY"])
answer = client.chat.completions.create(
model="<model>",
max_tokens=256,
messages=[{"role": "user", "content": "Разбери адрес: Казань, ул. Баумана, 15."}],
response_format={
"type": "json_schema",
"json_schema": {
"name": "postal_address",
"strict": True,
"schema": {
"type": "object",
"properties": [
{"name": "city", "type": "string", "description": "Город."},
{"name": "street", "type": "string", "description": "Улица."},
{"name": "house", "type": "string", "description": "Номер дома."},
],
},
},
},
)
address = json.loads(answer.choices[0].message.content)Как выглядит схема
Схема идёт под именем — оно несётся поставщику так, как этот протокол его несёт, — и подчиняется опубликованному профилю.
nameИмя схемы: буквы, цифры, дефис и подчёркивание, до 64 символов.strictОбязателен и равен `true`.schemaСама схема: корень — объект, поля перечислены списком.properties — список именованных полей, а не карта. У поля есть name, type и необязательное description; типов пять: boolean, integer, null, number, string. Список, а не карта, потому что каждый объект этого контракта замыкает сам себя, а карта произвольных имён этого не умеет.
Другого вида формата у этого члена нет. Голого режима «просто JSON» здесь не предлагают: он обещает валидный JSON и ничего не обещает о его форме, а такое понижение платформа молча не делает.
Что значит «строго»
strict: true значит, что поставщик обязан проследить за схемой, а не быть к ней склонён. Другого значения этот член не принимает: strict: false отвергается по имени, потому что схема, за исполнением которой никто не следит, — это пожелание, а не контракт, и обслуживать под одним именем две разные гарантии было бы хуже, чем отказать.
Строгость доказывается отдельно от самого структурированного вывода. Пара модели и канала, не доказавшая строгих семантик, отвергает такой запрос до обращения наверх, а не пробует и не понижает.
На других диалектах
На Responses то же требование живёт в text.format и несёт четыре члена сразу: type, name, strict и schema.
{
"text": {
"format": {
"type": "json_schema",
"name": "postal_address",
"strict": true,
"schema": {
"type": "object",
"properties": [{ "name": "city", "type": "string" }]
}
}
}
}У диалекта Messages члена структурированного вывода нет вовсе. Когда нужен разобранный объект, объявите там инструмент: его input_schema — ваша собственная схема, а аргументы вызова приходят объектом.
В потоке
Структурированный ответ стримится теми же дельтами, что и обычный текст: JSON приходит кусками и становится разбираемым только целиком. Разбирайте после завершающего кадра, а не по дороге.
Стриминг структурированного вывода — своя доказанная возможность, отдельная и от стриминга, и от самого структурированного вывода: в каталоге у пары есть отдельная клетка structured_output_streaming_status.
Когда модель отказывается
Модель может отказаться отвечать. Тогда content приходит null, а рядом лежит refusal — её собственная формулировка. Это ответ, а не ошибка: запрос обслужен, поставщик его посчитал, и оплачен он как любой другой. Конверт ошибки в этом случае не приходит, статус остаётся успешным, и клиент обязан читать refusal сам.
На Responses отказ приходит частью вывода вида refusal, а в потоке — событием response.refusal.delta.
Поддержка схемы подтверждается отдельно для каждой модели
Структурированный вывод — доказанная возможность точной пары модели и канала. Каталог держит для неё четыре клетки: structured_output_status, structured_output_mode, strict_semantics_status и structured_output_streaming_status. Каждая из трёх «статусных» принимает proven, unproven или unsupported, и «не подтверждено» никогда не значит «наверное, поддерживает»: запрос к недоказанной паре отвергается до обращения наверх. Что доказано у конкретной модели, видно на её карточке в каталоге.