Структурированный вывод
Получение ответа строго в формате JSON по заданной схеме — без парсинга текста и без шанса получить «почти-JSON».
Структурированный вывод гарантирует, что модель вернёт валидный JSON и, при необходимости, соответствующий конкретной JSON Schema. Это убирает целый класс ошибок: «модель прислала JSON с лишним текстом», «закрывающая скобка съехала», «вместо числа — строка с числом».
Поддерживается большинством современных моделей. Жёсткое следование схеме (strict: true) — у OpenAI gpt-4o и новее, Claude 3.5 Sonnet и новее, Gemini 2.0+. Карточка модели в каталоге подскажет.
Два режима
Параметр response_format принимает один из трёх вариантов:
| Тип | Что гарантирует |
|---|---|
{ "type": "text" } | Свободный текст. То же, что не передавать response_format вовсе |
{ "type": "json_object" } | Ответ — синтаксически валидный JSON. Структура произвольная |
{ "type": "json_schema", ... } | Ответ — JSON, соответствующий вашей схеме |
Режим json_object достаточен, если вы сами в системном промпте описали ожидаемые поля. Режим json_schema — строже: апстрим валидирует ответ модели и при strict: true гарантирует точное соответствие схеме.
json_object — простой случай
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-4o-mini",
"messages": [
{"role": "system", "content": "Верни поля: name (строка), age (число)."},
{"role": "user", "content": "Меня зовут Алексей, мне 34 года."}
],
"response_format": { "type": "json_object" }
}'Ответ — гарантированно валидный JSON, но конкретные поля зависят от модели:
{
"choices": [{
"message": {
"role": "assistant",
"content": "{\"name\": \"Алексей\", \"age\": 34}"
}
}]
}Контент по-прежнему приходит строкой — JSON.parse(...) обязателен.
json_schema — строгий режим
Передайте схему в response_format.json_schema. Поле strict: true включает жёсткую валидацию: апстрим заранее ограничивает токены, которые модель может выдать, чтобы получить именно соответствующий вашей схеме JSON.
curl -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-d '{
"model": "openai/gpt-4o-mini",
"messages": [
{"role": "user", "content": "Извлеки данные из квитанции: пицца 750₽, кола 150₽, чаевые 100₽."}
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "receipt",
"strict": true,
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"price_rub": {"type": "number"}
},
"required": ["name", "price_rub"],
"additionalProperties": false
}
},
"tip_rub": {"type": "number"},
"total_rub": {"type": "number"}
},
"required": ["items", "tip_rub", "total_rub"],
"additionalProperties": false
}
}
}
}'Ответ:
{
"choices": [{
"message": {
"content": "{\"items\":[{\"name\":\"пицца\",\"price_rub\":750},{\"name\":\"кола\",\"price_rub\":150}],\"tip_rub\":100,\"total_rub\":1000}"
},
"finish_reason": "stop"
}]
}Требования к схеме при strict: true
- Корневой объект — обязательно
"type": "object". - Все поля, которые могут присутствовать в ответе, должны быть в
required[]. Опциональные поля нужно делать union сnull:{"type": ["string", "null"]}. - На каждом уровне объекта обязателен
"additionalProperties": false. - Нельзя использовать
oneOf,allOf,not, регулярные выражения в pattern, рекурсивные ссылки.
Если схема не проходит этим требованиям — апстрим вернёт ошибку валидации до старта генерации.
OpenAI SDK
Python (pydantic + helper parse):
from openai import OpenAI
from pydantic import BaseModel
client = OpenAI(
base_url="https://api.hubris.pw/v1",
api_key="sk-gw-...",
)
class ReceiptItem(BaseModel):
name: str
price_rub: float
class Receipt(BaseModel):
items: list[ReceiptItem]
tip_rub: float
total_rub: float
resp = client.chat.completions.parse(
model="openai/gpt-4o-mini",
messages=[{"role": "user", "content": "Пицца 750, кола 150, чаевые 100."}],
response_format=Receipt,
)
receipt = resp.choices[0].message.parsed
print(receipt.total_rub) # 1000.0TypeScript (zod + helper parse):
import OpenAI from 'openai';
import { z } from 'zod';
import { zodResponseFormat } from 'openai/helpers/zod';
const client = new OpenAI({
baseURL: 'https://api.hubris.pw/v1',
apiKey: 'sk-gw-...',
});
const Receipt = z.object({
items: z.array(z.object({
name: z.string(),
price_rub: z.number(),
})),
tip_rub: z.number(),
total_rub: z.number(),
});
const resp = await client.chat.completions.parse({
model: 'openai/gpt-4o-mini',
messages: [{ role: 'user', content: 'Пицца 750, кола 150, чаевые 100.' }],
response_format: zodResponseFormat(Receipt, 'receipt'),
});
const receipt = resp.choices[0].message.parsed;
console.log(receipt?.total_rub); // 1000Хелперы parse сами добавят правильный response_format и при успешном ответе вернут уже распарсенный и провалидированный объект в .parsed.
Что делать с отказами
Если модель решит, что задача нерешаема (например, в квитанции совсем нет цен), она вернёт refusal вместо контента:
{
"choices": [{
"message": {
"role": "assistant",
"content": null,
"refusal": "Не могу извлечь цены — в тексте нет числовых данных."
},
"finish_reason": "stop"
}]
}В коде проверяйте оба поля — иначе при refusal ваш JSON.parse(content) упадёт на null.
Стриминг
stream: true работает. Чанки в delta.content приходят кусками всё ещё валидной JSON-строки — собирайте, как обычный текст, парсите финал. Для прогрессивного парсинга (показывать ответ по мере прихода) понадобится партиальный JSON-парсер на клиенте.
Биллинг
Структурированный вывод не имеет отдельного тарифа. Платите за обычные prompt- и completion-токены:
- Описание схемы попадает в prompt-токены каждого запроса.
- Финальный JSON-ответ — в completion-токенах.
Длинные схемы (десятки полей) могут заметно раздувать prompt-расход — учитывайте при тарификации.
Что дальше
- Вызов инструментов — если вместо одного JSON-ответа нужно вызвать функцию и продолжить диалог.
- POST /v1/chat/completions — полная схема параметров.
- Каталог моделей — поиск моделей с поддержкой
json_schema.
Обновлено:
Вызов инструментов (tool calling)
Подключение функций к моделям — модель просит вызвать вашу функцию, вы исполняете её локально, отдаёте результат и получаете финальный ответ.
Поиск в интернете
Server-side инструмент — модель сама ищет в интернете и пишет ответ с источниками. Никакого ручного исполнения у вас на стороне.