Structured Outputs 2026: OpenAI JSON Mode, Anthropic Tool Use ve Pydantic ile Güvenilir LLM Çıktıları
OpenAI strict schema, Anthropic tool use ve Instructor + Pydantic ile üç sağlayıcıyı tek bir arayüzden çağırıp LLM çıktılarını üretim için güvenilir hale getiren pratik rehber.
Structured outputs (yapılandırılmış çıktılar), bir büyük dil modelinin serbest metin yerine önceden tanımlanmış bir JSON şemasına birebir uyan yanıt üretmesini garanti eden mekanizmadır. 2026 itibarıyla OpenAI'nin response_format alanı, Anthropic'in tool use API'si ve Instructor gibi Pydantic tabanlı kütüphaneler bir araya geldiğinde, LLM yanıtlarını doğrudan üretim veritabanına yazacak kadar güvenilir hale getirmek artık standart bir pratik. Bu yazıda, üç sağlayıcıyı tek bir şema soyutlaması altında birleştiren dayanıklı bir katman kuruyorum. Açıkçası, ilk defa üretime aldığımda kaç kez ayağıma sıktığımı da anlatacağım.
OpenAI'nin strict: true modu, JSON Schema'yı dilbilgisi düzeyinde zorlar ve şema dışı token üretimini imkânsız kılar.
Anthropic'te structured output için resmi yol tool use'dur; tool_choice ile modeli belirli bir aracı çağırmaya zorlarsınız.
Pydantic ve Instructor birleşimi, üç sağlayıcı arasında tek bir Python arayüzü sunar ve doğrulama hatasında otomatik yeniden deneme yapar.
Grammar-constrained decoding (Outlines, XGrammar) açık kaynak modeller için OpenAI'nin strict moduyla aynı garantiyi verir.
Streaming yapılandırılmış çıktı için tek yol Instructor'ın Partial tipi veya OpenAI'nin stream parametresinin JSON parser ile birleştirilmesidir.
Şemayı ne kadar sığ tutarsanız, hem token maliyeti hem de doğrulama hatası oranı o kadar düşer.
Structured output nedir ve neden kritik?
Bir LLM'i pipeline'ın ortasına koyduğunuz an, çıktısı serbest metin olarak kalırsa iki şey olur. Ya sonraki adımda kırılgan bir regex ile parse etmek zorunda kalırsınız, ya da modelin her sürüm güncellemesinde format sürprizleri yaşarsınız. Yapılandırılmış çıktı bu sorunu kökten çözer. Modele "cevabın tam olarak bu şemaya uymalı" dersiniz ve API katmanı bunu ya token seçim (constrained decoding) ya da post-hoc doğrulama (retry loop) ile garanti eder.
Pratikte üç farklı garanti seviyesi var. En zayıfı prompt içinde "sadece JSON döndür" demektir; bu bir yalvarıştır, garanti değildir. Orta seviye, JSON mode gibi modeli JSON üretmeye zorlayan ama şemayı doğrulamayan bir mekanizmadır. En güçlüsü, OpenAI'nin duyurduğu strict schema veya Anthropic'in tool_choice ile zorlanan tool use gibi, modelin şemayı ihlal eden bir token üretmesini fiziksel olarak imkânsız kılan yaklaşımlardır. Üretim pipeline'ları için sadece üçüncü kategoriye güvenmelisiniz. Orta seviye yalnızca prototiplerde kabul edilebilir.
Structured output'un asıl değeri güvenilirlikte değil, görünmezlikte ortaya çıkar. LLM'i pipeline'ın diğer bileşenlerinden ayırt edilemez hale getirir. Model çağrısı; artık DB sorgusu veya REST endpoint'i gibi, tipli bir girdi alan ve tipli bir çıktı üreten sıradan bir fonksiyon olur. Bu görünmezlik, hata izlemeden monitoring'e kadar her operasyonel katmanı basitleştirir. Bağlam yönetimi katmanının nasıl kurulacağını LLM bağlam mühendisliği rehberinde ayrıntılı işlemiştim; structured output, o katmanın çıktı tarafındaki eşdeğeridir.
OpenAI JSON mode ve strict schema kullanımı
OpenAI'de yapılandırılmış çıktı için üç seçenek var: (1) eski response_format={"type": "json_object"}, (2) yeni response_format={"type": "json_schema", "json_schema": {...}, "strict": true}, ve (3) function calling / tools. Üretim için sadece strict json_schema'yı öneririm; diğer ikisi ya şema garantisi vermiyor ya da eski API'de kalıyor.
Strict mode arka planda modelin token örneklemesini şemaya uyacak şekilde kısıtlar. Yani model, mevcut cursor pozisyonunda şemayı ihlal edecek bir token üretmesin diye logit maskesi uygulanır. Bunun bedeli, şemanın ilk çağrıda derlenmesi için 1-2 saniyelik bir gecikme (aynı şema önbelleğe alınır, sonraki çağrılarda ekstra maliyet yok) ve şemada bazı JSON Schema özelliklerinin desteklenmemesidir: tüm alanlar required olmalı ve additionalProperties: false zorunludur.
client.chat.completions.parse metodu Pydantic modelini otomatik olarak JSON Schema'ya çevirir, strict flag'ini set eder ve yanıtı doğrudan model örneğine parse eder. Manuel response_format sözlüğü ile uğraşmanıza gerek kalmaz.
Anthropic tool use ile yapılandırılmış çıktı
Anthropic'in Claude ailesinde structured output için resmi yol tool use'dur. Modele tek bir "sanal" araç tanımlarsınız, tool_choice={"type": "tool", "name": "extract_invoice"} ile o aracı çağırmaya zorlarsınız ve modelin ürettiği tool_use bloğundaki input alanını yapılandırılmış çıktınız olarak alırsınız. Bu yaklaşım, Anthropic'in resmi dokümantasyonunda tavsiye edilen paterndir.
Anthropic yaklaşımının OpenAI'ye göre iki farkı var. Birincisi, şema constrained decoding ile zorlanmaz. Model şemayı görür ve uymak için elinden geleni yapar ama tokenizer katmanında sert bir garanti yoktur. Pratikte Claude 3.5 Sonnet ve üstü modellerde uyum oranı yüzde 99'un üzerinde, ancak nadir kenar durumlar için Pydantic ile post-doğrulama şart. İkincisi, tool use'un doğal bir yan etkisi olarak Claude bazen ekstra bir text bloğu ("İşte fatura bilgileri:") üretir; sadece tool_use bloklarını dinleyerek bunu yoksayın.
Pydantic ve Instructor: tek arayüz, üç sağlayıcı
Üç ayrı sağlayıcıyla üç ayrı SDK yazmak ölçeklenmez. Instructor kütüphanesi, Pydantic modellerini OpenAI, Anthropic, Gemini, Cohere ve yerel modeller için ortak bir arayüzden çağırmanızı sağlar. Şemayı bir kez tanımlarsınız, sağlayıcıyı configürasyonla değiştirirsiniz. Doğrulama hatası durumunda otomatik yeniden deneme, kısmi (partial) streaming ve şema hata mesajlarını LLM'e geri besleme dahildir.
import instructor
from anthropic import Anthropic
from openai import OpenAI
from pydantic import BaseModel, field_validator
class ExtractedInvoice(BaseModel):
invoice_number: str
total_amount: float
currency: str
line_items: list[str]
@field_validator("invoice_number")
@classmethod
def check_prefix(cls, v: str) -> str:
if not v.startswith("INV-"):
raise ValueError("Fatura numarası INV- ile başlamalı")
return v
# OpenAI ile
oa_client = instructor.from_openai(OpenAI())
invoice = oa_client.chat.completions.create(
model="gpt-4o-2024-08-06",
response_model=ExtractedInvoice,
max_retries=3,
messages=[{"role": "user", "content": prompt}],
)
# Anthropic'e gecis, tek satir degisir
ant_client = instructor.from_anthropic(Anthropic())
invoice = ant_client.chat.completions.create(
model="claude-opus-4-8",
response_model=ExtractedInvoice,
max_retries=3,
max_tokens=1024,
messages=[{"role": "user", "content": prompt}],
)
Instructor'ın çekirdek numarası şu. Bir field_validator bir ValidationError attığında, hata mesajını yeni bir user mesajı olarak sohbete ekleyip modeli yeniden çağırır. Model, "önceki çıktın şu doğrulamayı geçemedi, düzelt" bağlamıyla düzeltilmiş bir yanıt üretir. max_retries=3 tipik pipeline'lar için yeterli; bunun üzerinde bir yeniden deneme sayısı genelde şema tasarımının kötü olduğunun işaretidir. Bir müşteri projesinde max_retries=8 gördüğümde sorun şemadaki iç içe opsiyonel alanlardaydı, modelde değil.
Açık kaynak modellerde grammar-constrained decoding
Llama 3.3, Mixtral veya Qwen2.5 gibi açık kaynak modelleri kendi altyapınızda çalıştırıyorsanız, OpenAI'nin strict modunun eşdeğeri grammar-constrained decoding'dir. vLLM içindeki XGrammar entegrasyonu, Outlines, LMQL ve llama.cpp'nin GBNF grammar desteği bu ailedendir. Prensip aynı: her adımda modelin logit dağılımına, şemayı ihlal eden token'ları -∞ yapan bir maske uygulanır.
XGrammar 2026'da varsayılan backend haline geldi çünkü diğer alternatiflere göre 3-10 kat daha az CPU overhead'i var. Şema büyüdükçe fark açılır. Grammar-constrained decoding'in tek dezavantajı, tam olarak neyi ne zaman zorlayacağınıza karar vermenizi gerektirmesidir. Örneğin şemanın enum alanları grammar ile mükemmel çalışır, ama serbest metin alanları için grammar sadece tür kontrolü sağlar. İçeriğin anlamlı olması ayrı bir sorundur ve bu noktada eval katmanınıza güvenirsiniz (bkz. LLM eval altyapı rehberi).
Streaming ve partial parsing
UI tarafında kullanıcıya yanıtı token token göstermek istiyorsanız yapılandırılmış çıktıyı da stream edebilirsiniz, ama saf JSON parser bu iş için uygun değildir çünkü tamamlanmamış JSON geçerli değildir. İki yol var: (1) Instructor'ın Partial[Model] tipini kullanmak, (2) OpenAI'nin native structured streaming'ini parse metoduyla birleştirmek.
from instructor import Partial
stream = oa_client.chat.completions.create_partial(
model="gpt-4o-2024-08-06",
response_model=ExtractedInvoice,
messages=[{"role": "user", "content": prompt}],
stream=True,
)
for partial_invoice in stream:
# partial_invoice, her adimda dolan yeni bir ExtractedInvoice ornegidir
# henuz gelmemis alanlar None olarak kalir
print(partial_invoice.model_dump_json(indent=2))
Partial parser, tamamlanmamış JSON'u lookahead ile "kapatır" ve o ana kadar geçerli olan tüm alanları döndürür. Kullanıcı arayüzünde form alanlarının canlı dolmasını göstermek için biçilmiş kaftan. Ancak Pydantic doğrulayıcıları partial akışta atlanır. Nihai doğrulama sadece stream kapandığında çalışır. UI mantığınızı buna göre kurun: kullanıcıya "yükleniyor" göstergesi, doğrulama tamamlanana kadar kaybolmamalı.
Yapılandırılmış çıktı hataları nasıl çözülür?
Strict mode ve grammar-constrained decoding kullansanız bile üretimde hata görürsünüz. En sık üç kaynak: (1) modelin yanıtı üretmeye zaman bulamayıp finish_reason: "length" ile kesilmesi (tamamlanmamış JSON parse edilemez), (2) tool use'da modelin text bloğu ile başlayıp yanılan bir hallüsinasyon, (3) alan içerikleri şema tipine uysa da field validator'a takılması (örn. tarih formatı).
Instructor'ın max_retries parametresi ilk savunma hattıdır ama sınırsız değil. Üretim pipeline'ında şöyle bir katmanlı strateji öneririm: ilk çağrıda strict/constrained mode ile hızlı model (Haiku, GPT-4o mini), doğrulama hatası olursa aynı prompt daha büyük modele (Opus, GPT-4o) gönderilir, ikinci hatada isteği kuyruğa alıp async olarak insan denetimine düşürürsünüz. Bu pattern, hem maliyet hem güvenilirlik açısından tek katmanlı "büyük modeli tekrar tekrar çağır" stratejisinden çok üstündür.
Function calling ile JSON mode arasındaki fark nedir?
Bu iki terim sık karıştırılır. JSON mode, çıktının geçerli JSON olacağının garantisidir; şema doğrulamaz. Function calling (ya da yeni adıyla tool use), modele bir veya birden fazla fonksiyon tanımı verip modelin hangi fonksiyonu hangi argümanlarla çağıracağına karar vermesini ister. Çıktı, seçilen fonksiyon adı artı JSON Schema'ya uyan argümanlardır. Structured output için tool use kullanmak, sadece tek bir "sanal" fonksiyon tanımlayıp modeli onu çağırmaya zorlamaktır.
Aşağıdaki tablo üç yaklaşımın maliyet, gecikme ve garanti profilini özetliyor. Rakamlar Temmuz 2026 itibarıyla üretim workload'larımdan elde edilen ortalamalardır; kendi profillemenizi yapmanız şart.
Özellik
Prompt-only JSON
JSON mode
Strict schema / tool use
Geçerli JSON garantisi
Yok
Var
Var
Şema uyumu garantisi
Yok
Yok
Var (strict)
İlk çağrı gecikmesi
Düşük
Düşük
Şema derleme +200-2000ms
Sonraki çağrılar
Baseline
Baseline
Baseline (şema önbellekli)
Token maliyeti
~1x
~1x
~1.05x (şema token'ları)
Retry gereksinimi
Yüksek
Orta
Düşük (~%1)
Üretim için önerilir mi?
Hayır
Prototip
Evet
Şemayı sığ tutmak hem token maliyetini hem doğrulama hata oranını düşürür. Derin iç içe (nested) alanlar, opsiyonel alanların bolca bulunduğu şemalar ve uzun enum'lar, modeli zorlayan başlıca patternlerdir. Bir alanı çıkarabiliyorsanız çıkarın; başka bir LLM çağrısıyla ikinci aşamada ekleyebiliyorsanız pipeline'ı bölün. Yapılandırılmış çıktı, tek büyük şemayla değil birbirini besleyen küçük şemalarla ölçeklenir; agentic RAG pipeline rehberimizde anlattığım aynı "küçük adımlar" felsefesidir.
Üretim checklist'i
Bir yapılandırılmış çıktı katmanını üretime almadan önce şu maddeleri geçtiğinizden emin olun. Bu liste kendi pipeline'larımda kullandığım postmortem'lerin damıtılmış halidir:
Her sağlayıcı için ayrı ayrı schema uyum oranını (>%99 hedef) ölçen bir smoke test suite'i.
finish_reason: "length" alarmı: max_tokens'a takılan çağrı oranı %0.1'in üzerinde olmamalı.
Field validator hatalarını alan bazında etiketleyen metric.
Şema versiyonlaması: şemayı değiştirdiğinizde eski logları parse edebilmek için versiyon numarası ekleyin.
Prompt caching: aynı system prompt ve şema kombinasyonu tekrar ediyorsa cache'le, gecikme %40'a kadar düşer.
Yapılandırılmış çıktı, LLM'i "sihirli bir metin üreteci"nden "pipeline'da başka bir sıradan bileşen"e dönüştüren tek en önemli adımdır. Doğru kurulduğunda, model çağrısı kod tabanınızın geri kalanı için bir if/else kadar öngörülebilir hale gelir. Bu görünmezlik, otomasyon mimarisinin nihai hedefidir.
Sıkça Sorulan Sorular
Yapılandırılmış çıktı için OpenAI mi yoksa Anthropic mi daha iyidir?
Sert şema garantisi istiyorsanız OpenAI'nin strict mode'u avantajlıdır çünkü constrained decoding kullanır. Anthropic'in tool use'u yüzde 99'un üzerinde uyum verir ama teorik olarak şemayı ihlal edebilir. Kısa yanıt: en yüksek güvence için OpenAI, kod ve mantık ağır çıktılar için Claude Opus artı Instructor kombinasyonu.
Pydantic ile LLM yanıtları nasıl doğrulanır?
Pydantic modelini BaseModel'den türetip Instructor kütüphanesine response_model olarak verirsiniz. Instructor, şemayı sağlayıcı formatına çevirir, yanıtı model örneğine parse eder ve ValidationError durumunda hata mesajını sohbete geri besleyip yeniden dener. field_validator dekoratörüyle özel iş kuralı doğrulamaları ekleyebilirsiniz.
OpenAI strict mode'u tüm JSON Schema özelliklerini destekler mi?
Hayır. Strict mode oneOf, anyOf, allOf, minLength, maxLength, pattern, format gibi anahtarları desteklemez. Tüm alanların required olması ve additionalProperties: false gerekir. Bu kısıtlamaları aşmak için şemayı yalın tutup ek doğrulamayı Pydantic field_validator'lara bırakın.
Açık kaynak modellerde structured output nasıl garanti edilir?
vLLM ve XGrammar, Outlines, LMQL veya llama.cpp'nin GBNF desteği ile grammar-constrained decoding kullanabilirsiniz. Prensip aynıdır: her token örnekleme adımında şemayı ihlal eden token'lara -∞ logit uygulanır. 2026 itibarıyla XGrammar, düşük CPU overhead'i sayesinde varsayılan tercih.
Structured output streaming ile birlikte kullanılabilir mi?
Evet. Instructor'ın Partial[Model] tipi veya OpenAI'nin native parse streaming'i, kısmi JSON'u lookahead ile kapatarak her token grubunda geçerli bir model örneği döndürür. Ancak Pydantic doğrulayıcıları sadece stream kapandığında tam olarak çalışır; UI mantığınızı buna göre tasarlayın.
LLM bağlam pencerelerinde context rot, prompt caching ve üç katmanlı ajan hafızası deseni. Claude Opus 4.7 ve GPT-5 üzerinde Python örnekleriyle 2026 rehberi.