Tech カテゴリのイメージ

AIエージェントの冪等性とは?再試行しても二重送信・二重登録を起こさない

AIエージェントがメール送信、予定作成、注文登録、返金申請などを実行すると、応答が途中で途切れることがある。タイムアウトした画面には失敗と見えても、外部サービスでは処理が完了しているかもしれない。そのまま再試行すると、同じ相手へメールを二度送り、予定を二件作る可能性がある。

冪等性とは、同じ意図の操作を複数回受け取っても、最終的な副作用を一回分に保つ性質である。LLMの回答文を同じにする仕組みではない。再試行やイベントの再配信が避けられない分散システムで、外部状態を安全に変更するための契約である。

タイムアウトは失敗を意味しない

利用者が「予定を作成して」と依頼し、エージェントが予定表APIを呼んだとする。予定表側は登録を完了したが、応答が返る直前に接続が切れた。呼び出し元には成功か失敗かを判断できない。

この状態で単純に同じ操作をやり直すと、二つ目の予定ができる。逆に、再試行しなければ、実際には失敗していた場合に依頼が未完了のまま残る。

重要なのは、通信の結果と業務処理の結果を分けることである。HTTP応答を受け取れなかったという事実だけでは、外部状態が変わっていないとは言えない。

同じ入力と同じ意図は一致しない

重複判定をリクエスト本文のハッシュだけで行うと、同じ内容を本当に二回実行したい場合を区別できない。同じ顧客へ同額の請求を二回作ることも、同じ形式の会議を毎週登録することもあり得る。

反対に、再試行時に時刻や自動生成文が少し変わると、同じ意図でも本文ハッシュが変わる。これでは重複を止められない。

そこで、呼び出し側が一つの業務上の意図へ一意のキーを割り当てる。AWSのBuilders’ Libraryは、呼び出し側が明示する一意のclient request identifierをAPI契約へ含める方法を推奨している。GoogleのAIP-155も、request IDを安全な再試行と重複排除のために使い、同じIDの要求へ冪等性を保証する設計を示している。

一つの利用者操作に一つのキーを発行する

冪等性キーは、API呼び出しのたびではなく、利用者が確定した一つの操作に対して作る。最初の呼び出しと再試行では同じキーを使い、利用者が新しく同じ操作を依頼したら新しいキーを作る。

例えば、エージェントが予定内容を提案し、利用者が承認した時点で操作IDを確定する。その後の通信再試行、ワーカーへの再配信、処理再開では同じ操作IDを引き継ぐ。

画面、アプリサーバー、キュー、Tool Calling、外部APIの各層で別々のキーを無関係に発行すると、一つの意図を追跡できなくなる。利用者操作を表す上位IDと、各下流処理を表す子IDの関係を記録する。

キーの有効範囲を狭くする

キーだけを全システムで一意とみなすのではなく、誰のどの操作かと組み合わせて扱う。一般には、利用者やテナント、道具名、操作種別、冪等性キーを重複判定の範囲に含める。

利用者Aと利用者Bが偶然同じキーを使っても、互いの処理結果を返してはいけない。同じキーを create_event と send_email で共有しても衝突しないようにする。

キーには推測しにくい十分な長さの乱数を使う。UUID v4などが一般的である。ただし、ランダムな値を使うだけでは冪等性にならない。再試行時に同じ値を保存して再利用することが必要である。

同じキーで引数が変わったら拒否する

同じ冪等性キーの二回目の要求で、送信先、金額、本文、対象IDが変わっていたら、最初の操作の再試行として扱ってはいけない。古い結果を返すと、呼び出し側は変更後の内容が実行されたと誤認する。

最初の要求を受けたとき、業務上重要な引数を正規化して保存する。二回目は保存済みの引数と比較し、一致しなければキー再利用のエラーを返す。

比較対象から、トレースID、送信時刻、再試行回数のような実行ごとに変わる値は除く。一方、利用者、対象、操作、金額、確定した内容は含める。どの値が同じ意図を定義するかは、道具ごとに決める。

キーの記録と副作用を分離しない

実装で難しいのは、キーを保存する処理と実際の変更の間で障害が起きる場合である。

先にキーだけ保存してから予定を作ると、その間の障害によって「処理済み記録はあるが予定はない」状態になる。逆に、予定を作ってからキーを保存すると、その間の障害後に再試行され、二件目が作られる。

同じデータベース内の更新なら、キーの予約、業務状態の変更、結果の保存を一つのトランザクションにまとめる。外部APIのように同じトランザクションへ入れられない場合は、外部API自身の冪等性キーを使うか、操作状態を保存して照合するワークフローが必要である。

「データベースに記録した後で外部へ送る」処理では、outboxパターンも使える。業務更新と送信予定を同じトランザクションで保存し、別のワーカーが送信予定を何度読んでも、同じ操作IDで外部へ送る。

処理中・成功・失敗を分けて保存する

冪等性ストアには、結果だけでなく処理状態を持たせる。

  • processing: 同じキーの最初の処理が進行中
  • succeeded: 副作用が完了し、返せる結果がある
  • failed: 再試行してよい失敗か、確定した失敗かを区別
  • unknown: 外部の結果を照会する必要がある

