言語を切り替える
テーマを切り替える

AI エージェントの監視アラートと障害復旧:ログからステートマシンまでの設計実践

Gartner の 2024 年レポートでは、企業向け AI エージェントプロジェクトの 87% が、本番投入から 3 か月以内にタスク失敗率が 25% を超えると報告されています。原因は入れ子になったツール呼び出しの奥に隠れ、ログはあちこちに散らばり、追跡できないことが多い。

本質はアラートの数ではなく、エージェント向けの監視アーキテクチャそのものが誤っていることです。エージェントは普通のバックエンドサービスではありません。非決定性ゆえに従来の監視では足りない——実行パスは動的で、同じタスクでも毎回ルートが変わります。本記事では、ログ・メトリクス・トレースからステートマシンまでの設計を一通り示し、「制御不能なブラックボックス」を「すべての失敗が追跡・復旧できる透明なシステム」に変える方法を説明します。

第 1 章:なぜ従来の監視はエージェントでは機能しないのか

こんな経験はありませんか。エージェントのタスクが失敗し、ログを延々と追うと LLM 出力の断片ばかりで、実行の全体像が組み立てられない。結局やり直して、今度こそ成功を祈るしかない。

従来のバックエンド監視はこうです。リクエストが入り、マイクロサービス A・B・C を経て、各ノードが状態とタイムスタンプを記録する。障害時はチェーンを辿ればよい。エージェントは違います。

実行パスは動的に生成されます。同じタスクでも、1 回目はツール A、2 回目はツール B、3 回目はツール呼び出しをスキップするかもしれません。OpenAI の 2024 年レポートでは、エージェントの平均タスク完了率は 61.8% にとどまります。背景には、推論中にエージェント自身が意思決定し、その決定に不確実性が伴うことがあります。

さらに厄介なのが God Prompt——エージェントのロジック全体を巨大な 1 本のプロンプトに詰め込むやり方です。ArizenAI の技術ブログはこれを「本番環境の最大のリスク」と呼んでいます。理由は 3 つ。テスト不能、デバッグ不能、予測不能。

5000 字のプロンプトを単体テストすることはできません。どの推論ステップが壊れたのか精密に特定もできません。パラメータを 1 つ変えただけで連鎖的に崩れるかも予測できません。あるプロジェクトでは God Prompt の例を 1 つ変えただけで、成功率が 70% から 30% に落ちました。1 週間調査した末、新しい例が「ツール A を優先呼び出し」と学習させたが、そのシーンでは A を呼ぶべきではなかった、という結論でした。

OpenAI の報告では、エージェント失敗の 82% は修復可能なエラーです。能力不足ではなく、設計の脆弱さが原因です。監視は「問題を見つける」だけでなく、「エージェントを改善するフィードバックループ」であるべきです。各状態の成功率、各ツール呼び出しの所要時間、各エラーの出現頻度——これらがどこを直すべきかを示します。

従来の監視思考は「障害が起きてから調べる」。エージェントの監視思考は「すべてのステップに痕跡を残し、失敗そのものを学習機会にする」。この観念の転換が、システム設計の出発点です。

第 2 章:AI エージェントの可観測性 3 層アーキテクチャ

エージェントの監視は単一手段ではなく、ログ・メトリクス・トレースの 3 層の重ね合わせです。各層が異なる次元の問題を解きます。

第 1 層:混沌としたログから構造化記録へ

エージェントの生ログを見たことはありますか。LLM 生成テキストの断片とエラースタックが混ざり、タイムスタンプも散らばる。こうしたログは事後の「発掘」には使えても、リアルタイム監視には向きません。

構造化ログの鍵は、各レコードにラベルを付けることです。エージェント ID、タスク ID、現在状態、入出力の要約——これでタスク単位の集約、状態でのフィルタ、時系列ソートが可能になります。

# 構造化ログの例
import structlog

logger = structlog.get_logger()

def log_agent_step(agent_id: str, task_id: str, state: str, input: dict, output: dict):
    logger.info(
        "agent_step",
        agent_id=agent_id,
        task_id=task_id,
        state=state,
        input_summary=str(input)[:100],  # 切り詰めてログ肥大化を防止
        output_summary=str(output)[:100],
        timestamp=time.time()
    )

