Seri · The Quiet Machine · Bölüm 11

Karar Vermek İçin Hiçbir Zaman LLM Çağırmayan Bir Zorluk Router'ı

Gateway bir süredir ayaktaydı ama hâlâ bir router’ı yoktu: box üzerinde birkaç yerel model ve isteğe bağlı bulut yuvaları barındıran yerel bir gateway, tek bir OpenAI-uyumlu endpoint’in arkasından yanıt veriyor, test sohbeti “ok” dönüyordu. Eksik olan tek şey, bir fikrinin olmasıydı. Her oturumda model adını kendim yazıyordum: küçük bir edit için daha küçük yerel model, bir görevin daha ağır olabileceği aklıma geldiğinde daha büyüğü, tahmin etmekten yorulduğumda da Claude.

Bu, elle verilen bir routing kararı — üstelik kötü bir karar. Bu yüzden gateway’in önüne, herhangi bir modele dokunulmadan bu kararı veren küçük bir script kondu.

Önce kurallar, ağ gerekmez

router.py, her şeyden önce ucuz bir geçiş yapıyor: bir anahtar kelime ve dosya-boyutu taraması; ne API çağrısı var ne embedding, timeout’a düşebilecek hiçbir şey yok.

Sert sinyaller görevi doğrudan Claude’a yolluyor: refactor, migrate, security, audit, architecture, race condition, deadlock, cross-module, ya da 400 satırı aşan bir dosyaya dokunulması. Rutin sinyaller ise daha küçük yerel modele gönderiyor: rename, format, typo, docstring, add test — yeter ki dosya 60 satır veya altında olsun, ya da ortada hiç dosya olmasın.

python3 router.py "rename the variable foo to bar" --no-execute
# {"tier": "routine", "source": "rules"}

python3 router.py "refactor the authentication module architecture across services" --no-execute
# {"tier": "hard", "source": "rules"}
# -> escalate to Claude, no gateway call

Bazı görevler iki listeden hiçbirine tam oturmuyor. “Bu component’teki hata mesajlarını iyileştir” ne bir rename ne de bir refactor. Kuralların, tahmin yürütmek yerine işi pas geçtiği yer işte burası.

Fallback bir embedding’e yaslanıyor

Bir görevin zor mu kolay mı olduğunu bir dil modeline sormak, bütün mantığı boşa çıkarır: kendisi bir generation çağrısına mal olan bir routing kararı, en baştan route etmenin sebebini ortadan kaldırır — o sebep de compute’u yalnızca hak edildiği yerde harcamaktır.

Bunun yerine fallback, memory framework’ün arama indeksi için box üzerinde zaten kurulu olan embedding modelini — qwen3-embedding:0.6b‘yi — yeniden kullanıyor. Kurallarla sonuca bağlanamayan görev metni embed ediliyor, sonra iki küçük örnek-cümle kümesiyle cosine karşılaştırmasına giriyor: biri rutin iş gibi, öteki zor iş gibi okunsun diye yazılmış iki küme; Aurelio’nun Semantic-Router’ının kullandığı desenle. Rutin kümeye daha yakınsa rutin kazanıyor, zor kümeye daha yakınsa zor kazanıyor, ayırt edilemeyecek kadar ortadaysa da medium oluyor.

python3 router.py "improve error messages in this component" --no-execute
# inconclusive on rules -> semantic step
# {"tier": "medium", "source": "semantic"}

Bu yolun hiçbir yerinde generative bir çağrı yok. Embedding’i üretmek, bir vektör döndürüp hiç metin üretmeyen tek bir encoder-model çağrısı; arama da onun ardından gelen cosine karşılaştırmasından ibaret. Bu, memory framework’ün box’taki diğer neredeyse-ücretsiz araçların etrafında zaten çizdiği aynı discriminative-versus-generative çizgisi.

Tier      Engine                Executed via
--------  --------------------  --------------------------------------------
routine   smaller local model   gateway, on the box
medium    bigger local model    gateway, on the box
hard      Claude Code, direct   not the gateway - escalation signal only

Rutin ve medium katmanlarını gateway üzerinden çalıştırmak owner-gated: çağrı geçmeden önce elle ayarlanan bir key gerekiyor. O key olmadan da router kararını veriyor ve ekrana yazıyor; sadece çağrıyı yapmıyor. --no-execute, hangi katman olursa olsun bu gateway çağrısını atlıyor — yukarıdaki iki kural örneği ağa hiç dokunmuyor, ama semantic örneği vektörünü üretmek için embedding modelini yine de çağırıyor.

