ログインお問い合わせ無料で開始
Reliability and Routing2026年7月30日Flatkey Team

LLM API のオブザーバビリティ: メトリクス、トレース、ログ、コスト

検証済みの成功指標、分散トレース、安全な構造化ログ、SLO、アラート、そして受理されたタスクあたりのコストを使って LLM API を監視するための本番向けガイドです。

LLM API のオブザーバビリティ: メトリクス、トレース、ログ、コスト

LLM API のオブザーバビリティとは、あらゆるモデル呼び出しを、次の 4 つの本番上の問いに答えられる十分な構造化された証拠へと変える実践です。

  1. リクエストは成功したか?
  2. ユーザーはどれくらい待ったか?
  3. リクエストは何を消費し、いくらかかったか?
  4. システムはなぜそのモデル、リトライ、またはフォールバックを選んだのか?

通常の HTTP モニタリングは必要ですが、それだけでは不十分です。200 OK であっても、無効な JSON、空の応答、拒否された応答、壊れたツール呼び出し、あるいはアプリケーション契約に違反する出力を含むことがあります。リクエストは 3 回の試行の後に成功しても、期待よりも 4 倍のコストがかかっている場合があります。

実践上の目的は、すべてのプロンプトをログに残すことではありません。ユーザー データを漏らすことなく、アプリケーションの結果とモデル、プロバイダー、ルート、レイテンシ、トークン使用量、リトライ、コストを結びつける、小さく一貫したテレメトリ契約を作ることです。

このガイドでは、メトリクス、トレース、構造化ログ、サービスレベル目標、ダッシュボード、アラートを使ってその契約を構築する方法を示します。

LLM API のオブザーバビリティが説明すべきこと

有用なオブザーバビリティ システムがあれば、オンコール エンジニアは症状から原因へ素早く移動できます。

本番上の問い 必要な証拠
なぜレイテンシが急増したのか? エンドツーエンドの所要時間、プロバイダーの所要時間、キュー時間、最初のトークンまでの時間、モデル、リージョン、リトライ回数
なぜコストが上昇したのか? 入力トークン、出力トークン、利用可能な場合はキャッシュ済みトークン、モデル価格のスナップショット、試行回数、受理タスク率
なぜユーザーは不正な結果を見ているのか? 出力バリデーターの結果、スキーマ エラー、拒否状態、ツール呼び出し結果、評価スコア、プロンプト バージョン
なぜトラフィックは別のモデルに移動したのか? ルート ポリシー、選択されたターゲット、フォールバック理由、サーキットブレーカーの状態、プロバイダー エラー
インシデントはプロバイダー固有か? プロバイダー、モデル、アカウントまたはデプロイ、リージョン、ステータスコード、プロバイダー リクエスト ID
1 件のリクエストを再現できるか? 内部リクエスト ID、トレース ID、サニタイズ済み入力フィンガープリント、プロンプト バージョン、モデル パラメーター

最初の設計原則はシンプルです。トランスポート契約だけでなく、アプリケーション契約を測定することです。

5 つのテレメトリ レイヤー

LLM API のモニタリングは、すべてのシグナルを 1 つのダッシュボードに押し込むのではなく、5 つのレイヤーに分けると容易になります。

1. リクエスト メトリクス

メトリクスは傾向を示し、アラートを支えます。次の項目についてカウンターとヒストグラムを記録します。

  • リクエスト数
  • エンドツーエンドのレイテンシ
  • ストリーミング応答の最初のトークンまでの時間
  • プロバイダーまたはモデル呼び出しのレイテンシ
  • 成功、失敗、キャンセル、タイムアウトしたリクエスト
  • HTTP 429 および 5xx 応答
  • リトライおよびフォールバックの試行
  • 入力、出力、キャッシュ済みトークン
  • 見積もりコストと照合済みコスト

メトリクスにはラベルの上限が必要です。適切なラベルには providermodelrouteenvironmentstatuserror_type があります。ユーザー ID、リクエスト ID、プロンプト本文、完全な URL のような高カーディナリティのラベルは避けてください。

2. 分散トレース

トレースは、1 件のリクエストが API、キュー、検索レイヤー、ツール呼び出し、ゲートウェイ、モデルプロバイダーをまたいでたどる経路を説明します。

