Idempotensi Agen AI: Mencegah Penagihan Ganda Akibat Percobaan Ulang

Percobaan ulang agen membuat tagihan duplikat dan pesanan duplikat. Pelajari cara kerja kunci idempoten, cara menghasilkannya di setiap langkah tugas, dan cara menguji bahwa panggilan kedua tidak mengubah apa pun.

Ashley Innocent

Ashley Innocent

26 August 2026

Idempotensi Agen AI: Mencegah Penagihan Ganda Akibat Percobaan Ulang

Apidog untuk Perusahaan

Penerapan On-Premises

SSO & RBAC

Sesuai SOC 2

Jelajahi Apidog Enterprise

Agen Anda memanggil titik akhir pembayaran. Permintaan berhasil, tagihan masuk, dan kemudian respons kehabisan waktu dalam perjalanan kembali. Agen tidak pernah melihat 200, jadi agen melakukan apa yang Anda perintahkan saat terjadi kegagalan: agen mencoba lagi. Sekarang pelanggan telah ditagih dua kali, dan tidak ada dalam log Anda yang terlihat seperti kesalahan.

Ini adalah mode kegagalan yang memisahkan agen dari klien API biasa. Manusia yang mengklik "Bayar" sekali melihat roda berputar dan menunggu. Agen dalam loop percobaan ulang melihat keheningan dan mencoba lagi, kadang-kadang tiga atau empat kali berturut-turut, lebih cepat dari yang bisa dilakukan siapa pun. Setiap kebijakan percobaan ulang yang Anda tambahkan untuk membuat agen lebih andal juga membuat penulisan ganda lebih mungkin terjadi. Solusinya adalah idempoten: membuat permintaan berulang menghasilkan hasil yang sama dengan satu permintaan.

Panduan ini mencakup apa arti idempoten pada tingkat HTTP, cara menghasilkan kunci yang benar-benar dapat digunakan kembali oleh agen, apa yang harus disimpan oleh server untuk menghormatinya, dan cara menguji semuanya sebelum pelanggan sungguhan ditagih dua kali. Jika Anda belum membaca pilar kami tentang mengapa agen AI rusak dalam produksi, penulisan ganda adalah mode kegagalan yang tersembunyi di balik sebagian besar laporan "agen melakukannya dua kali".

Apidog muncul di bagian pengujian ini. Idempoten adalah sesuatu yang Anda bangun ke dalam API dan lapisan alat agen Anda. Yang Anda butuhkan setelahnya adalah cara untuk mengirim permintaan yang sama dua kali dan membuktikan bahwa yang kedua tidak mengubah apa pun, yang merupakan tes yang dapat Anda simpan dan jalankan di CI.

Mengapa agen lebih sering melanggar idempoten daripada manusia

Tiga hal tentang lalu lintas agen membuat duplikasi umum.

Yang pertama adalah volume percobaan ulang. Kerangka kerja agen mencoba lagi secara agresif secara default karena kegagalan jaringan sementara adalah penyebab paling umum dari kegagalan. Panduan kami tentang pemulihan kesalahan agen membahas backoff dan pemutus sirkuit, dan setiap teknik di dalamnya meningkatkan jumlah kali suatu permintaan tertentu mencapai server Anda.

Yang kedua adalah ambiguitas waktu tunggu. Ketika sebuah permintaan kehabisan waktu, klien tidak mengetahui apakah server memprosesnya. Sebuah 504 dari proxy bisa berarti penulisan tidak pernah terjadi atau bahwa penulisan terjadi dan respons hilang. Manusia biasanya memeriksa sebelum mencoba lagi. Agen biasanya tidak, karena "periksa dulu" adalah panggilan alat tambahan yang harus diputuskan oleh model untuk dilakukan.

Yang ketiga adalah loop. Agen yang gagal dalam suatu tugas dapat memulai kembali seluruh tugas, bukan hanya langkah yang gagal. Jika langkah pertama membuat pesanan dan langkah keempat gagal, restart yang naif akan membuat pesanan kedua. Di sinilah agen multi-langkah sangat berbeda dari skrip: batas percobaan ulang tidak jelas, dan model, bukan kode Anda, yang memutuskan di mana ia dimulai.

Gabungkan semua itu dan Anda akan mendapatkan gambaran masalahnya. Bukan berarti agen mengirimkan permintaan yang buruk. Mereka mengirimkan permintaan yang benar lebih dari sekali.

Apa yang sebenarnya dijamin oleh idempoten

Sebuah operasi bersifat idempoten ketika melakukannya berkali-kali memiliki efek yang sama dengan melakukannya sekali. GET, PUT, dan DELETE didefinisikan sebagai idempoten dalam RFC 9110, spesifikasi semantik HTTP. POST tidak, itulah mengapa operasi berbahaya cenderung berupa panggilan POST: membuat pesanan, mengirim pesan, memulai transfer.

