Membuat Aksi Agen AI Idempoten agar Percobaan Ulang Tidak Menagih Dua Kali
Agen Anda memanggil endpoint pembayaran, server memprosesnya, tetapi respons habis waktu sebelum kembali ke agen. Karena tidak melihat 200, agen mencoba lagi—dan pelanggan ditagih dua kali. Ini bukan kegagalan permintaan; ini kegagalan menangani percobaan ulang untuk operasi yang mengubah status.
Agen lebih rentan terhadap masalah ini daripada klien API biasa. Mereka mencoba ulang secara agresif, tidak selalu dapat membedakan timeout dari kegagalan server, dan dapat mengulang seluruh alur kerja ketika satu langkah gagal. Hasilnya: permintaan yang valid dikirim lebih dari sekali.
Solusinya adalah idempotensi: mengirim operasi logis yang sama berulang kali harus menghasilkan efek server yang sama seperti satu kali eksekusi.
Untuk konteks kegagalan agen di lingkungan nyata, lihat mengapa agen AI rusak dalam produksi.
Apa yang dijamin oleh idempotensi
Sebuah operasi bersifat idempoten jika menjalankannya berkali-kali memiliki efek yang sama dengan menjalankannya sekali.
Menurut RFC 9110, GET, PUT, dan DELETE bersifat idempoten. POST tidak, sehingga operasi seperti membuat pesanan, mengirim pesan, dan memulai transfer perlu perlindungan tambahan.
Dua hal penting:
-
Idempoten tidak berarti aman.
DELETEidempoten, tetapi tetap menghapus sumber daya. - Idempoten tidak berarti respons harus berbeda. Respons kedua dapat mengembalikan hasil tersimpan dari respons pertama. Yang tidak boleh berubah adalah status di server: satu pembayaran, satu pesanan, atau satu email.
Agen membutuhkan izin minimal untuk alat berbahaya sekaligus idempotensi. Lihat juga panduan kunci API hak istimewa terkecil untuk agen.
Gunakan Idempotency-Key untuk membuat POST aman
Pola standar adalah klien mengirim kunci unik bersama permintaan. Server menyimpan kunci, sidik jari payload, dan hasilnya. Jika kunci yang sama datang lagi, server mengembalikan hasil yang sudah tersimpan tanpa melakukan pekerjaan kedua kali.
Pola ini dipopulerkan oleh dokumentasi idempotensi Stripe, dan sedang distandardkan melalui header Idempotency-Key.
POST /v1/payments HTTP/1.1
Host: api.yourservice.com
Authorization: Bearer [REDACTED]...
Idempotency-Key: 9f2b7c14-6d3a-4b18-9d55-1e2a7c0b4f31
Content-Type: application/json
{
"amount": 4900,
"currency": "usd",
"customer_id": "cus_8812",
"description": "Pro plan, August"
}
Kunci ini sebaiknya tidak membawa makna bisnis. UUID cukup baik, selama UUID tersebut mewakili operasi logis, bukan satu upaya HTTP.
Buat kunci yang dapat digunakan ulang oleh agen
Kesalahan umum adalah membuat UUID baru setiap kali pembungkus alat dipanggil. Saat retry terjadi, kunci berubah dan perlindungan idempotensi gagal.
Aturannya sederhana:
Buat kunci ketika agen memutuskan untuk melakukan tindakan, lalu gunakan kembali kunci tersebut untuk setiap retry tindakan itu.
import uuid
class PaymentTool:
def __init__(self, client):
self.client = client
self._keys = {}
def charge(self, task_id, step_id, amount, customer_id):
# Satu kunci per (tugas, langkah).
# Retry untuk langkah yang sama memakai kunci yang sama.
op = f"{task_id}:{step_id}"
if op not in self._keys:
self._keys[op] = str(uuid.uuid4())
return self.client.post(
"/v1/payments",
headers={"Idempotency-Key": self._keys[op]},
json={"amount": amount, "customer_id": customer_id},
)
Untuk bertahan dari restart proses, gunakan kunci deterministik yang berasal dari identitas tugas, langkah, dan payload:
import hashlib
def idempotency_key(task_id: str, step_id: str, payload: dict) -> str:
raw = f"{task_id}|{step_id}|{sorted(payload.items())}"
return hashlib.sha256(raw.encode()).hexdigest()[:32]
Jangan membuat kunci dari timestamp atau nilai acak per retry. Jika agen memulai tugas baru dan memang perlu membuat pembayaran baru, task_id berubah—begitu juga kuncinya.
Implementasi server yang benar
Server tidak cukup hanya mencari kunci di cache. Implementasi yang aman perlu menangani duplikasi berurutan dan konkuren.
- Klaim kunci sebelum bekerja. Masukkan kunci ke tabel dengan batasan unik sebelum melakukan efek samping.
-
Tolak payload yang berbeda. Jika kunci sama tetapi hash payload berbeda, kembalikan
422 Unprocessable Entity. -
Tangani pekerjaan yang masih berjalan. Jika permintaan pertama belum selesai, kembalikan
409 Conflictagar pemanggil melakukan backoff. - Simpan hasil akhir. Setelah pekerjaan selesai, simpan status dan respons untuk dikembalikan pada setiap replay berikutnya.
CREATE TABLE idempotency_records (
key TEXT PRIMARY KEY,
request_hash TEXT NOT NULL,
state TEXT NOT NULL, -- in_progress | completed
response_status INT,
response_body JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expires_at TIMESTAMPTZ NOT NULL
);
Tetapkan masa berlaku untuk record. Retensi 24 jam biasanya mencakup jendela retry yang realistis dan mencegah tabel tumbuh tanpa batas. Stripe juga menggunakan masa berlaku 24 jam sebagai acuan praktis.
Uji bahwa replay tidak mengubah apa pun
Respons 200 saja tidak membuktikan idempotensi. Dua pembayaran yang berhasil dapat sama-sama mengembalikan 200.
Uji efek server, bukan hanya respons:
- Respons kedua harus memiliki ID sumber daya yang sama dengan respons pertama.
-
GETberikutnya harus menunjukkan satu record, bukan dua. - Saldo, penghitung, atau efek samping lain hanya boleh berubah sekali.
Di Apidog, buat skenario pengujian dengan langkah berikut:
- Kirim
POSTdenganIdempotency-Keytetap. - Kirim ulang
POSTyang identik dengan kunci yang sama. - Simpan ID dari respons pertama sebagai variabel.
- Pastikan respons kedua mengembalikan ID yang sama.
- Panggil endpoint daftar atau detail untuk memastikan hanya satu sumber daya dibuat.
Simpan skenario tersebut dan jalankan di CI pada setiap perubahan jalur pembayaran. Pendekatan ini melengkapi panduan pengujian kontrak API.
Tambahkan dua kasus penting:
-
Kunci sama, payload berbeda: harus menghasilkan
422, bukan sukses diam-diam. - Duplikasi konkuren: kirim dua permintaan bersamaan dan pastikan hanya satu yang membuat efek samping. Ini memverifikasi bahwa batasan unik database benar-benar bekerja.
Saat API pembayaran belum tersedia, gunakan mock yang memahami semantik idempotensi. Dengan begitu, logika retry agen dapat diuji sebelum menyentuh produksi. Baca mengapa agen harus menggunakan mock API daripada produksi.
Jika API tidak mendukung kunci idempotensi
Jika API pihak ketiga tidak mendukung Idempotency-Key, gunakan strategi berikut berdasarkan preferensi.
1. Gunakan operasi yang idempoten secara alami
Jika Anda mengontrol desain API, gunakan ID yang ditentukan klien:
PUT /orders/{client_order_id}
PUT ke URI sumber daya yang sama idempoten secara desain dan tidak memerlukan tabel kunci tambahan.
2. Periksa sebelum menulis
Minta agen mencari record dengan kunci alami yang sama sebelum membuat record baru. Ini lebih lemah karena masih ada race condition antara pemeriksaan dan penulisan, tetapi mengurangi kasus duplikasi akibat timeout.
3. Hilangkan duplikasi di konsumen
Untuk pesan atau event, sertakan ID pesan yang stabil dan lakukan deduplikasi di sisi konsumen. Ini adalah pola umum dalam sistem berbasis event dan cocok dengan panduan webhook andal.
4. Tambahkan gerbang persetujuan
Untuk tindakan yang tidak dapat dibatalkan dan tidak dapat dibuat idempoten, minta persetujuan manusia sebelum eksekusi. Ini adalah pola penting dalam pelindung agen AI.
Simpan identitas eksekusi
Idempotensi menghentikan duplikasi, tetapi Anda tetap perlu tahu eksekusi mana yang membuat suatu record saat insiden terjadi.
Catat task_id, step_id, kunci idempotensi, status percobaan, dan ID sumber daya pada setiap upaya. Pada runtime agen seperti Sharkly, eksekusi dapat ditautkan ke tugas asal dan hasilnya, sehingga penulisan berulang dapat dilacak ke eksekusi tertentu, bukan ke retry anonim.
Daftar periksa rilis
- Setiap alat non-idempoten yang dapat dipanggil agen mengirim kunci idempotensi.
- Kunci berasal dari tugas dan langkah, bukan dari upaya HTTP.
- Server mengklaim kunci sebelum melakukan pekerjaan.
- Kunci sama dengan payload berbeda menghasilkan error.
- Duplikasi konkuren ditangani oleh batasan database.
- Pengujian tersimpan membuktikan panggilan kedua tidak mengubah status dan berjalan di CI.
- Record idempotensi memiliki masa berlaku dan dibersihkan secara terjadwal.
Pertanyaan umum
Apakah alat hanya-baca membutuhkan kunci idempotensi?
Tidak. GET sudah aman dan idempoten. Gunakan kunci untuk operasi yang membuat, menagih, mengirim, atau mengubah status.
Di mana kunci harus dibuat?
Di pembungkus alat, berdasarkan pengenal tugas dan langkah agen. Jangan meminta model membuat kunci karena model dapat menghasilkan nilai baru saat retry.
Status apa yang dikembalikan untuk replay?
Kembalikan status asli beserta body yang sama. Jika permintaan pertama menghasilkan 201, replay sebaiknya juga menghasilkan 201. Anda dapat menambahkan header seperti:
Idempotent-Replay: true
Header ini berguna untuk debugging dan aman bagi klien yang mengabaikannya.
Berapa lama kunci harus disimpan?
Umumnya 24 jam. Retensi lebih lama jarang membantu retry dan hanya memperbesar tabel. Setelah jendela ini berlalu, anggap permintaan sebagai operasi baru.
Apakah idempotensi menggantikan transaksi?
Tidak. Idempotensi mencegah permintaan duplikat membuat efek duplikat. Transaksi menjaga satu permintaan tetap atomik. Gunakan keduanya, dan bila memungkinkan, klaim kunci dalam transaksi yang sama dengan operasi bisnis.
Bagaimana mengujinya tanpa penyedia pembayaran sungguhan?
Arahkan agen ke mock yang mendukung Idempotency-Key, termasuk respons 422 saat payload berbeda. Gunakan Apidog untuk menyatukan mock, skenario retry, dan pengujian API dalam satu proyek.


Top comments (0)