powered by TechFeed
表示モード
Deep Dive

AIエージェントは「エラーなし、でも完全に誤答」する — 従来の監視が見逃す失敗をOpenTelemetryで可視化する方法

10月1日、Machine Learning Masteryが「AI Agent Observability: Logging, Tracing, and Debugging Explained」と題した記事を公開した。カスタマーサポートを担当するAIエージェントが、丁寧で読みやすい、しかし完全に誤った回答でチケットをクローズする。refund-lookupツールを2回呼び出し、2回目の結果を採用して自信満々に応答した。クラッシュなし。エラーなし。ダッシュボードは緑のまま。問題が発覚するのは、2日後に困惑した顧客から返信が届いてからだ。

10月1日、Machine Learning Masteryが「AI Agent Observability: Logging, Tracing, and Debugging Explained」と題した記事を公開した。カスタマーサポートを担当するAIエージェントが、丁寧で読みやすい、しかし完全に誤った回答でチケットをクローズする。refund-lookupツールを2回呼び出し、2回目の結果を採用して自信満々に応答した。クラッシュなし。エラーなし。ダッシュボードは緑のまま。問題が発覚するのは、2日後に困惑した顧客から返信が届いてからだ。


「エラーなし、でも答えは完全に間違い」——従来の監視では見えない失敗

記事はこの実例から始まる。AIエージェントは「それっぽい正解」を返しながら完全に失敗できる。従来のHTTPサービスであればエラー200か例外を投げるかのどちらかだが、エージェントはそのどちらでもなく誤答する。アップタイム監視や例外ハンドラーで設計された既存ツールは、この種の失敗に対してほぼ盲目だ。

AI Agent Observability(オブザーバビリティ)とは、エージェントが実行するモデル呼び出し・ツール実行・推論ステップをすべて構造化データとして記録し、問題発生時に「何が起きたか」を正確に再現できるようにする実践だ。従来の3本柱(ログ・メトリクス・トレース)を借用しつつ、エージェント特有の失敗モードに対応する独自の規律として位置づけられている。


なぜエージェントは従来の監視モデルを壊すのか

記事は「エージェントは予測不能」という曖昧な説明を避け、具体的なメカニズムを列挙する。

  • 同じ入力が同じ動作を保証しない。temperatureの設定、RAGの検索結果、利用可能なツールの違いによって、同一プロンプトが異なるツール呼び出し列を生む。
  • コストとレイテンシの相関先が変わる。リクエスト数ではなくトークン消費量に相関する。1リクエストが通常の10倍のトークンを消費していても、RPS(秒間リクエスト数)ベースの監視では検知できない。
  • 1ユーザーリクエストが複数の障害点を持つ。モデル呼び出し、ツール呼び出し、検索クエリがそれぞれ独立した失敗点となり、集計エラーメトリクスでは区別できない。
  • プロンプトが個人情報を含む。素朴なログ実装でプロンプト全文をバックエンドに流すと、デバッグ価値より先にコンプライアンス問題が発生する。
シグナル 従来のアプリ LLM / AIエージェント
レイテンシの要因 CPU、I/O、ネットワーク トークン数、モデルサイズ、コンテキスト長
コスト単位 リクエスト数 消費トークン数
失敗モード 例外、タイムアウト ハルシネーション、コンテキスト溢れ、ツールエラー
デバッグ成果物 スタックトレース プロンプト・補完・その間の推論チェーン

ログ:すべてのログ行にtrace IDを紐付ける

記事が最初に強調するのは、ログの構造化と実行単位(run)との紐付けだ。「tool call failed」だけのログは、深夜2時に3ユーザーが同時に3つの異なるrunを走らせていたとき、ほぼ役に立たない。

OpenTelemetryを使えば、trace.get_current_span()で現在アクティブなスパンを取得でき、trace IDを関数の引数として手動で引き回す必要がない。これが実際のコードベースに後付けしやすい理由だ。

import logging
from opentelemetry import trace

