Provider-reported usage.cost is trusted verbatim as USD spend with no unit validation — causes astronomical phantom spend

当 OpenAI 兼容聚合器(aggregator)在响应或流式 usage 块里返回非 USD 单位的 usage.cost 字段时,LiteLLM 会把它原样当成美元计入 spend ,绕过你为模型配置的 input_cost_per_token / output_cost_per_token

快速结论:当 OpenAI 兼容聚合器(aggregator)在响应或流式 usage 块里返回非 USD 单位的 usage.cost 字段时,LiteLLM 会把它原样当成美元计入 spend,绕过你为模型配置的 input_cost_per_token/output_cost_per_token,导致记账金额被放大若干数量级并瞬间触发 max_budget。优先排查上游返回的 usage.cost 单位,以及该请求是流式还是非流式。

适用环境:Issue 中确认的场景为自托管 LiteLLM proxy(镜像 ghcr.io/berriai/litellm-database:main-latest),前置 OpenAI 兼容聚合器(MixRoute、cortecs.ai),使用 custom_llm_provider: openai + model: openai/<model> + api_base。问题在 v1.98.1、v1.102.0、v1.104.0 上均被复现。Issue 未提供操作系统、Python、CUDA、显卡等环境信息。

最快修复方案:暂无确认的一步修复方案。Issue 明确说明 v1.104.0 中该场景仍然存在,PR #39441 只修了 xAI 路径并限制了 OpenRouter 的 header 快捷方式,通用路径未修复。若你正被预算锁死,Issue 报告者恢复方式是手动执行 UPDATE "LiteLLM_UserTable" SET spend = 0 ... 并重启容器(spend 在进程内缓存),但这只是清账、不是修复。

注意事项:不建议仅靠“等官方修复”恢复生产;Issue 报告者验证过所有 custom-callback 钩子点都在 cost 被写入之后才执行,因此无法用 callback 在配置层面覆盖该值。上面的 SQL 清零会丢失真实的历史 spend 记录,属于应急手段;重启是必须的,否则进程内缓存的 spend 不会被刷新。

问题场景

你在自托管 LiteLLM proxy 后面接一个 OpenAI 兼容的聚合器/网关,通过 custom_llm_provider: openai 加 api_base 的方式把上游当作 openai/ 模型调用(例如 model: openai/grok-4.5),并为该模型组配置了自定义的 input_cost_per_token/output_cost_per_token。当上游在 chat completion 响应的 usage 里附带一个非标准的 cost 字段(流式请求会出现在拼接后的 usage 块中),LiteLLM 会把这个数字直接当作美元花费记账。

Issue 中两个真实案例:MixRoute 的 usage.cost: 3144000 实际是纳美元(nano-dollar,1e-9 USD),真实成本约 $0.003144,却被记为 $3,144,000;cortecs.ai 用微单位(micro-unit),真实成本 $0.000500878 的流式请求被记为 spend = 503.0,而同一请求的非流式版本记账正确。两条路径下自定义定价从未被使用,cost_breakdown.total_cost 为 0。因为 proxy 的预算控制直接读取这个膨胀后的 spend,一次请求就能把密钥预算打穿。

报错原文

Provider-reported usage.cost is trusted verbatim as USD spend with no unit validation — causes astronomical phantom spend

Budget has been exceeded! Current cost: 26364009.05191107, Max budget: 25.0

上游返回的原始 usage 片段示例:

"usage": {
  "prompt_tokens": 209,
  "completion_tokens": 19,
  ...
  "cost": 3144000
}

Issue 中 LiteLLM_SpendLogs 的两条异常记录:

request_id 9e9da91a-...   spend 368424000   prompt_tokens 18335   completion_tokens 65
request_id 2981dd9e-...   spend 26364000    prompt_tokens 743     completion_tokens 228

原因分析

按 Issue 中的代码追踪,根因在成本计算链路对 provider 上报值的“无单位信任”:

  • litellm/types/utils.py 中 Usage.cost: Optional[float] = None 是一等 Pydantic 字段,本意是给 Perplexity、OpenRouter 这类会自己上报准确 USD 成本的 provider 使用。
  • litellm/litellm_core_utils/streaming_handler.py 的 _propagate_usage_cost_to_hidden_params():只要拼接后的流式响应里 usage.cost 不为 None,就原样写进 response._hidden_params["additional_headers"]["llm_provider-x-litellm-response-cost"]。
  • litellm/cost_calculator.py 的 response_cost_calculator() 里,只要 get_response_cost_from_hidden_params() 返回非 None,就直接 return provider_response_cost,完全跳过按 token 和配置单价的本地计算。

