Alejandro Rioja.
AI Agents

AIエージェントが本番環境で失敗し続ける理由(そして修正方法)

Alejandro Rioja
Alejandro Rioja
2 分で読める
TL;DR

本番エージェントの失敗のほとんどは5つの原因から来ます:エッジケースを処理しない脆弱なプロンプト、一時的なAPIエラーのリトライロジックの欠如、何が壊れているか見えない可観測性の欠如、終了条件のないループ暴走、モデルが間違ったものを選ぶほど曖昧なツール定義。すべての5つはモデルやフレームワークを変えなくても修正可能です。

無料ニュースレター

毎週水曜。28,400人以上の読者。無駄なし。

目次

2026年6月更新。

TL;DR: 本番エージェントの失敗のほとんどは5つの原因から来ます:エッジケースを処理しない脆弱なプロンプト、一時的なAPIエラーのリトライロジックの欠如、何が壊れているか見えない可観測性の欠如、終了条件のないループ暴走、モデルが間違ったものを選ぶほど曖昧なツール定義。すべての5つはモデルやフレームワークを変えなくても修正可能です。

【オペレーターの視点】 本番で30以上のエージェントを動かしています。これらの失敗はすべて経験しました。最も時間を費やしたのはエキゾチックなものではありませんでした——対処済みだと思っていた退屈なインフラ失敗でした。

失敗1:エッジケースの入力で壊れる脆弱なプロンプト

テストケースで動くプロンプトは、予期しない入力で失敗します。これはモデルの限界ではありません——指示の書き方の問題です。

症状: エージェントが無意味な出力を生成する、間違ったツールを呼び出す、またはテストしたものと入力が少し異なるときに不正なJSONを出力する。

根本原因: システムプロンプトはハッピーパスのみを説明しています。データが欠落、不正、または曖昧なときに何をすべきかモデルに伝えていません。

修正: システムプロンプトに明示的なエッジケース処理を追加する:

code
If the input data is missing a required field, return:
{ "status": "error", "reason": "missing_field", "field": "<fieldname>" }
Do NOT attempt to infer or hallucinate missing values.

If you are uncertain which tool to call, call no tool and return:
{ "status": "clarification_needed", "question": "..." }

モデルはエッジケースの明示的な指示を確実に従います。ミスは、ハッピーパスの指示が乱雑なケースを処理するために一般化されると仮定することです。

失敗2:一時的なAPIエラーのリトライロジックなし

エージェントが呼び出すすべての外部APIはいつかは失敗します。Claude API、Meta Graph API、データベース——すべて5xxエラーを返したり、タイムアウトしたり、レート制限したりします。エージェントにリトライロジックがない場合、一つの一時的なエラーで全実行が終わります。

症状: エージェントの実行が異なるステップでランダムに失敗する。ログは503または429を示し、フォローアップの試みはない。

修正: すべての外部呼び出しを指数バックオフリトライでラップする:

typescript
async function withRetry<T>(fn: () => Promise<T>, retries = 3, baseDelayMs = 500): Promise<T> {
  for (let attempt = 0; attempt <= retries; attempt++) {
    try {
      return await fn();
    } catch (err: any) {
      const isTransient = err.status === 429 || err.status >= 500 || err.code === "ECONNRESET";
      if (!isTransient || attempt === retries) throw err;
      const delay = baseDelayMs * Math.pow(2, attempt) + Math.random() * 100;
      await new Promise((r) => setTimeout(r, delay));
    }
  }
  throw new Error("unreachable");
}

// Usage
const result = await withRetry(() => client.messages.create({ ... }));

指数バックオフを持つ3回のリトライは一時的な失敗の約99%を処理します。これをすべての外部呼び出しに追加すれば、ランダムな失敗の半分が消えます。

失敗3:可観測性なし——何が壊れているか見えない

これは本番で最も一般的な失敗モードであり、デバッグに最もコストがかかるものです:エージェントが静かに失敗するか間違った出力を生成し、チェーンのどこで何が間違ったかわかりません。

症状: 何かがおかしいとわかるが、ステップを特定できない。console.log文を追加して手動で再実行して再現しようとする。

修正: すべてのステップで構造化ロギング、実行全体をトレースする実行IDを持つ:

typescript
function createLogger(runId: string, agentName: string) {
  return {
    step: (step: string, data: object) =>
      console.log(JSON.stringify({ runId, agent: agentName, step, ts: new Date().toISOString(), ...data })),
    error: (step: string, err: unknown) =>
      console.error(JSON.stringify({ runId, agent: agentName, step, error: String(err), ts: new Date().toISOString() })),
  };
}

