追踪完全指南:从 Token 统计、按请求明细到原始载荷保留)
openai-agents-python 用量Usage追踪完全指南从 Token 统计、按请求明细到原始载荷保留【免费下载链接】openai-agents-pythonA lightweight, powerful framework for multi-agent workflows项目地址: https://gitcode.com/GitHub_Trending/op/openai-agents-python导读本文聚焦 openai-agents-pythonAgents SDK内置的LLM Token 用量自动追踪能力每次Runner.run(...)执行都会聚合该次运行中所有模型调用的用量数据并通过运行上下文Run Context暴露给开发者。读完本文你将掌握如何读取汇总用量与逐请求明细、如何让第三方模型适配器如 AnyLLM、LiteLLM正确上报用量、如何保留 provider 原始 usage 载荷preserve_raw_usage、以及用量在 Session、RunState 检查点与 Hooks 中的行为差异从而落地成本监控、用量限额与分析埋点。一、SDK 自动追踪哪些用量指标Agents SDK 的用量追踪是开箱即用的你不需要额外配置每次Runner.run(...)都会自动聚合该次运行内所有 LLM API 调用的用量。从源码看核心数据结构定义在 src/agents/usage.py其中Usage与RequestUsage两个 Pydantic dataclass 构成了用量模型的主体。被追踪的顶层指标包括字段含义requests发起的 LLM API 调用次数input_tokens所有请求发送的输入 token 总数output_tokens所有请求收到的输出 token 总数total_tokens输入 输出input_tokens output_tokensrequest_usage_entries逐请求用量明细列表每个元素是一个RequestUsage此外还有嵌套的 token 明细detailsinput_tokens_details.cached_tokens命中的缓存 token 数prompt 缓存读取input_tokens_details.cache_write_tokens写入缓存的 token 数output_tokens_details.reasoning_tokens推理模型reasoning model产出的推理 token 数从源码实现看这些明细字段在 usage.py 的Usage.__post_init__中会被统一归一化部分 provider 不填充可选明细字段可能为NoneSDK 会将其规范化为0避免下游计算时报TypeError。也就是说聚合后的Usage对象对调用方而言字段总是完整的。用量是如何聚合的Usage.add(other)src/agents/usage.py负责把一次模型调用的用量累加到运行总量上其行为值得注意所有顶层计数requests、input_tokens、output_tokens、total_tokens直接相加明细字段cached / cache_write / reasoning分别累加request_usage_entries会被自动保留如果被合并的对象已含逐请求明细则深拷贝合并否则当它代表单个请求且 token 数大于 0时会自动合成一条RequestUsage记录从而保住运行内有几次请求、每次各消耗多少的粒度。在运行循环内部每次拿到模型响应后都会执行context_wrapper.usage.add(...)见 src/agents/run_internal/run_loop.py 与 run_loop.py因此用量会横跨整个运行自动累计——包括产生工具调用tool call或交接handoff的那些模型调用它们都会计入同一次运行的 totals。二、从一次运行中读取用量运行结束后通过result.context_wrapper.usage即可访问该次运行的累计用量。RunContextWrapper在 src/agents/run_context.py 中持有usage: Usage字段注释明确说明这是agent 运行截至目前的使用量。最简单的读取方式import asyncio from agents import Agent, Runner async def main() - None: agent Agent( nameAssistant, instructionsYou are a concise assistant., ) result await Runner.run(agent, Whats the weather in Tokyo?) usage result.context_wrapper.usage print(Requests:, usage.requests) print(Input tokens:, usage.input_tokens) print(Output tokens:, usage.output_tokens) print(Total tokens:, usage.total_tokens) if __name__ __main__: asyncio.run(main())仓库自带的完整示例见 examples/basic/usage_tracking.py它还演示了如何结合工具tool修饰的get_weather打印逐请求明细。流式运行下的注意事项RunContextWrapper.usage的字段注释特别提示src/agents/run_context.py对于流式响应在流结束前 usage 可能是陈旧的。因此如果你通过Runner.run_streamed(...)读取用量应在流消费完毕后再读取汇总值避免拿到中间态。三、自动压缩Compaction对用量的影响用量聚合不仅覆盖普通模型调用。当使用OpenAIResponsesCompactionSession时如果它在运行结束前自动压缩历史那么这次responses.compact请求产生的用量也会被计入同一运行的 totals。但有例外如果是在运行之外手动调用run_compaction()此时没有外层运行上下文该次压缩的用量不会回流更新此前那次运行返回的 usage 对象。详情可参考 OpenAI Responses 压缩会话。四、第三方适配器下的用量启用用量上报在不同第三方适配器与 provider 后端之间差异较大。如果你的模型通过第三方适配器访问且需要准确的result.context_wrapper.usage数值请注意AnyLLMModel上游 provider 返回 usage 时用量会自动传播。但如果是从Chat Completions 后端流式响应可能需要设置ModelSettings(include_usageTrue)才会发出 usage 数据块LitellmModel部分 provider 后端默认不上报 usage因此通常必须设置ModelSettings(include_usageTrue)。include_usage是ModelSettings上的一个布尔字段src/agents/model_settings.py注释明确仅对 Chat Completions API 可用。在 litellm_model.py 与 any_llm_model.py 中可以看到流式模式下该设置会被透传为stream_options {include_usage: ...}。此外当 Litellm 后端完全未返回 usage 时SDK 仍会把这次请求计入requests记为Usage(requests1)并输出一条No usage information returned from Litellm的警告日志见 litellm_model.py——也就是说请求数不会因缺省 usage 而丢失只是 token 数为 0。具体配置方式from agents import Agent, ModelSettings agent Agent( nameAssistant, modellitellm/gpt-4o, model_settingsModelSettings(include_usageTrue), )建议对照 第三方适配器 一节的适配器说明并在你实际要部署的 provider 后端上验证用量上报是否符合预期。五、逐请求Per-request用量跟踪除了汇总值SDK 还会为每一个 API 请求维护一条明细记录存放在request_usage_entries列表中。这对于精确成本核算和监控上下文窗口消耗尤其有用——例如某次运行发起 3 次调用、输入分别为 100K / 150K / 80K聚合后的input_tokens是 330K但request_usage_entries保留了[100K, 150K, 80K]的粒度该设计意图写在 usage.py 的字段 docstring 中。读取方式import asyncio from agents import Agent, Runner async def main() - None: agent Agent(nameAssistant, instructionsYou are concise.) result await Runner.run(agent, Whats the weather in Tokyo?) for i, request in enumerate(result.context_wrapper.usage.request_usage_entries): print(fRequest {i 1}: {request.input_tokens} in, {request.output_tokens} out) print(f cached: {request.input_tokens_details.cached_tokens}) print(f reasoning: {request.output_tokens_details.reasoning_tokens}) if __name__ __main__: asyncio.run(main())RequestUsagesrc/agents/usage.py除input_tokens、output_tokens、total_tokens三个基础字段外还携带独立的input_tokens_details与output_tokens_details因此可以做更细的按请求缓存命中/推理 token 分析。测试 tests/extensions/experimental/hosted_multi_agent/test_model.py 也验证了多 agent 场景下request_usage_entries的长度与 totals 的一致性。六、保留 Provider 原始用量载荷preserve_raw_usage默认情况下Agents SDK 会把 provider 返回的 usage归一化成统一的Usage字段从而在不同模型 provider 之间得到一致的 totals。但如果你的应用需要保留 provider 特有的字段或者需要区分provider 未上报该字段与provider 上报了 0可以开启ModelSettings.preserve_raw_usage Trueimport asyncio from agents import Agent, ModelSettings, Runner async def main() - None: agent Agent( nameAssistant, instructionsYou are concise., model_settingsModelSettings(preserve_raw_usageTrue), ) result await Runner.run(agent, Whats the weather in Tokyo?) for response in result.raw_responses: print(response.raw_usage) if __name__ __main__: asyncio.run(main())关于该机制有几点需要准确理解快照语义SDK 会把每次模型调用的ModelResponse.raw_usage保存为独立detached、JSON 兼容的 provider 载荷快照。快照在归一化之前捕获见 any_llm_model.py 等处的_raw_usage_snapshot调用因此能保留字段存在性信息。不做跨运行聚合raw_usage只针对单次模型调用SDK不会对它们做跨运行汇总。什么情况下为None当关闭保留、provider 未返回 usage 载荷、或上游适配器已经丢弃了原始字段存在性信息时raw_usage保持None。从 usage.py 的实现看无法被 JSON 表示或校验失败的适配器专属值也会被安全地降级为None——因为它只是诊断性元数据绝不能因此让一次成功的模型调用失败。它不索取用量preserve_raw_usage只保留已经到达模型适配器的 usage 载荷并不会主动向 provider 请求 usage。因此当流式 Chat Completions provider 需要显式请求 usage 时还必须同时设置include_usageTrue。与 LiteLLM 的兼容性限制LitellmModel目前无论在流式还是非流式运行中都不会填充ModelResponse.raw_usage因此对 LiteLLM 适配器设置preserve_raw_usageTrue不生效。使用 LiteLLM 时应继续依赖归一化的Usage字段若确实需要 provider 特有字段的存在性信息应选择支持原始用量保留的适配器。preserve_raw_usage字段定义与完整 docstring 见 src/agents/model_settings.py。七、Session 场景下的用量独立性使用Session例如SQLiteSession时会话负责维护多轮对话历史但每一次Runner.run(...)返回的 usage 只属于该次运行from agents import Agent, Runner from agents.memory import SQLiteSession agent Agent(nameAssistant, instructionsYou are concise.) session SQLiteSession(my_conversation) first await Runner.run(agent, Hi!, sessionsession) print(first.context_wrapper.usage.total_tokens) # 第一次运行的用量 second await Runner.run(agent, Can you elaborate?, sessionsession) print(second.context_wrapper.usage.total_tokens) # 第二次运行的用量需要特别留意的是会话虽然跨运行保留了对话上下文但历史消息可能会被重新喂入后续每次运行的输入因此后续轮次的input_tokens计数会包含这些历史输入——换言之第二次运行的 token 数通常会大于第一次。Session 的实现与配置详见 会话文档。八、RunState 检查点中的用量快照RunResult.to_state()会捕获当前已累计用量的独立快照。从该检查点恢复运行时新运行会从捕获的 totals 起步再加上自身模型调用的用量而恢复后的运行不会把新增 totals 回写进原始RunResult也不会污染从该结果派生的其他检查点import asyncio from agents import Agent, Runner async def main() - None: agent Agent(nameAssistant, instructionsYou are concise.) first await Runner.run(agent, First request) checkpoint_a first.to_state() checkpoint_b first.to_state() resumed_a await Runner.run(agent, checkpoint_a) resumed_b await Runner.run(agent, checkpoint_b) assert resumed_a.context_wrapper.usage is not first.context_wrapper.usage assert resumed_b.context_wrapper.usage is not resumed_a.context_wrapper.usage if __name__ __main__: asyncio.run(main())这种隔离同样适用于Usage.request_usage_entries列表。从 run_context.py 的实现可以看到恢复运行时会copy.deepcopy(self.usage)得到独立副本避免共享同一实例导致的状态串扰。一个例外恢复的嵌套Agent.as_tool()运行是独立顶层记账的例外——其恢复后的模型用量会被刻意聚合进外层正在运行的 usage与其恢复前那次嵌套运行的模型调用保持一致。这意味着以工具形式嵌套的 agent其用量始终归属于外层的运行上下文。九、在 RunHooks 中使用用量如果使用RunHooks传给每个钩子的context对象同样携带usage你可以在关键生命周期节点记录用量from typing import Any from agents import Agent, RunContextWrapper, RunHooks class MyHooks(RunHooks): async def on_agent_end( self, context: RunContextWrapper, agent: Agent, output: Any, ) - None: u context.usage print(f{agent.name} → {u.requests} requests, {u.total_tokens} total tokens)RunContextWrapper会以引用共享的方式把 usage 传给子上下文run_context.py 中 fork 出的子上下文fork.usage self.usage因此钩子中看到的是与运行内累计一致的实时对象。配合 生命周期钩子 使用可以在 agent 开始、工具调用、agent 结束等时机做成本审计或用量告警。十、用量数据的序列化与持久化若需要把用量写入数据库或随 RunState 持久化src/agents/usage.py 提供了两个对称工具serialize_usage(usage)usage.py把Usage序列化为 JSON 友好的字典包括逐请求明细与嵌套 token detailsdeserialize_usage(usage_data)usage.py从序列化数据重建Usage对象对旧快照格式缺少 cache-write 字段等做了兼容处理。在 tests/fixtures/run_state/ 下可以看到大量携带request_usage_entries的 RunState 快照 fixtures如 v1_12_input_cache_write_usage.json它们正是用于验证用量随检查点序列化/反序列化往返一致性的测试语料。十一、用量与 Tracing 的关系用量数据不仅服务于运行结果还会进入追踪Tracing体系Usage对象可以通过 usage.py 中的model_usage_to_span_usage、total_usage_to_span_metadata、turn_usage_to_span_data、task_usage_to_span_data等辅助函数转换为 span 数据与 span 元数据含 cached / cache_write 明细。这意味着你在追踪面板中看到的每次模型调用、每个 turn、每个 task 的 token 统计都源自同一个归一化Usage对象。更多信息可参考 追踪文档。十二、API 参考速查符号说明定义位置Usage用量追踪数据结构含requests、input/output/total_tokens、request_usage_entries、token detailssrc/agents/usage.pyRequestUsage单次请求的用量明细src/agents/usage.pyRunContextWrapper.usage从运行上下文读取当前累计用量src/agents/run_context.pyRunHooks在生命周期钩子中访问 usagesrc/agents/run.pyModelSettings.preserve_raw_usage保留 provider 原始 usage 载荷src/agents/model_settings.pyModelSettings.include_usageChat Completions 流式请求中包含 usage 块src/agents/model_settings.py总结openai-agents-python 的用量追踪覆盖了从一次运行的汇总 totals到逐请求明细再到provider 原始载荷快照的完整层次并且与 Session、RunState 检查点、Hooks 和 Tracing 深度集成。实际落地时记住三条核心原则流式场景等流结束再读用量、第三方适配器按需开启include_usage、需要 provider 原始字段时开启preserve_raw_usage并注意 LiteLLM 适配器暂不支持原始用量保留。配合 examples/basic/usage_tracking.py 示例运行一遍即可快速验证你所在 provider 环境下的用量上报行为。【免费下载链接】openai-agents-pythonA lightweight, powerful framework for multi-agent workflows项目地址: https://gitcode.com/GitHub_Trending/op/openai-agents-python创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考