मैंने एक बार एक ऐसा agent deploy किया जो हर single request पर 40K-token का system prompt पढ़ता था। हर घंटे सैकड़ों requests, हर बार वही prompt, और हर बार पूरी input price पर billing। fix सिर्फ चार lines में था। Prompt caching ने उस recurring cost को लगभग दसवें हिस्से तक ला दिया। Cache reads की लागत base input price की लगभग 0.1 गुना होती है। यह headline number है, और यह real है।
लेकिन पहले मेरे साथ यह हुआ: मैंने cache marker जोड़ा और कुछ नहीं बदला। cache_read_input_tokens शून्य पर ही रहा। Cache चुपचाप कुछ नहीं कर रहा था, और मैं उसके लिए premium चुका रहा था। तो how-to से पहले, आपको वह एक नियम जानना होगा जिससे बाकी सब कुछ निकलता है।
Prompt caching एक prefix match है
बस इतना ही। Cache key आपके rendered prompt के exact bytes हैं, हर cache breakpoint तक। उस prefix में कहीं भी कोई भी बदलाव उसके बाद की हर चीज़ के लिए cache को invalid कर देता है। एक byte भी — एक reordered JSON key, एक timestamp, एक swapped tool — और आप पुरानी entry read करने की बजाय नई cache entry लिख रहे हैं।
Render order fixed है: tools, फिर system, फिर messages। तो जो सबसे पहले आता है वह आपके पास सबसे stable चीज़ होनी चाहिए, और जो per-request बदलता है वह बिल्कुल अंत में होना चाहिए। यह ordering सही कर लें और ज़्यादातर caching अपने आप काम करेगी। गलत कर लें और कितने भी markers कोई फर्क नहीं पड़ेगा।
जो चीज़ मेरी cache को बर्बाद कर रही थी
यह मेरे system prompt के शीर्ष पर था:
Current date: 2026-06-21 14:32:07
एक timestamp। यह हर request पर बदलता था, prefix के शुरुआत के पास था, और इसके बाद की हर चीज़ को invalidate कर देता था। पूरा 40K prompt एक 19-character string की वजह से uncacheable था जिसके बारे में मैंने सोचा ही नहीं था।
यह सबसे common caching bug है जो मैं देखता हूँ। लोग dynamic चीज़ें — current date, user name, session ID, एक mode flag — system prompt में interpolate करते हैं, और यह prefix को ज़हर दे देता है। fix यह है कि system prompt को freeze करें और dynamic bits को बाद में, messages array में inject करें, जहाँ वे उनसे पहले की किसी चीज़ को invalidate नहीं करते। Turn 5 का एक fact turns 1 से 4 के cache को नहीं छूता।
जब cache hit न हो तो मेरी audit checklist यह है। अपने prompt-building code में grep करें:
datetime.now()/Date.now()कहीं भी system prompt या tools मेंuuid4()या request IDs content में शुरुआत मेंjson.dumps(d)बिनाsort_keys=Trueके — Python dict iteration order bytes बदल सकती है- f-strings जो user या session ID को system prompt में interpolate करते हैं
- एक tool set जो per-user build होता है, तो tools block हर request पर अलग होता है
अगर दो requests में जो आप जानते हैं एक prefix share करती हैं, cache_read_input_tokens शून्य है, तो इनमें से एक culprit है। दो requests के बीच rendered bytes को diff करें और आप इसे ढूंढ लेंगे।
Breakpoint रखना
वास्तविक marker एक content block पर cache_control: {type: "ephemeral"} है। सबसे सरल case, एक बड़ा shared system prompt:
"system": [{
"type": "text",
"text": "<your big stable prompt>",
"cache_control": {"type": "ephemeral"}
}]
क्योंकि tools system से पहले render होते हैं, last system block पर marker tools और system दोनों को एक साथ cache करता है। आपके पास per request अधिकतम चार breakpoints हैं, इसलिए इन्हें समझदारी से खर्च करें।
Multi-turn conversations के लिए, breakpoint को सबसे recent turn के last block पर रखें। हर नई request पूरी पिछली conversation को cached prefix के रूप में reuse करती है, और जैसे-जैसे chat बढ़ता है hits जमा होते हैं। एक varying question के साथ shared preamble के लिए — few-shot examples plus हर बार अलग query — marker को shared part के अंत में रखें, कभी पूरे prompt के अंत में नहीं। अगर आप varying question के through cache करते हैं, तो हर request एक unique entry लिखती है और कुछ नहीं पढ़ती।
Economics, क्योंकि यह free नहीं है
Reads सस्ती हैं (~0.1×) लेकिन writes सामान्य request से ज़्यादा महंगी हैं: default 5-minute TTL के लिए 1.25×, 1-hour TTL के लिए 2×। तो caching तभी फायदेमंद है जब आप जितना लिखते हैं उससे ज़्यादा पढ़ते हैं। 5-minute TTL के साथ दो requests पर break even होता है। 1-hour TTL के साथ कम से कम तीन चाहिए, क्योंकि write premium दोगुना है।
कौन सा TTL? 1-hour तभी use करें जब आपके traffic में पाँच मिनट से लंबे gaps हों। अगर requests उससे ज़्यादा बार आती हैं, वे cache को खुद ही warm रखती हैं और default 5-minute TTL ठीक और सस्ता है। Default रूप से 1-hour TTL न पकड़ें — अगर traffic steady है तो doubled write cost चुपचाप आपकी बचत खा जाएगी।
दो gotchas जिनमें से हर एक ने मुझे एक debugging afternoon दी
20-block lookback। हर breakpoint पिछली cache entry ढूंढने के लिए अधिकतम 20 content blocks पीछे जाता है। एक agentic loop में बहुत सारे tool_use/tool_result pairs के साथ, एक single turn 20 से ज़्यादा blocks जोड़ सकता है — और फिर अगला request पिछला cache नहीं ढूंढ पाता और चुपचाप miss कर देता है। Fix: long turns में हर ~15 blocks पर एक intermediate breakpoint डालें।
Concurrent requests। एक cache entry तभी readable होती है जब पहला response stream होना शुरू हो जाता है। एक ही prefix के साथ दस parallel requests fire करें और सभी दस full price चुकाएंगे, क्योंकि कोई भी उसे नहीं पढ़ सकता जो दूसरे अभी लिख रहे हैं। Fan-out के लिए, एक request भेजें, पहले streamed token का इंतज़ार करें, फिर बाकी fire करें। वे उस पहली request द्वारा लिखा cache पढ़ेंगे।
एक नई trick जो जानने लायक है
Opus 4.8 पर एक साफ तरीका है mid-conversation instruction inject करने का बिना cache नष्ट किए: top-level system को edit करने की बजाय messages array में एक {"role": "system", ...} message append करें। Top-level system को edit करने से आपकी पूरी history से पहले का prefix बदलता है, इसलिए हर cached turn बिना cache के process होता है। एक system-role message इतिहास के बाद बैठता है और cached prefix को intact रखता है। यह non-spoofable operator channel भी है, जो एक अच्छा bonus है।
usage object से सब कुछ verify करें: cache_creation_input_tokens वह है जो आपने लिखा, cache_read_input_tokens वह है जो आपने सस्ती rate पर पढ़ा, input_tokens uncached remainder है। अगर आपका agent एक घंटे चला और input_tokens 4K दिखाता है, घबराएं नहीं — बाकी cache से आया। Single field नहीं, sum देखें।
Config की चार lines, एक नियम का सम्मान करें। Prefix का सम्मान करें और बिल खुद-ब-खुद कम हो जाएगा।
