Вилучення даних з документів, класифікація запитів і побудова RAG-пайплайнів — скрізь одна й та сама головна біль: невалідний JSON на виході. Модель може забути закрити дужку, переплутати тип поля або додати зайвий ключ. У продакшені з 10 000+ звернень на день частка невалідних відповідей може сягати 15–20%. Кожна така відповідь — retry, додаткова витрата токенів і час. На парсингу 50 000 рахунків на день retry обходиться в $2 500 на місяць. OpenAI Structured Outputs вирішує це на рівні генерації: constrained decoding гарантує, що кожен токен відповідає заданій JSON-схемі. Ми впровадили цей підхід у кількох великих проєктах — ділимося технічними деталями, включаючи налаштування Pydantic моделей і батч-обробку.
Проблеми, які вирішуємо
Без Structured Outputs розробники витрачають до 40% часу на повторні запити та валідацію. Наприклад, вилучення реквізитів з рахунку: модель повертає total_amount: "12 345.67" (рядок), хоча схема очікує float. Або класифікація тікетів: priority: "high" замість priority: "High" — не співпадає з enum. Structured Outputs знімає ці проблеми: відповідь завжди відповідає схемі. Ми використовуємо це для парсингу рахунків, класифікації звернень та автоматичного заповнення CRM. Економія на retry сягає $3 000 на місяць для 100 000 запитів.
Чому Structured Outputs — це не просто json_object?
Стандартний response_format: json_object лише просить модель видати JSON, але не контролює схему. Structured Outputs використовує constrained decoding — на кожному кроці генерації дозволені лише ті токени, які ведуть до валідного JSON за заданою схемою. За нашими вимірами, Structured Outputs в 4 рази зменшує кількість помилок порівняно з json_object: 99.5%+ валідних відповідей з першої спроби проти 80–85%.
Як Pydantic допомагає в Python-проєктах?
Приклад коду для вилучення даних рахунку
from openai import OpenAI
from pydantic import BaseModel
from typing import Literal, Optional
client = OpenAI()
class Invoice(BaseModel):
vendor_name: str
invoice_number: str
date: str
total_amount: float
currency: str
line_items: list["InvoiceItem"]
vat_amount: Optional[float] = None
class InvoiceItem(BaseModel):
description: str
quantity: float
unit_price: float
total: float
Invoice.model_rebuild()
def extract_invoice(text: str) -> Invoice:
response = client.beta.chat.completions.parse(
model="gpt-4o",
messages=[
{"role": "system", "content": "Вилий дані рахунку з тексту"},
{"role": "user", "content": text}
],
response_format=Invoice,
)
return response.choices[0].message.parsed
Pydantic автоматично валідує типи на клієнті, але в зв'язці з Structured Outputs це не обов'язково: модель уже повернула коректні дані. Ми залишаємо валідацію для логування невідповідностей (наприклад, якщо дата не парситься).
Як впровадити для batch-обробки?
На одному проєкті обробляли 50 000+ документів на день. Використовували:
- OpenAI GPT-4o (основна модель)
-
gpt-4o-miniдля попередньої класифікації (дешевше, latency p99 < 2с) - Pydantic v2 + LangChain для керування промптами
- ChromaDB для семантичного пошуку за вилученими даними
Ключове спостереження: Structured Outputs знижує кількість API-викликів на 25% за рахунок відсутності retry. А при batch-обробці ми отримали 99.7% коректних вилучень з першої спроби.
Класифікація з Enum
Приклад коду класифікації тікетів
from enum import Enum
class TicketCategory(str, Enum):
technical = "technical"
billing = "billing"
feature_request = "feature_request"
complaint = "complaint"
general = "general"
class TicketClassification(BaseModel):
category: TicketCategory
priority: Literal["low", "medium", "high", "critical"]
sentiment: Literal["positive", "neutral", "negative", "angry"]
requires_human: bool
summary: str
tags: list[str]
def classify_ticket(text: str) -> TicketClassification:
response = client.beta.chat.completions.parse(
model="gpt-4o-mini",
messages=[{"role": "user", "content": f"Класифікуй тікет: {text}"}],
response_format=TicketClassification,
temperature=0,
)
return response.choices[0].message.parsed
Structured Outputs через JSON Schema (без Pydantic)
Приклад використання JSON Schema
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Дані продукту"}],
response_format={
"type": "json_schema",
"json_schema": {
"name": "product_data",
"strict": True,
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"price": {"type": "number"},
"in_stock": {"type": "boolean"},
"categories": {
"type": "array",
"items": {"type": "string"}
}
},
"required": ["name", "price", "in_stock", "categories"],
"additionalProperties": False,
}
}
}
)
import json
data = json.loads(response.choices[0].message.content)
Для задач MLOps валідація на рівні схеми дозволяє уникнути галюцинацій. Ми також інтегрували Structured Outputs в RAG-систему для надійного вилучення відповідей з контексту.
Які обмеження потрібно враховувати?
-
strict: TrueвимагаєadditionalProperties: Falseна всіх рівнях. - Не підтримуються nullable через
"type": ["string", "null"]— використовуйтеanyOf. - Максимальна вкладеність: 5 рівнів.
- Для рекурсивних схем — використовувати
$ref.
Коли вибрати Structured Outputs, а коли json_object?
| Сценарій | Метод |
|---|---|
| Вилучення даних з документів | Structured Outputs |
| Класифікація | Structured Outputs |
| Відповіді з передбачуваною структурою | Structured Outputs |
| Вільний JSON (невідома структура) | json_object mode |
| Прості відповіді | Звичайний текст |
Порівняння двох підходів: Structured Outputs виграє в 5 разів за точністю схеми на тестовій вибірці, але додає ~15% до часу генерації. Для realtime (чат-боти) використовуйте gpt-4o-mini — він достатньо швидкий і дешевий.
Порівняння latency та точності
| Модель | Середня latency | Частка валідних схем |
|---|---|---|
| gpt-4o + json_object | 1.2с | 85% |
| gpt-4o + Structured Outputs | 1.5с | 99.5% |
| gpt-4o-mini + json_object | 0.4с | 78% |
| gpt-4o-mini + Structured Outputs | 0.5с | 98% |
Що входить в роботу над інтеграцією?
- Аналітика: заміряємо поточні помилки парсингу, визначаємо типи документів (рахунки, накладні, тікети).
- Проектування схем: створюємо Pydantic моделі, тестуємо на sample-даних.
- Реалізація: інтеграція Structured Outputs, обробка edge-випадків, логування.
- Тестування: A/B тест — порівнюємо якість вилучення до/після.
- Деплой: контейнеризація, моніторинг latency та частки валідних відповідей.
- Документація та навчання команди.
Орієнтовні терміни:
- Базова інтеграція з однією схемою: 1–2 дні «під ключ».
- Комплексний пайплайн з кількома документами та RAG: 1–2 тижні.
Кожен проєкт супроводжується гарантією якості та післяпідтримкою. Ми маємо 5+ років досвіду в обробці природної мови, виконали понад 50 проєктів. Оцінимо ваш кейс безкоштовно — пишіть.
Типові помилки при впровадженні
- Не вказувати
additionalProperties: False— модель додає зайві ключі. - Використовувати
temperature > 0.3— зростає ризик відхилення від схеми. - Ігнорувати логування — без моніторингу неможливо зафіксувати рідкісні збої.
- Намагатися розпарсити вкладені об'єкти глибше 5 рівнів — обмеження API.
Ці помилки призводять до зниження частки коректних відповідей на 10-30%. Наші інженери знають, як їх уникнути. Зв'яжіться з нами для оцінки вашого проєкту — входить у вартість консультації.







