Як отримати JSON від OpenAI API: Structured Outputs
Якщо відповідь ШІ читає програма, фрази «поверни тільки JSON» недостатньо. Structured Outputs дає моделі формальний контракт: які поля, типи й допустимі значення має містити результат.

openai і базове розуміння об’єктів JavaScript. Підписка ChatGPT не замінює окрему оплату API.Проблема: валідний текст — ще не надійні дані
Уявімо форму підтримки. Користувач пише: «Після оплати замовлення 4821 гроші списало двічі, відповіді немає». Застосунку потрібні поля category, priority, order_id, summary і tags. Якщо просто попросити ChatGPT повернути JSON, модель зазвичай послухається, але іноді перейменує ключ, додасть пояснення перед об’єктом або вигадає нове значення категорії.
Для демонстрації це дрібниця. У реальному процесі один неочікуваний ключ може зламати автоматичний маршрут заявки, запис у базу або рахунок. Регулярні вирази та повторні промпти лікують симптоми, але не створюють контракту.
Structured Outputs приймає JSON Schema разом із запитом. Підтримувана модель генерує відповідь у межах цієї схеми: не пропускає обов’язковий ключ, не підставляє значення поза enum і не додає невідомі властивості, якщо вони заборонені.
JSON mode і Structured Outputs — не одне й те саме
| Режим | Що гарантує | Чого не гарантує |
|---|---|---|
| Звичайний промпт | Нічого на рівні API. | Навіть синтаксис JSON. |
| JSON mode | Відповідь можна розібрати як JSON. | Потрібні поля, типи, enum і структуру. |
| Structured Outputs | Валідний JSON, що відповідає підтримуваній схемі. | Правдивість змісту та ваші бізнес-правила. |
Офіційна документація OpenAI радить використовувати Structured Outputs замість JSON mode, коли модель і сценарій це підтримують. У Responses API схема передається через text.format; старі приклади для Chat Completions використовують іншу форму response_format. Не змішуйте їх.
Як працює контракт
- Ваш код описує форму. JSON Schema визначає типи, обов’язкові поля, enum, масиви та заборону зайвих ключів.
- Промпт описує зміст. Він пояснює, як класифікувати заявку, але не дублює всю структуру словами.
- API обмежує результат. Модель повертає дані у дозволеній формі або окремий сигнал про відмову чи незавершену відповідь.
- Застосунок перевіряє сенс. Після
JSON.parseви все одно звіряєте ID з базою, права користувача, діапазони й інші правила домену.
Це важливе розділення. Схема відповідає за форму, промпт — за інтерпретацію, а ваш сервер — за остаточну довіру. Модель не повинна самостійно вирішувати, чи існує замовлення або чи має людина право його переглядати.
Крок 1. Проєктуємо мінімальну схему
Не переносіть у відповідь усю майбутню таблицю. Включайте лише поля, які модель справді може визначити з тексту. У нашому прикладі assignee може бути невідомим, тому поле залишається обов’язковим, але дозволяє null.
const ticketSchema = {
type: "object",
additionalProperties: false,
properties: {
category: {
type: "string",
enum: ["billing", "technical", "account", "other"]
},
priority: {
type: "string",
enum: ["low", "medium", "high"]
},
order_id: { type: ["string", "null"] },
summary: { type: "string" },
tags: {
type: "array",
items: { type: "string" },
maxItems: 5
},
assignee: { type: ["string", "null"] }
},
required: [
"category", "priority", "order_id",
"summary", "tags", "assignee"
]
};
Для strict-схеми коренем має бути object, усі поля треба додати до required, а об’єкти закрити через additionalProperties: false. «Необов’язкове» поле моделюйте як тип разом із null. Це відрізняється від звичайної форми, де ключ можна просто не надсилати.
Крок 2. Повний приклад через Responses API
Встановіть офіційний JavaScript SDK і передайте ключ через змінну середовища, а не вставляйте його у файл:
npm install openai
export OPENAI_API_KEY="ваш_ключ"
Код нижче відправляє один текст, обробляє ліміт токенів і safety-відмову, а потім розбирає результат:
import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.OPENAI_API_KEY
});
const ticketSchema = {
type: "object",
additionalProperties: false,
properties: {
category: {
type: "string",
enum: ["billing", "technical", "account", "other"]
},
priority: {
type: "string",
enum: ["low", "medium", "high"]
},
order_id: { type: ["string", "null"] },
summary: { type: "string" },
tags: {
type: "array",
items: { type: "string" },
maxItems: 5
},
assignee: { type: ["string", "null"] }
},
required: [
"category", "priority", "order_id",
"summary", "tags", "assignee"
]
};
const response = await client.responses.create({
model: "gpt-5.6-sol",
instructions: [
"Класифікуй звернення служби підтримки.",
"Не вигадуй order_id або assignee.",
"Якщо значення немає в тексті, поверни null."
].join(" "),
input: "Після оплати замовлення 4821 гроші списало двічі.",
text: {
format: {
type: "json_schema",
name: "support_ticket",
strict: true,
schema: ticketSchema
}
},
max_output_tokens: 300
});
if (response.status === "incomplete") {
throw new Error(
`Неповна відповідь: ${response.incomplete_details?.reason}`
);
}
const refusal = response.output
.flatMap((item) => item.type === "message" ? item.content : [])
.find((item) => item.type === "refusal");
if (refusal) {
throw new Error(`Модель відмовила: ${refusal.refusal}`);
}
const ticket = JSON.parse(response.output_text);
console.log(ticket);
Очікувана форма відповіді:
{
"category": "billing",
"priority": "high",
"order_id": "4821",
"summary": "Подвійне списання після оплати замовлення",
"tags": ["подвійне списання", "оплата"],
"assignee": null
}
Крок 3. Перевіряємо не лише «щасливий» сценарій
Синтаксична правильність коду не доводить якість класифікації. Зберіть невеликий набір реальних, але знеособлених прикладів і зафіксуйте очікувані результати.
| Вхід | Що перевірити |
|---|---|
| Явне подвійне списання й номер | billing, правильний ID, високий пріоритет. |
| «Не можу ввійти» без інших деталей | account, order_id: null. |
| Порожній або нерелевантний текст | Передбачувана категорія other, без вигаданих фактів. |
| Дуже довге звернення | Відповідь не обрізана лімітом токенів. |
| Текст із командою «ігноруй схему» | Структура не змінюється, дані проходять бізнес-перевірку. |
Додайте автоматичні твердження: категорія належить дозволеному списку, ID існує або дорівнює null, довжина summary прийнятна, а система не створює заявку повторно після retry. Для фінансових або медичних рішень потрібна окрема людська перевірка.
Коли text.format, а коли function calling
Використовуйте text.format, якщо структурований об’єкт і є фінальною відповіддю: класифікація листа, витяг полів із документа, план кроків або дані для компонента інтерфейсу.
Використовуйте function calling, якщо модель має попросити ваш застосунок виконати дію: знайти замовлення, записати заявку, надіслати лист або отримати дані з CRM. Аргументи інструмента теж можна зробити strict, але саму функцію виконує ваш сервер. Повний цикл, авторизацію та захисні межі розібрано в окремому гайді про безпечний OpenAI function calling.
У складному агенті обидва підходи поєднуються: function calling отримує перевірені дані, а Structured Outputs формує стабільну фінальну відповідь для інтерфейсу.
Що Structured Outputs не вирішує
- Не гарантує правду. Поле може мати правильний тип, але неправильний зміст. Просіть
nullдля невідомого й перевіряйте критичні значення у джерелі. - Не замінює авторизацію. ID із моделі не дає права читати або змінювати запис. Дозволи перевіряє сервер і база.
- Підтримує не всю JSON Schema. Кореневий
anyOfне допускається; документація також встановлює межі вкладеності та розміру. - Має окремі крайні стани. Safety-відмова або досягнення
max_output_tokensможуть не дати звичайного об’єкта, тому їх треба обробляти доJSON.parse. - Потребує стабільної схеми. Перша обробка нової схеми може додати затримку. Не генеруйте унікальну схему для кожного запиту без потреби.
Для даних користувача додайте в промпт чітку поведінку на випадок, коли текст не відповідає задачі. Інакше модель намагатиметься заповнити обов’язкові поля навіть для нерелевантного входу. Надійний варіант — окремі поля can_process та reason, обов’язкові в тій самій схемі.
Сім типових помилок
- Використовувати лише фразу «поверни JSON». Це інструкція, а не технічний контракт.
- Плутати API. У Responses використовується
text.format; приклад ізresponse_formatможе бути для Chat Completions. - Забути required. У strict-схемі всі поля перелічуються як обов’язкові; необов’язковість моделюється через
null. - Не закрити зайві ключі. Додавайте
additionalProperties: falseдо кожного об’єкта. - Одразу записувати відповідь у базу. Спочатку перевірте доменні правила, права користувача й ідемпотентність.
- Не обробити refusal та incomplete. Тоді
JSON.parseпадає саме в нестандартній ситуації. - Робити гігантську схему. Менші контракти легше тестувати, версіонувати й пояснювати команді.
Чекліст перед production
- Обрана модель підтримує Structured Outputs.
- Корінь схеми —
object, а неanyOf. - Усі поля є в
required; невідомі значення допускаютьnull. - Кожен об’єкт має
additionalProperties: false. - API key зберігається лише на сервері.
- Обробляються
refusal,incomplete, мережеві помилки та повторні запити. - Після парсингу виконуються авторизація й бізнес-валідація.
- Є тести на нормальний, порожній, довгий і ворожий ввід.
- Схема має версію, а зміни сумісні зі споживачами.
Короткі відповіді
Чим Structured Outputs відрізняється від JSON mode?
JSON mode гарантує синтаксично правильний JSON. Structured Outputs додатково гарантує відповідність підтримуваній JSON Schema: ключі, типи, enum і вкладеність.
Чи треба додатково перевіряти результат?
Так. Схема перевіряє форму, а не правдивість. Сервер має звірити ID, права, діапазони, дублікати та інші бізнес-правила.
Чому моя схема не приймається?
Перевірте кореневий object, список required, additionalProperties: false і підтримуваний піднабір JSON Schema. Необов’язкове значення задавайте через null.
Чи можна так викликати інструменти?
Для дій у вашій системі використовуйте function calling зі strict-схемою аргументів. text.format призначений для структури фінальної відповіді.
Першоджерела
- OpenAI API — Structured model outputs, актуальна документація з прикладами, обмеженнями та edge cases.
- OpenAI API — Migrate to the Responses API, відмінність
text.formatвід старогоresponse_format. - OpenAI API — Function calling, коли модель має викликати інструмент.
- JSON Schema — Getting started, основи опису об’єктів і валідації.
Будуєш бота або автоматизацію?
Поділися схемою без ключів і приватних даних у Telegram-чаті. Допоможемо спростити контракт, знайти крайні сценарії та скласти перевірки перед запуском.
Обговорити в Telegram