実用的なトレース階層は次のようになります。

POST /support/reply
├── retrieve_customer_context
├── llm.route
│   ├── llm.attempt provider_a/model_primary
│   └── llm.attempt provider_b/model_fallback
├── validate_structured_output
└── persist_draft

各モデル試行は、それぞれ独立した span にする必要があります。2 つのプロバイダーを試す場合、トレースには、両方を 1 つの不透明な llm.call span の中に隠すのではなく、2 回の試行が表示されるべきです。

OpenTelemetry's Generative AI semantic conventions は、生成 AI の span、メトリクス、イベントに関する有用な共通語彙を提供します。属性が進化したときに意図的に移行できるよう、規約のバージョンをテレメトリスキーマの一部として扱ってください。

3. 構造化ログ

ログは、メトリクスのラベルとしては高コストすぎる、または詳細すぎる個別の判断や診断コンテキストを記録します。

有用なイベントには次のものがあります。

  • llm.request.started
  • llm.route.selected
  • llm.retry.scheduled
  • llm.fallback.selected
  • llm.response.validated
  • llm.request.completed
  • llm.request.failed

すべてのイベントには、同じ相関フィールド、つまり request_idtrace_idroutemodelproviderprompt_version、および attempt を含める必要があります。

4. 品質と契約シグナル

品質はステータスコードからは推測できません。可能な限り、決定論的な検証を追加してください。

  • JSON が正常にパースされた
  • 必須のスキーマフィールドが存在する
  • ツール名と引数が許可されている
  • 必要な場合、引用リストが存在する
  • 出力長が製品の制限内である
  • 拒否または安全性の状態が認識される
  • ビジネスルールのチェックを通過する

主観的なタスクについては、後でサンプル化した評価結果を付与してください。オンラインのリクエストテレメトリとオフライン評価は、安定した request ID または sample ID を通じて結合しておきます。

本番モデルを置き換える前に、集計されたレイテンシとトークン単価だけに頼るのではなく、再現可能な AI model evaluation workflow を使用してください。

5. コストとビジネス成果

トークン数は利用状況のシグナルであって、ビジネス成果ではありません。モデルの利用を、製品が重視する単位に結び付けてください。

  • 承認されたサポート返信 1 件あたりのコスト
  • 完了したコーディングタスク 1 件あたりのコスト
  • レビュー担当者に承認された生成商品画像 1 枚あたりのコスト
  • 充実化された有望リード 1 件あたりのコスト
  • 成功した構造化抽出 1 件あたりのコスト

最も有用な式は次のとおりです。

有効な承認済みタスク 1 件あたりのコスト = モデル総コスト / 承認済みタスク数

これにより、見せかけの節約が明らかになります。安価なモデルでも、リトライ、検証失敗、または人手によるやり直しが増えると、有効コストは増加する可能性があります。

すべてのモデル呼び出しに対する最小限のテレメトリ契約

バージョン管理されたイベントスキーマから始めてください。正確なフィールド名はお使いのオブザーバビリティスタックに合わせてよいですが、概念は安定しているべきです。

{
  "schema_version": "llm-observability.v1",
  "timestamp": "2026-07-30T09:00:00Z",
  "request_id": "req_internal_01",
  "trace_id": "7c4b...",
  "environment": "production",
  "feature": "support_reply",
  "route": "support-default",
  "provider": "provider-a",
  "model": "model-primary",
  "prompt_version": "support-reply-v12",
  "attempt": 1,
  "stream": true,
  "status": "success",
  "http_status": 200,
  "latency_ms": 1840,
  "time_to_first_token_ms": 410,
  "input_tokens": 1640,
  "output_tokens": 284,
  "cached_input_tokens": 900,
  "estimated_cost_usd": 0.0068,
  "validator": "passed",
  "fallback_reason": null,
  "provider_request_id": "redacted-or-scoped-value"
}

生のプロンプトとレスポンスを必須フィールドにしないでください。明確な必要性、承認済みの保持ポリシー、適切なアクセス制御、安全なマスキング経路がある場合にのみ保存してください。

最初のダッシュボードに載せるべきメトリクス

最初から40枚のパネルで始めないでください。ユーザーがレイテンシとコストの予算内で有効な結果を受け取れているかを答えられる、1つの運用ダッシュボードを作成します。

