OpenAI Agents SDK 工程笔记:ModelSettings.preserve_raw_usage 保留供应商原始用量载荷、对账漂移与生产禁区
财务同学拿着账单问,为什么看板 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。

图注 左侧为 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_none | None | 无 raw | 默认不保留 |
| healthy_preserve | True | 有 raw,字段可哈希 | 健康对账 |
| F1_no_preserve_reconcile_raw | False 却宣称按 raw 对账 | 命中 F1 | 空字段对账 |
| F2_raw_dump_app_log | True 且整包进应用日志 | 命中 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_tokens | F4 分项漂移 |
| 用 raw 代替 Usage.add 累计 | 汇总面错位 |
| 长期 True 且无白名单 | 成本与隐私双涨 |
当天最小实验
九十分钟。
- 基线 None,断言无 raw,记 CSV。
- 开 True,断言有 raw,计算字段哈希。
- 种植 F1,未开却宣称 raw 对账,断言失败。
- 种植 F2,整包进日志,断言命中租户线索或扩展键。
- 种植 F3,取消后 claim_complete,断言失败。
- 种植 F4,只对 total,注入 cached_tokens 漂移,断言失败。
- 提交对账 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。
变更单三句话
- 对账窗口显式
preserve_raw_usage=True,并声明白名单键。 - CI 种植 F1 到 F4,禁止整包 raw 进应用日志。
- 监控采 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 对应脚本。
更多推荐
所有评论(0)