同じキーがprocessingの間に届いた場合、二つ目を並行実行しない。短く待つ、処理中を返す、後で状態を照会させるなど、API契約として決める。

成功済みなら、最初と意味が等しい結果を返す。単に「既に存在する」というエラーにすると、呼び出し側には自分の最初の要求で作られたのか、以前から存在したのかが分からない。

失敗結果をどこまで再利用するかを決める

すべての失敗を同じように保存すると、復旧後も古い一時障害を返し続ける場合がある。一方、失敗を一切保存しないと、入力不備や権限不足の要求が何度も高価な処理へ進む。

入力検証に失敗し、副作用が始まっていないなら、引数を直した新しい意図として新しいキーで送れるようにする。処理が始まった後の結果をどの期間保存するかは、外部サービスの契約と業務リスクに合わせる。

StripeのAPIは、同じ冪等性キーについて最初に実行が始まった要求のステータスコードと本文を保存し、後続要求へ同じ結果を返す。引数が一致しない場合は誤用として拒否し、検証段階で実行が始まらなかった要求は保存しない。この挙動をそのまま全用途へコピーするのではなく、どの時点から結果を固定するかを明文化する参考にできる。

保持期限を過ぎたキーは新しい要求になる

冪等性キーを永久保存すると容量が増え続けるため、多くの仕組みには保持期限がある。期限切れ後に同じキーが来たら、新しい要求として実行される可能性がある。

保持期限は、最大の再試行期間、キューの再配信期間、承認待ち、オフライン端末の再送、業務上の重複許容期間より長くする。例えば、ワーカーが三日間再試行するのにキーを一日で消せば、二日目以降の再送を新しい操作として扱ってしまう。

期限はAPI仕様として呼び出し側へ示す。長時間処理では、冪等性記録の期限と処理状態の保存期限を混同しない。

HTTPメソッドの性質だけに頼らない

RFC 9110では、同じ要求を複数回送っても意図したサーバー上の効果が一回と同じなら、そのHTTPメソッドは冪等である。標準ではPUT、DELETE、安全なメソッドが冪等とされる。

しかし、DELETEを二回送れるからといって、削除通知メール、請求、監査イベントまで自動で一回になるとは限らない。アプリケーション内部の副作用も冪等にする必要がある。

POSTも、冪等性キーとサーバー側の重複排除を組み合わせれば安全に再試行できる場合がある。反対に、GETのURLへ削除処理を隠すような設計は、安全なメソッドの意味を壊す。

LLMの生成と外部の副作用を分ける

同じプロンプトを再実行しても、LLMの出力は同一になるとは限らない。温度を下げても、モデルやシステムの変更で文章が変わる可能性がある。

そのため、「メール文を生成する」と「メールを送信する」を別の段階にする。承認前なら文章を再生成できるが、承認後の送信操作には確定した宛先と本文、操作IDを結び付ける。送信再試行時に本文を再生成しない。

Tool Callingでも、モデルが同じ道具をもう一度選ぶかどうかを重複防止に使わない。道具のサーバー側が冪等性キーを検証し、実行済み結果を返す。モデルの会話履歴から「たぶん送った」と推測させるのは認可にも整合性にも使えない。

まったく一度だけを安易に断言しない

分散した複数のシステムで、あらゆる障害に対して副作用を厳密に一回だけ実行する保証は難しい。キューは少なくとも一回配送し、利用側で重複排除する構成が一般的である。

APIが保証するのは、特定の利用者、操作、保持期間、失敗条件の範囲での重複防止である。外部サービスが冪等性を提供しない場合、結果照会や重複を修正する補償処理が必要になる。

「exactly once」と書く代わりに、どのキーを何時間保持し、同じキーと引数へ何を返し、結果不明時にどう照合するかを示す方が正確である。

障害の位置を変えて試験する

冪等性は正常系だけでは確認できない。副作用の前後で接続を切り、同じキーを再送する試験が必要である。

  • キー保存前に失敗する
  • キー保存後、副作用の前に失敗する
  • 副作用の完了後、結果保存前に失敗する
  • 結果保存後、応答送信前に失敗する
  • 同じキーを同時に二件送る
  • 同じキーで引数を変える
  • 保持期限の直前と直後に再送する
  • 別利用者が同じキーを使う

各試験で、副作用が一件だけであること、二回目へ意味が等しい結果が返ること、監査ログから最初の操作と再試行を追えることを確認する。

まとめ

AIエージェントの冪等性は、通信失敗や再配信が起きても、同じ業務上の意図による副作用を一回分に保つ仕組みである。タイムアウトを失敗と決めつけず、一つの利用者操作に一つのキーを割り当て、再試行で使い回す。

サーバーはキーの範囲、重要な引数、処理状態、結果、保持期限を管理する。同じキーで引数が変わったら拒否し、成功済みなら意味が等しい結果を返す。生成と送信を分け、道具の実行境界で重複を止めることで、再試行を二重送信や二重登録へ変えずに済む。

参考資料

生成AI・機械学習講座の一覧へ