Dua klarifikasi menghemat banyak kebingungan.

Idempoten tidak sama dengan aman. Metode yang aman tidak mengubah apa pun. DELETE bersifat idempoten tetapi destruktif: memanggilnya lima kali membuat sumber daya terhapus, sama seperti memanggilnya sekali, tetapi sumber daya tetap hilang. Agen membutuhkan kedua properti yang diurutkan secara terpisah, yang merupakan argumen yang dibuat oleh postingan kami tentang kunci API hak istimewa terkecil untuk agen dari sisi kredensial.

Idempoten juga tidak sama dengan respons yang identik. Panggilan kedua dapat mengembalikan hasil yang disimpan dari panggilan pertama, dan dapat mengembalikan kode status yang berbeda. Yang tidak boleh berubah adalah status di server. Satu tagihan. Satu pesanan. Satu email.

Kunci idempoten: pola yang membuat POST aman

Perbaikan standar adalah kunci yang dihasilkan klien yang dikirim dengan permintaan. Server mencatat kunci tersebut bersama dengan hasilnya, dan permintaan selanjutnya yang membawa kunci yang sama mengembalikan hasil yang dicatat alih-alih melakukan pekerjaan lagi.

Stripe mempopulerkan header tersebut, dan dokumentasi idempoten Stripe masih merupakan deskripsi semantik yang paling jelas. Ada juga upaya IETF untuk membakukannya sebagai Idempotency-Key header field, yang layak dibaca sebelum Anda membuat nama header Anda sendiri.

Permintaan terlihat seperti ini:

POST /v1/payments HTTP/1.1
Host: api.yourservice.com
Authorization: Bearer sk_live_...
Idempotency-Key: 9f2b7c14-6d3a-4b18-9d55-1e2a7c0b4f31
Content-Type: application/json

{
  "amount": 4900,
  "currency": "usd",
  "customer_id": "cus_8812",
  "description": "Pro plan, August"
}

Kunci tersebut adalah UUID. Kunci tersebut tidak memiliki arti bagi server selain "ini adalah operasi logis yang sama." Server menyimpannya, bersama dengan sidik jari badan permintaan dan respons yang dihasilkannya.

Membuat kunci yang dapat digunakan kembali oleh agen

Di sinilah sebagian besar implementasi agen salah. Jika pembungkus alat menghasilkan UUID baru pada setiap panggilan, kunci berubah pada setiap percobaan ulang, dan idempoten tidak berfungsi. Kunci harus terikat pada operasi logis, bukan pada upaya HTTP.

Aturannya: buat kunci ketika agen memutuskan untuk melakukan tindakan, dan tahan untuk setiap percobaan ulang keputusan tersebut.

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). Percobaan ulang langkah yang sama menggunakan 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},
        )

Kunci deterministik juga berfungsi, dan itu bertahan dari restart proses, yang tidak dilakukan oleh kamus dalam memori:

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]

Peroleh kunci dari eksekusi tugas dan langkah, jangan pernah dari stempel waktu atau nilai acak yang dihasilkan ulang per percobaan. Jika agen memulai kembali seluruh tugas dan benar-benar bermaksud membuat tagihan baru, ID tugas berubah dan begitu pula kuncinya. Itulah perilaku yang Anda inginkan.

Apa yang harus dilakukan server

Menangani header dengan benar membutuhkan lebih dari sekadar pencarian. Implementasi yang berfungsi melakukan empat hal:

  1. Saat tiba, coba klaim kunci. Masukkan ke dalam tabel dengan batasan unik sebelum melakukan pekerjaan apa pun. Jika penyisipan gagal, upaya lain memilikinya.
  2. Jika kunci ada dan sidik jari permintaan yang disimpan berbeda, tolak dengan 422. Kunci yang sama dengan badan yang berbeda berarti bug klien, dan mengembalikan hasil lama secara diam-diam akan menyembunyikannya.
  3. Jika kunci ada dan upaya pertama masih dalam proses, kembalikan 409 agar pemanggil mundur daripada berlomba.
  4. Ketika pekerjaan selesai, simpan kode status dan badan terhadap kunci, lalu kembalikan untuk setiap hit selanjutnya.
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 batas waktu kedaluwarsa. Dua puluh empat jam mencakup setiap jendela percobaan ulang yang realistis, dan menyimpan kunci selamanya mengubah tabel menjadi kewajiban. Stripe mengedaluwarsa kunci setelah 24 jam, yang merupakan default yang masuk akal untuk ditiru.

Menguji bahwa panggilan kedua tidak mengubah apa pun