logger = logging.getLogger("agent")
tracer = trace.get_tracer("agent-service")

def call_tool(tool_name: str, arguments: dict):
    span = trace.get_current_span()
    trace_id = format(span.get_span_context().trace_id, "032x")

    logger.info("tool_call_started", extra={
        "trace_id": trace_id,
        "tool_name": tool_name,
        "arguments": arguments,
    })

    try:
        result = execute_tool(tool_name, arguments)
        logger.info("tool_call_succeeded", extra={
            "trace_id": trace_id,
            "tool_name": tool_name,
            "result_length": len(str(result)),  # 全文ではなく長さだけ
        })
        return result
    except Exception as e:
        logger.error("tool_call_failed", extra={
            "trace_id": trace_id,
            "tool_name": tool_name,
            "error": str(e),
        })
        raise

ツール引数と結果の長さだけをログに残す設計は意図的なものだ。ツール出力はサイズが大きくなりがちで、機密データを含むこともある。長さや切り詰めたプレビューで問題の検知には十分であり、全文ログは不要なプライバシーリスクを生む。開始イベントと終了イベントの両方を記録するのは、後でそのツール呼び出しの所要時間を正確に計測するためだ。


トレース:「なぜそのツールを呼んだか」を親子関係で表現する

ログが時系列の点を記録するのに対し、トレースはその点を形に繋ぐ。1回のエージェントrunを最初のリクエストから最終回答まで記録し、各ステップをトリガーした親子関係のツリーとして表現する。

記事ではOpenTelemetry GenAI semantic conventionsに準拠した実装を紹介している。このspecはgen_ai.*スパン型と属性を標準化しており、以下の操作タイプが定義されている。

  • create_agent:エージェントの定義時
  • invoke_agent:1回のエージェントrun
  • invoke_workflow:複数エージェント間のオーケストレーション
  • execute_tool:個別のツール呼び出し
  • chat:モデル推論呼び出し本体

共通属性としてはgen_ai.request.model、gen_ai.usage.input_tokens、gen_ai.usage.output_tokensなどがある。異なるチームやフレームワークが生成したトレースが同じ構造を持つことで、ツールやダッシュボードの互換性が生まれる。

Pythonのwith tracer.start_as_current_span(…)ブロックのネスト構造が、そのままOpenTelemetryの親子スパン関係にマッピングされる。手動でスパンの親子を配線する必要はなく、コードのネスト構造が自動的に意味を持つ。


メトリクスとデバッグワークフロー

記事はトークンコストの追跡から実践的なデバッグシナリオまでをカバーしている。メトリクスの中核となるのがgen_ai.client.token.usageカウンターだ。モデル呼び出しのたびに入力・出力トークン数をこのカウンターに記録し、Prometheusへエクスポートすることで、「特定のrunが他の10倍のトークンを消費していた」といった異常をグラフ上で即座に検出できるようになる。RPSベースの監視では素通りしてしまうコスト爆発を、トークン単位の粒度で捕捉できる点が実用上の大きなメリットだ。

デバッグシナリオとしては、たとえば「ツール呼び出しが重複している」疑いがある場合、トレースのウォーターフォール(滝グラフ)を開いて同一ツールのexecute_toolスパンが複数並んでいないかを確認するアプローチが紹介されている。冒頭のrefund-lookupの二重呼び出しのような失敗も、トレースビューがあれば「いつ・何回・どの順序でツールが呼ばれたか」を数秒で特定できる。

エージェント開発の現場では、LangChainやLangSmith、あるいはArize AIなどのツールがオブザーバビリティの文脈で語られることが増えているが、この記事は特定フレームワークへの依存なしにOpenTelemetry標準だけで完結する実装を示している点が実用的だ。既存のどのオブザーバビリティスタックにも接続できる汎用性が、このアプローチの強みといえる。

詳細はAI Agent Observability: Logging, Tracing, and Debugging Explainedを参照していただきたい。