ガイドと洞察

AI API ゲートウェイでのストリーミング トークン アカウンティング: 最終的な使用、キャンセル、部分的な応答

ストリーミングにより体感的な遅延は改善されますが、ゲートウェイがバイトのみをプロキシする場合、AI の使用状況分析と請求に支障をきたす可能性があります。ここでは、最終的な使用状況、中止されたストリーム、プロバイダー エラー、部分的な応答をキャプチャするための実用的なステート マシン パターンを示します。

ストリーミング LLM 応答はプロキシするのが簡単ですが、正しく請求するのは困難です。 AI API ゲートウェイ がサーバーから送信されたイベントをクライアントに転送するが、最初のチャンクを使用状況レコードとして扱う場合、テナント分析はドリフトします。このドリフトは通常、「ユーザーは答えの半分しか見ていない」、「プロバイダーはダッシュボードに表示されている以上の請求を行った」、「クォータの解放が早すぎた」、または「タイムアウトによりトークンが生成されたが、請求書明細が表示されなかった」などの紛争に現れます

根本的な問題は、ストリーミングされた呼び出しが 1 つのイベントではないことです。これらは、リクエストの受け入れ、アップストリーム ストリームのオープン、配信されたバイト数、最終的な使用状況の報告、プロバイダーの停止、クライアントの切断、ゲートウェイのタイムアウト、および請求の決済というシーケンスです。信頼できるゲートウェイは、完了した HTTP 応答が唯一の成功パスであると想定するのではなく、これらの状態を明示的にモデル化する必要があります。

障害モード: ストリーミングにより会計境界が隠蔽される

非ストリーミング完了は通常、使用状況メタデータを含む 1 つの応答オブジェクトを返します。ゲートウェイは、その使用状況の正規化、台帳行の書き込み、割り当ての更新、および分析の出力を 1 つのパスで行うことができます。

ストリーミングにより境界が変更されます。ユーザー エクスペリエンスは段階的に変化しますが、請求の真実は、プロバイダー固有の最終イベント、累積デルタ、集約された SDK 応答を通じて、または後でプロバイダー レポート API を通じて最後に到着する可能性があります。最終的な使用イベントの前にクライアントが切断された場合、プロバイダーがまだ生成して追加のトークンを請求している間に、ゲートウェイは応答の一部しか配信していない可能性があります。

事実: OpenAI は、使用状況データが必要なストリーミング呼び出し元が stream_options を include_usage に設定する必要があることを文書化しています。 OpenAI は組織レベルの使用量とコストのエンドポイントも提供しますが、財務上の目的では使用量とコストが必ずしも完全に一致するとは限らないことに注意してください。

事実: Anthropic ストリーミングは、message_start、content_block_delta、message_delta、message_stop などのサーバー送信イベントを使用します。 message_delta 使用状況情報は累積的であるため、ゲートウェイは各使用量デルタを加算してはなりません。

事実: Gemini および Vertex スタイルのストリーミング API は増分チャンクを公開できますが、SDK は集約された応答オブジェクトを提供することもあります。ゲートウェイの場合、その集約されたパスは、表示されているチャンクだけよりも、完全な使用状況を把握するための優れたソースとなる可能性があります。

ブール値の成功フラグではなく、ストリーム ステート マシンを使用します

ストリーミングされたリクエストには、アップストリーム呼び出しが開始される前に永続的な使用記録が必要です。そのレコードは明示的な状態を経る必要があります。実際の最小値は次のとおりです:

  • accepted: ゲートウェイはキーを認証し、テナントに帰属し、オープンレジャー行を作成しました。
  • first_byte_sent: 少なくとも 1 つの出力イベントがダウンストリーム クライアントに到達しました。
  • provider_completed: 上流プロバイダは通常の停止信号または完了した応答オブジェクトを発行しました。
  • client_aborted: ダウンストリーム ソケットは通常のゲートウェイが完了する前に閉じられました。
  • provider_error: ストリームの開始後、または最終的な使用量が到着する前に、上流のプロバイダーがエラーを返しました。
  • gateway_timeout: ゲートウェイはレイテンシ バジェットを適用し、リクエストを終了しました。
  • settled: ゲートウェイは使用量をテナントのコストと割り当て消費量に変換しました。
  • reconciled: 後でプロバイダーの使用状況またはコスト データが行を確認または調整しました。