トラフィックと成功

  • 1分あたりのリクエスト数
  • トランスポート成功率
  • 検証済み成功率
  • キャンセル率
  • タイムアウト率
  • リトライ増幅率
  • フォールバック率

検証済み成功率を主要な可用性シグナルにするべきです:

検証済み成功率 = アプリケーション契約を通過したリクエスト / 対象リクエスト

これは 2xx responses / requests よりも厳密で、より有用です。

レイテンシ

平均ではなく分布を追跡します:

  • エンドツーエンドの p50、p95、p99 レイテンシ
  • プロバイダー呼び出しの p50、p95、p99 レイテンシ
  • 最初のトークンまでの時間の p50 と p95
  • キュー待ち p95
  • ツール実行 p95
  • 検証時間 p95

ストリーミング経路と非ストリーミング経路は分けてください。ストリーミング要求は、完了までの総時間が長くても、最初のトークンまでの時間が良ければ応答性が高く感じられます。

信頼性

  • プロバイダーおよびモデル別の 429 率
  • プロバイダーおよびモデル別の 5xx 率
  • ネットワークエラー率
  • 不正形式またはスキーマ無効のレスポンス率
  • ツール呼び出し失敗率
  • サーキットブレーカーのオープン状態
  • リトライ予算消化率

レート制限が頻繁な原因である場合は、各アプリケーションワーカーで無秩序にリトライするのではなく、上限付きの RPM と TPM 制限に対する LLM リトライ戦略を使ってください。

使用量とコスト

  • 機能別の入力トークンと出力トークン
  • 承認済みタスクあたりのトークン数
  • リクエストあたりの推定コスト
  • 承認済みタスクあたりのコスト
  • リトライコスト
  • フォールバックコスト差分
  • 予算に対する日次支出
  • コスト見積もりとプロバイダー請求書または使用量エクスポートとの比較

estimated_costreconciled_cost の両方を保持してください。前者はほぼリアルタイムの監視を可能にし、後者は信頼できる請求データが到着した後に推定値を修正します。

リトライとフォールバックルーティングをトレースする方法

リトライとフォールバックは、基本的な監視が通常機能しなくなる箇所です。すべての試行が 1 つのステータスフィールドを共有していると、高コストで劣化したリクエストも正常に見えてしまうことがあります。

各試行について、次のフィールドを記録してください:

フィールド 重要な理由
attempt 増幅と意思決定の順序を示す
target_id 秘密情報なしで、プロバイダー、デプロイメント、リージョン、モデルを識別する
reason タイムアウト、429、5xx、検証失敗、ポリシールーティングを区別する
remaining_budget_ms ルーターがユーザー向けの期限を尊重したことを証明する
safe_to_repeat 冪等性の判断を明示的にする
output_started ストリーミング出力がクライアントに届いた後の安全でないフォールバックを防ぐ
contract_compatible 次のターゲットが必要なスキーマ、ツール、モダリティをサポートしていることを確認する

本番環境向けの LLM API フォールバックルーティングのプレイブック では、判断ポリシーを定義するべきです。そのうえでオブザーバビリティが、ルーターがそれに従ったことを証明します。

TypeScript のインストルメンテーションパターン

次の例では、テレメトリを特定のモデル SDK から独立させたままにしています。1 つの親ルートスパンと、各試行ごとに 1 つの子スパンを記録します。

import { context, SpanStatusCode, trace } from "@opentelemetry/api";

const tracer = trace.getTracer("ai-gateway");

type ModelAttempt = {
  provider: string;
  model: string;
  reason: "primary" | "retry" | "fallback";
};

