· kaynak dev.to (home feed)
Prompt caching 12.000 token yazıp sıfır token okudu ve bir agent'ın faturasını %25 artırdı
Bir dev.to post-mortem'i, gizli bir timestamp'in prompt caching'i nasıl %25'lik bir ek yük haline getirdiğini anlatıyor: Her turda 12.184 token cache'e yazılıyor, ama hiçbir zaman geri okunmuyor.

Bir LLM agent döngüsünde prompt caching'i etkinleştirmek faturayı düşürmek yerine kabaca dörtte bir oranında artırdı; çünkü her istek cache'e yaklaşık 12.000 token yazıyor ve geriye tam olarak sıfır token okuyordu. dev.to'da yayımlanan bir post-mortem'in konusu bu; yazar sessiz arızayı aylar önce yazılmış bir logging yardımcısının enjekte ettiği bir timestamp'e kadar izliyor ve bunun tekrar yaşanmaması için gereken zihinsel modeli ortaya koyuyor.
Usage objesinin ortaya çıkardığı şey
Agent döngüsü her turda 12.000 token'lık bir system prompt'u yeniden gönderiyor ve bir tool döngüsünde faturanın büyük kısmını input token'lar oluşturduğu için caching bariz bir kazanç gibi görünüyordu. Bunun yerine hiçbir şey bozuk görünmüyordu: her istek 200 dönüyor, latency sabit kalıyordu ve gerçeği yalnızca usage objesindeki üç alan söylüyordu — 12.184 token cache creation olarak faturalandı, cache read olarak sıfır ve 291 düz input token. Yazar yazma primini üst üste yaklaşık 40 kez ödedi ve indirimli tek bir okuma bile toplamadı.
Fiyatlandırma, bir yanlış yapılandırmayı aktif bir regresyona dönüştüren şeydir. Yazıya göre cache yazmaları baz input fiyatının 1,25 katı (bir saatlik TTL için iki katı), okumalar ise yaklaşık onda bir. Öneki gerçekten paylaşan iki istek kârlı çıkar — birlikte baz input'un 1,35 katı, cache'siz 2,0 katına karşılık — ama hiç okunmayan bir yazma saf masraftır ve kırk tanesi faturada tek bir kalem olur.
Anahtar-değer deposu değil, prefix eşleşmesi
Temel zihinsel model şudur: cache, system prompt'unuza göre anahtarlanmaz. API isteği önce tools, sonra system, sonra messages olarak render eder, her cache_control breakpoint'ine kadar olan byte'ları hash'ler ve mevcut bir prefix arar. İlk farklılaşan byte eşleşmeyi sonlandırır ve sonrasındaki her şey soğuktur.
Yazı bunun nasıl ısırdığına dair üç yol listeliyor:
- 11.000 token'lık stabil bir playbook'un önüne yerleştirilmiş güncel zaman dizisi gibi dinamik bir başlık.
- Nedensel olmayan (nondeterministic) bir serializer — sort_keys olmadan .dumps, aynı dict için farklı anahtar sırası ve dolayısıyla farklı byte'lar üretir.
- Kullanıcıya özel tool listesi; tools sıfırıncı konumda render edildiği için ölümcüldür, kullanıcılar arasında hiçbir şey cache'lenmez.
Yazarın vakası ilkinin gizlenmiş halidir: timestamp, yığında üç çağrı çerçevesi yukarıda, uzun zaman önce logging için yazılmış bir yardımcıdan geliyordu.
Breakpoint nereye ait
Kural şudur: prompt'un paylaşılan kısmının sonunu işaretleyin, asla整个 isteğin sonunu değil. Üst düzey bir cache_control otomatik olarak son cache'lenebilir bloğa kayar; bu, büyüyen bir sohbet dizisi için uygundur ama büyük sabit bir girişin ardından benzersiz bir sorunun gelmesi durumunda başarısız olur: her istek o zaman kimsenin asla okumayacağı byte'lar üzerinden ayrı bir kayıt yazar.
Yazıdan ek yerleştirme kuralları: tarihleri, modları, kullanıcı adlarını ve feature flag'lerini prefix'in dışında tutun (Opus 5 ve Opus 4.8'de dinamik metin, cache'lenmiş geçmişten sonra messages[] içinde bir system message olarak eklenebilir); tool'ları nedensel (deterministic) şekilde serialize edin ve konuşma ortasında tool eklemeyin, çıkarmayın veya sıralarını değiştirmeyin; cache'lerin modele göre kapsamlı olduğunu unutmayın, yani döngü ortasında model değiştirmek tüm prefix'i elinizden alır; ayrıca minimum cache'lenebilir prefix modele bağlıdır — en yeni modellerde 512 token, Opus 4.8 ve Sonnet 5'te 1.024, Opus 4.6 ve Haiku 4.5'te 4.096 token — ve eşiğin altında hata verilmez, sadece sessizce cache'lenmez. İstekler en fazla dört breakpoint'e izin verir.
Byte diff'i geçen ıskalar
Yazıya göre iki mekanizma, byte diff'te temiz görünen ama yine de ıskalayan payload'lar üretiyor. Her breakpoint, önceki bir kayıt aramak için en fazla 20 konum geriye gider; ardışık tool_use veya tool_result bloğu dizilerinin her biri tek bir konum sayılır, yani yoğun paralel tool çağrısı sorun değil, ama 20'den fazla konum ekleyen sıralı bir tur önceki kaydı tamamen kaybeder. Ayrıca paralel fan-out'ta bir kayıt yalnızca ilk yanıt akışa başladığında okunabilir hale gelir.
Bunu kalıcı bir kontrol haline getirmek
Gerçek veri usage.cache_read_input_tokens'dır. Üç alan prompt'u böler — toplam token, input_tokens artı cache_creation_input_tokens artı cache_read_input_tokens'a eşittir — bu yüzden tek başına küçük input_tokens ne ucuzluğu ne de bozulmayı kanıtlar. Sağlıklı bir çok turlu döngüde okumalar turdan tura büyür, yazmalar yalnızca en yüksek isabetin ötesindeki deltayı faturalandırır ve düz input token'lar küçük bir kuyruktur. Cache creation ise her turda konuşmanın tam boyutuna yakınsa, yukarı akışta bir şey prefix'i yeniden yazıyordur.
Operasyonel tavsiye, byte olarak özdeş iki istek gönderip ikincisinin sıfırdan farklı cache read raporladığını doğrulayan bir entegrasyon testi tutmaktır; bir bozukluğu ararken de cache_control işaretlerini ayıkladıktan sonra ardışık loglanmış istek gövdelerinin örtüşen bölgesini diff'lemektir. Örtüşme içindeki ilk farklılaşma geçersiz kılandır.
Neden önemli
Tool ağırlıklı agent'larda maliyetin büyük kısmı input token'lardır ve prompt caching gerçek kollardan biridir — ama bu arıza modu sessizdir. İstekler başarılı olur, latency değişmez ve fatura sessizce yukarı sürüklenir. Yazının tarif ettiği en pahalı senaryo kötü bir ilk uygulama değil, aylar sonra birinin system prompt'a bir feature flag eklediğinde regresyona uğrayan çalışan uygulamadır. Usage objesini bir log artefaktı değil izlenen bir sinyal olarak ele almak, indirim ile ek yükü ayıran şeydir.
- #llm
- #prompt-caching
- #cost-optimization
- #ai-agents
- #api