このモデルは、テキストを生成したすべてのストリームを「成功かつ正確」としてマークするという、よくある分析のバグを防ぎます。ストリームは、ユーザーにとって有益であると同時に、プロバイダーからの不完全なストリーム、請求の見積もり、保留中の調整も同時に行うことができます。

推奨される元帳フィールド

リクエスト時の行は小さくても明示的にしてください:

{
  "request_id": "gw_req_...",
  "tenant_id": "tenant_123",
  "api_key_id": "key_456",
  "provider": "openai|anthropic|gemini|...",
  "provider_request_id": null,
  "model": "provider-model-id",
  "state": "accepted",
  "stream": true,
  "input_tokens": null,
  "output_tokens_billed": null,
  "output_tokens_delivered_estimate": 0,
  "provider_usage_source": null,
  "billing_status": "pending_reconciliation",
  "client_abort_at": null,
  "provider_completed_at": null,
  "settled_at": null,
  "error_class": null
}

重要な分離は output_tokens_billed と output_tokens_delivered_estimate です。ユーザーは、アプリケーションに何が届いたかを気にします。財務部門はプロバイダーに請求された金額を気にします。これらの数値は、切断、ツール呼び出しストリーム、非表示の推論トークン、キャッシュされたトークン、安全停止、またはゲートウェイのタイムアウト後には異なる場合があります。

プロバイダ固有のキャプチャ ルール

プロバイダーに依存しない OpenAI 互換の API はアプリケーション開発者にとって便利ですが、ゲートウェイ アダプターには依然としてプロバイダー固有のアカウンティング ルールが必要です。

OpenAI 互換ストリーミング

OpenAI ルートについては、サポートされている場合、アップストリームの使用状況レポートを有効にするゲートウェイ オプションを公開します。一般的なパターンは、

のようなゲートウェイ レベルのデフォルトを受け入れることです。
{
  "stream": true,
  "stream_options": {
    "include_usage": true
  }
}

ダウンストリームの呼び出し元がそれを省略した場合、ゲートウェイは互換性のあるルートにそれを挿入するかどうかを決定できます。一部のクライアントは正確なワイヤ互換性を期待しており、一部のモデルまたはアップストリームは同じ方法で最終的な使用をサポートしていない可能性があるため、この動作を文書化してください。

推奨事項: 初期のチャンクからテナント コストを決済しないでください。最終的な使用状況イベントがキャプチャされるか、プロバイダーの応答が使用されずに終了するか、ストリームがエラーまたはキャンセル パスに入るまで、レジャー行を開いたままにしておきます。

Anthropic ストリーミング

Anthropic の累積使用量には別のルールが必要です。ゲートウェイが出力トークン数 10、25、および 40 の 3 つの message_delta イベントを検出した場合、出力数は 75 ではなく 40 になります。

let latestUsage = null;

for await (const event of anthropicStream) {
  if (event.type === "message_delta" && event.usage) {
    if (latestUsage && event.usage.output_tokens < latestUsage.output_tokens) {
      emit("cumulative_usage_regressed", requestId);
    }
    latestUsage = event.usage;
  }

  forwardToClient(event);
}

settleFromLatestCumulativeUsage(latestUsage);

推奨事項: 最新の累積使用量値を記録し、それが後退した場合は可観測性イベントを発行します。回帰は、パーサーのバグ、重複したイベント、プロバイダーの変更、または混合ストリームを示している可能性があります。