単純に見えますが、多くのチームは LLM の生出力をそのままログに流し、grep で価値ある情報が取れると期待しています。それでは足りません。

第 2 層:エージェント専用メトリクス

メトリクスは「傾向分析」を担います。ログはある 1 回の失敗を教え、メトリクスは失敗率の上昇を教えます。

エージェントに必要なコアメトリクスは 4 類です。

指標タイプ具体指標アラート閾値の目安
トークン消費総消費、単一タスク消費、ツール呼び出し消費単一タスク > 10000 トークン
レイテンシP50、P99、ツール呼び出し所要時間P99 > 30 秒
エラー率タスク失敗率、ツール失敗率、再試行成功率失敗率 > 20%
コストタスクあたりコスト、日次総コスト日次コストが 50% 急増

LangSmith のダッシュボードは良い例です。エージェント単位でこれらを表示し、タスク詳細までドリルダウンできます。閾値は履歴データに基づき、感覚では決めないでください。1 週間稼働して正常範囲を統計し、正常上限のおよそ 1.5 倍に設定するのが現実的です。

第 3 層:OpenTelemetry トレース標準

トレースは「チェーンの再現」を担います。1 本の Trace がユーザー要求から意図認識、ツール選択、実行、検証、最終出力までを貫きます。各段階が Span で、タイムスタンプ・状態・入出力を含みます。

OpenTelemetry は業界標準になりつつあります。PredictionGuard のブログでは、フレームワークやツールをまたいでトレース形式を統一できると述べています。Pydantic AI、smolagents、Strands Agents、LangGraph など主要フレームワークがすでに対応しています。

# OpenTelemetry トレースの例
from opentelemetry import trace
from opentelemetry.sdk.trace.export import ConsoleSpanExporter

tracer = trace.get_tracer("agent_tracer")

async def run_agent_with_trace(task: str):
    with tracer.start_as_current_span("agent_task") as span:
        span.set_attribute("task_input", task)
        
        # 意図認識
        with tracer.start_as_current_span("intent_detection") as intent_span:
            intent = await detect_intent(task)
            intent_span.set_attribute("intent_result", intent)
        
        # ツール呼び出し
        with tracer.start_as_current_span("tool_call") as tool_span:
            result = await call_tool(intent)
            tool_span.set_attribute("tool_result", str(result)[:200])
        
        span.set_attribute("final_output", result)
        return result

Langfuse と LangSmith はどちらも OpenTelemetry のインポートに対応します。オープンソースでトレースを収集し、商用プラットフォームで可視化・分析する構成も可能です。単一ベンダーへのロックインを避けられます。

3 層を重ねる利点は明確です。ログで詳細、メトリクスで傾向、トレースで全体像——どの次元も取りこぼしません。

第 3 章:ステートマシン設計——失敗を可観測にする中核パターン

God Prompt の問題は本質的に「全部入り」です。ロジックが混ざると、どこが壊れたか分かりません。ステートマシンは大きな鍋を小さな鍋の連鎖に分けます。

ArizenAI の技術ブログは、ステートマシンで推論コストを 80% 削減できると示しています。各状態が 1 つのことだけを行うため、LLM が毎回全体を頭から推論する必要がないからです。

ステートマシン vs God Prompt:根本的な違い

次元God Promptステートマシン
テスト可能性単体テスト不可各状態を独立テスト
デバッグ可能性失敗位置が曖昧状態境界が明確
コスト制御毎回プロンプト全体を推論現在状態に必要な部分だけ
エラー処理プロンプト内に隠れるTyped transitions で明示

典型的なエージェントのステートマシン構造:

[初期化] → [意図認識] → [ツール選択] → [実行] → [検証] → [完了]
         ↘            ↗
           [エラー処理]

ArizenAI は 5〜12 状態を推奨します。少なすぎると God Prompt に戻り、多すぎると遷移が複雑になります。各状態には明確な入出力型——Typed transitions——が必要です。

# 状態定義の例(疑似コード)
from typing import TypedDict, Literal

class IntentState(TypedDict):
    task_input: str
    intent_type: Literal["query", "action", "clarify"]

