Arsitektur integrasi AI di backend — retry, timeout, rate limit
Kode contoh di tutorial memanggil client.messages.create() seolah selalu berhasil dalam sekejap. Di produksi, panggilan ini bisa lambat (detik, bukan milidetik), bisa gagal, dan dibatasi rate limit — arsitekturmu harus menanganinya.
Intisari
- Panggilan LLM API punya latensi ratusan milidetik sampai puluhan detik — jauh lebih lambat dari panggilan database biasa. Desain sistemmu (timeout, UI loading state) harus mengasumsikan ini.
- Rate limit dinyatakan dalam RPM (request per menit) DAN TPM (token per menit) sekaligus — kena limit salah satunya tetap ditolak dengan HTTP 429.
- Kegagalan sementara (429, 529, timeout jaringan) harus di-retry dengan exponential backoff — gagal langsung di percobaan pertama adalah pengalaman pengguna yang buruk untuk error yang sebenarnya sementara.
- SDK resmi (Anthropic, OpenAI) sudah punya retry bawaan untuk error yang aman diulang — tapi kamu tetap harus menangani kegagalan permanen (kredensial salah, prompt ditolak guardrails).
- Panggilan LLM yang lambat tidak boleh memblokir thread/permintaan lain — pola async atau antrean background job (dibahas juga di Fase 5–6) sering dibutuhkan begitu trafiknya nyata.
Ini bukan pemanggilan fungsi lokal
Kode tutorial biasanya menulis response = client.messages.create(...) di satu baris seolah
selalu instan dan selalu berhasil. Kenyataannya ini adalah panggilan HTTP ke layanan pihak
ketiga yang melintasi internet, antre di server provider, dan menjalankan komputasi yang mahal.
Tiga sifat yang harus diasumsikan sejak desain pertama:
| Sifat | Konsekuensi desain |
|---|---|
| Latensi tinggi (ratusan ms – puluhan detik) | UI butuh loading state / streaming; jangan blokir request HTTP utama tanpa timeout |
| Bisa gagal sementara | Perlu retry dengan backoff, bukan langsung menyerah atau langsung crash |
| Dibatasi kuota (rate limit) | Trafik tinggi bisa ditolak provider meski request-nya valid |
Rate limit: dua sumbu sekaligus
Rate limit LLM API biasanya dinyatakan dalam dua satuan sekaligus: RPM (request per menit)
dan TPM (token per menit). Kena batas salah satu saja — meski yang lain masih longgar —
tetap ditolak dengan status 429 Too Many Requests.
Kenapa TPM sering jadi limit sebenarnya yang kena duluan, bukan RPM. Aplikasi yang mengirim sedikit request tapi masing-masing membawa prompt panjang (dokumen besar, riwayat percakapan panjang) bisa kena limit TPM jauh sebelum RPM-nya habis. Ini alasan penting menghitung anggaran token per request, bukan cuma menghitung jumlah request.
Retry dengan exponential backoff
Error 429 (rate limit) dan 529 (server provider sedang kelebihan beban) biasanya
sementara — mengulang setelah jeda singkat sering berhasil. Pola standarnya: tunggu makin
lama di tiap percobaan ulang, dengan sedikit keacakan (jitter) supaya banyak klien tidak
mencoba ulang di detik yang persis sama.
import time
import random
import anthropic
def panggil_dengan_retry(client, max_percobaan=4, **kwargs):
for percobaan in range(max_percobaan):
try:
return client.messages.create(**kwargs)
except anthropic.RateLimitError:
if percobaan == max_percobaan - 1:
raise
jeda = (2 ** percobaan) + random.uniform(0, 1) # 1-2s, 2-3s, 4-5s, ...
time.sleep(jeda)
except anthropic.APIStatusError as e:
if e.status_code >= 500: # error sisi provider, aman diulang
time.sleep(2 ** percobaan)
continue
raise # error sisi klien (mis. 400 prompt invalid) — jangan diulang, pasti gagal lagi
SDK resmi sudah menangani sebagian ini secara otomatis (Anthropic & OpenAI SDK punya retry
bawaan untuk error yang aman diulang, dengan backoff terkonfigurasi). Kode manual di atas untuk
memperlihatkan apa yang terjadi di baliknya — di produksi, cek dulu opsi max_retries
bawaan SDK sebelum menulis ulang sendiri.
Kegagalan yang TIDAK boleh diulang
| Error | Artinya | Tindakan |
|---|---|---|
401 | API key salah/kedaluwarsa | Jangan retry — perbaiki kredensial |
400 | Request malformed (skema tool salah, dsb.) | Jangan retry — akan gagal lagi persis sama; perbaiki kode |
429 / 529 | Rate limit / server sibuk | Retry dengan backoff |
| Timeout jaringan | Koneksi terputus, tidak jelas apakah request sampai | Retry — idealnya dengan idempotency key kalau ada efek samping (mis. pembayaran) |
Jangan blokir request lain
Kalau backend-mu synchronous (satu thread menangani satu request sampai selesai), satu panggilan LLM yang memakan 10 detik berarti thread itu tertahan 10 detik penuh. Di beban tinggi, ini cepat menghabiskan pool worker. Dua pola umum untuk mengatasinya:
- Async I/O (mis.
asyncio+AsyncAnthropic) — satu proses bisa menangani banyak panggilan LLM yang sedang menunggu sekaligus tanpa menambah thread. - Antrean background job — untuk tugas yang tidak butuh jawaban instan, lempar ke worker terpisah (dibahas lagi saat membahas arsitektur chatbot di Fase 6).
Latihan: modifikasi fungsi panggil_dengan_retry di atas untuk mencatat (log) setiap
percobaan ulang beserta jeda yang dipakai. Lalu simulasikan kegagalan dengan sengaja memakai API key yang
salah, dan pastikan kodemu tidak mencoba mengulang error 401 — cukup gagal cepat
dengan pesan yang jelas.
Rangkuman ini sengaja dipangkas ke bagian yang dipakai di roadmap. Buka sumber aslinya saat kamu butuh detail lengkap atau referensi parameter.