财务同学拿着账单问,为什么看板 total_tokens 对得上,分项却对不上。工程侧打开 Usage 面板,只有归一化后的 input、output、total。供应商原始帧里的扩展字段、缓存命中计数,早就在归一化时丢掉了。有人说已经开了对账,实际从未打开 ModelSettings.preserve_raw_usage。保留原始载荷,与累计多 Agent 用量,是两件不同的事。

这篇只讲原始载荷保留开关与对账形状。不写 handoff 或 as_tool 漏计面。结论全部在 openai-agents 0.23.1 源码与离线假流式脚本上核对过,脚本不调用真实模型,结果落在 _w/fail-smoke/raw_usage.csv。

preserve_raw_usage 原始用量

图注 左侧为 0.23.1 实测行为,右侧为四类种植失败。

源码里的开关与生效点

ModelSettings.preserve_raw_usage 类型是 bool | None,默认 None。文档含义很直接。打开且适配器仍握有未归一化的供应商载荷时,ModelResponse.raw_usage 会留下一份 JSON 兼容快照,捕捉的是 Agents SDK 归一化缺失字段之前的形态。用量本身缺失,或上游已经丢掉字段存在信息时,它仍可以是 None。该设置不会向供应商请求用量。流式场景若需要用量,仍要单独处理 include_usage 一类选项。

生效点分散在多条模型路径。Chat Completions 非流在 preserve_raw_usage is True 时对 response.usage 做 _raw_usage_snapshot。流式经 chatcmpl_stream_handler.handle_stream(..., preserve_raw_usage=...),在 chunk 带 usage 时提取最后一帧快照,完成时 _attach_raw_usage_snapshot。Responses API 非流与流式 chunk 同样有分支。测试模型也会尊重该布尔值。判断条件统一写成 is True,因此 None 与 False 都不会保留。

和 Usage.add 的关系要写清。归一化 Usage 负责跨调用累计。raw_usage 负责对照账单侧原始字段。打开保留并不能替代多 Agent 汇总。关掉保留却去读 raw 字段,对账脚本只会读到空。

离线 smoke 四类种植失败

脚本构造假供应商 usage(含扩展租户线索与 cached_tokens),按开关决定是否生成 raw 快照,并种植日志整包与中途取消。

case配置结果说明
baseline_noneNone无 raw默认不保留
healthy_preserveTrue有 raw,字段可哈希健康对账
F1_no_preserve_reconcile_rawFalse 却宣称按 raw 对账命中 F1空字段对账
F2_raw_dump_app_logTrue 且整包进应用日志命中 F2扩展字段泄漏
F3_cancel_claim_complete流式取消仍宣称完整命中 F3残帧当全量
F4_only_total_tokens只比对 total命中 F4分项漂移漏检

F1 把开关当成气氛。变更单写了对账,代码路径从未 preserve_raw_usage=True,脚本却去取 cached_tokens 一类 raw 字段。结果要么空值报错被吞,要么用归一化字段冒充 raw,漂移被盖住。

F2 出在日志策略。打开保留后,有人把 raw_usage 整包打进应用日志。0.23.1 快照是供应商载荷的 JSON 兼容拷贝,可能含厂商扩展字段与租户线索。对账应落字段级哈希与白名单键,而不是整包原文进可检索日志。

F3 出在流式取消。中途取消时最后一帧可能残缺。业务仍把 raw_usage is not None 写成 raw 完整。完整需要额外条件,例如未取消、必要键齐全、与归一化 Usage 双列可对照。

F4 出在偷懒比对。只比对 total_tokens,账单侧 cached_tokens 或分项已经漂了也显示绿。对账表至少两列,归一化 Usage 与 raw 快照的字段级哈希或白名单键差值。

推荐契约

from agents import ModelSettings

model_settings = ModelSettings(
    preserve_raw_usage=True,  # 仅对账窗口打开;禁止默认长期 True 且整包打日志
)
# 对账输出:norm_total / raw_hash / whitelist_keys / stream_cancelled

契约五条。一,默认生产可保持 None 或 False。进入对账窗口再显式 True。二,禁止把 raw 整包写入应用日志。只落白名单键与哈希。三,流式取消不得宣称 raw 完整。四,对账不得只比对 total。五,保留 raw 不代替 Usage 累计,多 Agent 汇总另表。

生产禁区

禁区为什么
未开 preserve 却按 raw 字段对账F1
整包原始载荷进应用日志F2 泄漏风险
流式取消仍宣称 raw 完整F3
只对 total_tokensF4 分项漂移
用 raw 代替 Usage.add 累计汇总面错位
长期 True 且无白名单成本与隐私双涨

当天最小实验

九十分钟。

  1. 基线 None,断言无 raw,记 CSV。
  2. 开 True,断言有 raw,计算字段哈希。
  3. 种植 F1,未开却宣称 raw 对账,断言失败。
  4. 种植 F2,整包进日志,断言命中租户线索或扩展键。
  5. 种植 F3,取消后 claim_complete,断言失败。
  6. 种植 F4,只对 total,注入 cached_tokens 漂移,断言失败。
  7. 提交对账 runbook,白名单键与双列哈希入库。

