LLM'e "JSON döndür" demek, üretimde iki farklı hata sınıfına açık kapı bırakır: model bazen geçerli olmayan JSON üretir (kaçan tırnak, trailing comma, markdown code-fence sarmalama), bazen de geçerli JSON üretip alan adlarını ya da tipleri şemadan saptırır. Bu yazıda OpenAI'nin Structured Outputs özelliğiyle model çıktısını bir JSON Schema'ya zorlamayı, Anthropic tarafında tool use'un neden farklı bir mimari olduğunu ve Zod/Pydantic ile şemayı koddan türeterek LLM JSON çıktı structured output güvenilirliğini nasıl garanti altına alacağını adım adım göreceksin.
💡 Pro Tip: Şemayı elle string olarak yazma; Pydantic/Zod modelinden türet — SDK'nın kendi yardımcısı (client.responses.parseveyazodTextFormat) hem strict-mode kısıtlarını hem şema-kod senkronunu senin yerine yönetir.
İçindekiler
- "JSON döndür" demek neden yetmez
- Neyin garantisi, neyin değil
- Structured outputs vs tool/function calling
- Tool calling ile karıştırma
- JSON Schema tasarımı: enum, required, additionalProperties
- Minimum çalışan şema örneği
- Zod ve Pydantic ile uçtan uca tip güvenliği
- Doğrulama başarısızlığında retry ve repair deseni
- Kısmi/streaming JSON'u güvenle işlemek
- Şema sürümleme ve geriye dönük uyum
- Anthropic tarafında tool_use akışı (2025-10-14 dönemi)
- SSS
- LLM'den her seferinde geçerli JSON nasıl alınır?
- Structured outputs ile function calling arasındaki fark nedir?
- Şema doğrulaması başarısız olunca ne yapmalı?
- Enum ve zorunlu alanlar modelin çıktısını nasıl kısıtlar?
- Zod ile OpenAI Structured Outputs birlikte nasıl kullanılır?
- Anthropic'te OpenAI'deki gibi doğrudan bir JSON Schema garantisi var mı?
- Güncelleme (Eylül 2026)
- Sonuç
- Kaynaklar
"JSON döndür" demek neden yetmez
Salt prompt talimatıyla ("lütfen JSON döndür") modelden yapı istemek iki hata sınıfına açıktır: model üretilen metnin JSON olarak parse edilebilir olmasını garanti etmez, parse edilse bile alan adlarının ve zorunlu alanların şemaya uyacağının garantisi yoktur.
OpenAI bu boşluğu iki katmana ayırır: önce "JSON mode" (yalnız geçerli JSON üretimini garanti eder, şemaya uyumu garanti etmez), ardından "Structured Outputs" (hem geçerli JSON hem verilen JSON Schema'ya uyum garantisi verir). Structured Outputs'ın resmi tanımı nettir: model, tedarik edilen JSON Schema'ya her zaman uyan bir yanıt üretir — zorunlu bir alanın atlanması ya da geçersiz bir enum değerinin uydurulması riski ortadan kalkar. Pratik sonucu üç maddede özetlenir: yeniden doğrulama/retry döngüsüne ihtiyaç kalmaz, güvenlik kaynaklı redler ayrı ve programatik olarak tespit edilebilir bir alanda gelir, şema zaten tipi zorladığı için "kesinlikle bu formatta cevap ver" tarzı ağır prompt mühendisliğine gerek kalmaz.
Neyin garantisi, neyin değil
Structured Outputs çıktının şemaya UYMASINI garanti eder; çıktının doğru/anlamlı olmasını garanti etmez. Şemaya uygun ama içerik olarak hatalı bir yanıt almak tamamen mümkündür — bu ayrım aşağıdaki retry/repair bölümünde tekrar karşına çıkacak.
Structured outputs vs tool/function calling
Burada iki farklı ekosistemin iki farklı mimarisi var ve bunları eşitlemek yanlıştır. OpenAI tarafında ayrım nettir: modeli kendi sisteminizdeki araçlara/veriye/fonksiyonlara bağlıyorsanız function calling kullanılır; modelin kullanıcıya doğrudan verdiği nihai yanıtı belirli bir şemaya sokmak istiyorsanız text.format ile Structured Outputs kullanılır — ikisi API içinde ayrı response biçimleridir ama aynı JSON Schema alt kümesini paylaşır. Etkinleştirme, Responses API'de text: { format: { type: "json_schema", name: "cevap", strict: true, schema: ... } } şeklindedir.
Anthropic (Claude) tarafında 2025-10-14 dönemi resmi dokümantasyonu tek bir mekanizma sunar: tool use. Araçlar input_schema ile tanımlanır; model doğrudan nihai kullanıcı yanıtını şemaya zorlamaz, bunun yerine stop_reason: "tool_use" ile bir tool_use içerik bloğu döner ve çağıran uygulama bu aracı çalıştırıp sonucu ayrı bir tool_result bloğuyla geri gönderir; yalnız şemaya uyan parametreleri almak istiyorsan bu adım opsiyoneldir (Claude'un nihai yanıtı üretmesi için gereklidir). Bu üretim mimarisini bir planner-loop içinde ele almak istersen agentic tool-use ve planner loop yazımıza bakabilirsin — orada araç çağrısının kendisi konu, burada ise çağrının PARAMETRELERİNİN şemaya uyumu.
Yani Claude'da "şemaya uyum" bir ara adımın (araç çağrısı parametreleri) üzerinde çalışır, OpenAI'deki gibi nihai serbest-metin yanıtın doğrudan üzerinde değil. Araçlar ayrıca "client tools" (kullanıcı tanımlı + computer/text-editor gibi Anthropic-tanımlı araçlar, senin sisteminde çalışır) ve "server tools" (web_search/web_fetch gibi, Anthropic altyapısında çalışır) olmak üzere ikiye ayrılır.
Özellik | OpenAI Structured Outputs | Anthropic tool use (2025-10-14) |
|---|---|---|
Şemaya uyum hedefi | Nihai kullanıcı yanıtı | Araç çağrısı parametreleri (input_schema) |
Etkinleştirme | text.format: json_schema, strict: true | tools tanımı + input_schema |
Yürütme adımı | Yok, tek adımda döner | Nihai yanıt için var (parametre çıkarımında opsiyonel) |
Şema kaynağı önerisi | Pydantic/Zod (native SDK) | Elle tanımlanan input_schema |
Tool calling ile karıştırma
Modeli bir araca bağlamak (function/tool calling) ile modelin NİHAİ yanıtını şemaya zorlamak (structured output) farklı problemlerdir; ikisini aynı satırda anlatmak okuyucuyu yanlış yönlendirir.
JSON Schema tasarımı: enum, required, additionalProperties
OpenAI Structured Outputs, JSON Schema'nın tam kümesini değil, "strict mode" için belirlenmiş bir alt kümesini destekler; bu kısıtlar 2025-10-14 döneminde de geçerliydi:
- Kök obje kuralı: Şemanın en üst seviyesi
type: "object"olmalı,anyOfolamaz. Zod'un discriminated union deseni (z.discriminatedUnion) kökteanyOfürettiği için Structured Outputs şeması olarak geçersizdir (OpenAI dokümanındaki karşı-örnek bunu Chat Completions yardımcısızodResponseFormat()ile gösterir). requiredzorunluluğu: Strict modda tüm property'lerrequireddizisinde yer almalı; "opsiyonel alan" kavramı"type": ["string", "null"]gibi null-union ile taklit edilir — gerçek opsiyonellik yoktur, yalnız null döndürme izni vardır.additionalProperties: false: Her object seviyesinde zorunludur; modelin şemada tanımlanmayan ekstra alan üretmesini engeller, strict moda opt-in şartıdır.- Boyut/nesting limitleri: Toplam 5.000 object property, 10 seviye nesting; property adları, definition adları, enum değerleri ve const değerlerinin toplam karakter uzunluğu 120.000'i geçemez.
- Enum limitleri: Toplamda 1.000 enum değeri; tek bir string enum property'de 250'den fazla değer varsa, o değerlerin toplam karakter uzunluğu 15.000'i geçemez.
- Desteklenmeyen anahtar kelimeler:
allOf,not,dependentRequired,dependentSchemas,if/then/elsestrict modda kullanılamaz. Desteklenen string formatları isedate-time,time,date,duration,email,hostname,ipv4,ipv6,uuidilepattern(regex). - Key sırası korunur: Çıktı, şemadaki property tanım sırasıyla birebir aynı sırada üretilir — bu, önce açıklama sonra sonuç şeklinde alan sıralaması tasarlamak için kullanılabilir.
Minimum çalışan şema örneği
json
1{2 "type": "object",3 "properties": {4 "answer": { "type": "string" },5 "confidence": { "type": ["number", "null"] }6 },7 "required": ["answer", "confidence"],8 "additionalProperties": false9}Bu şemada confidence "opsiyonel" görünür ama aslında required içindedir — yalnız null döndürme izni vardır. Bu, strict modun "gerçek opsiyonel alan yok" kuralının somut hâlidir.
Zod ve Pydantic ile uçtan uca tip güvenliği
Resmi öneri, JSON Schema'yı elle string olarak yazıp uygulamanın gerçek tipinden ayrı tutmak yerine, native SDK yardımcılarıyla şemayı tipten türetmek: Python'da pydantic.BaseModel alt sınıfı tanımlayıp client.responses.parse(model=..., input=[...], text_format=MyModel) çağırmak; JavaScript/TypeScript'te z.object({...}) ile zodTextFormat() kullanmak. Bu, "JSON schema divergence" (şema ile kod tipinin zamanla birbirinden kayması) riskini SDK seviyesinde kapatır — OpenAI dokümantasyonu bunu açıkça güçlü bir öneri düzeyinde sunar ve alternatif olarak CI'da şema/tip senkron kontrolü önerir.
python
1from pydantic import BaseModel2from openai import OpenAI3 4class Cevap(BaseModel):5 answer: str6 confidence: float | None7 8client = OpenAI()9resp = client.responses.parse(10 model="gpt-4o-2024-08-06",11 input=[{"role": "user", "content": "Soruyu yanıtla."}],12 text_format=Cevap,13)14print(resp.output_parsed.answer)Pydantic tarafında altyapı zaten olgunlaşmıştı: PyPI'deki resmi paket açıklaması Pydantic V2'yi V1'e kıyasla sıfırdan yazılmış, yeni özellikler ve performans iyileştirmeleri getiren bir sürüm olarak tanımlar — V2, 2025-10-14'ten çok önce kararlı hale gelmişti.
typescript
1import { z } from "zod";2import { zodTextFormat } from "openai/helpers/zod";3import OpenAI from "openai";4 5const Cevap = z.object({6 answer: z.string(),7 confidence: z.number().nullable(),8});9 10const client = new OpenAI();11const resp = await client.responses.parse({12 model: "gpt-4o-2024-08-06",13 input: [{ role: "user", content: "Soruyu yanıtla." }],14 text: { format: zodTextFormat(Cevap, "cevap") },15});Zod tarafında dikkat edilmesi gereken nokta, discriminated union kalıbının (z.discriminatedUnion) kökte anyOf üretmesi nedeniyle doğrudan Structured Outputs kök şeması olarak kullanılamamasıdır — SDK yardımcısı schema-divergence riskini azaltır ama OpenAI'nin strict-mode kısıtlarını (kök-object, anyOf yasağı) otomatik olarak aşmaz; birleşim tipleri gerekiyorsa nested anyOf (kök değil, bir property içinde) kullanılmalıdır.
Doğrulama başarısızlığında retry ve repair deseni
Structured Outputs "her zaman şemaya uyar" dese de, iki gerçek çıkış yolu şemanın dışında kalır ve ayrıca ele alınmalıdır:
- Refusal: Model güvenlik gerekçesiyle isteği reddederse, yanıt nesnesinde ayrı bir
refusalalanı/type: "refusal"içerik bloğu döner; bu blok şemaya uymak zorunda değildir,messageitem'ınıncontentblokları içindeblock.type == "refusal"kontrolüyle ayrıştırılması gerekir. - Incomplete/max_tokens:
max_output_tokenssınırına takılan yanıtstatus: "incomplete",incomplete_details.reason: "max_output_tokens"ile döner; bu durumda parse edilmemiş/eksik JSON'u kullanmak yerine hatayı fırlatıp yeniden denemek önerilir.
python
1resp = client.responses.parse(2 model="gpt-4o-2024-08-06",3 input=[...],4 text_format=Cevap,5 max_output_tokens=200,6)7if resp.status == "incomplete":8 raise RuntimeError(resp.incomplete_details.reason)9for item in resp.output:10 for block in getattr(item, "content", []) or []:11 if getattr(block, "type", None) == "refusal":12 raise ValueError(block.refusal)Şemaya uysa bile içerik hatalı olabilir. Resmi öneri klasik prompt-mühendisliği repertuvarıdır: talimatları netleştirmek, sistem talimatına örnekler eklemek, görevi daha küçük alt-görevlere bölmek. "Repair" modelin kendi retry'ı değil, prompt seviyesinde tanımlanan bir kaçış kapısıdır, şemanın kendisi tarafından zorlanmaz.
Kısmi/streaming JSON'u güvenle işlemek
Structured Outputs streaming ile birlikte kullanılabilir; alanları tek tek göstermek ya da fonksiyon çağrısı argümanlarını üretilirken işlemek isteyen uygulamalar için resmi tavsiye, JSON'u elle parça parça parse etmek yerine SDK'nın stream yardımcısına güvenmektir: Python'da client.responses.stream(model=..., input=[...], text_format=MyModel) bir context manager açar ve olaylar üzerinden iterasyon yapılır. Bu yaklaşımın pratik faydası, kısmi/geçersiz JSON parçasını manuel json.loads ile parse etmeye çalışırken oluşabilecek hatalardan kaçınmaktır — SDK, şemaya göre kısmi nesneyi güvenle biriktirir.
Şema sürümleme ve geriye dönük uyum
Resmi dokümantasyon, doğrudan "şema versiyonlama" başlıklı bir API özelliği sunmaz; bunun yerine dolaylı iki mekanizma öne çıkar: "JSON schema divergence" önerisi kapsamında CI'a şema/tip senkronizasyon kontrolleri eklemek (şema değiştiğinde tip tanımının da güncellenmesini garanti eden bir süreç kontrolü, versiyonlamanın kendisi değil) ve opsiyonel alan taklidi (["string","null"] union) — yeni bir alan eklerken onu null-birleşimli yaparak eski tüketicilerin kırılmamasını sağlamak. Resmi bir versiyonlama API'si yok; disiplin (CI kontrolü + null-union) gerekiyor.
Anthropic tarafında tool_use akışı (2025-10-14 dönemi)
python
1import anthropic2 3client = anthropic.Anthropic()4resp = client.messages.create(5 model="claude-sonnet-4-5",6 max_tokens=1024,7 tools=[{8 "name": "kaydet_cevap",9 "input_schema": {10 "type": "object",11 "properties": {12 "answer": {"type": "string"},13 },14 "required": ["answer"],15 },16 }],17 messages=[{"role": "user", "content": "Soruyu yanıtla ve kaydet_cevap aracını çağır."}],18)19 20if resp.stop_reason == "tool_use":21 tool_call = next(b for b in resp.content if b.type == "tool_use")22 print(tool_call.name, tool_call.input)Dikkat: resp.stop_reason == "tool_use" döndüğünde model nihai yanıtı DEĞİL, bir araç çağrısı üretmiştir; uygulamanın bu aracı çalıştırıp sonucu ayrı bir tool_result mesajıyla geri göndermesi, Claude'un nihai metin yanıtını üretmesi için gerekir; yalnız parametre çıkarımı yapıyorsan 2. adımda durabilirsin.
Kısıtlama | Değer |
|---|---|
Maks. object property | 5.000 |
Maks. nesting derinliği | 10 seviye |
Toplam property/definition adı + enum/const değeri karakteri | 120.000 |
Toplam enum değeri | 1.000 |
Tek enum property karakter sınırı (>250 değer) | 15.000 |
ALTIN İPUCU
Bu yazının en değerli bilgisi
Bu ipucu, yazının en önemli çıkarımını içeriyor.
Easter Egg
Gizli bir bilgi buldun!
Bu bölümde gizli bir bilgi var. Keşfetmek ister misin?
Okuyucu Ödülü
Şemanı production'a almadan önce gözden geçirmen gereken kontrol listesini hazırladım. Aşağıdaki maddeleri sırayla işaretleyerek hem OpenAI hem Anthropic tarafında sürpriz yaşama riskini azaltabilirsin.
SSS
LLM'den her seferinde geçerli JSON nasıl alınır?
Prompt talimatına güvenmek yerine, OpenAI tarafında text.format: { type: "json_schema", strict: true } ile Structured Outputs kullanmalısın; bu, modelin yanıtının her zaman verilen şemaya uymasını garanti eder, ayrı bir doğrulama/retry döngüsüne gerek kalmaz.
Structured outputs ile function calling arasındaki fark nedir?
Function/tool calling modeli sistemindeki araçlara/veriye bağlar; structured outputs ise modelin kullanıcıya verdiği NİHAİ yanıtı bir şemaya zorlar. OpenAI'de ikisi ayrı response biçimleridir ama aynı JSON Schema alt kümesini paylaşır. Anthropic'te ise 2025-10-14 döneminde tek mekanizma vardır: tool use, ve şemaya uyum yalnız araç çağrısı parametrelerine uygulanır.
Şema doğrulaması başarısız olunca ne yapmalı?
İki durumu ayrı ele al: refusal (model reddetmiş, message item'ının content blokları içinde block.type == "refusal" ile ayrıştırılır) ve incomplete/max_output_tokens (hata fırlatıp retry). Şemaya uysa bile içerik hatalıysa çözüm modelin retry'ı değil, prompt'u netleştirmek ve görevi küçük alt-görevlere bölmektir.
Enum ve zorunlu alanlar modelin çıktısını nasıl kısıtlar?
required dizisi tüm alanları zorunlu kılar (opsiyonellik ["string","null"] union ile taklit edilir), additionalProperties: false ekstra alan üretimini engeller, enum ise değer kümesini modelin serbestçe metin üretmesine izin vermeden sabitler — toplamda 1.000 değer, 250'den fazla değerli tek property'de 15.000 karakter sınırı vardır.
Zod ile OpenAI Structured Outputs birlikte nasıl kullanılır?
zodTextFormat() yardımcısıyla z.object({...}) şeması doğrudan text.format'a verilir; ancak kökte z.discriminatedUnion kullanma — bu, kökte anyOf üretir ve strict mod kök seviyede anyOf'u reddeder.
Anthropic'te OpenAI'deki gibi doğrudan bir JSON Schema garantisi var mı?
2025-10-14 döneminde hayır — o dönem yalnız tool_use üzerinden, ara adım olarak bir garanti sunuluyordu. Bu durum sonradan değişti, ayrıntı için aşağıdaki güncelleme bölümüne bak.
Güncelleme (Eylül 2026)
2025-10-14 itibarıyla Anthropic tarafında şemaya zorlama yalnızca tool_use parametreleri üzerinden, ara adım olarak çalışıyordu. Bu makalenin yayınından sonra Anthropic, OpenAI'ye benzer daha doğrudan bir mekanizma yayınladı: Claude API artık resmi olarak adlandırılmış bir Structured Outputs özelliğine sahip, iki parçadan oluşuyor.
JSON outputs: output_config.format alanına type: "json_schema" vererek Claude'un metin yanıtını doğrudan verilen şemaya zorlayabiliyorsun — artık tool_use'a sarmaya gerek kalmadan.
Strict tool use: Tool tanımına "strict": true eklendiğinde, tool input alanı grammar-constrained sampling (derlenmiş bir gramerle kısıtlı token örnekleme) ile şemaya garantili uyumlu hale geliyor.
Python, TypeScript (Zod), Java, Ruby, PHP, C# ve Go SDK'ları artık native tip tanımından (client.messages.parse(), zodOutputFormat() gibi) şema türetebiliyor; SDK ayrıca desteklenmeyen kısıtları (minimum, maximum, minLength gibi) otomatik olarak açıklamaya taşıyıp additionalProperties: false ekliyor.
İki noktaya dikkat: ilk istekte şema bir grammar'a derleniyor ve bu derleme 24 saat önbellekleniyor (şema değişirse önbellek geçersizleşiyor — prod'da "ilk çağrı neden yavaş" sorusunun cevabı burada); ayrıca computer_toolset_20260801 ve browser_toolset_20260801 gibi bazı yerleşik tool set'leri strict: true'yu kabul etmiyor, ayarlanırsa istek reddediliyor. HIPAA-eligible ortamlarda strict tool use kullanılabiliyor ama şema tanımlarına (enum, const, pattern, alan adları) PHI/hasta verisi konmaması gerekiyor — derlenmiş şemalar mesaj içeriğinden ayrı önbelleklendiği için aynı korumaya tabi değil.
OpenAI tarafında bu makalenin kapsadığı temel mekanik (strict mode, key ordering, refusal/incomplete ayrımı) güncel resmi dokümantasyonda hâlâ aynı şekilde anlatılıyor. Model adlandırmaları her iki sağlayıcıda da değişti — kod örneklerinde kullandığın model adını her zaman güncel dokümantasyondan doğrula.
Sonuç
LLM'den güvenilir JSON almak, prompt mühendisliği değil mimari bir karardır: OpenAI'de text.format: json_schema + native SDK şeması (Pydantic/Zod), Anthropic'te (2025-10-14 döneminde) input_schema ile tanımlanmış tool_use + nihai yanıt gerekiyorsa uygulamanın round-trip ile tool_result geri beslemesi (yalnız parametre çıkarımında opsiyonel). Her iki tarafta da refusal/incomplete gibi şema-dışı çıkış yollarını ayrı kod yollarıyla ele almak ve şemayı elle değil tipten türeterek yazmak, production'da sessiz kırılmaları önler.
Bu konuyu daha geniş bir bağlamda incelemek istersen şu yazılarımıza bakabilirsin: Agentic AI: Tool Use, Planner Loops ve Production Agent Mimarisi, MCP (Model Context Protocol): AI Entegrasyon Standardı, Claude Prompt Caching: 10x Maliyet Azalt Rehberi, RAG mı Fine-tuning mi? Production LLM Kararları için Kesin Rehber ve LLM Benchmarks 2026: MMLU, HumanEval, SWE-bench ve Gerçek Performans.
Kaynaklar
- OpenAI Structured Outputs guide — JSON Schema alt kümesi, strict mode kısıtları, refusal/incomplete davranışı ve SDK örnekleri.
- Anthropic tool use overview (2025-09-30 Wayback arşivi) — client/server tool ayrımı ve tool_use akışı, 2025-10-14 dönemine en yakın doğrulanan görünüm.
- Understanding JSON Schema — JSON Schema'nın resmi, evergreen referans dokümanı.
- Pydantic — PyPI — Pydantic V2'nin resmi paket tanıtımı.
- Claude Structured Outputs (güncel) — Anthropic'in
output_config.formattabanlı JSON outputs özelliği. - Claude Strict Tool Use (güncel) — grammar-constrained sampling, 24 saatlik önbellek ve desteklenmeyen tool set istisnaları.