export async function runModelRoute(
  attempts: ModelAttempt[],
  callModel: (attempt: ModelAttempt) => Promise<{
    text: string;
    usage?: { inputTokens?: number; outputTokens?: number };
    providerRequestId?: string;
  }>,
) {
  return tracer.startActiveSpan("llm.route", async (routeSpan) => {
    routeSpan.setAttribute("app.llm.route", "support-default");
    routeSpan.setAttribute("app.llm.attempt_limit", attempts.length);

    try {
      for (const [index, attempt] of attempts.entries()) {
        const result = await tracer.startActiveSpan(
          "llm.attempt",
          { attributes: {
            "gen_ai.system": attempt.provider,
            "gen_ai.request.model": attempt.model,
            "app.llm.attempt": index + 1,
            "app.llm.reason": attempt.reason,
          } },
          context.active(),
          async (attemptSpan) => {
            const startedAt = performance.now();

            try {
              const response = await callModel(attempt);
              const valid = response.text.trim().length > 0;

              attemptSpan.setAttribute("app.llm.validated", valid);
              attemptSpan.setAttribute(
                "gen_ai.usage.input_tokens",
                response.usage?.inputTokens ?? 0,
              );
              attemptSpan.setAttribute(
                "gen_ai.usage.output_tokens",
                response.usage?.outputTokens ?? 0,
              );
              attemptSpan.setAttribute(
                "app.llm.latency_ms",
                performance.now() - startedAt,
              );

              if (!valid) {
                throw new Error("response_validation_failed");
              }

              attemptSpan.setStatus({ code: SpanStatusCode.OK });
              return response;
            } catch (error) {
              attemptSpan.recordException(error as Error);
              attemptSpan.setStatus({
                code: SpanStatusCode.ERROR,
                message: (error as Error).message,
              });
              return null;
            } finally {
              attemptSpan.end();
            }
          },
        );

        if (result) {
          routeSpan.setAttribute("app.llm.selected_attempt", index + 1);
          routeSpan.setStatus({ code: SpanStatusCode.OK });
          return result;
        }
      }

      throw new Error("llm_route_exhausted");
    } catch (error) {
      routeSpan.recordException(error as Error);
      routeSpan.setStatus({
        code: SpanStatusCode.ERROR,
        message: (error as Error).message,
      });
      throw error;
    } finally {
      routeSpan.end();
    }
  });
}

本番環境では、スパンに加えてメトリクスのカウンターと構造化ログイベントを追加してください。また、利用可能な場合はプロバイダーのリクエスト識別子も取得してください。これらは、モデルプロバイダーへインシデントをエスカレーションする際にしばしば不可欠です。そうした識別子は公開エラーメッセージに含めないでください。

プロンプト漏えいのないログ

最も安全なデフォルトは、メタデータ優先のログ記録です。

デフォルトで記録する

  • 内部リクエスト ID とトレース ID
  • プロバイダーのリクエスト ID
  • 機能名とルート名
  • プロバイダー、モデル、デプロイメントエイリアス
  • プロンプトテンプレートのバージョン
  • temperature や最大出力トークン数などのパラメータ
  • トークン使用量
  • レイテンシと最初のトークンまでの時間
  • エラークラスと再試行の判定
  • バリデーターの結果
  • サニタイズされたツール名

デフォルトで記録しない

  • 生のプロンプトや応答
  • API キーや認証ヘッダー
  • 顧客のシークレット
  • 取得したドキュメント
  • 個人情報または規制対象データを含むツール引数
  • 完全なファイルパスやデータベースレコード
  • 署名付き URL

デバッグや評価のためにコンテンツの取得が必要な場合は、別途サンプリングし、保存前にマスキングし、アクセスを制限し、暗号化し、保持期間を短く設定してください。安全な API キー管理ガイドでは、シークレット、ログ記録、ローテーション、インシデント対応に関する関連制御を扱っています。

LLM を活用した機能の SLO

LLM のサービスレベル目標は、プロバイダーアカウントではなく、ユーザーに見える機能を表すべきです。

構造化されたサポート返信機能の SLO の例:

SLO 例の目標値
検証済み可用性 対象リクエストの 99.5% が契約準拠の出力を返す
対話レイテンシ 95% が 1.5 秒以内に最初のトークンを生成する
完了レイテンシ 95% が 8 秒以内に完了する
コストガードレール 99% がリクエストあたりのコスト上限を下回る
フォールバックの抑制 ローリング 1 時間あたりでフォールバックが必要なのは 3% 未満

これらの数値は例であり、普遍的な目標ではありません。ユーザーの期待、タスクの複雑さ、プロバイダーの挙動、ユニットエコノミクスに基づいて設定してください。