Gemini および頂点スタイルのストリーミング

Gemini はストリーミング チャンクをサポートし、体感的な遅延を削減します。 Vertex スタイル SDK では、ストリーミングは非同期ストリームと集約された応答オブジェクトの両方を公開できます。ゲートウェイは、利用可能な場合、その集約されたパスを保存する必要があります。

const streamingResult = await model.generateContentStream(request);

for await (const chunk of streamingResult.stream) {
  forwardChunk(chunk);
  countDeliveredBytesOrText(chunk);
}

const aggregated = await streamingResult.response;
settleFromAggregatedUsage(aggregated);

推奨事項: SDK が完全な応答レコードを提供する場合は、表示されているチャンクからすべてのアカウンティングを構築することは避けてください。チャンクはレイテンシー用です。多くの場合、最終オブジェクトの方が請求と分析に適しています。

クライアントの切断を第一級のアカウンティング イベントとして処理する

クライアントの切断により、多くのゲートウェイは損失を被ったり、顧客に過剰な料金を請求したりします。ブラウザーのタブが閉じたり、モバイル ネットワークが切断されたり、アプリケーションがリクエストをキャンセルしたりします。ゲートウェイはダウンストリーム ソケットが閉じていることを認識しますが、アップストリーム プロバイダーがまだ生成している可能性があります。

ゲートウェイは明示的にポリシーを選択する必要があります:

  • アップストリームをすぐにキャンセルします: 無駄な生成とプロバイダーのコストが削減されますが、UI が切断された後もバックエンドが結果を必要とするワークフローが中断される可能性があります。
  • バックグラウンドでアップストリームを続行: はサーバー側のコンシューマの作業を保持できますが、ユーザーには生成および請求されたすべてのトークンが表示されない可能性があります。
  • ルート依存の動作: インタラクティブ チャットの場合はキャンセルし、ジョブのようなワークフローの場合は続行し、設定をテナントに表示します。

インタラクティブ ストリーミングの実質的なデフォルトは、ダウンストリーム クライアントが切断されたときにアップストリームをキャンセルし、レジャー行を client_aborted としてマークします。キャンセル中に最終的な使用量が到着した場合は、その正式な使用量から決済します。そうでない場合は、正確であるかのように振る舞うのではなく、行 estimated または pending_reconciliation をマークしてください。

downstream.on("close", async () => {
  if (!providerCompleted) {
    ledger.markClientAborted(requestId);
    await upstream.abort().catch(() => {
      ledger.emit("upstream_cancel_failed", requestId);
    });
  }
});

推奨事項: final、provider_reconciled、estimated、waived、pending_reconciliation などの透明な請求ラベルを公開します。これは、ストリーミングされたすべての呼び出しを即座に正確なものとして表示するよりも防御可能です。

ストリーム中のクォータの適用

通常、正確な請求はプロバイダーの最終的な使用量に依存しますが、割り当ての適用は常に最後まで待機できるわけではありません。正確な使用量は実行中は取得できないため、予算が厳しいテナントは無期限にストリーミングを許可すべきではありません。

2 つのメカニズムを一緒に使用します:

  1. プリフライト予約: モデル、要求された最大トークン、テナント ポリシー、現在の残高に基づいて推定最大値を予約します。
  2. ストリーミング圧力チェック: ストリーム中に配信される出力を推定し、リクエストが構成された安全境界を越えた場合に停止します。

これは制御メカニズムであり、最終的な請求書ではありません。プロバイダーは、キャッシュされたトークン、推論トークン、マルチモーダル トークン、または非表示のトークンをゲートウェイの推定とは異なる方法でカウントする場合があります。

トレードオフ: リアルタイムの見積もりは予算の執行に役立ちますが、プロバイダーが請求するトークンとは異なる可能性があります。最終的な決済では、利用可能な場合は権威プロバイダーの使用を使用する必要があり、調整では後で見積もりを調整する必要があります。

