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: ゲートウェイは使用量をテナントのコストと割り当ての消費量に変換しました。調整: 後でプロバイダーの使用状況またはコスト データが行を確認または調整しました。
このモデルは、テキストを生成したすべてのストリームを「成功かつ正確」としてマークするという、よくある分析のバグを防ぎます。ストリームは、ユーザーにとって役立つものであると同時に、プロバイダーからは不完全なもの、請求額が見積もられているもの、調整が保留中のものもあります。
推奨される元帳フィールド
リクエスト時の行は小さくても明示的にしてください:
{
"request_id": "gw_req_...",
"テナントID": "テナント_123",
"api_key_id": "key_456",
"プロバイダー": "オープンアイ|人類|ジェミニ|...",
"provider_request_id": null、
"モデル": "プロバイダーモデル ID",
"状態": "受け入れ済み"、
「ストリーム」: 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、
「エラークラス」: null
}
重要な区別は、output_tokens_billed と output_tokens_delivered_estimate です。ユーザーは、アプリケーションに何が届いたかを気にします。財務部門はプロバイダーに請求された金額を気にします。これらの数値は、切断、ツール呼び出しストリーム、非表示の推論トークン、キャッシュされたトークン、安全停止、またはゲートウェイのタイムアウト後には異なる場合があります。
プロバイダ固有のキャプチャ ルール
プロバイダーに依存しないOpenAI 互換 API はアプリケーション開発者にとって便利ですが、ゲートウェイ アダプターには依然としてプロバイダー固有のアカウンティング ルールが必要です。
OpenAI 互換ストリーミング
OpenAI ルートの場合、サポートされている場合、アップストリームの使用状況レポートを有効にするゲートウェイ オプションを公開します。一般的なパターンは、次のようなゲートウェイ レベルのデフォルトを受け入れることです。
{
「ストリーム」: true、
"ストリームオプション": {
"include_usage": true
}
}
ダウンストリームの呼び出し元がそれを省略した場合、ゲートウェイは、互換性のあるルートにそれを挿入するかどうかを決定できます。一部のクライアントは正確なワイヤ互換性を期待しており、一部のモデルまたはアップストリームは同じ方法で最終的な使用法をサポートしていない可能性があるため、この動作を文書化してください。
推奨事項: 初期のチャンクからテナント コストを決済しないでください。最終的な使用状況イベントがキャプチャされるか、プロバイダーの応答が使用されずに終了するか、ストリームがエラーまたはキャンセル パスに入るまで、レジャー行を開いたままにしておきます。
人為的なストリーミング
Anthropic の累積使用には別のルールが必要です。ゲートウェイが出力トークン数 10、25、40 の 3 つの message_delta イベントを検出した場合、出力数は 75 ではなく 40 になります。
let lastUsage = null;
for await (anthropicStream の const イベント) {
if (event.type === "message_delta" && events.usage) {
if (latestUsage &&event.usage.output_tokens < 最新Usage.output_tokens) {
Emit("cumulative_usage_regressed", requestId);
}
最新の使用量 = イベント.使用量;
}
forwardToClient(イベント);
}
SettleFromlatestCumulativeUsage(latestUsage);
推奨事項: 最新の累積使用量値を記録し、それが後退した場合は可観測性イベントを発行します。リグレッションは、パーサーのバグ、重複したイベント、プロバイダーの変更、または混合ストリームを示している可能性があります。
Gemini および Vertex スタイルのストリーミング
Gemini は、体感的な遅延を軽減するためにストリーミング チャンクをサポートしています。 Vertex スタイル SDK では、ストリーミングは非同期ストリームと集約された応答オブジェクトの両方を公開できます。ゲートウェイは、利用可能な場合、その集約されたパスを保存する必要があります。
const streamResult = await model.generateContentStream(request);
for await (streamingResult.stream の const チャンク) {
forwardChunk(チャンク);
countDeliveredBytesOrText(チャンク);
}
const 集約 = ストリーミング結果.応答を待ちます;
SettleFromAggregatedUsage(集約);
推奨事項: SDK が完全な応答レコードを提供する場合は、表示されているチャンクからすべてのアカウンティングを構築しないようにしてください。チャンクはレイテンシー用です。多くの場合、最終オブジェクトの方が請求や分析に適しています。
クライアントの切断を第一級のアカウンティング イベントとして処理する
クライアントの切断により、多くのゲートウェイは損失を被ったり、顧客に過剰な料金を請求したりします。ブラウザーのタブが閉じたり、モバイル ネットワークが切断されたり、アプリケーションがリクエストをキャンセルしたりします。ゲートウェイはダウンストリーム ソケットが閉じていることを認識しますが、アップストリーム プロバイダーがまだ生成している可能性があります。
ゲートウェイは明示的にポリシーを選択する必要があります。
- アップストリームをすぐにキャンセルする: 無駄な生成とプロバイダーのコストが削減されますが、UI が切断された後もバックエンドが結果を必要とするワークフローが中断される可能性があります。
- バックグラウンドでアップストリームを続行する: サーバー側のコンシューマの作業を保存できますが、生成および請求されたすべてのトークンがユーザーに表示されるわけではありません。
- ルート依存の動作: インタラクティブ チャットの場合はキャンセルし、ジョブのようなワークフローの場合は続行し、設定をテナントに表示します。
インタラクティブ ストリーミングの実質的なデフォルトは、ダウンストリーム クライアントが切断されたときにアップストリームをキャンセルし、レジャー行を client_aborted としてマークすることです。キャンセル中に最終的な使用量が到着した場合は、その正式な使用量から決済します。そうでない場合は、正確であるかのように振る舞うのではなく、行を estimated または pending_reconciliation としてマークします。
downstream.on("close", async () => {
if (!providerCompleted) {
ledger.markClientAborted(requestId);
上流で待機します。abort().catch(() => {
ledger.emit("upstream_cancel_failed", requestId);
});
}
});
推奨: final、provider_reconciled、estimated、waived、pending_reconciliation などの透明な請求ラベルを公開します。これは、ストリーミングされたすべての呼び出しを即座に正確なものとして表示するよりも防御可能です。
ストリーム中のクォータの適用
正確な請求は通常、プロバイダーの最終的な使用量によって異なりますが、クォータの適用は常に終了まで待つことができません。正確な使用量は飛行中は入手できないため、予算が厳しいテナントは無期限にストリーミングを許可すべきではありません。
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 つのパスを監査してください。最初の数チャンクの後でクライアントを強制的に切断し、台帳の行を検査します。正確に見えるトークン数で「成功」と表示される場合は、分析が嘘をついている可能性があります。
解決策は、ストリーミングを放棄しないことです。高速なユーザー エクスペリエンスを維持しながら、ストリームの完了、キャンセル、プロバイダー エラー、最終使用の欠落、および調整のアカウンティング状態を明示的にします。これにより、製品チームには応答性の高い出力、財務チームには擁護可能なコスト、サポート チームには推測することなく部分的な応答を説明するための十分な証拠が得られます。