DEV Community

Cover image for Idempotensi Agen AI: Mencegah Penagihan Ganda Akibat Percobaan Ulang
Walse
Walse

Posted on Originally published at apidog.com

Idempotensi Agen AI: Mencegah Penagihan Ganda Akibat Percobaan Ulang

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.

Coba Apidog hari ini

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.

Ilustrasi percobaan ulang agen dan operasi duplikat

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. DELETE idempoten, 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"
}
Enter fullscreen mode Exit fullscreen mode

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},
        )
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

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.

  1. Klaim kunci sebelum bekerja. Masukkan kunci ke tabel dengan batasan unik sebelum melakukan efek samping.
  2. Tolak payload yang berbeda. Jika kunci sama tetapi hash payload berbeda, kembalikan 422 Unprocessable Entity.
  3. Tangani pekerjaan yang masih berjalan. Jika permintaan pertama belum selesai, kembalikan 409 Conflict agar pemanggil melakukan backoff.
  4. 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
);
Enter fullscreen mode Exit fullscreen mode

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.
  • GET berikutnya harus menunjukkan satu record, bukan dua.
  • Saldo, penghitung, atau efek samping lain hanya boleh berubah sekali.

Contoh skenario pengujian idempotensi API

Di Apidog, buat skenario pengujian dengan langkah berikut:

  1. Kirim POST dengan Idempotency-Key tetap.
  2. Kirim ulang POST yang identik dengan kunci yang sama.
  3. Simpan ID dari respons pertama sebagai variabel.
  4. Pastikan respons kedua mengembalikan ID yang sama.
  5. 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}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)