LESSON 30分

ストーリー

田中VPoE
ツールの設計と選択戦略を学んだが、実運用ではエラーが必ず発生する。ここを適切に処理できないと、エージェントが止まったり暴走したりする
あなた
APIのタイムアウトとか、データが見つからないとか、よくありますよね
田中VPoE
その通り。重要なのは、エラーが発生してもエージェントが自律的にリカバリーできる設計にすることだ。人間が介入しなくてもエラーから復帰できるエージェントこそ、実務で使える
あなた
エラーもLLMに伝えて判断させるということですか?
田中VPoE
そうだ。エラー情報をLLMに戻して、リトライするか別の方法を試すかを判断させる。だからこそ、ツールのエラーレスポンスの設計が重要なんだ

エラーの分類

エージェントで発生するエラーの種類

エラー種別一般的な対策
一時的エラーAPIタイムアウト、レート制限リトライ
永続的エラー認証エラー、権限不足フォールバック or エスカレーション
データエラーレコード未発見、不正なデータ代替検索 or ユーザーに確認
バリデーションエラーLLMが不正なパラメータを生成エラー内容をLLMに返して再生成
ビジネスロジックエラー在庫切れ、与信NGビジネスルールに従った対応

リトライ戦略

Exponential Backoff

一時的なエラーに対しては、間隔を広げながらリトライします。

async function executeWithRetry(
  toolName: string,
  args: Record<string, unknown>,
  maxRetries: number = 3
): Promise<ToolResponse> {
  let lastError: Error | null = null;

  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      const result = await executeTool(toolName, args);

      if (result.success) {
        return result;
      }

      // リトライ不可能なエラーは即座に返す
      if (!isRetryable(result.error?.code)) {
        return result;
      }

      lastError = new Error(result.error?.message);
    } catch (error) {
      lastError = error as Error;
    }

    // Exponential Backoff: 1秒、2秒、4秒...
    const delay = Math.pow(2, attempt) * 1000;
    await sleep(delay);
  }

  return {
    success: false,
    error: {
      code: "MAX_RETRIES_EXCEEDED",
      message: `${maxRetries}回のリトライ後も失敗: ${lastError?.message}`,
      suggestion: "しばらく時間をおいて再試行するか、別の方法を検討してください"
    }
  };
}

function isRetryable(errorCode?: string): boolean {
  const retryableCodes = [
    "TIMEOUT",
    "RATE_LIMITED",
    "SERVICE_UNAVAILABLE",
    "CONNECTION_ERROR"
  ];
  return retryableCodes.includes(errorCode ?? "");
}

リトライ判断のフローチャート

エラー発生

一時的エラーか?
├── Yes → リトライ回数の上限内か?
│         ├── Yes → Exponential Backoff → リトライ
│         └── No  → フォールバック戦略へ
└── No  → フォールバック戦略へ

フォールバック戦略

リトライで解決しない場合の代替手段を設計します。

フォールバックパターン

パターン説明
代替ツール別のツールで同等の結果を得るメインDB不可 → キャッシュDB参照
縮退運転一部の情報なしで処理を続行配送追跡不可 → 注文情報のみで回答
ユーザー確認不足情報をユーザーに質問注文番号不明 → ユーザーに確認
エスカレーション人間のオペレーターに引き継ぐ重大エラー → サポートチームに通知
キャッシュ利用以前の結果をキャッシュから取得リアルタイムデータ不可 → 最終取得データ

フォールバックの実装

async function executeWithFallback(
  primaryTool: ToolCall,
  fallbackTools: ToolCall[]
): Promise<ToolResponse> {
  // プライマリツールを試行
  const primaryResult = await executeWithRetry(
    primaryTool.name,
    primaryTool.args
  );

  if (primaryResult.success) {
    return primaryResult;
  }

  // フォールバックツールを順番に試行
  for (const fallback of fallbackTools) {
    const result = await executeWithRetry(
      fallback.name,
      fallback.args,
      1 // フォールバックは1回のみ試行
    );

    if (result.success) {
      return {
        ...result,
        metadata: {
          usedFallback: true,
          originalTool: primaryTool.name,
          fallbackTool: fallback.name
        }
      };
    }
  }

  // 全フォールバック失敗
  return {
    success: false,
    error: {
      code: "ALL_FALLBACKS_FAILED",
      message: "すべての代替手段が失敗しました",
      suggestion: "人間のオペレーターにエスカレーションしてください"
    }
  };
}

エラー情報のLLMへの伝達

LLMが適切に判断するためのエラーレスポンス設計

良いエラーレスポンス(LLMが次のアクションを判断できる):
{
  "success": false,
  "error": {
    "code": "ORDER_NOT_FOUND",
    "message": "注文番号 ORD-99999 は見つかりませんでした",
    "suggestion": "注文番号を再確認するか、顧客IDや注文日で検索してください",
    "available_alternatives": ["search_by_customer_id", "search_by_date"]
  }
}

悪いエラーレスポンス(LLMが次のアクションを判断できない):
{
  "error": "Not found"
}

タイムアウトの設計

const TOOL_TIMEOUTS: Record<string, number> = {
  search_orders: 5000,      // 5秒
  track_shipment: 10000,    // 10秒(外部API依存)
  generate_report: 30000,   // 30秒(重い処理)
  send_email: 15000         // 15秒
};

async function executeWithTimeout(
  toolName: string,
  args: Record<string, unknown>
): Promise<ToolResponse> {
  const timeout = TOOL_TIMEOUTS[toolName] ?? 10000;

  try {
    const result = await Promise.race([
      executeTool(toolName, args),
      new Promise<never>((_, reject) =>
        setTimeout(() => reject(new Error("TIMEOUT")), timeout)
      )
    ]);
    return result;
  } catch (error) {
    return {
      success: false,
      error: {
        code: "TIMEOUT",
        message: `${toolName} が ${timeout / 1000}秒以内に応答しませんでした`,
        suggestion: "再試行可能です。サービスが一時的に遅延している可能性があります",
        retryable: true
      }
    };
  }
}

まとめ

ポイント内容
エラー分類一時的/永続的/データ/バリデーション/ビジネスロジック
リトライ戦略Exponential Backoffで一時的エラーに対応
フォールバック代替ツール、縮退運転、エスカレーション
エラーレスポンスLLMが次の判断をできる十分な情報を含める

チェックリスト

  • エージェントで発生するエラーの種類を分類できる
  • Exponential Backoffによるリトライ戦略を理解した
  • フォールバックパターンの使い分けを理解した
  • LLMに伝えるエラーレスポンスの設計方法を把握した

次のステップへ

次は「演習:業務ツールを実装しよう」です。NetShop社の業務に必要なツールを設計・実装する演習に取り組みます。


推定読了時間: 30分