Membangun idempoten adalah separuh pekerjaan. Membuktikan bahwa itu berlaku adalah separuh lainnya, dan itu adalah separuh yang dilewati, karena jalur sukses terlihat identik apakah fitur tersebut berfungsi atau tidak.

Tesnya mudah dijelaskan: kirim permintaan, tangkap hasilnya, kirim permintaan yang sama persis lagi, dan tegaskan bahwa server tidak melakukan pekerjaan dua kali. Bagian yang sulit adalah penegasan terakhir, karena responsnya saja tidak akan memberi tahu Anda. Dua tagihan yang berhasil keduanya mengembalikan 200.

Jadi, tegaskan pada status, bukan pada respons:

Di Apidog Anda dapat menghubungkan ini sebagai skenario pengujian: langkah pertama mengirim POST dengan Idempotency-Key tetap, langkah kedua mengulanginya, dan langkah ketiga mencantumkan sumber daya dan menegaskan jumlahnya. Simpan ID respons dari langkah pertama ke dalam variabel dan tegaskan langkah kedua mengembalikan nilai yang sama. Karena seluruh skenario disimpan, skenario ini berjalan di CI pada setiap perubahan ke jalur pembayaran, di mana regresi sebenarnya muncul. Teknik yang sama berlaku untuk pola yang lebih luas dalam panduan pengujian kontrak API kami.

Dua kasus lagi yang perlu dibahas, karena kasus ini menangkap bug nyata:

Mocking juga membantu di sini. Jika Anda masih membangun agen dan API pembayaran belum ada, mock dengan respons yang sadar idempoten sehingga logika percobaan ulang agen diuji sejak awal. Postingan kami tentang mengapa agen harus menggunakan mock API daripada produksi membuat kasus yang lebih luas untuk kebiasaan itu.

Ketika Anda tidak dapat menambahkan kunci

Terkadang API bukan milik Anda dan tidak memiliki dukungan idempoten. Anda masih memiliki pilihan, dalam urutan preferensi kasar.

Jadikan operasi secara alami idempoten. Sebuah PUT ke jalur sumber daya yang dipilih klien adalah idempoten berdasarkan konstruksi: PUT /orders/{client_order_id}. Jika Anda mengontrol desain API, lebih suka ini daripada POST ditambah header. Ini tidak memerlukan tabel tambahan.

Periksa sebelum menulis. Minta agen menanyakan catatan yang sudah ada dengan kunci alami yang sama sebelum membuatnya. Ini lebih lemah, karena perlombaan antara pemeriksaan dan penulisan masih dapat menghasilkan dua catatan, tetapi ini menghilangkan kasus waktu tunggu yang umum.

Hilangkan duplikasi di hilir. Jika penulisan adalah pesan atau peristiwa, tempatkan penghilangan duplikasi di konsumen. Lampirkan ID pesan yang stabil dan minta konsumen menghilangkan pengulangan. Ini adalah praktik standar dalam sistem berbasis peristiwa dan berpasangan dengan panduan di panduan webhook andal kami.

Gerbang tindakan. Untuk operasi yang benar-benar tidak dapat diubah dan tidak dapat dibuat idempoten, tempatkan manusia di depan. Itulah pola gerbang persetujuan dari postingan kami tentang pelindung agen AI, dan itu adalah jawaban yang tepat ketika biaya duplikasi cukup tinggi.

Ketahui eksekusi mana yang melakukan apa

Idempoten menghentikan duplikasi. Itu tidak memberi tahu Anda upaya mana yang membuat catatan, dan itulah pertanyaan yang Anda dapatkan setelah insiden.

Pertahankan identitas eksekusi yang terlampir pada pekerjaan. Ketika agen adalah layanan Anda sendiri, itu berarti ID tugas dan ID langkah dari derivasi kunci di atas, dicatat dengan setiap upaya. Ketika agen adalah runtime pengkodean yang menjalankan pekerjaan yang ditugaskan, platform biasanya menyimpannya untuk Anda: di Sharkly, setiap eksekusi terlampir pada Tugas asalnya, dengan status eksekusi dan hasil yang disimpan bersama dengan utas komentar, sehingga penulisan berulang melacak kembali ke eksekusi tertentu daripada ke percobaan ulang anonim.

Daftar periksa sebelum Anda merilis

Selesaikan daftar itu dan cerita tagihan ganda berhenti menjadi mungkin, yang berarti kebijakan percobaan ulang Anda bisa menjadi lebih agresif daripada kurang. Itulah hasil nyata: idempoten adalah apa yang memungkinkan Anda membuat agen tangguh tanpa membuatnya berbahaya.

Pertanyaan yang sering diajukan

Mengembangkan API dengan Apidog

Apidog adalah alat pengembangan API yang membantu Anda mengembangkan API dengan lebih mudah dan efisien.