Prompt Caching
Prompt caching bisa memangkas biaya input sampai 90%. Kuncinya satu: cache bekerja dengan mencocokkan awalan prompt.
Intisari
- Satu aturan yang menjelaskan semuanya: cache adalah prefix match. Satu byte berubah di depan, semua cache setelahnya hangus.
- Urutan render prompt:
toolsโsystemโmessages. Taruh yang stabil di depan. - Baca cache โ 0,1ร harga input. Tulis cache โ 1,25ร (TTL 5 menit) atau 2ร (TTL 1 jam).
- Pembatal senyap yang paling sering: timestamp di system prompt, UUID,
json.dumpstanpasort_keys. - Verifikasi dengan
usage.cache_read_input_tokens. Kalau selalu 0, ada yang membatalkan cache.
Satu invarian yang menjelaskan semuanya
Prompt caching adalah pencocokan awalan (prefix match). Kunci cache dihitung dari byte
prompt yang sudah dirender, sampai ke titik cache_control. Perubahan satu byte
di posisi ke-N membatalkan cache untuk semua titik pada posisi โฅ N.
Urutan render:
tools โ system โ messages
Jadi menandai blok system terakhir dengan cache_control akan meng-cache
tools dan system sekaligus. Dan mengubah satu tool membatalkan seluruhnya, karena
tools berada di posisi paling depan.
Cara memakainya
Otomatis โ paling sederhana
response = client.messages.create(
model="claude-opus-5",
max_tokens=16000,
cache_control={"type": "ephemeral"}, # cache blok terakhir yang bisa di-cache
system=dokumen_besar,
messages=[{"role": "user", "content": "Ringkas poin utamanya"}],
)
Manual โ kendali penuh
response = client.messages.create(
model="claude-opus-5",
max_tokens=16000,
system=[
{"type": "text", "text": INSTRUKSI_TETAP},
{
"type": "text",
"text": dokumen_besar,
"cache_control": {"type": "ephemeral"}, # titik potong di sini
},
],
messages=[{"role": "user", "content": pertanyaan}], # bagian yang berubah-ubah
)
{"type": "ephemeral"} # TTL 5 menit (default)
{"type": "ephemeral", "ttl": "1h"} # TTL 1 jam
Ekonominya
| Operasi | Biaya relatif |
|---|---|
| Input biasa | 1ร |
| Tulis cache, TTL 5 menit | 1,25ร |
| Tulis cache, TTL 1 jam | 2ร |
| Baca cache | 0,1ร |
Titik impasnya:
- TTL 5 menit: impas di request kedua (1,25 + 0,1 = 1,35 vs 2 tanpa cache)
- TTL 1 jam: butuh tiga request (2 + 0,2 = 2,2 vs 3)
Batas minimum yang bisa di-cache
| Model | Minimum |
|---|---|
| Claude Opus 5 | 512 token |
| Opus 4.8, Sonnet 5, Sonnet 4.6 | 1.024 token |
| Opus 4.7 | 2.048 token |
| Opus 4.6, Haiku 4.5 | 4.096 token |
Prompt di bawah batas ini tidak akan di-cache โ tanpa error apa pun.
Kamu hanya akan melihat cache_creation_input_tokens: 0. Perhatikan bahwa angkanya
tidak berurutan antar generasi model, jadi cek tabelnya, jangan mengira-ira.
Pembatal senyap
Saat meninjau kode, cari pola berikut di bagian yang menyusun awalan prompt:
| Pola | Kenapa merusak |
|---|---|
datetime.now() di system prompt | Awalan berubah tiap request |
uuid4() atau request ID di awal konten | Sama โ selalu unik |
json.dumps(d) tanpa sort_keys=True | Urutan key bisa berubah |
Iterasi atas set | Urutannya tidak dijamin |
| ID pengguna di-interpolasi ke system prompt | Tiap pengguna punya awalan sendiri |
System prompt dirakit dengan if flag: | Tiap kombinasi flag = awalan berbeda |
tools=build_tools(user) | Tools di posisi 0 โ tidak ada yang ter-cache |
Prinsip arsitektur
1. Bekukan system prompt
# โ awalan berubah tiap request
system = f"Kamu asisten. Tanggal hari ini: {datetime.now():%Y-%m-%d}"
# โ
konteks dinamis ditaruh di messages, jauh di belakang
system = "Kamu asisten."
messages = [
*riwayat,
{"role": "user", "content": f"[tanggal: {hari_ini}]\n\n{pertanyaan}"},
]
2. Jangan ubah tools atau model di tengah percakapan
Tools berada di posisi 0. Menambah, menghapus, atau mengubah urutannya membatalkan seluruh cache. Serialisasikan daftar tool secara deterministik (urutkan berdasarkan nama). Berganti model juga membatalkan cache โ cache bersifat per-model.
3. Untuk instruksi yang datang di tengah
messages = [
*riwayat,
{"role": "user", "content": pesan_pengguna},
{"role": "system", "content": "Mode ringkas aktif โ jawab di bawah 40 kata."},
]
Pesan role: "system" di dalam messages (didukung Claude Opus 5
dan Opus 4.8) menambahkan instruksi operator tanpa mengubah awalan. Mengedit
system tingkat atas akan membuat seluruh riwayat percakapan diproses ulang
dengan harga penuh.
Verifikasi
u = response.usage
print(f"tulis cache : {u.cache_creation_input_tokens}")
print(f"baca cache : {u.cache_read_input_tokens}")
print(f"tanpa cache : {u.input_tokens}")
Kalau cache_read_input_tokens selalu 0 pada request berulang dengan awalan
yang seharusnya sama, ada pembatal senyap. Cara menemukannya: rakit prompt untuk dua request
berturut-turut, simpan ke file, lalu diff byte-nya.
Perhatikan juga: input_tokens hanya menghitung sisa yang tidak ter-cache.
total = u.input_tokens + u.cache_creation_input_tokens + u.cache_read_input_tokens
Hierarki pembatalan
| Yang berubah | Cache tools | Cache system | Cache messages |
|---|---|---|---|
| Definisi tool | โ | โ | โ |
| Ganti model | โ | โ | โ |
| Isi system prompt | โ | โ | โ |
tool_choice, aktif/nonaktif thinking | โ | โ | โ |
| Isi pesan | โ | โ | โ |
Jadi mengubah tool_choice per request itu aman โ cache tools dan system tetap terpakai.
Batas teknis
- Maksimal 4 titik
cache_controlper request - Titik potong menelusuri mundur maksimal 20 content block โ di loop agentic yang panjang, pasang titik antara tiap ~15 blok
- Request paralel dengan awalan sama tetap bayar penuh semua โ kirim satu dulu, tunggu token pertama, baru kirim sisanya
Di mana ini paling berdampak
| Kasus | Yang di-cache | Penghematan |
|---|---|---|
| Chatbot RAG | System prompt + definisi tool | Besar โ dipakai tiap giliran |
| Tanya jawab atas satu dokumen besar | Isi dokumen | Sangat besar |
| Loop agentic | Riwayat percakapan yang tumbuh | Sangat besar |
| Klasifikasi batch | Instruksi + contoh few-shot | Besar |
| Panggilan sekali, prompt pendek | โ | Tidak ada; jangan pakai cache |
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.