Перейти к содержимомуKumoДокументация
Разделы
На странице
Шлюз

Структурированный вывод

Ответ по именованной JSON-схеме — какой формат принимает API, что значит строгость, как это выглядит на трёх диалектах и почему поддержка проверяется отдельно для каждой модели.

Открыть как Markdown

Быстро

Добавьте 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, и «не подтверждено» никогда не значит «наверное, поддерживает»: запрос к недоказанной паре отвергается до обращения наверх. Что доказано у конкретной модели, видно на её карточке в каталоге.

Что дальше