验收看四类失败能否复现,以及 has_raw_usage、claim_raw_complete、only_total_tokens、raw_in_app_log 可查。

和 Usage 面板并排

第一,归一化 Usage。它是跨调用累加的主账本。缺请求次数、缺 input 或 output 时,先修 Usage 采集,再谈 raw。

第二,raw 快照。它解释账单侧为什么和 Usage 不一致。常见差在缓存命中、厂商改名、流式末帧。

第三,多 Agent。handoff 与 as_tool 会引入漏计面。那是另一篇的题目。本篇只要求单调用上 Usage 与 raw 双列可对。

并排最小表头建议为 case、preserve_raw_usage、has_raw_usage、claim_raw_complete、only_total_tokens、fail。

变更单三句话

  1. 对账窗口显式 preserve_raw_usage=True,并声明白名单键。
  2. CI 种植 F1 到 F4,禁止整包 raw 进应用日志。
  3. 监控采 has_raw、cancel 标志、total 与白名单键差值。

审计字段最少清单

run_id、preserve_raw_usage、has_raw_usage、raw_hash、norm_total、stream_cancelled、claim_raw_complete、whitelist_diff。缺 raw_hash 或 whitelist_diff,F4 会绿着。

预发口令

第一,要对账的窗口是否显式打开 True?第二,日志是否只有白名单与哈希?第三,取消路径是否禁止完整声称?第四,比对是否包含分项?四句都过,原始载荷保留才对账有用。

白名单怎么定

先从账单 CSV 表头反推。凡是财务会问的列,进白名单。厂商扩展且与钱无关的键,默认丢弃。租户标识、请求 id、模型内部路由类字段,禁止进应用日志。可进加密审计库的,走单独管道,保留期限写进变更单。

白名单变更要版本化。供应商改名时,旧哈希比对会失败。预发应用同一份假载荷跑新旧两套白名单,确认迁移窗口。

流式与非流的差异

非流路径通常在最终响应上拍快照,形状稳定。流式路径依赖 usage 出现在哪些 chunk。有的供应商只在最后一帧带 usage。中途取消就可能没有完整帧。契约上应把 include_usage(若适用)与 preserve_raw_usage 写在同一变更里,避免只开保留却从未收到 usage 帧。

LiteLLM 与 AnyLLM 等扩展路径在 0.23.1 也有对应分支。不要假设只有官方 Chat Completions 才尊重该开关。升级后用种植脚本在你实际走的适配器上重跑。

失败注入与回归节奏

每次改对账脚本或换供应商,先跑种植包。回归最小集是 baseline、healthy_preserve 与 F1 到 F4。新增白名单键时加一行,断言该键进入哈希且不进应用日志正文。

数字与行为均按本机 openai-agents 0.23.1 核实。preserve_raw_usage 默认 None。仅 True 启用。流式与非流均可挂快照。离线 CSV 见 _w/fail-smoke/raw_usage.csv。升级次版本先重跑种植脚本再改 runbook。

对账窗口怎么开怎么关

不要把 preserve_raw_usage=True 写成全局永恒默认。更稳的做法是对账任务、回归任务与抽样稽核任务打开,普通在线推理保持关闭。打开时同步打开字段采集与白名单。关闭时确认没有脚本还在读 raw_usage。

窗口可以按环境切。预发常开,生产抽样开。抽样比例写进变更单,并保证抽样命中的请求能复现到同一模型适配器路径。只在文档里写抽样、代码里全关,等于 F1。

归一化前后差在哪

归一化会补齐或折叠缺失字段,便于 Usage.add。代价是字段存在信息可能消失。供应商若省略某键表示零,另一家省略表示未知,归一化后两边都变成同一数字,账单却仍按各自规则计费。raw 快照的价值,正在于保住归一化前的键集合与原值。

对比时先比键集合,再比值。键集合多出来的扩展键进白名单或丢弃策略。值不一致的键进 whitelist_diff。只看 total,会把键集合问题吞掉。

与日志、隐私、成本

raw 快照本身通常不大,但高频全量落库会堆出明显成本。更麻烦的是检索系统会把扩展字段编进索引。一旦含租户线索或内部路由,权限模型就要按敏感日志重建。工程上宁可少存,也要按白名单存。

若必须留原始帧做纠纷举证,放进受控对象存储,密钥与访问审计分开,应用日志只留哈希与对象键。种植 F2 就是为拦「图省事 print 整包」。

取消、重试、重复帧

流式取消后的 raw 可能是 partial。重试成功后应以后一次完整帧为准,并在审计里留下取消次数。重复帧(usage 在多个 chunk 出现)以最后一次为准,与 0.23.1 handler 保留最后载荷的思路一致。对账脚本若误用第一次帧,会出现稳定假漂移。

排障口令补充

看板 total 绿、财务分项红时,按顺序查。preserve 是否 True。has_raw 是否 True。raw_hash 是否变化。whitelist_diff 里是哪个键。cancel 标志是否被忽略。五步走完仍不清,再去查供应商账单口径,而不是先改 Usage 累加公式。

数字与行为均按本机 openai-agents 0.23.1 核实。升级次版本先重跑 _w/fail-smoke/raw_usage.csv 对应脚本。

Logo

更多推荐