DEV Community

Cover image for Yapay Zeka Ajanı İdempotansı: Tekrar Denemelerin Çift Ücretlendirmesini Engelleme
Tobias Hoffmann
Tobias Hoffmann

Posted on Originally published at apidog.com

Yapay Zeka Ajanı İdempotansı: Tekrar Denemelerin Çift Ücretlendirmesini Engelleme

Bir ajan ödeme uç noktasını çağırdı. İstek başarıyla iletildi, ödeme gerçekleşti; ancak yanıt dönerken zaman aşımına uğradı. Ajan hiçbir zaman 200 görmedi ve başarısızlıkta yeniden denemesi gerektiği için isteği tekrar gönderdi. Sonuç: müşteri iki kez ücretlendirildi, günlüklerde ise belirgin bir hata yok.

Apidog'u bugün deneyin

Bu, ajanları sıradan API istemcilerinden ayıran hata modudur. İnsan, “Öde” düğmesine bir kez tıklayıp dönen çarkı bekler. Yeniden deneme döngüsündeki ajan ise sessizliği hata olarak yorumlayıp aynı isteği arka arkaya üç veya dört kez gönderebilir.

Çözüm idempotentliktir: Aynı mantıksal isteğin tekrarlanması, tek bir isteğin sonucuyla aynı sunucu durumunu üretmelidir.

Bu kılavuzda şunları ele alacağız:

  • HTTP düzeyinde idempotentliğin anlamı
  • Ajanın yeniden kullanabileceği anahtarların oluşturulması
  • Sunucunun bu anahtarları güvenli biçimde işlemesi
  • Aynı isteğin iki kez gönderilmesini CI içinde test etme
  • Idempotentlik desteği olmayan API'lerde kullanılabilecek alternatifler

Üretimde yapay zeka ajanlarının neden bozulduğuna dair ana makalede de yinelenen yazma işlemlerinin, “ajan aynı işlemi iki kez yaptı” raporlarının başlıca nedenlerinden biri olduğu açıklanıyor.

Ajanlar neden insanlardan daha sık idempotentliği bozuyor?

Ajan trafiğinde yinelenen istekleri yaygınlaştıran üç özellik vardır.

1. Agresif yeniden denemeler

Ajan çerçeveleri, geçici ağ hatalarını gidermek için varsayılan olarak agresif biçimde yeniden dener. Ajan hata kurtarma kılavuzunda ele alınan geri çekilme ve devre kesici gibi teknikler dayanıklılığı artırır; ancak belirli bir isteğin sunucuya ulaşma sayısını da artırabilir.

2. Zaman aşımının belirsizliği

Bir istek zaman aşımına uğradığında istemci, sunucunun isteği işleyip işlemediğini bilemez. Bir proxy'den gelen 504 şu iki durumdan herhangi biri anlamına gelebilir:

  • Yazma işlemi hiç gerçekleşmedi.
  • Yazma gerçekleşti, ancak yanıt kayboldu.

İnsanlar çoğu zaman yeniden denemeden önce durumu kontrol eder. Ajanlar ise bunu genellikle yapmaz; çünkü “önce kontrol et” ek bir araç çağrısı ve model kararı gerektirir.

3. Tüm görevin yeniden başlatılması

Başarısız olan bir ajan yalnızca son adımı değil, tüm görevi yeniden başlatabilir. Birinci adım sipariş oluşturuyor ve dördüncü adım başarısız oluyorsa, basit bir görev yeniden başlatma ikinci siparişi oluşturur.

Sorun ajanların kötü istekler göndermesi değildir. Sorun, doğru istekleri birden fazla kez göndermeleridir.

Idempotentlik neyi garanti eder?

Bir işlem, birden çok kez çalıştırıldığında tek kez çalıştırılmasıyla aynı etkiyi yaratıyorsa idempotenttir. RFC 9110, GET, PUT ve DELETE yöntemlerini idempotent olarak tanımlar. POST ise varsayılan olarak idempotent değildir; sipariş oluşturma, mesaj gönderme ve transfer başlatma gibi işlemler genellikle POST kullanır.

Idempotentlik güvenli olmak demek değildir

Güvenli bir HTTP yöntemi sunucu durumunu değiştirmez. DELETE ise idempotent olmasına rağmen yıkıcıdır: Beş kez çağrıldığında kaynak, bir kez çağrıldığındaki gibi silinmiş kalır.

