[Bug]: Streamed usage.cost leaves out the Claude web search fee (server_tool_use dropped)

当 Claude 模型在 LiteLLM Proxy 中以流式方式发起 web search 请求,并且开启 include_cost_in_streaming_usage 时,最终流式 chunk 中的 usage.cost 只计算 token 费用,漏掉 server_tool_use.web_

快速结论:当 Claude 模型在 LiteLLM Proxy 中以流式方式发起 web search 请求,并且开启 include_cost_in_streaming_usage 时,最终流式 chunk 中的 usage.cost 只计算 token 费用,漏掉 server_tool_use.web_search_requests 对应的搜索费用;优先排查代理重建流式 usage 时是否保留 server_tool_use 字段。

适用环境:Issue 已确认的环境为 LiteLLM v1.105.0 Proxy(镜像 ghcr.io/berriai/litellm-non_root),同时报告者在提交时阅读 main 分支确认存在同样代码;未提供操作系统、Python、CUDA、显卡等环境信息。

最快修复方案:暂无确认的一步修复方案。Issue 中原作者在 v1.105.0 本地验证:在 _openai_stream_usage_kwargs 重建 usage 时额外透传 ("server_tool_use", usage.get("server_tool_use")),可让含一次搜索的同一 chunk 费用变为 0.06102。该修复对应的 PR #48528 已在评论中确认合并,建议升级到包含该修复的 LiteLLM 版本;若暂时无法升级,可优先尝试按此方向做本地补丁。

注意事项:该问题只影响客户端从流式 chunk 读取的 usage.cost,spend logs 不受影响,因为花费日志使用 stream_chunk_builder 组装后的响应计价,仍保留 server_tool_use。Gemini 路径不受影响,其搜索次数位于 prompt_tokens_details.web_search_requests,该字段原本就会保留。本地补丁属于临时方案,需自行承担与后续版本冲突的风险。

问题场景

用户通过 LiteLLM Proxy 请求 Claude 模型,使用流式输出并开启 stream_options: {"include_usage": true},同时通过 include_cost_in_streaming_usage 让代理在最终 chunk 注入费用。模型实际执行了 web search,chunk 中可见 server_tool_use.web_search_requests: 1,但 usage.cost 只按输入 token 与输出 token 计价,缺少每次搜索的费用。Issue 中的离线复现直接调用代理自身的注入函数,无需真实上游调用。

报错原文

[Bug]: Streamed usage.cost leaves out the Claude web search fee (server_tool_use dropped)

Actual:   0.05102 (tokens only)
Expected: 0.06102 (tokens + one $0.01 search)

_litellm/proxy/common_request_processing.py_:
ProxyBaseLLMRequestProcessing._openai_stream_usage_kwargs rebuilds the usage from:
prompt_tokens, completion_tokens, total_tokens, completion_tokens_details, prompt_tokens_details

It drops server_tool_use. StandardBuiltInToolCostTracking never sees the Claude search count.

原因分析

最可能的原因是:ProxyBaseLLMRequestProcessing._openai_stream_usage_kwargs(位于 litellm/proxy/common_request_processing.py)在重建流式 usage 时只保留 prompt_tokens、completion_tokens、total_tokens、completion_tokens_details、prompt_tokens_details 五个字段,丢弃了 server_tool_use。随后 _inject_cost_into_usage_dict 基于重建后的 ModelResponse(usage=...) 计算费用,StandardBuiltInToolCostTracking 拿不到 Claude 的 web search 次数,因此只计算 token 费用,不追加 search_context_cost_per_query 对应的搜索费。

Gemini 不受影响,是因为它的搜索次数放在 prompt_tokens_details.web_search_requests 中,而该字段在重建时被保留。Issue 同时说明,spend logs 计价走的是 stream_chunk_builder 组装后的完整响应,server_tool_use 仍在,因此花费日志正确,只有客户端读取的流式 usage.cost 错误。

环境排查

  • 确认 LiteLLM 版本:Issue 在 v1.105.0 复现,提交时 main 分支同样存在该逻辑;请对照所使用版本中 litellm/proxy/common_request_processing.py 的 _openai_stream_usage_kwargs 实现。
  • 确认请求是否使用流式输出,并开启 stream_options: {"include_usage": true} 与 include_cost_in_streaming_usage。
  • 确认模型 cost map 中存在 search_context_cost_per_query,Issue 示例为 vertex_ai/claude-opus-5-5,值为 0.01。
  • 确认最终 chunk 是否带有 server_tool_use.web_search_requests,且数值大于 0。
  • 无需依赖操作系统、Python、CUDA 或显卡环境;Issue 未涉及这些条件。

解决步骤

  1. 先建立基线:用 Issue 提供的离线脚本调用 P._inject_cost_into_usage_dict(chunk, MODEL),记录当前 usage.cost。若含一次搜索却只得到 0.05102,即符合本 Issue 现象。
  2. 升级 LiteLLM 到包含 PR #48528 修复的版本。评论中已确认该 PR 已合并,这是首选处理方式。
  3. 若暂时无法升级,可优先尝试临时补丁:在 _openai_stream_usage_kwargs 重建 usage 的字段集合中加入 ("server_tool_use", usage.get("server_tool_use")),使该字段透传到后续计价逻辑。
  4. 补丁后重新运行同一离线脚本,对比搜索次数为 1 和为 0 两种情况下的费用差异。
  5. 切回真实代理链路验证:用 Claude 模型发起带 web_search_options: {} 与 stream_options: {"include_usage": true} 的流式请求,检查最终 chunk 的 usage.cost。

验证方法

离线复现中,同一 chunk(prompt_tokens=10760、completion_tokens=399、total_tokens=11159、server_tool_use.web_search_requests=1)在修复后 usage.cost 应为 0.06102,即 token 费用 0.05102 加上一次 0.01 的搜索费;去掉 server_tool_use 或搜索次数为 0 时应回到 0.05102。真实代理链路中,最终 chunk 的 usage.cost 应等于 litellm.completion_cost(...) 对相同 usage(含 server_tool_use)的计算结果,而不再只等于 token 部分。

参考来源

BerriAI/litellm #46365

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 30623

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注