Issue 报告者进一步指出这是个 fail-open 的设计问题:v1.104.0 中 litellm/main.py:8868 的 _CALCULATOR_PRICED_REPORTED_COST_PROVIDERS 只有 {LlmProviders.XAI.value} 一个例外,即“原样信任”是所有 provider 的默认行为,xAI 反而是唯一被排除的;非流式路径本已通过 _without_provider_stated_cost()(cost_calculator.py:1194)在存在自定义定价时剥离 provider 上报成本,流式路径却绕过了它,所以同一部署下流式与非流式记账结果不一致。

由于裸数字不带单位(xAI 自己的数值就需要除以 10,000,000,000 才是 USD),任何 provider 新增或改变一个 cost 形状的字段,都会静默改变 custom_llm_provider: openai 这一整个自定义端点生态的计费行为。这是 可能原因层面的判断:Issue 已确证流式路径会原样采用上报值,但具体到你自己的上游 cost 字段是什么单位,需要用 curl 直接验证。

环境排查

  • 确认 LiteLLM 版本:Issue 在 v1.98.1、v1.102.0、v1.104.0 上均复现,v1.104.0 仍 live。
  • 确认部署方式与镜像标签(Issue 使用 ghcr.io/berriai/litellm-database:main-latest)。
  • 确认模型配置里 custom_llm_provider 是否为 openai,model 是否带 openai/ 前缀,以及 api_base 指向的聚合器地址。
  • 确认 model_info/litellm_params 中是否配置了 input_cost_per_token/output_cost_per_token(Issue 中该配置存在但被忽略)。
  • 确认请求是否为流式(stream),因为流式与非流式路径行为不同。
  • 确认 litellm_settings.max_budget 的设置值,用于判断打穿预算的阈值。
  • 用 curl 直连上游 api_base,不带 LiteLLM,检查响应 usage 里是否存在 cost 字段及其数量级——这是判断单位的关键证据。
  • Issue 未涉及 Python、CUDA、PyTorch、显卡版本,无需据此排查。

解决步骤

  1. 先用 curl 绕过 LiteLLM 直接请求上游,抓取原始响应(流式请求要拿到拼接后的 usage 块),确认 usage.cost 是否存在、数值大约是多少,并对照真实费用推算其单位(纳美元 ×10⁹、微单位 ×10⁶ 等)。
  2. 在 proxy 数据库中查询 LiteLLM_SpendLogs,按 request_id 对比记录下来的 spend 与 prompt_tokens/completion_tokens,看是否存在与 token 数完全不匹配的异常大额记录。
  3. 同时发一条流式请求和一条完全相同的非流式请求,比较两者记账。Issue 中非流式走的是 _without_provider_stated_cost() 路径、记账正常,流式则被上游数值覆盖——这个对比可以快速定位是流式路径的问题。
  4. 确认自己确实被 max_budget 锁死(报错形如 Budget has been exceeded! Current cost: ... Max budget: ...)后,按 Issue 报告者做法应急恢复:对 LiteLLM_UserTable(以及相关 team/key 预算表)执行 UPDATE ... SET spend = 0,然后重启容器,因为 spend 在进程内缓存,不重启不会刷新。
  5. 等待或跟进上游修复。PR #39441 只修了 xAI 路径并限制了 OpenRouter 的 header 快捷方式,没有解决通用场景;Issue 报告者建议的方向是:存在运维配置定价时该定价不可被响应覆盖、仅由 adapter 显式声明 USD 契约时才信任上报值、加入与 token 计算成本的 10× 量级校验,以及提供 trust_provider_cost: true 之类的显式开关。这些属于建议而非已验证的一步修复。
  6. 如果无法立即升级且上游字段无法关闭,可优先尝试的缓解方向是临时切到非流式调用,或与上游沟通去掉该 cost 字段——但这只是规避,Issue 中并未给出经过验证的配置级禁用方式,且报告者已验证所有 custom-callback 钩子点都在 cost 写入之后执行。

验证方法

修复或规避生效后,重放同一条会触发问题的流式请求,再查 LiteLLM_SpendLogs:该条记录的 spend 应当与按 input_cost_per_token/output_cost_per_token 和实际 token 数算出的金额一致(Issue 中对应场景应只有几分钱),而不是出现 368424000、26364000、503.0 这类量级的数字。同时确认不再触发 Budget has been exceeded!,并且流式请求与非流式孪生请求的记账结果一致(Issue 中的异常正是两者不一致)。

参考来源

BerriAI/litellm #35829

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 27375

发表回复

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