エラーバジェットを使って、機能リリースの速度を落とすか、ルートポリシーを厳格化するか、トラフィックを移すかを判断します。プロバイダーが自社の可用性目標を満たしていても、キューイング、ツール、検証、またはフォールバックの挙動によって失敗が追加されるため、あなたの製品が SLO を満たせないことがあります。

症状でアラートし、原因で診断する

ユーザーへの影響がある場合にオペレーターを呼び出してください。原因の可能性があるものには、低重要度のアラートやダッシュボードの注釈を使います。

ページング対象の症状

  • 検証済み成功率が SLO を下回る
  • p95 の最初のトークンまでの時間がユーザー向けしきい値を超える
  • ルート枯渇率が急増する
  • 承認されたタスクあたりのコストがガードレールを超える
  • 重要機能に、健全で契約互換のターゲットがない

診断シグナル

  • 特定プロバイダーの 429 発生率が上昇する
  • 特定モデルのスキーマ失敗率が変化する
  • 再試行増幅が増加する
  • キュー待ち時間が増える
  • サーキットブレーカーが開く
  • プロンプトのリリース後にトークン使用量が変化する

すべてのプロバイダーの 5xx でページングしないでください。フォールバックが機能しており、ユーザーが依然としてレイテンシ予算内で有効な応答を受け取っているなら、そのイベントはオンコールエンジニアを起こすことなく調査が必要なだけかもしれません。

3 つのダッシュボード運用モデル

ダッシュボード 1: ユーザー体験

機能ごとに、検証済み可用性、レイテンシ、最初のトークンまでの時間、タスク完了、ユーザーに見えるエラーを表示します。

Dashboard 2: ルーティングとプロバイダー

トラフィックシェア、プロバイダーエラー、リトライ、フォールバック、サーキットブレーカー、ルート枯渇、モデルおよびターゲット別のレイテンシを表示します。

Dashboard 3: 利用状況と経済性

トークン、推定支出、突合済み支出、受理されたタスクあたりのコスト、予算差異、コスト上位の機能を表示します。

3つすべてでデプロイメントとプロンプトバージョンの注釈を維持してください。そうしないと、リリース直後に始まった回帰が、単なるプロバイダーのばらつきに見えてしまうことがあります。

ロールアウトのチェックリスト

  1. 1つのバージョン管理されたイベントスキーマを定義する。
  2. プロダクト境界で内部リクエストIDを生成する。
  3. キュー、ツール、モデル呼び出しを通じてトレースコンテキストを伝播する。
  4. 各モデル試行ごとに子スパンを作成する。
  5. 返却されたプロバイダーのリクエストIDを記録する。
  6. 決定論的な出力検証を追加する。
  7. リトライとフォールバックの理由を明示的に追跡する。
  8. バージョン管理された価格表から推定コストを算出する。
  9. 推定値を正規の利用状況または請求エクスポートと突合する。
  10. プロバイダーダッシュボードより前に、1つのユーザー結果ダッシュボードを構築する。
  11. 検証済み成功とレイテンシのSLOを設定する。
  12. プロンプト、応答、シークレット、および機密性の高いツールデータをマスキングまたは除外する。
  13. タイムアウト、429、5xx、壊れた出力、ルート枯渇に対する失敗テストを実行する。
  14. 本番環境でメトリクスを有効化する前にラベルのカーディナリティを確認する。
  15. リスクに応じてトレースをサンプリングする。通常の成功よりもエラーや遅いリクエストを多めに保持する。

一般的なオブザーバビリティのミス

すべての200を成功として扱う

契約バリデーターを追加し、検証済み成功を別途報告してください。

すべてのリクエストで生のプロンプトをログに記録する

これはプライバシー、セキュリティ、保持期間、コストの問題を生みます。メタデータと制御されたサンプリングを優先してください。

1つの所要時間の中にリトライを隠す

1回の試行ごとに1つのスパンと1つのイベントを作成し、オペレーターが増幅を確認できるようにしてください。

モデル名を唯一のルート識別子として使う

プロバイダー、デプロイメントまたはアカウントの別名、リージョン、ルートポリシーを追跡してください。同じモデルでもターゲットによって挙動が異なる場合があります。

平均レイテンシだけでアラートを出す

平均値では末端の問題が見えません。p95 と p99 を使用し、最初のトークンまでの時間と総完了時間を分けてください。

推定コストを永遠に信頼する