Reddedilen yaklaşım: RouteLLM

RouteLLM diye bir şey var ve buna yakın bir iş yapıyor: bilinmeyen kullanıcılara büyük ölçekte hizmet veren ürünler için yapılmış, ucuz bir modelin ne zaman yeteceğine ve ne zaman pahalı olana yükselmek gerektiğine karar vermek üzere eğitilmiş bir router.

Ama çözdüğü problem, benim masamdakinden başka. RouteLLM, karmaşıklığının karşılığını, bilinmeyen kullanıcılara ve bilinmeyen sorgulara hizmet eden bir ürün ölçeğinde alıyor — kimsenin sorgu dağılımını önceden bilmediği, dolayısıyla kuralları elle yazamadığı bir yerde. Bendeyse sorgular kendi repolarımdaki kendi kodlama görevlerim ve görev tipleri baştan belli: rename’ler rename’e benziyor, cross-module refactor’lar cross-module refactor’a. Zaten bir anahtar kelime listesi olarak yazabildiğim bir ayrımı yeniden keşfetsin diye bir sınıflandırıcı eğitmek, bende olmayan bir probleme emek harcamak olurdu.

Yani karar katmanı 200 satırın altında ve yalnızca standard library: önce kurallar, sonra embedding’ler; eğitim adımı yok, bakımını üstleneceğin bir kalibrasyon kümesi yok. Memory framework’ün başka yerlerde zaten kullandığı cost-tiering mantığının kodlamaya uyarlanmış hali.

Claude neden gateway’in dışında kalıyor

Gateway bir tesisat; Claude Max ise onun dışında, hem de bilerek duruyor.

Gateway’in bulut yuvaları, çağrı başına ücretlendirilen API key’lerin üzerine kurulu. Claude Max ise çağrı başına ölçülen bir key değil, bir abonelik; dolayısıyla onu da aynı gateway’in arkasına koymak, ona hiç verilmemiş bir API key’i varmış gibi davranmak olurdu.

Router’ın sert katmanı bu ayrımı yansıtıyor: gateway üzerinden hiçbir şey çağırmıyor. Bir escalation basıp orada kalıyor. Claude Code görevi, onun için hiç tasarlanmamış bir proxy üzerinden çıplak bir completion çağrısıyla değil, doğrudan alıyor — kendi tam harness’ı ve session context’iyle. Claude’a escalate etmek box’a da bağlı değil: box ya da gateway kapalı olsa bile escalate çalışıyor, çünkü Claude internet üzerinden kendi başına ulaşıyor.

Nerede yanlış yönlendirebilir

Bir anahtar kelime listesi ve bir cosine similarity, sonuçta birer sezgisel yöntem; sezgisel yöntemlerse yanılır. “Hata mesajlarını iyileştir” tek satırlık bir string değişikliği de olabilir, bütün hata-yönetimi yolunun yeniden düşünülmesi gerektiğinin işareti de. Router’ın bu ikisini birbirinden ayırmak için, cümlenin birkaç örnek cümleye ne kadar yakın durduğundan başka bir dayanağı yok.

Embedding çağrısı hiçbir yere ulaşamıyorsa — ne box’ın endpoint’ine ne de Mac’in kendi local fallback’ine — router, iki yönden birine yanlış tahmin etmektense orta katmana, medium‘a düşüyor. Ama bu fallback bedava değil: orta katmanın modeli, Part 10’un saniyede üç ila dört token ölçüp interactive iş için kullanılamaz dediği o 32B model; üstelik box’ın kendisine ulaşılamazken bu model hiç çalışamıyor.

Şu an nerede

Gateway canlı. Yukarıdaki örnek kararlar bir dry run olarak kontrol edildi; bir rutin görev — bir typo düzeltmesi — gateway üzerinden uçtan uca çalıştı, 2026 Ağustos sonunda. Gateway’in key’i o zamana dek zaten elle ayarlanmıştı; router’ı çalıştırmaksa hâlâ elle atılan bir adım — tıpkı bu serinin önceki bölümlerindeki owner-gated adımların, bir insanın bir kez harekete geçmesini beklemesi gibi.

Karar, router’ın çalıştığı Mac’te veriliyor: Claude Code’a bağlı değil, her görevin önünde de duran bir şey değil. Elle çalışıyor, aklıma geldikçe. Box’ın işi daha küçük: embedding çağrısını sağlamak ve seçilen local katmanı çalıştırmak. Box’ın kendisi, kimse klavye başında yokken yeniden başladığında ne olacağı ise ayrı bir problem — sıradaki de o.