Kullanıcıların gönderdiği faturalardan yapılandırılmış veri çıkarmak için GPT-4'ü çağıran bir özellik
yayına aldın. Development'ta mükemmel çalışıyor — model her seferinde temiz bir JSON objesi döndürüyor. Sonra
production'da, sabah 2'de, bir Sentry alert'ü alıyorsun: JSON.parse: unexpected token. Model
asıl payload'dan önce cevabını "Elbette! İşte istediğin JSON:" ile açmaya karar vermiş. Bir hafta sonra,
aynı özellik, farklı bug: model total_amount yerine totalAmount döndürüyor ve
aşağı akış database write'ın alanı sessizce düşürüyor. Eğer LLM çıktısının güvenilirliği etrafında
prompt ile yol almaya çalışıyorsan,
OpenAI Structured Outputs
beklediğin çözümdür.
OpenAI tarafından Ağustos 2024'te yayınlanan Structured Outputs, response_format parametresi
üzerinden bir
JSON Schema
vermeni ve bu şemayla tam olarak eşleşen geçerli olması garantili bir cevap almanı sağlar.
Bu, sadece çıktının geçerli JSON olduğunu garanti eden — belirli bir şekil olduğunu değil —
eski JSON modundan ({"type": "json_object"}) farklıdır. Ayrıca, modelin çıktısını bir tool çağrısına
yönlendiren ama kendi tören katmanını ekleyen function calling'den de farklıdır. Structured Outputs en temiz yoldur:
istediğin şekli tarif et, her seferinde tam olarak o şekli geri al. Arka planda OpenAI kısıtlı decoding kullanır —
modelin token sampling'i senin şeman tarafından yönlendirilir, yani model geçersiz bir cevap
üretemez.
İlk Structured Output'un
from openai import OpenAI
import json
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o-2024-08-06",
messages=[
{
"role": "user",
"content": "Extract the vendor name, invoice number, and total amount from this text: "
"Invoice #INV-2024-0892 from Acme Supplies Ltd. Total due: $1,450.00"
}
],
response_format={
"type": "json_schema",
"json_schema": {
"name": "invoice_extract",
"strict": True,
"schema": {
"type": "object",
"properties": {
"vendor_name": {"type": "string"},
"invoice_number": {"type": "string"},
"total_amount": {"type": "number"}
},
"required": ["vendor_name", "invoice_number", "total_amount"],
"additionalProperties": False
}
}
}
)
data = json.loads(response.choices[0].message.content)
print(data)
# {"vendor_name": "Acme Supplies Ltd", "invoice_number": "INV-2024-0892", "total_amount": 1450.0}Burada dikkat edilecek üç şey. Birincisi, model gpt-4o-2024-08-06 — Structured Outputs,
onu açıkça destekleyen bir model gerektirir (GPT-4o için -2024-08-06 snapshot'ı veya sonrası ya da
gpt-4o-mini). İkincisi, response_format.type, "json_object" değil
"json_schema"'dır. Üçüncüsü, sana garantiyi veren "strict": True'dur —
onsuz tekrar best-effort bölgesindesin. name alanı modelin gördüğü bir etikettir; parsing üzerinde
etkisi yoktur ama API loglarını okunabilir yapar.
JSON Şemasını Tasarlamak
İşte bir ürün kataloğu çıkarma görevi için daha gerçekçi bir şema — yapılandırılmamış ürün açıklamalarından, e-ticaret ilanlarından veya PDF veri sayfalarından yapılandırılmış veri çekmek için kullanacağın türden. Şemanı API çağrılarına bağlamadan önce görsel olarak kurmak ve doğrulamak için JSON Schema Generator'ı kullan.
{
"type": "object",
"properties": {
"product_name": {
"type": "string",
"description": "The full commercial name of the product"
},
"sku": {
"type": "string",
"description": "Stock keeping unit identifier"
},
"price_usd": {
"type": "number",
"description": "Price in US dollars, numeric only"
},
"in_stock": {
"type": "boolean"
},
"categories": {
"type": "array",
"items": { "type": "string" }
},
"dimensions": {
"type": "object",
"properties": {
"width_cm": { "type": "number" },
"height_cm": { "type": "number" },
"depth_cm": { "type": "number" }
},
"required": ["width_cm", "height_cm", "depth_cm"],
"additionalProperties": false
}
},
"required": [
"product_name", "sku", "price_usd",
"in_stock", "categories", "dimensions"
],
"additionalProperties": false
}- Strict mode'da tüm property'ler
required'da olmalıdır. Opsiyonel alanların olamaz. Bir alan kaynak veride bulunmayabilirse, union tipi kullan:{"type": ["string", "null"]}ve her zamanrequired'a dahil et. additionalPropertiesher object seviyesindefalseolmalıdır. Bu özyinelemeli olarak uygulanır — iç içe object'lerin de buna ihtiyacı var, sadece root değil.- Strict mode'da desteklenen tipler:
string,number,integer,boolean,null,array,object. Tip union'ları (["string", "null"]) izinlidir. - Strict mode'da
$refveya özyinelemeli şema yok. Her şey inline olmalıdır. Yeniden kullanılabilir bir tanıma ihtiyacın varsa kopyala. descriptionalanlarını cömertçe ekle. Model onları okur. "Amerikan doları cinsinden fiyat, yalnızca sayısal — para birimi sembolü ekleme" demek, modelin doğru tahmin etmesini ummaktan daha temiz çıktı verir.- Enum'lar çalışır.
{"type": "string", "enum": ["pending", "shipped", "delivered"]}tam olarak desteklenir ve model yalnızca o üç değerden birini emit eder.
Strict Mode vs Non-Strict
# Strict mode — guaranteed conformance, tighter schema rules
response_format_strict = {
"type": "json_schema",
"json_schema": {
"name": "product_extract",
"strict": True, # <-- the key flag
"schema": product_schema
}
}
# Non-strict — more schema flexibility, best-effort conformance
response_format_lenient = {
"type": "json_schema",
"json_schema": {
"name": "product_extract",
"strict": False,
"schema": product_schema
}
}strict: True ile OpenAI, şemanı ilk kullanıldığında ön-işlemeye tabi tutar ve kısıtlı
decoder'ı cache'ler. Yeni bir şemayla ilk çağrı biraz daha uzun sürer; aynı şemayla sonraki çağrılar hızlıdır.
Karşılığında aldığın şey: model çıktısı yapısal olarak garantilidir — json.loads()'ı
çağırıp defansif kontroller olmadan alanlara doğrudan erişebilirsin. Vazgeçtiklerin: $ref,
yapısal varyantlar arasında anyOf ve özyinelemeli şemalar desteklenmez. Non-strict mode daha geniş bir
JSON Schema
özellik yelpazesini kabul eder ama best-effort'a düşer — model şemayı takip etmeye çalışır ama token seviyesinde
kısıtlanmaz. Production çıkarma pipeline'ları için her zaman strict mode kullan. Bir kez anladığında şema
kısıtlamaları yönetilebilirdir.
İç İçe Object'ler ve Array'ler
İç içe yapılar iyi çalışır, ancak her iç içe object kendi "additionalProperties": false'ına
ve tüm property'leri listeleyen kendi "required" array'ine ihtiyaç duyar. Yaygın bir hata, strict
kurallarını root object'e uygulayıp çocukları unutmaktır — OpenAI şemayı bir validation error ile reddeder.
from openai import OpenAI
import json
client = OpenAI()
order_schema = {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"customer": {
"type": "object",
"properties": {
"name": {"type": "string"},
"email": {"type": "string"}
},
"required": ["name", "email"],
"additionalProperties": False # required on nested objects too
},
"line_items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"description": {"type": "string"},
"quantity": {"type": "integer"},
"unit_price": {"type": "number"}
},
"required": ["description", "quantity", "unit_price"],
"additionalProperties": False # required on array item schemas too
}
},
"total_usd": {"type": "number"}
},
"required": ["order_id", "customer", "line_items", "total_usd"],
"additionalProperties": False
}
response = client.chat.completions.create(
model="gpt-4o-2024-08-06",
messages=[{
"role": "user",
"content": (
"Parse this order: Order #ORD-5531 for Jane Smith ([email protected]). "
"2x Wireless Keyboard at $49.99 each, 1x USB Hub at $29.99. Total: $129.97"
)
}],
response_format={
"type": "json_schema",
"json_schema": {
"name": "order_extract",
"strict": True,
"schema": order_schema
}
}
)
order = json.loads(response.choices[0].message.content)
for item in order["line_items"]:
print(f"${item['unit_price']:.2f} x{item['quantity']} {item['description']}")Reddetmeleri (Refusal) Ele Almak
Structured Outputs ile bile model cevap vermeyi reddedebilir — tipik olarak prompt bir içerik
politikasını tetiklediğinde (zararlı bir şeyden veri çıkarmayı istemek gibi). Bu olduğunda,
finish_reason "stop"'tur ama message.content null'dur ve
message.refusal reddetme metnini içerir. Bunu kontrol etmezsen, json.loads(None)
çağırmaya çalıştığında AttributeError alırsın. Ayrıca finish_reason == "length"'e dikkat et
— cevap max_tokens nedeniyle kesildiyse, Structured Outputs'tan bağımsız olarak JSON eksik ve
parse edilemez olur.
import json
from openai import OpenAI
client = OpenAI()
def extract_invoice(raw_text: str) -> dict | None:
response = client.chat.completions.create(
model="gpt-4o-2024-08-06",
messages=[{"role": "user", "content": f"Extract invoice fields: {raw_text}"}],
response_format={
"type": "json_schema",
"json_schema": {
"name": "invoice_extract",
"strict": True,
"schema": invoice_schema
}
},
max_tokens=1024
)
choice = response.choices[0]
if choice.finish_reason == "length":
raise ValueError("Response truncated — increase max_tokens or simplify your schema")
if choice.message.refusal:
# Model refused to answer — log and return None rather than crashing
print(f"Model refused: {choice.message.refusal}")
return None
return json.loads(choice.message.content)
result = extract_invoice("Invoice #2024-441 from BuildRight Inc., due $3,200 by Dec 15")
if result:
print(result["vendor_name"], result["total_amount"])Aynı Şemayı Doğrulama için Kullanmak
Yeterince kullanılmayan bir pattern: OpenAI'ye gönderdiğin aynı
JSON Schema'yı
diğer kaynaklardan — webhook'lar, dosya yüklemeleri, üçüncü taraf API'ler — gelen veriyi de
doğrulamak için kullan. Bu sana veri şeklin için tek bir gerçeklik kaynağı verir. Python'da
jsonschema kütüphanesini kullan. Node.js'de
Ajv'yi kullan. Kod yazmadan hızlı manuel
bir sağlık kontrolü yapmak için şemanı JSON Validator'a da yapıştırabilirsin.
import json
import jsonschema
from jsonschema import validate, ValidationError
# The same schema used in your OpenAI call
invoice_schema = {
"type": "object",
"properties": {
"vendor_name": {"type": "string"},
"invoice_number": {"type": "string"},
"total_amount": {"type": "number"}
},
"required": ["vendor_name", "invoice_number", "total_amount"],
"additionalProperties": False
}
def validate_invoice(data: dict) -> bool:
try:
validate(instance=data, schema=invoice_schema)
return True
except ValidationError as e:
print(f"Validation failed: {e.message}")
print(f" Path: {' -> '.join(str(p) for p in e.path)}")
return False
# Validate a payload from a webhook — same schema, zero extra work
webhook_payload = json.loads(request_body)
if not validate_invoice(webhook_payload):
return HTTPResponse(status=400, body="Invalid invoice payload")
# Validate the OpenAI output too, for belt-and-suspenders safety
llm_output = json.loads(openai_response.choices[0].message.content)
assert validate_invoice(llm_output), "LLM output failed schema validation — check schema definition"
print(f"Processing invoice {llm_output['invoice_number']} for ${llm_output['total_amount']}")response_format'una kopyala.JavaScript / Node.js Sürümü
OpenAI Node.js SDK
Python API'sini neredeyse birebir yansıtır. Ana fark, strict'in aynı şekilde json_schema
object'inin içinde yer almasıdır ve cevabı JSON.parse() ile parse edersin.
Ajv ile doğrulama, Python'un
jsonschema kütüphanesinin Node.js karşılığıdır — daha hızlıdır ve mükemmel TypeScript desteği vardır.
import OpenAI from "openai";
import Ajv from "ajv";
const client = new OpenAI();
const ajv = new Ajv();
const invoiceSchema = {
type: "object",
properties: {
vendor_name: { type: "string" },
invoice_number: { type: "string" },
total_amount: { type: "number" },
line_items: {
type: "array",
items: {
type: "object",
properties: {
description: { type: "string" },
amount: { type: "number" }
},
required: ["description", "amount"],
additionalProperties: false
}
}
},
required: ["vendor_name", "invoice_number", "total_amount", "line_items"],
additionalProperties: false
};
const validateInvoice = ajv.compile(invoiceSchema);
async function extractInvoice(rawText) {
const response = await client.chat.completions.create({
model: "gpt-4o-2024-08-06",
messages: [{ role: "user", content: `Extract invoice fields: ${rawText}` }],
response_format: {
type: "json_schema",
json_schema: {
name: "invoice_extract",
strict: true,
schema: invoiceSchema
}
}
});
const choice = response.choices[0];
if (choice.message.refusal) {
throw new Error(`Model refused: ${choice.message.refusal}`);
}
const data = JSON.parse(choice.message.content);
// Validate even though strict mode guarantees structure —
// useful for catching schema drift between environments
if (!validateInvoice(data)) {
console.error("Schema validation errors:", validateInvoice.errors);
throw new Error("Output failed schema validation");
}
return data;
}
const invoice = await extractInvoice(
"Invoice #INV-881 from Nordic Parts AS. " +
"3x Brake Pads at $28.50 each. Total: $85.50"
);
console.log(`${invoice.vendor_name} — Invoice ${invoice.invoice_number}`);
invoice.line_items.forEach(item =>
console.log(` ${item.description}: $${item.amount}`)
);Toparlayalım
Structured Outputs, tüm bir production bug kategorisini ortadan kaldırır — modelin sabah 2'de parser'ını
bozan neredeyse-doğru JSON döndürdüğü tür. İş akışı basittir: şemanı dikkatli tasarla (her iç içe object'in
additionalProperties: false'a ve tam bir required array'e ihtiyacı vardır),
strict: true'u ayarla ve reddetmeleri ve kesilmeleri açıkça ele al. Şema yerleştiğinde
tüm stack'inde yeniden kullanabilirsin — OpenAI çağrısında, webhook doğrulamasında, test fixture'larında —
jsonschema (Python) veya
Ajv (Node.js) gibi kütüphanelerle. Şema
sıfırdan başlıyorsan, bir örnek payload'dan çalışan bir temel şema elde etmenin en hızlı yolu
JSON Schema Generator'dır. Güvenilir JSON çıktısına giden yolda
prompt-engineering yapma günleri bitti — bunun için yapılmış aracı kullan.