価格表、キャッシュの扱い、プロバイダーの会計は変わる可能性があります。請求データと照合し、価格表のバージョンを記録してください。

テレメトリラベルを無制限に増やす

リクエストIDや顧客識別子はメトリクスのラベルではなく、トレースまたはログに含めるべきです。

AI APIゲートウェイが役立つ場面

マルチプロバイダーのアプリケーションでは、認証、モデル名、リトライ、利用フィールド、エラー、請求エクスポートごとに個別のアダプターが必要になることがあります。ゲートウェイは、内部テレメトリでプロバイダーとモデルの詳細を保持しながら、アプリケーションに1つの安定したAPI境界を提供することで、その統合面を縮小できます。

Flatkeyは、1つのAPIキー、1つのOpenAI互換エンドポイント、そして主要プロバイダー全体のモデルへのアクセスを提供します。これにより、ワークロードが異なるテキスト、画像、または動画モデルを使用していても、アプリケーション側のテレメトリ契約を一元化できます。ゲートウェイはプロダクトレベルのオブザーバビリティを置き換えるものではありません。アプリケーションは引き続き、機能、プロンプトバージョン、検証結果、ユーザーに見えるレイテンシ、受理されたタスクの結果を記録する必要があります。

チームがプロバイダーを統合している場合は、まず AI API ゲートウェイ アーキテクチャガイド を確認し、その後、本番トラフィックを流す前にこの記事のテレメトリ契約を追加してください。

よくある質問

LLM API のオブザーバビリティとは何ですか?

LLM API のオブザーバビリティとは、モデル搭載機能に対するメトリクス、トレース、ログ、品質チェック、使用量、コストデータの収集と相関付けです。これは、プロバイダーの挙動と、アプリケーションが有効なユーザー成果を返したかどうかの両方を説明します。

LLM API では何を監視すべきですか?

検証済み成功率、エンドツーエンドのレイテンシー、最初のトークンまでの時間、プロバイダーレイテンシー、429 および 5xx の発生率、リトライ、フォールバック、トークン使用量、推定コスト、受け入れ済みタスクあたりのコスト、出力契約の失敗を監視してください。

プロンプトとレスポンスはトレースに保存すべきですか?

デフォルトでは保存しません。まずメタデータを保存してください。コンテンツの取得は、明確に定義されたデバッグまたは評価目的に限り、マスキング、アクセス制御、暗号化、サンプリング、保持ポリシーを伴って行ってください。

LLM モニタリングと LLM オブザーバビリティの違いは何ですか?

モニタリングは、既知のメトリクスが閾値を超えたことを知らせます。オブザーバビリティは、アプリケーション、ルート、プロバイダー、モデル、ツール、出力契約にまたがる新しい障害モードを調査するのに十分な相関済み証拠を提供します。

LLM のリクエストあたりのコストはどのように計算しますか?

課金対象の入力、出力、キャッシュ入力、メディア、またはその他の使用単位に、バージョン管理された価格表を掛け、その後でリトライとフォールバック試行のコストを加算します。推定値はプロバイダーまたはゲートウェイの請求データと照合してください。

どのリクエスト ID を保存すべきですか?

独自の内部リクエスト ID とトレース ID を作成し、API が返す場合はプロバイダーのリクエスト ID も保存してください。内部 ID はシステム同士をつなぎ、プロバイダー ID は外部サポートや障害エスカレーションに役立ちます。

インシデントの前にテレメトリ契約を構築する

モデル呼び出しで何を記録するかを決める最適なタイミングは、本番トラフィックが到着する前です。検証済み成功、レイテンシー分布、試行ごとの 1 スパン、制限付きメトリクスラベル、メタデータ優先のログ、受け入れ済みタスクあたりのコストから始めてください。そのうえで、ルーターが処理することを期待する失敗を意図的に発生させてシステムをテストします。

その基盤があれば、「AI 機能が遅くて高コストだ」という曖昧な報告は、追跡可能な判断に変わります。どの機能か、どのルートか、どのモデルか、どの試行か、どの失敗か、どれだけの遅延か、どれだけのコストか、です。

1 つの OpenAI 互換 API の背後にあるマルチモデルルートを比較できる準備ができたら、Flatkey の料金をご覧ください。