Claude Code'u terminalden tipli bir fonksiyon gibi çağırmak — --json-schema'yı ölçtüm
--json-schema geçerli JSON garantisi değil, gizli bir araç çağrısı. Altı koşu yaptım: iki kat yavaş çıktı, alanlar kayboldu, başarısız olduğunda sessizce sıfırla çıktı.
Bu yazının İngilizcesi: English version.
Claude Code'u sohbet penceresi olarak kullanmak kolay kısmı. Beni asıl ilgilendiren şu: onu terminalde, jq'ya boru ile bağlanabilen normal bir komut gibi çağırabilir miyim? Bir betiğin içine koy, girdiyi ver, çıktıyı al, if ile dallan.
Bunun için -p (print) modu var, onu çoğu kişi biliyor. Az bilinen ise yanına konan bayrak: --json-schema. Bir JSON Schema veriyorsunuz, Claude Code cevabı o şemaya uydurmakla yükümlü oluyor. CLI referansındaki tanım net: "ajan iş akışını tamamladıktan sonra bir JSON Schema'ya uyan doğrulanmış JSON çıktısı" — ve yalnız print modunda çalışıyor.
Kulağa "artık bozuk JSON derdi yok" gibi geliyor. Ölçtüm. Öyle değil, ve nedeni ilginç.
Makinemdeki sürüm 2.1.251, ölçümler 27 Ağustos 2026'da, --model sonnet ile yapıldı.
Deney
Altı satırlık Türkçe kullanıcı yorumu uydurdum (Zıpla'ya gelmiş gibi: donma, reklam, pil, bozuk Türkçe karakter). Görev: her yorumu id, category, severity, version, summary_en alanları olan bir kayda çevir.
İki kol kurdum, her biri üç koşu:
- A kolu: şema yok. Promptun sonuna "sadece şu şekilde JSON döndür, düzyazı yok, kod çiti yok" yazdım ve örnek şekli gösterdim.
- B kolu: o cümle yok, yerine
--json-schemaile aynı yapı verildi.
Girdiyi cat yorumlar.txt | ile boruya verdim ki dosya okuma izni denkleme girmesin. İkisi de --output-format json ile koştu, ölçümleri o zarfın içindeki alanlardan aldım.
Birinci sonuç: satış konuşması yanlış
A kolunun üç koşusunun üçü de doğrudan jq'dan geçen, kusursuz JSON üretti. Kod çiti yok, "işte istediğiniz JSON" cümlesi yok. Yani modern bir modele düzgün bir promptla JSON sorarsanız, zaten JSON alıyorsunuz.
O hâlde --json-schema'yı "geçerli JSON garantisi" diye satmak yanlış. Ücreti ise şu:
| Ölçü | A (şema yok) | B (--json-schema) |
|---|---|---|
| Tur sayısı | 1 | 2 |
| Ortalama süre | 5,3 sn | 11,0 sn |
| Ortalama çıktı token | 404 | 1.011 |
| Düşünme token | 0 | 326–567 |
Aynı iş, iki katı süre, iki buçuk katı çıktı token. Dolar rakamlarını tabloya koymadım çünkü önbellek durumu koşudan koşuya oynadığı için gürültülüydü; token ve süre daha dürüst ölçüler.
İkinci sonuç: asıl fark burada, ve beklediğim yönde değil
A kolunda version alanı altı kaydın altısında da vardı; sürüm anılmayan yorumlarda boş dize ("") olarak geldi. Üç koşuda da aynı.
B kolunda ise şemamda version alanını required listesine koymamıştım. Sonuç: üç koşunun ikisinde, sürüm anılmayan yorumlarda anahtar tamamen yok. .version çağıran betik null alıyor.
Yani şema, çıktının şeklini A kolundan daha kararlı yapmadı; daha az kararlı yaptı. Çünkü şemanın sözleşmesi properties değil, required. properties "bu alan olursa şöyle olur" demek. required dışında bıraktığınız her alan, betiğinizin eksik ihtimaline karşı yazması gereken bir daldır. Ben bunu bilerek yapmıştım ama üç koşuda iki farklı şekil almayı beklemiyordum.
Bir de tuhaf bir yan gözlem: iki kolun kategori listesi birebir aynıydı, ama sınıflandırmalar farklı çıktı. "2.3.9'da pil bitiyordu, düzelmiş, teşekkürler" yorumunu A kolu üç kez performance, B kolu üç kez praise dedi. İkisi de kendi içinde 3/3 tutarlı, birbirlerine göre farklı. Etiket setini siz verseniz bile, çıktıyı hangi yoldan istediğiniz cevabı değiştirebiliyor.
Neden böyle: bu bir kısıtlı çözümleme değil, araç çağrısı
Buradaki yaygın yanlış anlama şu: insanlar --json-schema'yı token seviyesinde bir gramer kısıtı sanıyor. Değil.
Akışı açıp baktım:
claude -p '...' --output-format stream-json --verbose --json-schema '...'
system/init olayındaki araç listesinde fazladan bir isim duruyor: StructuredOutput. Akışın devamında model o aracı çağırıyor ve karşılığında Structured output provided successfully diye bir araç sonucu geliyor. Tablodaki "2 tur" bu; ekstra süre ve düşünme tokenları da buradan.
Bu, dokümandaki ifadeyle de örtüşüyor: yapılandırılmış çıktı sayfası doğrulamanın uyuşmazlık hâlinde yeniden sorarak (re-prompting) yapıldığını, deneme sınırı aşılırsa sonucun hata olduğunu söylüyor. Garanti değil, denetimli bir döngü.
Bunu bir kez anlayınca geri kalan her tuhaflık yerine oturuyor.
Model bu aracı çağırmayı reddedebilir
Tatmin edilmesi imkânsız bir şema verdim: aynı alan için minLength: 5 ve maxLength: 2.
Model çelişkiyi fark etti ve aracı hiç çağırmadı. Sonuç zarfında subtype yine success, is_error yine false, ama structured_output alanı yok. result alanında düzyazı bir açıklama duruyordu.
Doküman bu durumu açıkça uyarıyor: sonuç success olup structured_output boş gelebilir, "bunu da başarısızlık sayın" diyor. Ben okumadan önce denedim ve tam olarak başıma geldi.
Beni gerçekten yakalayacak olan tuzak
Bunu tesadüfen buldum. --json-schema'yı --output-format json olmadan da kullanabiliyorsunuz — dokümandaki örneklerde hep ikisi birlikte geçiyor ama zorunlu değil. Tek başına kullanınca stdout'a doğrudan şemaya uyan JSON düşüyor, zarf yok, jq '.structured_output' gerekmiyor. Betik yazan için daha temiz.
Ta ki başarısız olana kadar. İmkânsız şemayı bu sefer --output-format json olmadan koştum:
- stdout'ta JSON yok, düzyazı bir açıklama var
- stderr boş
- çıkış kodu 0
Yani claude -p ... --json-schema '...' | jq -r '.alan' yazan bir CI adımı, model şemayı reddettiğinde hata vermez; jq çöp yer, komut başarılı görünür. Ben bunu üretimde yakalasaydım günümü yerdim.
Doğru kullanım şu: her zaman --output-format json ile koşun ve .structured_output'un varlığını kendiniz kontrol edin.
out=$(cat girdi.txt | claude -p "..." --output-format json --json-schema "$(cat sema.json)")
echo "$out" | jq -e '.structured_output != null' >/dev/null || { echo "sema tutmadi" >&2; exit 1; }
echo "$out" | jq '.structured_output'
required sizden veri uydurmasını isteyebilir
Bir deneme daha yaptım. Aynı yorumlara, veride hiç olmayan alanları zorunlu koyan bir şema verdim: reporter_email, device, crash_count.
Çıktı kusursuz geçerliydi. İçeriği ise şuydu: reviewer1@example.com, reviewer2@example.com, ... altı yorum için altı uydurma adres. crash_count alanı da 0 ve 1'lerle dolduruldu — yorumlarda böyle bir sayı hiç geçmiyor.
Ders açık: required, "bu alanı doldur" emridir. Veri yoksa model uydurur ve şema doğrulaması bunu yakalamaz, çünkü doğrulayıcı tipe bakar, gerçeğe değil. Elde olmayabilecek her alanı required dışında bırakın. Bu, dokümandaki "şemayı göreve uydurun" tavsiyesinin en somut hâli.
Bir de küçük bir not: format anahtarı ("format": "email" gibi) kabul ediliyor ama uygulanmıyor, yalnızca not olarak duruyor. Yukarıdaki uydurma adresler zaten geçerli e-posta biçimindeydi, o yüzden bu ölçümde farkı görülmedi.
İki hızlı tuzak daha
Geçersiz şema verirseniz koşu daha başlamadan düşüyor:
Error: --json-schema is not a valid JSON Schema: data/type must be equal to one of the allowed values...
Çıkış kodu 1. Bu iyi haber — CI'da yakalanır.
Asıl can sıkıcı olanı şu: şemanıza "$schema": "https://json-schema.org/draft/2020-12/schema" satırı koyduysanız reddediliyor. Doğrulayıcı draft-07 bekliyor. Zod varsayılan olarak 2020-12 üretiyor, yani z.toJSONSchema(...) çıktısını doğrudan yapıştıran herkes bu duvara toslar. Doküman çözümü yazıyor: dönüştürürken target: "draft-7" verin. Pydantic'in model_json_schema() çıktısında $schema anahtarı olmadığı için o taraf sorunsuz geçiyor.
Kullanır mıyım
Evet, ama dar bir yerde.
Kullanmayın: tek adımlık, araç kullanmayan bir ayıklama işi yapıyorsanız. Ölçtüm — prompta "sadece JSON" yazmak aynı işi yarı sürede yapıyor ve şeklini daha kararlı tutuyor. İki katı süreyi bedavaya vermeyin.
Kullanın: ajan gerçekten iş yapıyorsa. Dosya okuyup, komut çalıştırıp, sonra bir özet dönmesi gereken bir koşuda son mesaj doğal olarak anlatı olmaya meyilli; oradaki JSON'u prompt disipliniyle korumak zor. StructuredOutput aracı tam da bunun için var: ajan istediği kadar gezinsin, kapıda tek bir tipli çıktı bırakmak zorunda kalsın.
Ölçümün sınırı: tek bir görev, altı satır girdi, üçer koşu, tek model. --bare bayrağını hiç denemedim çünkü API anahtarı istiyor ve ben abonelik oturumuyla koşuyorum. Sayılarım eğilim gösterir, kanun koymaz. Ama şu üçü ölçüme bağlı değil, doğrudan davranış: şema bir araçtır, model onu çağırmayabilir, ve zarfsız koştuğunuzda bu size sıfır çıkış koduyla döner.
Kendi denemeniz on dakika sürer. Ben de o on dakikayı harcamasam, jq'nun neden boş döndüğünü haftalarca arayacaktım.
Bu blogda reklam vermek ya da birlikte iş yapmak
MCALAB bağımsız bir stüdyo. Sponsorluk, çapraz tanıtım ya da bir iş birliği için:
ads@mcalab.com.trAyrıntılar: Reklam & iş birliği. Kullanıcı desteği için destek sayfası.