class ToolState(TypedDict):
    intent: IntentState
    selected_tool: str
    tool_params: dict

class ErrorState(TypedDict):
    failed_state: str
    error_type: str
    retry_count: int

# 状態遷移:明示的なエラーパス
def transition_from_intent(intent: IntentState) -> ToolState | ErrorState:
    try:
        tool = select_tool(intent)
        return {"intent": intent, "selected_tool": tool, "tool_params": {}}
    except IntentError as e:
        return {"failed_state": "intent", "error_type": "ambiguous", "retry_count": 0}

各状態の監視ポイント

ステートマシンの利点は、各状態が自然に監視単位になることです。混沌としたログを掘らなくても、状態別にメトリクスを見られます。

  • 初期化状態:タスク開始時刻、入力の完全性チェック結果
  • 意図認識状態:意図タイプ分布、認識所要時間、曖昧率
  • ツール選択状態:ツール呼び出し頻度、選択所要時間、マッチなし率
  • 実行状態:ツール実行時間、成功率、失敗タイプ分布
  • 検証状態:検証合格率、修正試行回数
  • エラー処理状態:エラータイプ分布、再試行成功率、デグレード発火回数

これらの指標で、どの段階に問題があるか一目で分かります。意図認識が 2 秒から 10 秒に急増?プロンプトが長くなった可能性。ツール失敗率が 5% から 30%?特定 API の障害かもしれません。

ステートマシンは監視の粒度を「タスク全体」から「各ステップ」へ下げます。問題の特定そのものが監視の一部になる——どのアラートルールより効くことが多いです。

第 4 章:障害復旧のエンジニアリング実践

監視は問題を見つけ、復旧は問題を解く。ただし復旧は「再試行」だけではありません。盲目的な再試行は事態を悪化させます。

エラー分類:すべての失敗が同じではない

実プロジェクトでは、おおよそ次の 3 類に分かれます。

タイプ割合特徴処理方法
一時的エラー〜60%API タイムアウト、サービス揺らぎ、レート制限指数バックオフで再試行(最大 5 回)
論理的エラー〜30%パラメータ形式誤り、存在しないツール、意図の曖昧さ自己反省 + 戦略調整
カスケードエラー〜10%コアサービス障害、設定ミス遮断 + デグレード

アリババクラウドのデータでは、適切な再試行で API 成功率を 85% から 99.5% まで引き上げられるとされています。前提は「適切」であることです。

再試行の罠:コンテキスト汚染

2026 年 5 月の Arxiv 論文は、直感に反する現象を指摘しています。単純な再試行だけでは、成功率がむしろ下がることがある。

理由は、失敗情報が後続推論を「汚染」するからです。

シーンを想像してください。エージェントがツール A を呼び出して失敗し、エラーが会話履歴に追記されます。エージェントは「A に問題がある、B を試そう」と推論するかもしれません。B も失敗すると、履歴に 2 件の失敗が残ります。エージェントは「タスクが複雑すぎる、諦めよう」と結論づくこともあります。

これがコンテキスト汚染(Context Contamination)です。失敗情報そのものが推論パスを変え、後続の試行を諦めや誤戦略に寄せます。

対策は状態隔離です。再試行ごとに完全な失敗履歴を引き継がず、「クリーンな状態」からやり直す。または再試行前に、生のスタックトレースではなく構造化されたエラー要約だけを渡す。

# 状態隔離による再試行の例
async def retry_with_clean_state(task: str, error: AgentError, max_retries: int = 3):
    for attempt in range(max_retries):
        # 完全な失敗履歴は渡さず、構造化されたエラー要約のみ
        error_summary = {
            "type": error.type,
            "failed_step": error.step,
            "hint": get_recovery_hint(error)
        }
        
        result = await run_agent_state(
            start_state="error_recovery",
            context={"original_task": task, "error_summary": error_summary}
        )
        
        if result.success:
            return result
    
    return {"status": "failed", "reason": "max_retries_exceeded"}

デグレード:失敗を認め、優雅に退出する

自動復旧できないエラーもあります。3〜5 回連続失敗したら、デグレードを起動すべきです。