const log = createLogger(crypto.randomUUID(), "newsletter-agent");
log.step("fetch_topic", { topicId: topic.id, topic: topic.name });
// ... do work ...
log.step("draft_complete", { subject: draft.subject, wordCount: draft.body.split(" ").length });

Cloudflare Workersを使用している場合、これらのログはLogpushまたはWorkers Tailに送られます。ローカルまたはVPSで実行している場合、ログアグリゲーターにパイプします。構造化JSONによりrunIdでフィルタリングして、単一の実行で何が起きたかを正確に確認できます。

失敗4:終了条件のないループ暴走

エージェントループ——モデルがツールを呼び出して条件が満たされるまで反復する——は、その条件が決して満たされないか、モデルが誤って識別すると永遠に実行される可能性があります。

症状: エージェントがタイムアウトする前に何百ドものAPIコストを費やす。または進展なく同じツール呼び出しを繰り返し実行する。

修正: 常にハードな反復上限と進行チェックを持つ:

typescript
const MAX_ITERATIONS = 10;
let iterations = 0;
let lastToolCallName = "";
let sameToolCallCount = 0;

while (true) {
  iterations++;
  if (iterations > MAX_ITERATIONS) {
    log.error("loop", { reason: "exceeded_max_iterations" });
    break;
  }

  const response = await client.messages.create({ ... });

  // Detect stuck loops: same tool called 3x in a row
  const toolCall = response.content.find(b => b.type === "tool_use");
  if (toolCall?.name === lastToolCallName) {
    sameToolCallCount++;
    if (sameToolCallCount >= 3) {
      log.error("loop", { reason: "stuck_loop", tool: toolCall.name });
      break;
    }
  } else {
    sameToolCallCount = 0;
    lastToolCallName = toolCall?.name ?? "";
  }

  if (response.stop_reason === "end_turn") break;
}

これは「長く走りすぎた」と「その場で回った」両方の失敗モードを捕まえます。上限はハッピーパスには十分寛大で、爆発半径を制限するのに十分タイトである必要があります。

失敗5:モデルが間違って解決する曖昧なツール定義

モデルに重複した説明を持つ2つのツールを与えると、時々間違ったものを呼び出します。これはsearch_databaseget_recordsend_emailcreate_draftなどのツールで特に一般的です。

症状: モデルは正しいカテゴリのツールを呼び出すが、間違った特定のものを選ぶ。または間違ったコンテキストでツールを呼び出す(読み取りだけが適切な場合に書き込みツールを使用する)。

修正: ツールの説明を相互に排他的にし、明示的に「いつ使用しないか」を追加する:

typescript
const tools = [
  {
    name: "get_subscriber",
    description: "Fetch a single subscriber record by email. Use ONLY when you have a specific email address. Do NOT use for searching or listing subscribers.",
    input_schema: { ... }
  },
  {
    name: "search_subscribers",
    description: "Search subscribers by tag, segment, or status. Use when you need to find subscribers matching a criteria — NOT when you have a specific email address.",
    input_schema: { ... }
  }
];

「Xの場合は使用しない」条項は、ほとんどの人がスキップする部分です。これが最も重要な部分です。モデルは積極的な説明からそれを推論するよりも、明示的な否定的制約に従うのが得意です。

もう一つのこと:悪い入力でエージェントをテストする

ほとんどのエージェントはクリーンなハッピーパスの入力のみでテストされます。本番環境には汚い入力があります:空の文字列、nullフィールド、Unicodeエッジケース、200を返すが予期しないスキーマのAPIレスポンス。

以下を明示的にテストするテストスイートを追加する:

これらのいずれかでエージェントが壊れた場合、本番公開前に修正してください。本番環境はあなたが行ったすべての仮定を見つけます。

オペレーターの最終結論

本番でのほとんどのエージェント失敗は、モデルの問題に偽装したインフラの問題です。モデルを切り替える前に、プロンプトにリトライ、構造化ロギング、ループキャップ、明示的なエッジケース処理を追加してください。曖昧なツール定義を修正してください。それから悪い入力でテストしてください。モデルを責める前にそれらすべてをやってください——私の経験では、モデルは通常、変更が必要な最後のものです。

続きを読む

AIプレイブックをメールでお届け

毎週水曜。28,400人以上の読者。無駄なし。

↵ すべての結果を見る esc esc で閉じる