Ajanların idempotentlik ve güvenlik özelliklerini ayrı ayrı ele alması gerekir. Kimlik bilgileri için ajanlarda en az ayrıcalıklı API anahtarları yaklaşımı da bu ayrımı temel alır.

Aynı yanıtı döndürmek zorunlu değildir

İkinci çağrı, ilk çağrının depolanmış sonucunu döndürebilir ve farklı bir durum kodu kullanabilir. Değişmemesi gereken şey sunucu durumudur:

  • Tek bir ödeme
  • Tek bir sipariş
  • Tek bir e-posta

Idempotency anahtarları: POST isteklerini güvenli hale getirme

Standart desen, istemcinin istekle birlikte gönderdiği bir anahtardır. Sunucu bu anahtarı istek parmak izi ve yanıtla birlikte kaydeder. Aynı anahtarı taşıyan sonraki isteklerde işlem tekrar yapılmaz; kayıtlı sonuç döndürülür.

Stripe'ın idempotentlik belgeleri bu semantiğin açık bir açıklamasını sunar. Ayrıca Idempotency-Key başlık alanını standartlaştırmaya yönelik bir IETF çalışması da bulunur.

Örnek istek:

POST /v1/payments HTTP/1.1
Host: api.yourservice.com
[REDACTED CREDENTIAL] [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

Anahtar genellikle UUID'dir. Sunucu açısından anlamı, “bu aynı mantıksal işlemdir” bilgisidir. Anahtar, istek gövdesinin parmak izi ve üretilen yanıtla birlikte saklanmalıdır.

Ajanın yeniden kullanabileceği bir anahtar oluşturma

En yaygın hata, araç sarmalayıcısının her HTTP çağrısında yeni bir UUID üretmesidir. Böylece her yeniden deneme yeni bir anahtar taşır ve idempotentlik etkisiz hale gelir.

Anahtar, HTTP denemesine değil, mantıksal işleme bağlı olmalıdır:

Ajan bir eyleme karar verdiğinde anahtarı oluşturun ve bu kararın tüm yeniden denemelerinde aynı anahtarı kullanın.

import uuid

class PaymentTool:
    def __init__(self, client):
        self.client = client
        self._keys = {}

    def charge(self, task_id, step_id, amount, customer_id):
        # Her (görev, adım) için bir anahtar.
        # Aynı adımın yeniden denemeleri bu anahtarı kullanır.
        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

Deterministik bir anahtar da kullanılabilir. Bu yaklaşım, bellekteki sözlükten farklı olarak işlem yeniden başlatmalarından sağ çıkar:

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

Anahtarı görev ve adımdan türetin. Şunları kullanmayın:

  • Zaman damgası
  • Her denemede yeniden oluşturulan rastgele değer
  • Modelin kendi ürettiği, kalıcı olmayan değer

Ajan tüm görevi yeniden başlatıp gerçekten yeni bir ödeme yapmak isterse görev kimliği değişir ve anahtar da değişir. İstenen davranış budur.

Sunucunun yapması gerekenler

Idempotency başlığını işlemek yalnızca bir arama yapmaktan ibaret değildir. Sağlam bir uygulama şu adımları izler:

  1. Anahtarı, herhangi bir iş yapmadan önce benzersiz bir kısıtlamayla kaydetmeye çalışın. Ekleme başarısız olursa başka bir istek anahtarı almıştır.
  2. Anahtar mevcutsa ve istek parmak izi farklıysa 422 döndürün. Aynı anahtarın farklı gövdelerle kullanılması istemci hatasıdır.
  3. Anahtar mevcutsa ve ilk deneme hâlâ çalışıyorsa yarışmayı önlemek için 409 döndürün.
  4. İş tamamlandığında durum kodunu ve gövdeyi anahtara karşı kaydedin. Sonraki isteklerde bu sonucu döndürün.

Örnek tablo:

CREATE TABLE idempotency_records (
  key             TEXT PRIMARY KEY,
  request_hash    TEXT NOT NULL,
  state           TEXT NOT NULL,      -- devam ediyor | tamamlandı
  response_status INT,
  response_body   JSONB,
  created_at      TIMESTAMPTZ NOT NULL DEFAULT now(),
  expires_at      TIMESTAMPTZ NOT NULL
);
Enter fullscreen mode Exit fullscreen mode

Anahtarlar için son kullanma süresi belirleyin. Yirmi dört saat çoğu gerçekçi yeniden deneme penceresini kapsar. Anahtarları sonsuza kadar saklamak tabloyu gereksiz yere büyütür; Stripe da anahtarları 24 saat sonra sona erdirir.

İkinci çağrının hiçbir şeyi değiştirmediğini test etme

Idempotentliği uygulamak işin yalnızca yarısıdır. Özelliğin çalışıp çalışmadığı, mutlu yol yanıtlarından anlaşılmayabilir: İki başarılı ödeme de 200 döndürebilir.

Test senaryosu:

  1. Sabit bir Idempotency-Key ile isteği gönderin.
  2. Yanıtı ve kaynak kimliğini kaydedin.
  3. Aynı isteği aynı anahtarla tekrar gönderin.
  4. İkinci yanıtın ilk kaynak kimliğini döndürdüğünü doğrulayın.
  5. Kaynağı listeleyin ve tek kayıt olduğunu doğrulayın.
  6. Sayaç veya bakiyenin yalnızca bir kez değiştiğini kontrol edin.

Idempotent API isteklerini test etme

Apidog içinde bunu bir test senaryosu olarak düzenleyebilirsiniz:

  • Birinci adım sabit bir Idempotency-Key ile POST gönderir.
  • İkinci adım aynı isteği tekrarlar.
  • Üçüncü adım kaynağı listeler ve kayıt sayısını doğrular.
  • Birinci adımın kaynak kimliği değişkene kaydedilir.
  • İkinci adım aynı kimliği döndürmelidir.

Senaryoyu kaydedip CI'da çalıştırdığınızda, ödeme akışındaki değişiklikler regresyona dönüşmeden yakalanır. Bu yaklaşım, API sözleşme testi kılavuzundaki daha genel test kalıplarıyla da uyumludur.

Aşağıdaki iki durumu ayrıca test edin:

Aynı anahtar, farklı gövde

Sessiz bir başarı yerine 422 bekleyin. Sunucu, eski yanıtı döndürerek istemci hatasını gizlememelidir.

Eşzamanlı kopyalar

İki isteği aynı anda gönderin ve tam olarak birinin kazandığını doğrulayın. Bu test, sıralı testlerin yakalayamayacağı eksik benzersiz kısıtlamaları ortaya çıkarır.

Mock kullanmak de yararlıdır. Ödeme API'si henüz hazır değilse, ajanları üretim yerine sahte API'lerle test etme yaklaşımıyla 422 yük uyuşmazlığı dahil olmak üzere idempotentlik semantiğini uygulayan bir API oluşturabilirsiniz.

Idempotency anahtarı ekleyemediğinizde

Her API size ait değildir ve bazıları idempotentlik desteği sunmaz. Bu durumda şu seçenekleri değerlendirin:

  • İşlemi doğal olarak idempotent hale getirin. İstemcinin seçtiği kaynak yoluna yapılan PUT yapısı gereği idempotenttir:
  PUT /orders/{client_order_id}
Enter fullscreen mode Exit fullscreen mode

API tasarımını kontrol ediyorsanız, POST ve özel başlık yerine bu yaklaşımı tercih edin.

  • Yazmadan önce kontrol edin. Ajanın kayıt oluşturmadan önce aynı doğal anahtara sahip bir kaydı sorgulamasını sağlayın. Bu yöntem kontrol ve yazma arasındaki yarış nedeniyle daha zayıftır; yine de yaygın zaman aşımı senaryosunu azaltır.

  • Yinelenenleri aşağı akışta kaldırın. Mesaj veya olay yazıyorsanız sabit bir mesaj kimliği ekleyin ve tekrarları tüketicinin atmasını sağlayın. Bu, olay odaklı sistemlerde standart bir yaklaşımdır ve güvenilir webhook tasarımı rehberliğiyle uyumludur.

  • Eylemi kısıtlayın. Geri döndürülemez ve idempotent hale getirilemeyen işlemlerde insan onayı ekleyin. Yapay zeka ajanı koruma önlemlerindeki onay kapısı deseni, kopyanın maliyeti yüksek olduğunda doğru çözümdür.

Hangi çalışmanın ne yaptığını bilin

Idempotentlik yinelenenleri durdurur; ancak kaydı hangi denemenin oluşturduğunu tek başına açıklamaz. Bir olaydan sonra sorulacak soru genellikle şudur: “Bu işlemi hangi çalışma yaptı?”

Çalışma kimliğini işle ilişkilendirin. Ajan kendi hizmetinizse görev kimliğini ve adım kimliğini her denemede günlüğe kaydedin. Ajan, atanmış bir işi yürüten kodlama çalışma zamanıysa platform genellikle bunu sizin için saklar.

Örneğin Sharkly'de her çalışma geldiği göreve eklenir. Yürütme durumu ve sonucu yorum dizisinin yanında tutulur; böylece yinelenen bir yazma işlemi anonim bir yeniden deneme yerine belirli bir çalışmaya kadar izlenebilir.

Göndermeden önce kontrol listesi

  • Ajanın çağırdığı her idempotent olmayan araç idempotentlik anahtarı gerektirir.
  • Araç sarmalayıcısı anahtar olmadan isteği göndermeyi reddeder.
  • Anahtarlar denemeden değil, görev ve adımdan türetilir.
  • Sunucu anahtarı işi yapmadan önce kaydeder.
  • Farklı yükle kullanılan aynı anahtar, önbelleğe alınmış yanıt yerine hata döndürür.
  • Eşzamanlı kopyalar uygulama zamanlamasıyla değil, veritabanı kısıtlamasıyla ele alınır.
  • Kaydedilmiş test, ikinci çağrının sunucu durumunu değiştirmediğini kanıtlar ve CI'da çalışır.
  • Anahtarların sona erme süresi vardır ve tablo düzenli olarak temizlenir.

Bu yaklaşım uygulandığında iki kez ücretlendirme senaryosu engellenir. Böylece yeniden deneme politikanızı daha az agresif yapmak yerine daha güvenilir biçimde agresif tutabilirsiniz. Idempotentlik, bir ajanı tehlikeli hale getirmeden dayanıklı kılmanın temelidir.

Sıkça sorulan sorular

Salt okunur araçlar için idempotentlik anahtarlarına ihtiyacım var mı?

Hayır. GET istekleri zaten idempotent ve güvenlidir. Anahtarları durum oluşturan, ücretlendiren, mesaj gönderen veya başka biçimde değişiklik yapan çağrılar için kullanın.

Anahtar nerede oluşturulmalı?

Araç sarmalayıcısında oluşturulmalıdır. Anahtar, ajanın görev ve adım tanımlayıcılarına göre üretilmelidir. Modelin anahtar oluşturmasına izin vermek hatalıdır; model yeniden denemelerde farklı değerler üretebilir ve görevler arasında çakışma oluşturabilir.

Tekrarlanan istek hangi durum kodunu döndürmeli?

Orijinal çağrının kaydedilmiş durumunu döndürün. İlk çağrı 201 döndürüyorsa aynı gövdeyle tekrarlanan çağrı da 201 döndürebilir. Bazı API'ler yeniden oynatmayı belirtmek için Idempotent-Replay: true gibi bir başlık ekler. Bu, hata ayıklama için yararlıdır ve başlığı görmezden gelen istemciler için zararsızdır.

Anahtarlar ne kadar süre saklanmalı?

Yirmi dört saat, neredeyse her yeniden deneme penceresini kapsar. Daha uzun saklama nadiren faydalıdır ve tabloyu sınırsızca büyütür. İstemci bu süreden sonra yeniden denerse isteği yeni bir işlem olarak değerlendirin.

Idempotentlik işlemlerin yerini alır mı?

Hayır. Idempotency anahtarları yinelenen isteklerin yinelenen etkiler üretmesini önler. İşlemler ise tek bir isteği atomik tutar. Her ikisine de ihtiyacınız vardır. Veritabanınız izin veriyorsa anahtar kaydı ile iş aynı işlem içinde yazılmalıdır.

Gerçek ödeme sağlayıcısı olmadan nasıl test edebilirim?

Yük uyuşmazlığında 422 döndürmek dahil olmak üzere anahtar semantiğini uygulayan bir sahte API kullanın. Yapay zeka ajanlarını sahte API'lere karşı test etme kılavuzu kurulumu açıklar. Sahte API'yi ve yeniden deneme testlerini aynı projede tutmak için Apidog'u indirin.

Ajan API testleri

Top comments (0)