シーン別のデグレード戦略:

  • タスクの簡略化:複雑タスクを単純版に分解し、部分結果を返す
  • 人間の介入依頼:タスクを保留し、運用担当やユーザーに通知
  • フォールバック応答:定型の汎用回答で UX の中断を防ぐ

NIST SP 800-61 Rev. 3(2025 年更新)は、インシデント対応の 6 機能を定義しています。Govern(統治)、Identify(識別)、Protect(保護)、Detect(検知)、Respond(対応)、Recover(復旧)。もともとサイバーセキュリティ向けですが、エージェント運用にそのまま当てはまります。

NIST フレームワークをエージェントにマッピングすると:

  • Govern:失敗閾値、デグレード戦略、責任分界を定義
  • Identify:エラータイプの分類、失敗チェーンの追跡
  • Protect:デグレード戦略の事前設定、サーキットブレーカー
  • Detect:リアルタイム監視、異常検知
  • Respond:再試行またはデグレードの起動、イベント記録
  • Recover:正常サービスへの復帰、振り返りと改善

このフレームワークの強みは、復旧を一時しのぎではなく一連のプロセスとして扱う点です。

第 5 章:実践事例とツール推奨

理論のあとは実装です。具体的な統合案をいくつか示します。

LangGraph + Langfuse の監視設定

LangGraph は OpenTelemetry をネイティブサポートし、Langfuse への接続は数行で済みます。

from langfuse import Langfuse
from langfuse.callback import CallbackHandler

langfuse_handler = CallbackHandler(
    public_key="pk-xxx",
    secret_key="sk-xxx",
    host="https://cloud.langfuse.com"
)

# LangGraph コンパイル時にコールバックを注入
agent = graph.compile()
result = agent.invoke(
    {"input": task},
    config={"callbacks": [langfuse_handler]}
)

Langfuse は各ノードのトレースデータ——入出力、所要時間、トークン消費——を自動収集します。ダッシュボードでタスク ID 単位の実行チェーンを確認できます。

CrewAI のヘルスチェックエンドポイント

CrewAI には組み込み監視がないため、ヘルスチェックエンドポイントを自前で設計します。

from fastapi import FastAPI
from crewai import Crew

app = FastAPI()

@app.get("/health")
async def health_check():
    # 直近 100 タスクの成功率を確認
    recent_tasks = get_recent_tasks(limit=100)
    success_rate = sum(1 for t in recent_tasks if t.status == "success") / len(recent_tasks)
    
    return {
        "status": "healthy" if success_rate > 0.8 else "degraded",
        "success_rate": success_rate,
        "last_error": recent_tasks[-1].error_summary if recent_tasks[-1].status == "failed" else None
    }

このエンドポイントは Kubernetes のヘルスチェックや、アラートシステムのデータソースに接続できます。

ツール推奨マトリクス

シーン推奨ツール特徴向くチーム
トレースLangfuseOpenTelemetry ネイティブ、オープンソース、セルフホスト可カスタムデプロイが必要なチーム
監視LangSmithLangChain 公式、アラート連携が充実LangChain / LangGraph 利用チーム
ログLoki + Grafana低コスト、K8s 向き、既存基盤と相性良い大規模デプロイ、予算重視
異常検知Luna-2 小モデルエージェント特有パターンの認識、ノイズ低減アラートノイズが深刻なチーム

PredictionGuard のブログでは、小規模言語モデル(Luna-2 など)がエージェント特有の失敗パターンを理解し、従来の閾値アラートより賢くなると述べています。アラートが毎日数十件で 90% がノイズなら、検討の価値があります。

まとめ

エージェント監視体系の有無で、どれだけ差が出るか。

次元監視なし監視あり
問題特定ログを延々追う状態単位で秒級に特定
障害復旧盲目的再試行、成功率低下分類処理で的確に復旧
アラート品質ノイズ爆発、根本原因が埋もれる集約・ノイズ低減でシグナルが明確
エージェント改善感覚でパラメータ調整データ駆動の最適化

God Prompt からステートマシンへ、混沌としたログから OpenTelemetry トレースへ、盲目的再試行から状態隔離による復旧へ——この転換は「あると良い」ではなく、本番投入の必須条件です。

