第1章 前提知識 — 解答例
本文: 第1章 前提知識
解答
各呼び出しが独立に成功率98%なので、12回すべて成功する確率は
- 0.98¹² = 0.785 → 完遂率 78.5%(失敗率 21.5%)
再試行機構で個々の失敗確率を0.5%に下げると
- 0.995¹² = 0.942 → 完遂率 94.2%(失敗率 5.8%)
失敗率は 21.5% → 5.8% となり、約3.7分の1に減る。個々のツールの失敗確率を4分の1にしただけで、タスク全体の失敗はほぼそれに比例して減る。
なぜ再試行設計を強調するのか: 完遂率は「1 −(失敗率)」ではなく (1 − 失敗率)^ステップ数 で決まるため、ステップ数が増えるほど個々の失敗が指数的に効いてくる(1.1節の「5%失敗 × 10ステップ ≒ 4割失敗」も同じ計算である)。ここで効くのはプロンプトの巧拙でもモデルの賢さでもなく、配管の堅牢さである。1.4.2節の表のとおり、429 / 5xx / タイムアウトを指数バックオフ+ジッターで再試行するだけで個々の失敗確率は大きく下がり、その効果が全体の完遂率に増幅されて現れる。これが1.6節で「エラーハンドリングと再試行の設計はエージェントの成功率に直接効く」と述べた理由である。
採点の観点: 数値の正確さより、「ステップ数のべき乗で効く」という構造を説明できているかを見る。
(a) 社内在庫APIが 404
再試行しない。設定やリクエストの誤りではなく「対象が存在しない」という正常な事実なので、例外にして落とすのではなく、ツールの実行結果として「該当する在庫レコードが見つかりませんでした」とモデルに返す。モデルはこれを受けて、型番を確認し直す・別の検索条件を試す・ユーザーに問い直す、といった回復行動を取れる。
(b) 429 + Retry-After: 30
再試行する。ただし指数バックオフの計算値を使わず、Retry-After の指定(30秒)を優先して待つ。サーバーが提示した待機時間を無視して短い間隔で再送すると、レート制限がさらに延長されることがある。再試行回数には上限(例: 5回)を設け、超えたらエラーとして扱う。
(c) スキーマ違反で 400
同じリクエストの再試行はしない。何度送っても同じ結果になり、待ち時間とコストが増えるだけである。修正すべきはリクエスト側(プロンプトまたはスキーマ)。エージェントの文脈では、「どのフィールドがどう不正か」「期待するスキーマは何か」をモデルが読める形でツール結果として返すのが正しい扱いで、これによりモデルが引数を直して呼び直せる(5.3.1節の ToolRegistry.execute が実装例)。これは「同一リクエストの再試行」ではなく「修正後の再送」であり、(b)とは別物である点を区別すること。
→ 参照: 1.4.2節(冪等性) / 7.5.3節 / 5.7.1節
方法1: 冪等キー(idempotency key)を使う ツールの必須引数に、その送信を一意に識別するキーを持たせる。サーバー側(またはツール実装側)は送信済みキーの台帳を持ち、同じキーで再度呼ばれたらメールを送らず前回の結果を返す。
def send_confirmation_email(to: str, subject: str, body: str, idempotency_key: str) -> ToolOutcome: if existing := sent_log.get(idempotency_key): return ToolOutcome( f"このメールは既に送信済みです(送信時刻: {existing.sent_at})。再送していません。", is_error=False, ) message_id = mail_api.send(to=to, subject=subject, body=body) sent_log.put(idempotency_key, sent_at=now(), message_id=message_id) return ToolOutcome(f"送信しました (message_id={message_id})")function sendConfirmationEmail( to: string, subject: string, body: string, idempotencyKey: string): ToolOutcome { // Python のセイウチ演算子 (existing := ...) に相当する const existing = sentLog.get(idempotencyKey) if (existing !== undefined) { return { content: `このメールは既に送信済みです(送信時刻: ${existing.sentAt})。再送していません。`, // Python は is_error の既定値 False に頼れるが、 // ToolOutcome の isError は必須のため明示する isError: false, } } const messageId = mailApi.send({ to, subject, body }) sentLog.put(idempotencyKey, { sentAt: now(), messageId }) return { content: `送信しました (message_id=${messageId})`, isError: false }}キーはアプリケーション側で生成するのが安全である。モデルに生成させると、再試行時に別のキーを作ってしまい二重送信を防げないことがある(「注文ID + テンプレート種別」のように、業務上の一意キーから決定的に導出するのが確実)。
方法2: 読み取り/書き込みの分離 + 人間の承認
send_email という副作用のあるツールをモデルに与えず、draft_email(下書きを作るだけ、副作用なし)のみを与える。実際の送信は人間の承認操作、またはアプリケーション側の確定処理として1回だけ実行する。再試行が起きるのは副作用のない下書き生成の側だけになるので、構造的に二重送信が起こらない(5.7.1節のパーソナルアシスタント設計と同じ考え方)。
別解として認められるもの: 宛先・件名・本文・時間窓から内容ハッシュを取り、一定時間内の同一ハッシュを重複とみなす方式(冪等キーの簡易版)。7.5.3節の「確認用引数を必須にする」(ポカヨケ)も二重実行そのものは防げないが、宛先取り違えの防止として併用する価値がある。
Built with Astro ・ Deployed on Cloudflare Pages
© 2026 watakumi — made with 💜 & ☕ ・watakumi.page