Referensi cepat menekan biaya LLM. Token counting, prompt caching, batch API, model routing, context engineering, dan monitoring spend.
Biaya LLM didorong tiga hal: jumlah token input, jumlah token output, dan harga per token dari model yang kamu pilih. Latensi biasanya bergerak searah dengan biaya. Request yang mengirim prompt panjang dan meminta jawaban panjang akan mahal dan lambat.
Rumus sederhana yang wajib diingat:
biaya per request =
(input_tokens x harga_input)
+ (output_tokens x harga_output)
+ (cache_write_tokens x harga_cache_write, kalau ada)
+ (cached_input_tokens x harga_cache_read)Di hampir semua provider, output token lebih mahal daripada input. Sering kali berkali lipat. Menyingkat jawaban yang bertele-tele menyelamatkan lebih banyak uang daripada menyingkat prompt dengan jumlah token yang sama.
Sebelum berhemat, ukur dulu. Tanpa angka, kamu akan sibuk mengutak-atik bagian yang tidak pernah memakan biaya. Catat token per feature, per model, per tenant, lalu cari puncaknya.
Token adalah unit yang dipakai model untuk memproses teks. Satu token kira-kira empat karakter bahasa Inggris, atau tiga perempat kata. Aturan "satu token sama dengan empat karakter" hanya perkiraan. Kata bahasa Indonesia, simbol, spasi, dan kode bisa berbeda jauh.
Tiap provider punya tokenizer sendiri. Token yang dihitung cl100k_base untuk model OpenAI belum tentu sama dengan tokenizer Claude. Gunakan estimator resmi dari provider yang kamu pakai.
import tiktoken
enc = tiktoken.encoding_for_model("gpt-4o")
text = "Halo, selamat datang di BelajarKoding."
tokens = enc.encode(text)
print(len(tokens)) # jumlah token
print(enc.decode(tokens)) # teks asliEstimasi input sebelum request memungkinkan kamu menolak request yang melebihi anggaran, memangkas konteks di sisi aplikasi, atau memilih model lebih murah. Token yang tidak pernah terkirim adalah token gratis.
def token_count(text: str, model: str = "gpt-4o") -> int:
enc = tiktoken.encoding_for_model(model)
return len(enc.encode(text))
BUDGET_INPUT_TOKENS = 4_000
def fit_to_budget(text: str, model: str) -> str:
enc = tiktoken.encoding_for_model(model)
tokens = enc.encode(text)
if len(tokens) <= BUDGET_INPUT_TOKENS:
return text
return enc.decode(tokens[:BUDGET_INPUT_TOKENS])Catatan penting: memotong teks secara buta bisa membuang bagian akhir yang penting. Kalau dokumen panjang, potong per bagian, rangkum per bagian, atau potong dengan kesadaran struktur, bukan asal ambil seribu token pertama.
System prompt yang statis, instruksi tools, dan contoh few-shot dipakai sama persis di banyak request. Taruh bagian itu di awal dan jangan diubah, supaya prefix-nya bisa di-cache dan tidak dihitung sebagai token baru di setiap request.
SUSUNAN YANG BURUK (setiap request mengirim ulang semuanya):
[instruksi produk, 800 token] <- statis
[riwayat chat penuh, 2.000 token]
[pesan user baru, 40 token]
SUSUNAN YANG BAIK (prefix stabil):
[instruksi produk, 800 token] <- stabil, jadi prefix cache
[riwayat ringkas, 300 token]
[pesan user baru, 40 token]Instruksi yang bertele-tele juga mahal karena ikut dihitung di setiap permintaan. Tulis instruksi sepadat mungkin. Buang kata yang tidak mengubah perilaku model.
Untuk percakapan panjang, jangan kirim seluruh riwayat selamanya. Pilih satu:
1. Sliding window: kirim N pesan terakhir saja.
Murah, tapi model kehilangan konteks awal.
2. Summarize: rangkum pesan lama jadi paragraf, gabung dengan pesan baru.
Lebih mahal sekali, menjaga inti lebih baik.
3. Hybrid: ringkas pesan yang sangat lama, simpan pesan baru apa adanya.
Paling seimbang untuk agent chat.Ukur lewat evaluasi, bukan selera. Rangkuman yang terlalu agresif membuat model melupakan detail penting dan jawaban jadi salah, yang jauh lebih mahal daripada token yang kamu hemat.
Contoh membantu model stabil, tapi tiap contoh dihitung sebagai token input. Mulai dari satu atau dua contoh. Tambah hanya kalau evaluasi menunjukkan kenaikan kualitas yang sebanding. Contoh panjang dengan prompt yang hampir sama juga bisa dirapikan menjadi satu contoh yang lebih padat.
Kalau output diproses mesin, minta format ringkas. Setiap kata tambahan adalah biaya output.
Kurang hemat:
"Jawab pertanyaan berikut dengan penjelasan yang ramah dan terperinci."
Lebih hemat:
"Jawab hanya dengan JSON: {\"status\": \"ok\" | \"failed\", \"reason\": string}."max_tokens adalah rem darurat, bukan alat penghemat utama. Model yang dipaksa berhenti di tengah jawaban bisa menghasilkan output yang rusak dan bikin kamu retry. Lebih baik minta format pendek secara eksplisit.
Tugas sederhana tidak butuh model paling mahal. Klasifikasi intent, ekstraksi field, perbaikan ejaan, dan jawaban FAQ sederhana bisa ditangani model kecil atau model medium.
| Tugas | Model yang cocok |
|---|---|
| Klasifikasi, ekstraksi, intent | model kecil atau medium |
| Ringkasan dan draft biasa | model medium |
| Reasoning kompleks, coding, multi-step | model besar |
| Embedding | model embedding khusus |
Buat router yang mengirim request ke model sesuai tingkat kesulitan, bukan satu model untuk semuanya.
def route_model(prompt: str) -> str:
tokens = token_count(prompt)
if tokens < 500:
return "gpt-4o-mini"
return "gpt-4o"
# Atau routing bertahap: mulai dari model kecil,
# kalau gagal validasi, naikkan ke model besar.Routing sederhana memakai panjang atau klasifikasi cepat. Routing lebih maju memakai skor confidence: kalau model kecil tidak yakin, eskalasi ke model besar. Sisihkan sebagian request untuk evaluasi offline agar router tidak diam-diam mengirim semua ke model mahal.
Harga berbeda antara input, output, dan model. Perbandingan satu provider dengan provider lain sebaiknya dilakukan lewat tabel harga resmi dan tokenizer masing-masing, bukan dari ingatan. Pindah model karena "lebih murah" tanpa menguji kualitas sama saja menukar tagihan dengan support ticket.
Ketika prefix prompt sama di beberapa request, provider bisa memakai ulang komputasi bagian itu. Request pertama membayar penulisan cache (kadang lebih mahal dari token biasa), request berikutnya membaca dari cache dengan harga jauh lebih murah.
Caching membantu saat kamu memakai ulang instruksi, tool definition, atau konteks bersama yang panjang. Caching tidak membantu request sekali pakai yang prefix-nya selalu berbeda.
request 1: [prefix stabil A][konten baru B]
-> A ditulis ke cache (bayar write rate)
request 2: [prefix stabil A][konten baru C]
-> A dibaca dari cache (bayar read rate, murah)Prefix harus sama persis di awal untuk mendapatkan cache hit. Kalau kamu mengubah instruksi di tengah atau menaruh timestamp di baris pertama, cache tidak akan kena. Taruh bagian yang berubah-ubah di belakang.
BURUK: menaruh timestamp user di awal, memindahkan prefix stabil.
[2026-09-19 05:30 user=123] <- berubah tiap request
[instruksi produk panjang]
BAIK: prefix stabil di awal.
[instruksi produk panjang] <- selalu sama
[2026-09-19 05:30 user=123] <- berubah, di belakangProvider menetapkan panjang minimum prefix agar bisa di-cache. Di banyak model, minimum ada di sekitar 1.024 token. Prefix yang lebih pendek dari minimum tidak akan di-cache. Ini penting karena ada jebakan: memanjangkan prefix supaya lolos minimum kadang lebih murah daripada mengirim ulang prefix pendek berkali-kali tanpa cache, tapi hanya kalau reuse-nya cukup sering.
Hitung sederhana untuk jebakan itu. Kalau prefix pendek kamu kirim N kali tanpa cache, biayanya N x L. Kalau kamu panjangkan ke minimum M, biayanya M x (write_rate + (N-1) x read_rate). Perluas hanya kalau N cukup besar sehingga penghematan read lebih besar dari biaya menambah token. Contoh dengan minimum 1.024 token, write rate 1.25x, dan read rate 0.1x: kalau prefix asli kamu di bawah sekitar 220 token, menambahnya baru untung setelah lebih dari belasan request. Cek angka provider yang kamu pakai sebelum memutuskan.
def build_messages(shared_prefix: list[dict], turn: dict) -> list[dict]:
# shared_prefix stabil di awal, turn baru di belakang.
return [*shared_prefix, turn]
shared_prefix = [
{"role": "system", "content": INSTRUCTIONS}, # panjang dan stabil
{"role": "user", "content": CONTEXT_BLOCK}, # konteks bersama
]Saat membuat agent loop, replay instruksi dan tool definition yang sama di tiap langkah. Dengan begitu prefix stabil dan langkah-langkah berikutnya membaca cache.
Pantau metrik cache: provider mengembalikan jumlah cached_tokens atau cache_read_input_tokens dan cache_creation_input_tokens di usage. Hitung rasio cache hit untuk memastikan optimasi kamu benar-benar terjadi, bukan hanya di atas kertas.
Batch API menjalankan banyak request sekaligus dengan harga diskon, imbalannya hasil tidak langsung. Cocok untuk pekerjaan yang tidak butuh respons segera: labeling dataset, evaluasi massal, generating metadata, ekstraksi dokumen dalam jumlah besar.
Tidak cocok untuk chat real-time. Menunda jawaban user beberapa jam demi hemat 50 persen adalah keputusan produk yang buruk.
| Skenario | Real-time | Batch |
|---|---|---|
| Chat user | ya | tidak |
| ETL dan enrichment | tidak | ya |
| Eval offline | tidak | ya |
| Generate deskripsi produk massal | tidak | ya |
| Answering agent interaktif | ya | tidak |
1. Bangun file input dengan banyak request.
2. Kirim file ke batch API, dapat batch id.
3. Poll status batch sampai selesai.
4. Unduh output, cocokkan dengan id per item.
5. Verifikasi jumlah sukses dan gagal, retry yang gagal.Batch bukan berarti gratis dari kegagalan. Pastikan tiap item punya ID sendiri supaya hasil bisa dicocokkan, dan hitung ulang item yang gagal. Jangan asumsikan semua item sukses hanya karena status batch selesai.
Biaya RAG didominasi oleh jumlah token yang dimasukkan ke prompt. Retriever yang mengembalikan sepuluh chunk panjang akan membuat tiap query mahal. Retriever yang mengembalikan tiga chunk relevan menghemat banyak.
| Teknik | Efek pada biaya |
|---|---|
| Batasi jumlah chunk | langsung menurunkan input token |
| Batasi panjang tiap chunk | menurunkan token per chunk |
| Reranking lalu ambil top-k kecil | memangkas chunk tak relevan |
| Caching embedding query | menghindari hit embedding berulang |
| Chunk yang lebih besar untuk dokumen padat | mengurangi jumlah chunk per jawaban |
Embedding juga berbiaya. Kalau query yang sama datang berulang, cache embedding-nya. Untuk korpus besar, pertimbangkan cache jawaban yang sudah jadi untuk pertanyaan yang sering muncul.
Semakin panjang konteks, semakin besar token input di tiap request. Gunakan hanya bagian yang relevan. Hapus boilerplate, metadata tidak berguna, dan chunk yang isinya duplikat. Untuk dokumen panjang, pertimbangkan summarization sebelum menjawab pertanyaan lanjutan.
Pada agent long-running, compaction (meringkas riwayat lama jadi catatan padat) menahan pertumbuhan token. Pilih frekuensi compaction lewat evaluasi. Compaction yang terlalu sering menambah biaya summarization; terlalu jarang membuat prompt membengkak. Bandingkan biaya total keduanya pada workload nyata.
Di banyak model, output token harganya beberapa kali input token. Perintah yang membuat model menulis panjang lebar adalah pengali biaya langsung.
Mahal:
"Jelaskan secara rinci semua langkah dan alasannya."
Hemat:
"Berikan jawaban maksimal 3 kalimat."Minta jawaban singkat, poin saja, atau format ketat. max_tokens sebaiknya jadi pagar pengaman, bukan satu-satunya pengontrol panjang jawaban. Kalau jawaban terpotong oleh max_tokens, kamu harus retry dan membayar output dua kali.
Structured output (misal JSON dengan schema) membantu aplikasi memakai jawaban langsung tanpa parsing ulang dan tanpa meminta model menjelaskan format. Streaming tidak menurunkan token, tetapi menurunkan latensi persepsi dan memungkinkan user berhenti lebih awal pada jawaban yang tidak dibutuhkan.
input_tokens
output_tokens
cached_input_tokens
cache_write_tokens
latency_ms
cost_usdJumlahkan per feature, per model, per tenant, per hari. Satu request mahal yang jarang tidak seburuk request murah yang dipanggil jutaan kali. Fokus pada total, bukan satu-satunya request outlier.
Untuk memantau biaya, kamu tidak perlu menyimpan seluruh prompt dan jawaban di dashboard observability. Simpan jumlah token, model, fitur, dan status. Simpan teks lengkap hanya kalau dibutuhkan untuk debugging, dengan retention dan akses yang dibatasi.
Pasang budget per feature dan per hari. Kirim alert saat mendekati batas, dan potong akses saat melampaui. Pastikan penurunan fitur tidak bikin user marah: tampilkan pesan jelas ketika limit tercapai.
def check_budget(spend_today: float, budget: float) -> bool:
if spend_today > budget * 0.9:
notify("approaching budget")
return spend_today <= budgetSaat mengganti model, mengubah prompt, atau menambah caching, uji perubahan pada subset traffic dan bandingkan biaya serta kualitas. Keputusan penghematan tanpa evaluasi kualitas biasanya kembali sebagai regresi yang lebih mahal.
Paling berdampak, mulai dari sini:
1. Hapus token input yang tidak perlu (instruksi, riwayat, contoh).
2. Potong atau ringkas konteks panjang.
3. Pilih model sesuai kompleksitas, jangan satu ukuran.
4. Stabilkan prefix supaya prompt caching bekerja.
5. Pakai batch untuk pekerjaan yang tidak real-time.
6. Minta output pendek dan format ketat.
7. Pantau token per feature, bukan perasaan.Urutan ini sengaja menaruh pemangkasan token di atas. Sebagian besar tagihan membengkak karena token yang tidak perlu, bukan karena caching yang belum diaktifkan.
Login atau daftar akun gratis untuk membaca cheat sheet ini.