巨大な 1 本のプロンプトでエージェントを支えているなら、今日から状態に分割しましょう。5〜12 の離散状態、各状態は単一責務、失敗パスは明示的に定義。

まだ OpenTelemetry を入れていないなら、今が最適なタイミングです。主要フレームワークは対応済みで、Langfuse と LangSmith はトレースデータを直接取り込めます。

再試行は万能薬ではありません。コンテキスト汚染は、単純な再試行を深みに引きずります。状態隔離を設計することが正道です。

エージェントの本番化は、「良いプロンプトを書けば終わり」ではありません。監視と復旧こそ、本当に制御可能にする最後の一歩です。

AI エージェントの可観測性システムを構築する

ログからステートマシンまでの監視体系を一式で構築

⏱️ 目安時間: 45 分

  1. 1

    ステップ1: 構造化ログ形式を設計する

    各ログにエージェント ID、タスク ID、現在状態、入出力の要約を付与。structlog などで形式を統一し、長文は切り詰めてログ肥大化を防ぐ。
  2. 2

    ステップ2: エージェントのコアメトリクスを設定する

    トークン消費(単一タスク閾値 10000)、レイテンシ(P99 閾値 30 秒)、エラー率(失敗率閾値 20%)、コスト(日次コストが 50% 急増)を監視。
  3. 3

    ステップ3: OpenTelemetry トレースを接続する

    ユーザー要求から最終出力まで、各段階を Span で定義。LangGraph や Pydantic AI など主要フレームワークはネイティブ対応。Langfuse や LangSmith で可視化。
  4. 4

    ステップ4: ステートマシンアーキテクチャに分割する

    God Prompt を 5〜12 の離散状態に分割。各状態は単一責務とし、Typed transitions で明示的なエラーパスを定義。
  5. 5

    ステップ5: エラー分類と復旧を実装する

    一時的エラーは指数バックオフで再試行(最大 5 回)、論理的エラーは自己反省を起動、カスケードエラーは遮断してデグレード。再試行ごとに状態隔離し、コンテキスト汚染を避ける。

FAQ

なぜ従来の監視はエージェントでは機能しないのか?
エージェントの実行パスは動的に生成され、同じタスクでも毎回ルートが変わる。従来監視は固定チェーン前提のため、非決定的な判断を追えない。さらに God Prompt はすべてのロジックを 1 つのプロンプトに詰め込むため、失敗時にどの段階が原因か特定できない。
ステートマシンパターンはどう推論コストを下げるのか?
各状態は 1 つのことだけを行うため、LLM が毎回ロジック全体を頭から推論する必要がない。ArizenAI のデータではステートマシンで推論コストを 80% 削減できる。各状態を独立テストでき、失敗時に問題箇所を精密に特定できる点も大きい。
コンテキスト汚染(Context Contamination)とは?
失敗情報が後続の推論を汚染する現象。ツール呼び出しに失敗するとエラーが会話履歴に追記され、エージェントが誤った戦略を選んだりタスクを諦めたりする。対策は状態隔離による再試行で、完全な失敗履歴を引き継がないこと。
エージェントのアラート閾値はどう設計する?
まず 1 週間稼働してベースラインを収集し、正常範囲を統計する。閾値は正常上限のおよそ 1.5 倍に設定。感覚で決めると低すぎてノイズが爆発し、高すぎて本当の問題を見逃す。
OpenTelemetry と LangSmith はどちらを選ぶ?
カスタムデプロイが必要なら Langfuse(オープンソース)。LangChain / LangGraph エコシステムなら LangSmith(アラート連携が充実)。どちらも OpenTelemetry のインポート・エクスポートに対応し、ベンダーロックインを避けられる。
再試行が失敗し続けたらどうする?
3〜5 回連続失敗でデグレード:タスクを簡略化して部分結果を返す、人間の介入を依頼する、またはフォールバック応答で UX を維持する。NIST SP 800-61 は復旧を一時しのぎではなく、一連のプロセスとして扱うよう示す。

6分で読めます · 公開日: 2026年5月27日 · 更新日: 2026年6月8日

関連記事

コメント

GitHubアカウントでログインしてコメントできます