アカウンティングのバグを捕捉する可観測性イベント

ゲートウェイが一般的なリクエスト ログだけを発行するのではなく、対象のイベントを発行すると、ストリーミング課金の失敗のデバッグが容易になります。次のようなイベントを追加します:

  • final_usage_missing: ストリームは正式な使用なしに終了しました。
  • cumulative_usage_regressed: 累積トークン数が後方に移動しました。
  • stream_ended_without_stop_event: 通常のプロバイダー停止マーカーは観察されませんでした。
  • aborted_after_provider_completion: プロバイダーは完了しましたが、ゲートウェイが転送を完了する前にダウンストリーム クライアントが閉じました。
  • settled_from_estimate: 最終的な使用量が入手できないため、テナント台帳では推定値が使用されました。
  • reconciliation_adjusted_usage: プロバイダーのレポートにより、後で行が変更されました。

事実: OpenTelemetry GenAI のセマンティック規則では、プロバイダーから返された使用状況情報が利用可能な場合はストリーミング応答に使用することを推奨しており、トークン数を効率的または正確に取得できない場合は使用状況メトリクスを報告しないように警告しています。

AI 使用状況分析 の場合、これはダッシュボードが信頼レベルをサポートする必要があることを意味します。ラベルのない最終値、推定値、調整値を組み合わせたグラフはきれいに見えますが、財務チームやサポート チームに誤解を与える可能性があります。

ストリーミング アカウンティングの適合テスト

ハッピーパス チャット プロンプトを使用した手動テストに依存しないでください。各プロバイダー アダプターには、元帳を破るケースに対する適合性テストが必要です:

  • 通常のストリーム: 最終使用量が到着し、停止イベントが観察され、台帳は final として解決されます。
  • ツール呼び出しストリーム: ツール呼び出しデルタが転送され、使用状況がキャプチャされ、構造化メタデータによってトークンのカウントが損なわれることはありません。
  • 安全停止または拒否停止: プロバイダーは早期に停止しますが、使用量は引き続き正しく安定します。
  • クライアントの強制切断: 部分的な出力後にダウンストリームが閉じられます。アップストリームはポリシーに従ってキャンセルまたは継続されます。
  • 部分出力後のアップストリーム 5xx: ゲートウェイは部分配信を記録し、リクエストを正常な成功としてマークしません。
  • 最終使用前のゲートウェイ タイムアウト: 行は推定または調整保留になります。
  • 最終イベントが欠落しています: アダプターは final_usage_missing を発行し、正確な請求ラベルを回避します。

これらのテストは、状態遷移、元帳フィールド、発行された可観測性イベント、およびダウンストリーム動作をアサートする必要があります。バイトごとのストリーム互換性だけでは十分ではありません。会計上の副作用は契約の一部です。

実際の実装チェックリスト

  • アップストリーム リクエストをディスパッチする前に、使用状況台帳の行を作成します。
  • リクエスト時にテナント、キー、ユーザー、モデル、ルート、プロバイダー、およびリクエスト識別子を保存します。
  • OpenAI 互換の stream_options.include_usage など、サポートされている場合はプロバイダーの最終使用状況レポートを有効にします。
  • 累積プロバイダーの場合は、イベントを合計する代わりに最新の使用量の値を保存します。
  • SDK が提供する場合、集約された応答オブジェクトを保持します。
  • プロバイダーに請求される使用量とは別に、配信された出力を追跡します。
  • 切断時に、ルート ポリシーに従ってアップストリームをキャンセルし、client_aborted をマークします。
  • 透明性のある請求ステータスを使用します: 最終、見積、調整保留、プロバイダ調整済み、または放棄。
  • アカウンティング固有の可観測性イベントを発行します。
  • リクエスト時のテナントの属性を維持しながら、プロバイダーの使用状況またはコストのレポートが利用可能な場合は、後で照合します。

