DEV Community

Cover image for Pola Pemulihan Kesalahan Agen AI: Retry, Timeout, Backoff, dan Circuit Breaker
Walse
Walse

Posted on • Originally published at apidog.com

Pola Pemulihan Kesalahan Agen AI: Retry, Timeout, Backoff, dan Circuit Breaker

Agen Anda memanggil API. API mengembalikan 429. Agen segera mencoba lagi, mendapat 429 lagi, lalu mengulanginya sampai proses mati atau biaya membengkak. Tidak ada yang sengaja menulis loop itu; biasanya ini muncul dari implementasi naif “penanganan error”. Pola ini juga sering muncul di diskusi Anthropic SDK.

Coba Apidog hari ini

Pemulihan error membedakan demo agen yang rapi dari sistem produksi yang dapat diandalkan. Model bukan satu-satunya masalah: kode Anda harus tahu apa yang dilakukan ketika tool call lambat, terkena rate limit, atau gagal total. Artikel ini membahas empat pola inti:

  1. Retry dengan exponential backoff dan jitter.
  2. Timeout pada setiap panggilan keluar.
  3. Circuit breaker untuk dependensi yang sedang gagal.
  4. Kunci idempoten untuk operasi yang mengubah data.

Di bagian akhir, Anda akan menguji semua jalur tersebut dengan mock. Untuk konteks yang lebih luas, baca mengapa agen AI rusak dalam produksi.

Anda tidak bisa menguji pemulihan terhadap API yang sehat

Dalam development, dependensi biasanya sehat: request berhasil, demo lancar, dan kode recovery tidak pernah dieksekusi. Akibatnya, pertama kali backoff, timeout, atau circuit breaker berjalan adalah saat insiden produksi—ketika pengguna sedang menunggu.

Aturannya sederhana: uji recovery dengan kegagalan yang disengaja.

Buat mock untuk API yang dipanggil agen, lalu program mock tersebut untuk mengembalikan:

  • 429 Too Many Requests
  • 500 Internal Server Error
  • respons yang sangat lambat atau timeout
  • body JSON yang tidak valid
  • respons sukses setelah beberapa kegagalan

Arahkan tool agen ke mock tersebut dan verifikasi perilakunya. Dengan pendekatan ini, kegagalan menjadi skenario test yang dapat Anda ulang, bukan kejutan pukul 3 pagi. Apidog dapat digunakan untuk membuat mock dan menyusun urutan respons tersebut.

Retry dengan exponential backoff dan jitter

Retry adalah garis pertahanan pertama, tetapi retry langsung adalah pola yang berbahaya:

while True:
    try:
        return call_api()
    except Exception:
        continue
Enter fullscreen mode Exit fullscreen mode

Jika layanan sedang terbebani, banyak klien akan gagal dan mencoba lagi secara bersamaan. Hasilnya adalah retry storm: layanan yang sudah kesulitan menerima beban tambahan tepat ketika sedang mencoba pulih.

Gunakan exponential backoff:

percobaan 1: tunggu 1 detik
percobaan 2: tunggu 2 detik
percobaan 3: tunggu 4 detik
percobaan 4: tunggu 8 detik
Enter fullscreen mode Exit fullscreen mode

Lalu tambahkan jitter, yaitu variasi acak pada waktu tunggu. Jitter mencegah ribuan klien melakukan retry pada detik yang sama.

Contoh Python:

import random
import time

MAX_RETRIES = 4
MAX_DELAY_SECONDS = 30

def retry_with_backoff(operation):
    for attempt in range(MAX_RETRIES):
        try:
            return operation()
        except Exception:
            if attempt == MAX_RETRIES - 1:
                raise

            base_delay = min(2 ** attempt, MAX_DELAY_SECONDS)
            jitter = random.uniform(0, base_delay)
            time.sleep(jitter)
Enter fullscreen mode Exit fullscreen mode

Batasi dua hal:

  • Jumlah retry, agar kegagalan permanen tidak menjadi loop tanpa akhir.
  • Delay maksimum, agar aplikasi tidak menunggu terlalu lama di antara percobaan.

Tiga sampai lima percobaan biasanya cukup untuk error sementara. Lebih dari itu sering berarti Anda mengulang request yang tidak akan berhasil.

