エラーハンドリングと再試行戦略 ─ エージェントの信頼性を高めるリトライ設計
結論から言うと、エラーは避けられない。ネットワーク障害・レート制限・タイムアウトは分散システムに必ず起きる「仕様」であり、エージェントはそれを前提に設計する必要がある。適切な再試行戦略を組み込むと、同じコードでも稼働率が大幅に変わる。
エラーを「一時的」と「永続的」に分ける
エラーを見たとき、最初に問うべきは「もう一度試せば成功するか?」だ。
一時的エラー(リトライして良い)
429 rate_limit_error— リクエストが多すぎる(少し待てば通る)500 api_error— サーバー内部の一時的な問題504 timeout_error— 処理が時間内に終わらなかった529 overloaded_error— APIが一時的に混雑している- ネットワーク接続エラー全般
永続的エラー(リトライしても無駄)
400 invalid_request_error— リクエストの形式が間違っている401 authentication_error— APIキーが無効403 permission_error— そのリソースへの権限がない
永続的エラーをリトライし続けると、エラーを増やして意味のない処理をするだけになる。分類が先決だ。
指数バックオフ ─ 待ち時間を指数的に伸ばす
一時的エラーが起きたとき、「即座に再試行」するのは逆効果なことが多い。例えばレート制限で弾かれた直後に高速リトライすると、さらに多くのリクエストを送ることになり状況が悪化する。
そこで使うのが 指数バックオフ(Exponential Backoff)だ。再試行のたびに待ち時間を指数的に増やす。
1回目失敗 → 1秒待つ
2回目失敗 → 2秒待つ
3回目失敗 → 4秒待つ
4回目失敗 → 8秒待つ ...
さらに各待ち時間に小さなランダム幅(ジッター)を加えると、多数のクライアントが同時にリトライしてサーバーを再び圧迫する「サンダリングハード」問題を軽減できるとされる。
AnthropicのSDKが自動でやってくれること
Anthropicの公式Python SDKは、デフォルトで一時的エラーに対して自動的に2回リトライする(短い指数バックオフ付き)。対象は接続エラー・408・409・429・500番台のエラーだ。
from anthropic import Anthropic
# デフォルトはmax_retries=2
client = Anthropic()
# リトライ回数を変える例
client = Anthropic(max_retries=0) # リトライ無効
client = Anthropic(max_retries=5) # 5回まで
# リクエスト単位での設定
client.with_options(max_retries=3).messages.create(...)
ただし自動リトライには限界もある。LLMの呼び出しは冪等(べきとう)でない場合が多く、途中まで実行されたツール呼び出しが重複すると問題が起きることがある。どこまで自動に任せ、どこからアプリ側で制御するかを意識するのが重要だ。
エージェントでのエラー設計パターン
エージェントが複数のツールを連続呼び出しするとき、エラーハンドリングをループの外に任せるか、ループ内で扱うかを設計する。
# 擬似コード
for step in agent_loop():
try:
result = call_tool(step)
except TransientError as e:
if retry_count < MAX_RETRIES:
wait_with_backoff(retry_count)
retry_count += 1
continue
else:
report_failure_to_agent(e) # モデルに伝えて別手段を探させる
except PermanentError as e:
abort_with_explanation(e)
失敗をモデルに伝えると、エージェントが別のアプローチを試みることができる。これは「リフレクション」(レッスン12)の応用でもある。
まとめ
- エラーは「一時的(リトライ可)」と「永続的(リトライ不可)」に分類する
- 一時的エラーには指数バックオフ(+ジッター)で再試行する
- AnthropicのPython SDKはデフォルトで自動リトライ2回(調整可能)
- エージェントループ内でのエラーはモデルに伝えて対処策を探させるのも有効
- 429・5xx は一時的、400・401・403 は永続的と大まかに覚えておくと判断が速い