OpenAI API · практичний гайд

Як отримати JSON від OpenAI API: Structured Outputs

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

29 вересня 202614 хв читанняСкладність: середня
Авторська ілюстрація до теми: Як отримати JSON від OpenAI API: Structured Outputs
Ілюстрація, створена за допомогою ШІ · Вайб-Кодинг
Що буде на виході. Зберемо невеликий JavaScript-скрипт, який перетворює текст звернення клієнта на передбачуваний JSON для CRM. Потрібні Node.js 20+, OpenAI API key, пакет 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. Не змішуйте їх.

Як працює контракт

  1. Ваш код описує форму. JSON Schema визначає типи, обов’язкові поля, enum, масиви та заборону зайвих ключів.
  2. Промпт описує зміст. Він пояснює, як класифікувати заявку, але не дублює всю структуру словами.
  3. API обмежує результат. Модель повертає дані у дозволеній формі або окремий сигнал про відмову чи незавершену відповідь.
  4. Застосунок перевіряє сенс. Після 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
}
Не копіюйте API key у фронтенд. Виклик OpenAI робіть на сервері або в захищеній serverless-функції. Якщо ключ уже потрапив у браузер, GitHub чи скриншот, відкликайте його та створюйте новий. Докладний план є в нашому гайді з безпеки API-ключів.

Крок 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, обов’язкові в тій самій схемі.

Сім типових помилок

  1. Використовувати лише фразу «поверни JSON». Це інструкція, а не технічний контракт.
  2. Плутати API. У Responses використовується text.format; приклад із response_format може бути для Chat Completions.
  3. Забути required. У strict-схемі всі поля перелічуються як обов’язкові; необов’язковість моделюється через null.
  4. Не закрити зайві ключі. Додавайте additionalProperties: false до кожного об’єкта.
  5. Одразу записувати відповідь у базу. Спочатку перевірте доменні правила, права користувача й ідемпотентність.
  6. Не обробити refusal та incomplete. Тоді JSON.parse падає саме в нестандартній ситуації.
  7. Робити гігантську схему. Менші контракти легше тестувати, версіонувати й пояснювати команді.

Чекліст перед 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 призначений для структури фінальної відповіді.

Першоджерела

  1. OpenAI API — Structured model outputs, актуальна документація з прикладами, обмеженнями та edge cases.
  2. OpenAI API — Migrate to the Responses API, відмінність text.format від старого response_format.
  3. OpenAI API — Function calling, коли модель має викликати інструмент.
  4. JSON Schema — Getting started, основи опису об’єктів і валідації.

Будуєш бота або автоматизацію?

Поділися схемою без ключів і приватних даних у Telegram-чаті. Допоможемо спростити контракт, знайти крайні сценарії та скласти перевірки перед запуском.

Обговорити в Telegram
Обговорити в Telegram