指导和见解

AI API 网关中的流式令牌记账:最终使用、取消和部分响应

流媒体可以改善感知延迟,但如果网关仅代理字节,则可能会破坏人工智能使用分析和计费。这是一个实用的状态机模式,用于捕获最终使用、中止的流、提供者错误和部分响应。

流式 LLM 响应很容易代理,但难以正确计费。如果AI API网关将服务器发送的事件转发到客户端,但将第一个块视为使用记录,则租户分析将会发生偏差。这种偏差通常出现在争议中,例如:“用户只看到了一半的答案”、“提供商的账单比我们的仪表板显示的要多”、“配额释放得太早”或“超时产生了令牌但没有发票行。”

根本问题是流式通话不是一项事件。它们是一个序列:请求接受、上游流打开、字节交付、最终使用情况报告、提供者停止、客户端断开连接、网关超时和计费结算。可靠的网关应该显式地对这些状态进行建模,而不是假设完成的 HTTP 响应是唯一成功的路径。

失败模式:流式传输隐藏了计费边界

非流式完成通常会返回一个包含使用情况元数据的响应对象。网关可以标准化该使用情况、写入账本行、更新配额并一次性发出分析结果。

流媒体改变了边界。用户体验是增量式的,但计费真相可能会在特定于提供商的最终事件中、在累积增量中、通过聚合的 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:至少有一个输出事件到达下游客户端。
  • 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 的三个 message_delta 事件,则输出计数为 40,而不是 75。

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 和 Vertex 式流式传输

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。这比立即准确地显示每个流式调用更具防御性。

直播期间的配额强制执行

准确的计费通常取决于最终提供商的使用情况,但配额强制执行不能总是等到最后。不应允许具有硬预算的租户无限期地进行流式传输,因为在飞行过程中无法获得确切的使用情况。

同时使用两种机制:

  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.
  • 使用透明的结算状态:最终、估计、待核对、提供商核对或放弃。
  • 发出特定于会计的可观察性事件。
  • 稍后根据提供商使用情况或费用报告(如果有)进行协调,同时保留请求时租户归因。

向租户展示什么内容

租户不需要所有内部活动,但他们确实需要诚实的标签。有用的用法表可能会显示:

  • 状态:最终、估计或核对。
  • 请求结果:已完成、客户端中止、提供商错误或网关超时。
  • 交付的输出:发送到客户端的近似文本或字节。
  • 计费令牌:用于成本的提供商标准化使用量。
  • 调整:任何以后的调节增量。

这种设计减少了支持的歧义。如果用户只看到部分响应,信息中心可以说明提供商是否已完成、网关是否取消了上游以及费用是最终费用还是估算费用。

建议与预测

建议:将流式请求视为状态机,在准确结算之前等待权威的最终使用情况,将交付的输出与计费使用情况分开,并诚实地标记估计行。提供程序适配器应对提供程序特定的使用语义进行编码,而不是将每个流扁平化为通用字节代理。

预测:随着模型暴露更多隐藏工作(推理令牌、缓存令牌折扣、多模式处理、工具使用跟踪和安全停止),流式计算将变得更加重要。与仅计算流式文本的网关相比,已经将提供商计费的使用量与客户端可见的输出分开的网关将更容易适应。

可行的结论

如果您的网关支持流式传输,请立即审核一条路径:在前几个块之后强制客户端断开连接并检查账本行。如果它显示“成功”且令牌计数看起来准确,则您的分析可能在撒谎。

解决办法不是放弃流式传输。保持快速的用户体验,但使流完成、取消、提供者错误、丢失最终使用以及对账明确的会计状态。这为产品团队提供响应式输出、为财务团队提供合理的成本,并为支持团队提供足够的证据来解释部分响应,而无需猜测。

相关阅读

FAQ

常见问题

网关是否应该根据估计的令牌计数对流式响应进行计费?
必要时使用实时配额保护的估算,但在可用时根据提供商返回的使用情况确定准确的租户成本。如果最终使用量丢失,请将该行标记为估计或待对账。
为什么交付的输出令牌与计费令牌不同?
客户端可能会断开连接,网关可能会超时,提供者可能会对隐藏推理或多模式令牌进行计数,或者提供者可能会在用户停止接收字节后完成生成。跟踪交付的输出与提供商计费的使用情况分开。
最常见的 Anthropic 流式会计错误是什么?
汇总累积使用事件。 Anthropic message_delta 使用计数是累积的,因此网关应该存储最新值而不是添加每个事件。
当浏览器在流传输期间断开连接时会发生什么?
对于交互式路由,实际的默认设置是取消上游请求,将账本行标记为 client_aborted,并仅根据权威提供者的使用(如果到达)进行结算。否则标记估计或待调节的行。