テナントに表示する内容

テナントはすべての内部イベントを必要とするわけではありませんが、正直なラベルが必要です。有用な使用状況表には次のようなものがあります:

  • ステータス: 最終、推定、または調整済み。
  • リクエストの結果: 完了、クライアントが中止、プロバイダー エラー、またはゲートウェイ タイムアウト。
  • 提供された出力: クライアントに送信されたおおよそのテキストまたはバイト数。
  • 請求されたトークン: プロバイダーによって正規化された使用量が費用として使用されます。
  • 調整: 以降の調整差分。

この設計により、サポートの曖昧さが軽減されます。ユーザーが応答の一部だけを見た場合、ダッシュボードは、プロバイダーがすでに完了したかどうか、ゲートウェイが上流でキャンセルしたかどうか、料金が最終的なものであるか見積もられたものであるかを説明できます。

推奨事項と予測

推奨事項: ストリーミングされたリクエストをステート マシンとして扱い、正確な決済の前に正式な最終使用量を待ち、配信された出力を請求された使用量から分離し、推定行に正直にラベルを付けます。プロバイダ アダプタは、すべてのストリームを汎用バイト プロキシにフラット化するのではなく、プロバイダ固有の使用セマンティクスをエンコードする必要があります。

予測: ストリーミング アカウンティングは、トークンの推論、キャッシュされたトークンの割引、マルチモーダル処理、ツール使用の追跡、安全停止などの隠れた作業をモデルがより多く公開するにつれて、より重要になるでしょう。プロバイダーに請求される使用量とクライアントに表示される出力をすでに分離しているゲートウェイは、ストリーム テキストのみをカウントするゲートウェイよりも簡単に適応できます。

実用的な結論

ゲートウェイがストリーミングをサポートしている場合は、今すぐ 1 つのパスを監査してください。最初の数チャンクの後でクライアントを強制的に切断し、台帳の行を検査します。正確に見えるトークン数で「成功」と表示される場合は、分析が嘘をついている可能性があります。

解決策は、ストリーミングを放棄しないことです。高速なユーザー エクスペリエンスを維持しながら、ストリームの完了、キャンセル、プロバイダー エラー、最終使用の欠落、および調整のアカウンティング状態を明示的にします。これにより、製品チームには応答性の高い出力が提供され、財務チームには擁護可能なコストが提供され、サポート チームには推測することなく部分的な応答を説明するための十分な証拠が得られます。

関連書籍

FAQ

よくある質問

ゲートウェイの請求書では、推定されたトークン数から応答をストリーミングする必要がありますか?
必要に応じてリアルタイム クォータ保護の見積もりを使用しますが、利用可能な場合は、プロバイダーから返された使用量から正確なテナント コストを精算します。最終的な使用法が欠落している場合は、その行に推定または保留中の調整としてラベルを付けます。
配信された出力トークンが請求されたトークンと異なる場合があるのはなぜですか?
クライアントが切断されたり、ゲートウェイがタイムアウトになったり、プロバイダーが隠れた推論やマルチモーダル トークンをカウントしたり、ユーザーがバイトの受信を停止した後にプロバイダーが生成を終了したりする場合があります。プロバイダーに請求される使用量とは別に、配信された出力を追跡します。
Anthropic ストリーミング アカウンティングの最も一般的なバグは何ですか?
累積使用量イベントを合計します。 Anthropic message_delta の使用数は累積されるため、ゲートウェイはすべてのイベントを追加するのではなく、最新の値を保存する必要があります。
ストリーム中にブラウザが切断された場合はどうすればよいでしょうか?
対話型ルートの場合、実際のデフォルトは、アップストリーム要求をキャンセルし、台帳行を client_aborted としてマークし、到着した場合は権限のあるプロバイダーの使用のみから解決します。それ以外の場合は、行を推定済みまたは保留中の調整としてマークします。