Anthropic SDK sudah melakukan retry untuk error koneksi dan status tertentu pada panggilannya sendiri, dengan batas yang dapat diatur melalui max_retries. Namun, mekanisme tersebut tidak otomatis mencakup API lain yang dipanggil tool agen Anda. Untuk endpoint berisiko tinggi seperti pembayaran, pelajari pola retry yang lebih ketat dalam logika percobaan ulang untuk API berisiko tinggi.

Tetapkan timeout pada setiap panggilan

Retry hanya berguna jika request benar-benar gagal. Kasus yang lebih buruk adalah request yang tidak pernah selesai: koneksi diterima, tetapi server tidak mengirim respons.

Tanpa timeout, satu socket yang menggantung dapat memblokir tool call dan membuat seluruh eksekusi agen macet.

Setiap panggilan keluar sebaiknya memiliki:

  • Connection timeout: batas waktu untuk membuat koneksi.
  • Read timeout: batas waktu untuk menunggu respons.
  • Total execution budget: batas waktu total untuk satu run agen.

Contoh dengan httpx:

import httpx

timeout = httpx.Timeout(
    connect=3.0,
    read=10.0,
    write=10.0,
    pool=5.0,
)

with httpx.Client(timeout=timeout) as client:
    response = client.get("https://api.example.com/data")
    response.raise_for_status()
Enter fullscreen mode Exit fullscreen mode

Pilih angka berdasarkan metrik latensi nyata, bukan tebakan. Gunakan p99 dependensi sebagai titik awal, lalu tambahkan ruang cadangan.

Jika API memiliki p99 sekitar 2 detik, timeout baca 3–5 detik mungkin masuk akal. Jika Anda menetapkan timeout terlalu rendah, request yang sebenarnya valid akan dibatalkan. Jika terlalu tinggi, agen akan menunggu dependensi mati terlalu lama.

Untuk respons streaming, gunakan anggaran tersendiri. Respons streaming yang panjang memang dapat berlangsung lebih lama daripada request JSON biasa.

Picu circuit breaker saat dependensi tidak berfungsi

Backoff cocok untuk kegagalan sementara. Namun, jika dependensi benar-benar mati, terus mencoba ulang hanya menambah beban dan memperpanjang waktu tunggu pengguna.

Circuit breaker memiliki tiga status:

Status Perilaku
closed Request berjalan normal dan kegagalan dihitung.
open Request langsung gagal tanpa memanggil dependensi.
half-open Satu atau beberapa request probe diizinkan untuk memeriksa pemulihan.

Alurnya:

  1. Dependensi mulai gagal.
  2. Jumlah kegagalan melewati ambang batas.
  3. Breaker berubah ke open.
  4. Request berikutnya gagal cepat selama cooldown.
  5. Setelah cooldown, breaker berubah ke half-open.
  6. Jika probe berhasil, breaker kembali ke closed.
  7. Jika probe gagal, breaker kembali ke open.

Pseudocode sederhana:

if breaker.is_open():
    raise DependencyUnavailable("Payment API sedang tidak tersedia")

try:
    result = call_payment_api()
    breaker.record_success()
    return result
except Exception:
    breaker.record_failure()

    if breaker.failure_count >= 5:
        breaker.open_for(seconds=30)

    raise
Enter fullscreen mode Exit fullscreen mode

Untuk agen, circuit breaker mengubah puluhan timeout lambat menjadi satu error cepat yang dapat ditangani dengan jelas.

Pasang circuit breaker per dependensi, bukan global. API pencarian yang gagal tidak boleh menghentikan agen menggunakan API penagihan yang sehat.

Amankan retry dengan kunci idempoten

Retry mengasumsikan operasi aman untuk diulang. Untuk operasi yang mengubah state, asumsi ini sering salah.

Contoh masalah:

  1. Agen mengirim POST /charge.
  2. Server berhasil memproses pembayaran.
  3. Respons timeout sebelum diterima agen.
  4. Agen menganggap request gagal.
  5. Agen melakukan retry.
  6. Pelanggan ditagih dua kali.

Solusinya adalah kunci idempoten.

Klien membuat satu ID unik untuk setiap tindakan logis, lalu mengirimkannya dalam header Idempotency-Key:

import uuid
import httpx

idempotency_key = str(uuid.uuid4())

response = httpx.post(
    "https://api.example.com/charge",
    headers={
        "Idempotency-Key": idempotency_key,
    },
    json={
        "customer_id": "cus_123",
        "amount": 50000,
    },
)
Enter fullscreen mode Exit fullscreen mode

Kunci tersebut harus dibuat sebelum loop retry, bukan di dalamnya:

# Benar: satu kunci untuk semua retry
idempotency_key = str(uuid.uuid4())

for attempt in range(3):
    send_charge_request(idempotency_key)
Enter fullscreen mode Exit fullscreen mode
# Salah: setiap retry menjadi tindakan baru
for attempt in range(3):
    idempotency_key = str(uuid.uuid4())
    send_charge_request(idempotency_key)
Enter fullscreen mode Exit fullscreen mode

Di sisi server, request dengan kunci yang sama harus mengembalikan hasil operasi pertama alih-alih menjalankan aksi lagi.

Gunakan kunci idempoten untuk setiap aksi yang membuat atau mengubah state, misalnya:

  • pembayaran
  • pesanan
  • email terkirim
  • pembuatan record
  • pembaruan data
  • pengiriman pesan

Panggilan read-only seperti GET /users/123 umumnya aman di-retry tanpa kunci idempoten. Panduan kunci idempoten membahas implementasi klien dan server secara lebih lengkap.

Bertahan dari rate limit dan loop RateLimitError

Rate limit memerlukan penanganan khusus karena server biasanya memberi instruksi kapan Anda boleh mencoba lagi.

Respons melebihi batas laju biasanya berupa 429 dengan header berikut:

Retry-After: 30
Enter fullscreen mode Exit fullscreen mode

Artinya: tunggu minimal 30 detik sebelum melakukan retry.

Jangan mengabaikan header itu. Jika server meminta Anda menunggu 30 detik tetapi kode Anda mencoba lagi dalam 2 detik, Anda akan mendapat 429 lagi dan membangun loop RateLimitError.

Implementasi dasar:

import random
import time

def get_retry_delay(response, attempt):
    retry_after = response.headers.get("Retry-After")

    if retry_after:
        try:
            return float(retry_after)
        except ValueError:
            pass

    base_delay = min(2 ** attempt, 30)
    return random.uniform(0, base_delay)
Enter fullscreen mode Exit fullscreen mode

Urutan prioritasnya:

  1. Jika ada Retry-After, hormati nilainya.
  2. Jika header tidak ada atau tidak valid, gunakan exponential backoff dengan jitter.
  3. Tetap batasi jumlah retry.
  4. Setelah batas tercapai, kembalikan error yang jelas.

Anthropic SDK sudah menghormati Retry-After untuk request SDK itu sendiri. Namun, Anda tetap perlu menerapkan aturan yang sama untuk API lain yang digunakan oleh tool agen Anda. Masalah serupa juga dibahas di thread Anthropic SDK ini.

Selain recovery, lakukan pencegahan secara proaktif. Jika provider memberi batas request per menit, gunakan rate limiter seperti token bucket agar agen tidak terus-menerus menabrak batas tersebut.

Cara menguji jalur pemulihan

Pola recovery hanya berguna jika sudah terbukti berjalan. Jangan menunggu API produksi gagal untuk mengetahui apakah retry dan timeout Anda benar.

Gunakan alur test berikut.

1. Mock dependensi

Buat mock untuk setiap API yang dipanggil tool agen. Dengan mock, Anda mengontrol:

  • status code
  • header
  • response body
  • delay
  • urutan respons

Tidak ada pembayaran, email, atau perubahan data nyata yang terjadi selama pengujian.

2. Program urutan respons

Contoh satu skenario recovery penuh:

Request 1 → 429 + Retry-After: 2
Request 2 → 500
Request 3 → 200 + body valid
Enter fullscreen mode Exit fullscreen mode

Satu endpoint dapat mensimulasikan kegagalan sementara, rate limit, lalu pemulihan.

3. Arahkan agen ke URL mock

Konfigurasikan base URL tool agen agar menggunakan mock, bukan layanan produksi:

API_BASE_URL = "https://mock.example.test"
Enter fullscreen mode Exit fullscreen mode

Jalankan skenario agen dari awal hingga akhir.

4. Verifikasi perilaku, bukan hanya hasil akhir

Pastikan test memeriksa hal-hal berikut:

  • Agen menunggu minimal 2 detik setelah menerima 429.
  • Agen menghormati Retry-After.
  • Agen melakukan retry setelah 500.
  • Agen berhasil pada respons ketiga.
  • Agen tidak melebihi batas retry.
  • Agen mengembalikan error yang jelas jika semua retry gagal.

Tambahkan skenario khusus untuk tiap pola:

Skenario Verifikasi
Semua request gagal Agen berhenti pada batas retry.
Respons menggantung Timeout aktif dan agen tidak macet.
Kegagalan beruntun Circuit breaker berubah ke open.
Respons aksi tulis hilang Retry mengirim Idempotency-Key yang sama.

Untuk test idempoten, buat mock menerima request yang mengubah data, proses request pertama, lalu putuskan respons. Ketika agen melakukan retry, periksa bahwa kedua request membawa header yang sama:

Idempotency-Key: 5a6f7c2e-...
Enter fullscreen mode Exit fullscreen mode

Jika retry menggunakan kunci baru, Anda telah menemukan potensi pengiriman ganda sebelum pelanggan menemukannya. Untuk pendekatan yang lebih menyeluruh, lihat panduan menguji agen yang memanggil API Anda.

Daftar periksa pemulihan error

Sebelum mendorong agen ke produksi, pastikan semua poin ini terpenuhi:

  • [ ] Setiap panggilan keluar memiliki connection timeout dan read timeout.
  • [ ] Eksekusi agen memiliki anggaran waktu total.
  • [ ] Retry menggunakan exponential backoff dengan jitter.
  • [ ] Jumlah retry dan delay maksimum dibatasi.
  • [ ] Respons 429 membaca dan menghormati Retry-After.
  • [ ] Circuit breaker dipasang per dependensi.
  • [ ] Request yang mengubah state menggunakan kunci idempoten stabil.
  • [ ] Jalur gagal mengembalikan error jelas, bukan loop atau penantian tanpa batas.
  • [ ] Semua jalur recovery diuji terhadap mock yang sengaja gagal.

Jika semua poin ini tercentang, agen Anda pulih karena desain, bukan karena keberuntungan.

Di mana Apidog sesuai, dan di mana tidak

Peran alat harus jelas. Apidog bukan framework agen, model host, atau runtime orkestrasi. Apidog tidak membangun atau menjalankan agen Anda, dan tidak menilai output model.

Namun, Apidog relevan pada lapisan API yang dipanggil agen Anda—lapisan tempat kegagalan, retry, dan validasi request terjadi.

Gunakan Apidog untuk:

  1. Mem-mock dependensi yang dipanggil agen.
  2. Membuat respons gagal seperti 429, 500, timeout, atau body salah format.
  3. Menyusun urutan respons untuk menguji recovery.
  4. Memvalidasi request yang diterima mock.
  5. Memastikan header seperti Idempotency-Key ada dan stabil.
  6. Memastikan jumlah request tidak melebihi batas yang Anda tetapkan.

Itulah kecocokan yang tepat: mock kegagalan yang harus ditangani agen, lalu periksa request yang dikirim agen saat mencoba pulih.

Pertanyaan yang sering diajukan

Bukankah Anthropic SDK sudah menangani retry?

Untuk panggilan SDK-nya sendiri, ya. Anthropic SDK melakukan retry untuk error tertentu dengan exponential backoff dan menghormati Retry-After. Anda dapat mengatur batasnya melalui max_retries.

Namun, mekanisme itu tidak mencakup API lain yang dipanggil tool agen Anda. Untuk API pembayaran, CRM, pencarian, email, atau sistem internal, Anda perlu menerapkan pola yang sama sendiri.

Kapan saya memerlukan kunci idempoten?

Gunakan untuk setiap operasi yang membuat atau mengubah state: pembayaran, pesanan, pesan, email, atau record baru. Buat satu kunci per tindakan logis dan gunakan kunci tersebut untuk seluruh retry.

Apakah semua error perlu di-retry?

Tidak. Jangan retry error yang jelas bersifat permanen, seperti validasi request yang gagal atau autentikasi yang tidak valid. Retry terutama cocok untuk error sementara seperti timeout, kegagalan koneksi, 429, dan sebagian 5xx.

Latih satu kegagalan minggu ini

Anda tidak perlu membangun semua pola sekaligus. Mulailah dari risiko terbesar di sistem Anda—biasanya loop rate limit atau retry pada request yang tidak idempoten.

Buat satu mock yang mengembalikan 429, tambahkan Retry-After, lalu lihat apakah agen menunggu dengan benar. Atau buat mock yang memproses request pembayaran tetapi menjatuhkan responsnya, lalu pastikan retry memakai satu Idempotency-Key yang sama.

Ketika Anda melihat agen melakukan backoff yang benar dan mencegah aksi ganda, Anda punya alasan yang lebih kuat untuk mempercayainya daripada sekadar demo yang mulus.

Top comments (0)