Agent'ınıza bir araç eklediğinizi düşünün: örneğin, "bir platformun herkese açık gönderilerini getir." İki hafta boyunca prod'da sorunsuz çalışır. Ardından fonksiyondaki limit varsayılanını 25'ten 20'ye çekip sort enum'una yeni bir değer eklersiniz. Kod değişir, testler yeşildir, PR merge edilir.
Üç gün sonra prod'da aralıklı hatalar başlar. Model, geçen hafta sildiğiniz bir enum değeriyle aracı çağırmıştır; çalışma zamanı doğrulaması bunu reddeder ve stack trace dispatch katmanını işaret eder. Dispatch koduna yarım saat bakarsınız. Oysa orada hatalı tek satır yoktur. Asıl sorun bambaşka yerdedir: fonksiyon imzasını değiştirip modelin okuduğu araç açıklamasını güncellememişsinizdir. Model hâlâ eski şemaya göre çağrı üretiyordur; yeni yapı da doğal olarak bu çağrıları kabul etmez.
Bunun adı araç açıklaması sapmasıdır. Agent mühendisliğinde en sık görülen ve teşhisi en zor hata sınıflarından biridir. Zorluğunun belirli bir nedeni vardır: hata ile kök neden farklı yerlerde yaşar. Hata yürütme katmanında görünür; sebep ise kimsenin açmayı düşünmediği bir JSON dosyasındadır. Bu yazının konusu, bu hata kaynağını yapısal olarak ortadan kaldırmak. "Senkron tutmayı unutmayın" demek değil; sapabilecek ikinci bir kopya bırakmayacak bir düzen kurmak.
Sapma neden kaçınılmaz olur?
Temeline indiğinizde sorun basit: iki ayrı doğruluk kaynağını aynı anda yönetiyorsunuz.
İlki, gerçekten çalışan koddur: fonksiyon imzası, argüman doğrulama mantığı, varsayılanlar ve enum kısıtları. Bu katman katıdır. Yanlışsa gürültülü biçimde hata verir.
İkincisi ise modelin okuduğu araç açıklamasıdır: name, description ve parameters JSON şeması. Bu katman yumuşaktır. Yanlış olduğunda anında hiçbir şey patlamaz. Model yalnızca hatalı bir çağrı üretir; hata sonradan yürütme katmanında ortaya çıkar.
Bu iki yapının uyumunu insan eliyle koruduğunuz sürece, sapma bir ihtimal değil zaman meselesidir. Kodda bir argümanı değiştirir, açıklamayı unutursunuz. Açıklamayı değiştirir, kodu unutursunuz. İkisini de güncellersiniz ama anlamları yine birbirini tutmaz. Bunların hiçbiri değişiklik anında kendini belli etmez. Modelin farkı tetikleyen bir çağrı üretmesini beklerler; o noktada da iki hafta önce neyi değiştirdiğinizi çoktan unutmuş olursunuz. Çözümün tek yönü vardır: iki kopyayı tek kopyaya indirmek.
Tek kaynak: arayüzün kendisi deklarasyon
Buradaki zihniyet değişimi şu: Aslında o araç açıklaması JSON'una ihtiyacınız yok.
Bir fonksiyonun parser deklarasyonu ve docstring'i, araç açıklamasında gereken tüm alanları zaten içerir. Sade bir argparse deklarasyonu şöyle görünür:
subreddit = commands.add_parser("subreddit", help="Query a public board's feed")
subreddit.add_argument("subreddit")
subreddit.add_argument(
"--sort",
choices=("hot", "new", "top", "rising", "controversial"),
default="hot",
)
subreddit.add_argument("--limit", type=int, default=25)
help, komutun tek satırlık açıklamasıdır. choices enum kısıtını, default varsayılan değeri, type parametre türünü belirtir; positional argüman ise zorunlu alandır. Modelin aracı çağırmak için ihtiyaç duyduğu her şey buradadır: komutun ne yaptığı, aldığı parametreler, zorunlu alanlar, enum değerleri ve varsayılanlar. Üstelik çalışma zamanı ayrıştırma ve doğrulama için de aynı deklarasyon kullanılır. Bu nedenle yürütme mantığından kopamaz; çünkü zaten yürütme mantığının kendisidir.
Dolayısıyla ikinci bir araç açıklaması yazmayı bırakın. Doğru yaklaşım, böyle bir belgenin hiç var olmadığını kabul etmektir. Yalnızca kod vardır; araç açıklamasına ihtiyaç duyduğunuzda onu koddan üretirsiniz. Akış her zaman koddan açıklamaya doğrudur, asla tersi değil.
Deklarasyonlardan tüm yetenek kataloğunu üretmek
Deklarasyonun arayüz olduğunu kabul ettiğiniz anda, araç açıklamalarını elle yazmak anlamsızlaşır. Hepsini bir türetici üretmelidir.
Türeticinin yaptığı iş mekaniktir. Her platform bağlamını dolaşır, parser'ını içe aktarır ve argparse action listesini üç değişmez yapıya dönüştürür: Platform, Command ve Parameter. Her Parameter; adını, türünü, zorunluluk bilgisini, enum değerlerini, varsayılanını ve yardım metnini taşır. Bu, tamamen koddan türetilmiş bir arayüz okuma modelidir.
Bu okuma modeli elinizdeyken, tüm çıktı biçimleri onun aşağı akışındadır. describe --format json, Agent'ın araç seçimine verilecek makine tarafından okunabilir tam arayüzü üretir. render_skill() ise insanın ya da modelin okuyabileceği bir yetenek kataloğu oluşturur. Katalogdaki komut sayısı elle girilmiş bir sabit değildir; o anda hesaplanan sum(len(platform.commands)) değeridir. Şu anda sonuç 22 platform bağlamı ve 241 komut; bunların tek bir tanesi bile kataloğa elle yazılmış değil.
Bunun rahatlatıcı bir sonucu var. Yeni bir platform eklemek, yalnızca platform bağlamını eklemek demektir; katalog tüm komutlarını kendiliğinden alır. Bir parametreyi değiştirmek, parser deklarasyonunu düzenlemektir; katalogdaki ilgili enum ve varsayılan da otomatik güncellenir. "Yeni komutu yazdım ama kaydetmeyi unuttum" ya da "parametre değişti ama katalog eski kaldı" durumlarıyla karşılaşmazsınız; çünkü ayrı bir kayıt işlemi yoktur. Katalog bakımı yapılan değil, hesaplanan bir yapıdır.
(Bakım yapmak yerine türetme refleksi, diller arasında gerçekten neyin taşındığını belirlemek için küme işlemleri kullandığınızda da karşınıza çıkar; bunun örneği diller arası migrasyon yazısında var.)
CI ile sapmayı commit anında yakalayın
Türetme, "yeni komut kataloğa otomatik girer" sorununu çözer; ancak bir açık hâlâ kalır. Birisi parser deklarasyonunu değiştirir, türeticiyi yeniden çalıştırmayı unutur ve üretilen kataloğu commit etmez. Repodaki kopya yeniden eskir; sapma arka kapıdan geri gelir.
Son bariyer CI'a konur ve özü tek bir kontroldür:
docs-check:
$(PYTHON) -c 'from pathlib import Path; from reverse.catalog import render_skill; \
path = Path("skill/SKILL.md"); \
assert path.read_text(encoding="utf-8") == render_skill(), \
"skill/SKILL.md is out of sync with the code; run make docs"'
Bu kontrol, repoya commit edilmiş kataloğu mevcut koddan yeniden üretilen katalogla bayt bayt karşılaştırır. Tek karakter bile farklıysa CI başarısız olur ve kataloğun güncel olmadığını, make docs çalıştırmanız gerektiğini söyler.
Bu tek satırın değeri, sapmanın ne zaman bulunduğunu değiştirmesinde yatıyor. Önceden sapma bir çalışma zamanı hayaletiydi: iki hafta sonra prod'da patlar, stack trace de yanlış yeri gösterirdi. Artık commit anında görünen kırmızı bir X. Pull request'te durdurulursunuz, hata size kataloğun eski olduğunu söyler ve yeniden üretmek sorunu çözer. Sapma, "iz sürmesi en zor hata" olmaktan çıkar; tek komutla temizlenen bir derleme hatasına dönüşür. Arayüzü kod olarak ele almanın tam döngüsü budur. Deklarasyon kaynaktır, yetenek kataloğu build artifact'tır, CI ise type check'tir. Bir build artifact'ı elle yazmaz veya kaynağıyla çelişmesine izin vermezsiniz; araç açıklaması da aynı muameleyi hak eder.
Neyi kodda sabitlemeli, neyi modele bırakmalısınız?
Türetme ve CI, arayüz açıklamasının doğru olmasını garanti eder. Ancak bundan önce verilmesi gereken başka bir karar vardır: Belirli bir yetenek sabit kod olarak mı yazılmalı, yoksa modelin o anda orkestre etmesine mi bırakılmalı? Bu ayrımı yanlış yaparsanız, doğru bir arayüz açıklaması sizi kurtarmaz.
Yetenekleri üç katmanda değerlendirin.
Düşük seviyeli bir primitive, tek bir veri türünü okur ya da net bir eylemi gerçekleştirir. Girdisi stabildir, çıktısı yapılandırılmıştır ve bağımsız test edilebilir. Bu katman tamamen koddur; hiç reasoning harcamaz. 241 komutun büyük çoğunluğu burada yer alır.
Deterministik workflow ise tek bir platform içindeki, sıralaması güçlü biçimde tanımlanmış; ortak duruma ve açık bir başarı koşuluna sahip süreçtir. Örneğin creative-pipeline adlı bir yaratıcı pipeline sırasıyla fırsat bulma, Top Ads, creator eşleştirme, creative brief ve generation preflight çalıştırır. Adımların sırası ve aralarındaki bağımlılıklar sabittir. Sıra zaten belliyse modeli her seferinde yeniden plan yapmaya zorlamak hem daha yavaş hem de daha az kararlıdır; bu yüzden bu katman da kodda sabitlenmelidir. İşaretlemek için tek satır yeterlidir: komuta set_defaults(_command_level="workflow") verin. Kod tabanındaki tek böyle satır budur; katalog da bu sayede workflow'ları ve primitive'leri iki ayrı seviyede gösterir.
Agent orkestrasyonu ise platformlar arası araştırma, anlık dengeler ve bir hata sonrasında yeniden yönlendirme katmanıdır. Sonraki sorgunun ne olacağı, önceki sorgunun ne döndürdüğüne bağlıdır; bunu önceden kodda tanımlayamazsınız. Bu nedenle modeli asıl burada serbest bırakırsınız.
Test oldukça nettir. Bir yetenek kararlı aşama durumu, ortak bağlam veya üretim yan etkileri gerektiriyorsa kodda sabitleyin. Sorgu genişletme, platformlar arası doğrulama ya da hata sonrasında rota değiştirme içeriyorsa modele bırakın. Her iki hata da maliyetlidir. Bir araştırma hipotezini istemciye sabitlemek aşırı dondurmadır; platform değiştiği gün yeniden kod düzenlersiniz. Sabit bir sırayı her seferinde modelin yeniden kurmasına bırakmak ise yetersiz dondurmadır: tek bir model kararından tasarruf eder, karşılığında ciddi bir kararsızlık biriktirirsiniz.
Modelin bozunma kararını vermesi için altı aşama durumu
Orkestrasyon katmanının karar verebilmesi için, alt katmandan dönen sonuçların model açısından anlaşılır olması gerekir. Opak bir başarılı/başarısız boolean'ı yeterli değildir. Modele success: false verdiğinizde, sonraki adımı ancak tahmin edebilir.
Bu yüzden workflow içindeki her aşama boolean yerine aşama durumu döndürür ve bunlar altı tanedir: completed, empty, ready, skipped, unavailable ve blocked. Asıl bilgi, ilerlemeyen durumların birbirinden ayrılmasındadır:
skipped, operatörün bu adımı bilinçli olarak kapattığı anlamına gelir; örneğin bir toplama yolunun limitini 0 yapmıştır. Bu hata değildir ve model yeniden denememelidir.unavailable, bu aşamanın bağımlı olduğu bir şeyin geçici olarak kullanılamadığı anlamına gelir; örneğin bir arayüz hata veriyordur ya da session eksiktir. Model bu adımı atlayıp devam edebilir veya yeni bir session isteyip geri dönebilir.blocked, bir önkoşulun sağlanmadığını ifade eder; örneğin araştırma kanıtı boştur ya da preflight başarısız olmuştur. Model sonraki aşamayı zorla geçirmemeli, geri dönüp kanıtı tamamlamalıdır.
Bu yaratıcı pipeline'ı ele alalım. "Platform preflight hazır" ve "araştırma kanıtı hazır" durumlarını ayrı değerlendirir; sonrasında ready = platform_ready and research_ready ile nihai sonucu üretir. Bunlardan biri başarısız olursa üretim aşaması, neye takıldığını belirten bir blockers listesiyle blocked döner. Ticari arama sonuçlarının tamamı boşsa da üretim işi hiç gönderilmez.
Bu tasarım neden modelin yararınadır? seedance_generation: blocked ve blockers: [research_evidence_empty] gören bir orkestrasyon modeli, gönderimi yeniden denemek yerine kanıt toplama aşamasına geri dönmesi gerektiğini anlar. organic_discovery: skipped gördüğünde bunun kullanıcı tercihi olduğunu, hata olmadığını bilir ve adımı rahat bırakır. Bir aşama unavailable durumundaysa, onun çevresinden dolaşabileceğini bilir. "Bilinçli olarak kapatıldı", "geçici olarak erişilemez" ve "önkoşul sağlanmadı" ayrımını yaptığınızda model doğru bozunma yolunu seçebilir. Üçünü de false değerine indirgerseniz, güçlü bir model bile yerinde dönüp durur.
Katmana göre model seçimi
Yukarıdaki stack, her katmanda modelden çok farklı şeyler ister. (Dört aşamalı reverse-engineering yazısı, aynı dört katmanlı tabloyu reverse-engineering bağlamında ele alıyor; burada bunu Agent stack'ine taşıyoruz.) Katman bazlı seçim yaptığınızda kapasiteyi boşa harcamazsınız:
Agent stack'indeki iş | Gerekli yetenek | Tercih | model id |
|---|---|---|---|
Araç seçmek için 241 komuta ait | Uzun context, tüm kataloğu tek seferde okuyabilme | Kimi K3 |
|
Orkestrasyon: aşama durumunu ve blockers'ları okuyup bozunma, yeniden yönlendirme ya da devam kararını vermek | Güçlü reasoning, duruma göre doğru kararı verebilme | Claude Opus 5 |
|
Docstring'lerden model dostu araç açıklaması metinlerini toplu üretmek | Uygun maliyetli, yüksek eşzamanlılıkta yüzlerce çağrı çalıştırabilme | Claude Sonnet 5 |
|
Araç çağrısı hata ataması: hatayı ve deklarasyonu okuyup bunun sapma mı yoksa upstream değişikliği mi olduğuna karar vermek | Orta seviye reasoning, belirli alanlara dayanarak açıklama yapabilme | GPT-5.6 Sol |
|
Özellikle orkestrasyon katmanına odaklanmak gerekiyor. Sonraki hamleyi belirlemek için blocked ile skipped durumlarını okumak, model değiştirmenin sonucu gözle görülür biçimde etkilediği tek adımdır. Çünkü burada test edilen şey, modelin bir durum bilgisine göre doğru karar verip verememesidir. Daha zayıf bir model skipped durumunu hata sayıp yeniden dener ya da blocked görmesine rağmen gönderimi yapar. Güçlü reasoning modeli ise blockers listesini okuyup rotayı isabetli biçimde değiştirir. Bu, fingerprinting yazısındaki karşı kanıt bölümünün gerçekten kendi tezine karşı argüman üretip üretmediğiyle aynı türden bir farktır: aday üretmek herkesin yapabileceği iştir, zor olan muhakeme gerektiren karardır.
Bu fark için bana inanmak zorunda değilsiniz. Test edin:
Kendi workflow'larınızdan birinin
stagesveblockersiçeren gerçek dönüşünü alın; ya dablockers: [research_evidence_empty]ileblockeddönen yapay bir yanıt oluşturun.Bu yanıtı, yetenek kataloğunuzla birlikte (
describejson'u) ve sonraki eylemi belirleme talimatıyla ayrı ayrıclaude-opus-5vegpt-5.6-solmodellerine verin.Tek bir şeye bakın: Modelin önerdiği sonraki eylem,
blocked(kanıt toplamaya dön),skipped(kullanıcı tercihi, dokunma) veunavailable(session al ya da çevresinden dolaş) durumlarını doğru ayırıyor mu? Yoksaskippeddurumunu başarısızlık sanıp yeniden mi deniyor?Doğru bozunma yollarının oranı, model seçim ölçütünüzdür. Agent'ınızın gerçek bir hata altında yerinde dönüp dönmeyeceğini veya kendi kendine alternatif rotaya geçip geçmeyeceğini bu belirler.
Asıl engel: model değiştirme maliyeti
Dört model üç farklı sağlayıcıdan geliyor ve function calling söz konusu olduğunda geçiş maliyeti özellikle ağır. OpenAI'nin tools / tool_calls formatı ile Anthropic'in tool_use / tool_result formatı birbirinden farklı. Orkestrasyon katmanında daha iyi muhakeme yapan bir modele geçmek istediğinizde tüm tool-dispatch ve hata ayrıştırma yolunuzu yeniden yazmanız gerekir. Çoğu kişinin, aşama durumlarını sık sık yanlış yorumlasa bile tek bir modeli orkestrasyon katmanına kilitlemesinin asıl nedeni budur.
AIReiter bu katmanı aradan çıkarıyor. Tek anahtar, OpenAI uyumlu tek arayüz, arkasında dört model; geçiş yapmak ise istek gövdesindeki model alanını değiştirmek kadar basit.
# Orkestrasyon kararı: reasoning katmanına kataloğu ve blocked dönen bir workflow yanıtını verin, sonraki eylemi isteyin
curl https://aireiter.com/api/v1/chat/completions \
-H "Authorization: Bearer $AIREITER_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "claude-opus-5",
"messages": [{"role": "user", "content": "<describe json> + <stages/blockers response> + decide the next action"}]
}'
# Araç açıklaması metinlerini toplu üretmek için: model alanını değiştirin, geri kalan aynı kalsın
# "model": "claude-sonnet-5"
# Hata ataması:
# "model": "gpt-5.6-sol"
Native function calling için yalnızca bir tools dizisi eklenir; OpenAI tool protokolü bu arayüzden değişmeden geçtiği için model değiştirmek yine tek alanlık bir düzenlemedir. Zaten OpenAI SDK kullanıyorsanız base_url değerini https://aireiter.com/api/v1 olarak işaretleyin, başka hiçbir şeyi değiştirmenize gerek yok. Anthropic SDK'da ise aynı anahtarla POST /api/v1/messages kullanın.
Fiyat tarafında Claude modelleri liste fiyatından %30 indirimli, GPT modelleri yarı fiyatına ve Kimi K3 de aynı anahtarla erişilebilir durumda. Bu stack için indirim, maliyetin yoğunlaştığı noktaya geliyor. Orkestrasyon katmanının attığı her ileri adım yeni bir reasoning-tier çağrısıdır; bu da onu Agent'ın tamamındaki en sık kullanılan ve en pahalı katman yapar. Claude indirimi doğrudan burada etkisini gösterir. 241 docstring'den araç açıklaması üretmek ise yüksek eşzamanlılıklı Sonnet işidir ve o da indirimlidir. Maliyetin büyük kısmı bu iki kalemden oluşur. Hata ataması için yapılan GPT-5.6 çağrıları çok daha seyrektir.
Kayıt olmadan deneyin: birkaç turu elle çalıştırın, aynı
blockedyanıtını iki modele de verin ve orkestrasyon katmanına birini bağlamadan önce hangisinin doğru bozunma yolunu seçtiğini kendiniz görün.
Sonuç
Araç açıklaması sapmasını "senkron tutmayı hatırla" yaklaşımı çözemez. Bu, yapısal bir kusuru kişisel disiplin meselesine indirgemekten ibarettir. Gerçek çözüm iki kaynaklı yapıyı ortadan kaldırmaktır: parser deklarasyonu ve docstring tek kaynaktır, yetenek kataloğu ondan türetilen bir build artifact'tır, tek bir CI kontrolü de type check görevini üstlenir. Böylece sapma, çalışma zamanında ortaya çıkan bir hayaletten commit anındaki kırmızı X'e dönüşür.
Ancak türetme, yalnızca açıklamanın doğru olduğunu garanti eder. Katmanlamanın doğru olup olmadığı hakkında hiçbir şey söylemez. Hangi yetenekleri kodda sabitleyeceğiniz, hangilerini modelin orkestre etmesine bırakacağınız ve modelin "yeniden dene mi, bozun mu" kararını okuyabilmesini sağlayan altı aşama durumu, Agent'ınızın kendi başına çalışıp çalışamayacağını belirleyen iki unsurdur. Modelin bu stack'te iki somut işi vardır: orkestrasyon katmanında denge kararını vermek ve bir araç çağrısı başarısız olduğunda sebebi atamak. Bir yeteneğin sabitlenip sabitlenmeyeceği ile hangi bozunma yolunun izleneceği, model tarafından değil tasarladığınız aşama durumları ve yazdığınız CI tarafından belirlenir.
Bu yaklaşım, küme tabanlı migrasyon mutabakatı ve birleşik bir response Model oluşturmama yazılarındaki tutumla aynıdır: AI tek bir adımın süresini kısaltır, hüküm ise sizin kodda sabitlediğiniz kısıtların içinde kalır. Tüm yapı sorunsuz çalışmaya başladığında geriye kalan tek sürtünme model değiştirmektir; bu da tek bir birleşik arayüzün çözdüğü bir altyapı problemidir.