[Bug]: register_model() persists synthesized zero costs on re-registration, silently disabling all budget enforcement

用户在生产环境中运行 LiteLLM Proxy(基于数据库的代理)。触发场景:通过 Admin UI 创建模型(未设置自定义定价),之后在同一个进程内模型被第二次注册(例如路由器重建、数据库配置同步、使用 /model/update 接口更新模型、或在 Admin UI 中编辑模型)。此问题在 l

[Bug]: register_model() persists synthesized zero costs on re-registration, silently disabling all budget enforcement

[Bug]: register_model() persists synthesized zero costs on re-registration, silently disabling all budget enforcement

快速结论:该问题出现在 LiteLLM Proxy 部署过程中。当模型通过 Admin UI 配置(无自定义定价)并在同一进程内被重新注册时,register_model() 会将 get_model_info() 合成的 input_cost_per_token / output_cost_per_token 零值持久化到 litellm.model_cost 字典中,导致所有预算检查(每标签、每密钥、每团队、每用户、每组织)被静默跳过。优先检查是否在同一个进程中进行了多次模型注册操作,以及 litellm.model_cost 中对应条目的成本键是否被错误写入为 0。

问题场景

用户在生产环境中运行 LiteLLM Proxy(基于数据库的代理)。触发场景:通过 Admin UI 创建模型(未设置自定义定价),之后在同一个进程内模型被第二次注册(例如路由器重建、数据库配置同步、使用 /model/update 接口更新模型、或在 Admin UI 中编辑模型)。此问题在 litellm 1.85.0 到当前主分支版本中均存在。

报错原文

Tag, key, team, user and org budgets are all skipped for that model group.
Billing and /model/info still resolve prices by model name, so spend keeps being charged correctly and everything looks healthy; only enforcement is off.
A pod restart fixes it until the next re-registration, so the symptom flickers and is hard to attribute.

原因分析

根本原因是 get_model_info() 在查找模型成本信息时,若 input_cost_per_token / output_cost_per_token 缺失,默认将其填充为 0(表示“价格未知”,与“免费”共享相同表示)。而 register_model() 函数在重新注册模型时,会将该合成后的条目(包含零成本值)写回到 litellm.model_cost 字典中。具体代码路径如下(位于 litellm/utils.pyregister_model() 函数中):

try:
    existing_model = cast(dict, get_model_info(model=key))   # synthesizes costs = 0
    model_cost_key = existing_model["key"]
except Exception:
    existing_model = {}
    model_cost_key = key
updated_dictionary = _update_dictionary(existing_model, value)
litellm.model_cost.setdefault(model_cost_key, {}).update(updated_dictionary)  # zeros persisted

第一次注册时,get_model_info(uuid) 会抛出异常(条目尚不存在),因此条目保持为干净的稀疏标记。第二次注册时,该函数找到了稀疏条目,填写了缺失的成本值(0),并将合并结果写回。条目从“无成本键”变为“成本键显式设置为 0”。这导致 _is_cost_explicitly_configured() 函数(来自 PR #24949)误认为成本已显式配置,进而使 _is_model_cost_zero() 返回 True,最终 common_checks 跳过了所有预算检查。

环境排查

  • 确认 LiteLLM 版本:1.85.0 及更高版本(包括 1.88.1)均受影响。
  • 确认数据库后端:问题出现在 DB-backed Proxy 场景中。
  • 确认模型配置方式:模型通过 Admin UI 创建,且未设置自定义定价(custom pricing)。
  • 确认触发操作:是否在同一个进程内进行了第二次模型注册(如 Router 重建、config sync、model update、UI 编辑)。

解决步骤

  1. 临时规避:重启 Pod。这能清除 litellm.model_cost 中的错误写入,直到下一次模型注册触发。
  2. 根本修复:应用 Issue #30201 中提出的补丁。该补丁将 register_model() 中的写入逻辑改为:仅当某个字段在原始条目和调用者提供的 value 中均缺失时,才执行 pop 操作(即不写入)。这既允许显式零值(BYOK、免费模型覆盖)正常通过,又能防止合成零值被持久化。
  3. 确认补丁覆盖范围:补丁应对以下五种场景进行测试:双 Router 复现、稀疏条目(只含 ID)、显式零值条目、实际定价条目、首次注册。

验证方法

应用修复后,运行以下复现代码,检查 litellm.model_cost["fixed-uuid-123"] 在第二次注册后是否仍然仅包含 iddb_model 字段(无 input_cost_per_token/output_cost_per_token):

import litellm
from litellm import Router
from litellm.proxy.auth.auth_checks import _is_model_cost_zero

ml = [{"model_name": "gpt-4o-mini",
       "litellm_params": {"model": "gpt-4o-mini", "custom_llm_provider": "openai", "api_key": "k"},
       "model_info": {"id": "fixed-uuid-123", "db_model": True}}]

Router(model_list=ml)
print(litellm.model_cost["fixed-uuid-123"])

r2 = Router(model_list=ml)
print(litellm.model_cost["fixed-uuid-123"])

gi = r2.get_model_group_info(model_group="gpt-4o-mini")
print(gi.input_cost_per_token, gi.output_cost_per_token)
print(_is_model_cost_zero("gpt-4o-mini", r2))

预期输出:第二次注册后,model_cost["fixed-uuid-123"] 不应包含 input_cost_per_token: 0 / output_cost_per_token: 0_is_model_cost_zero 返回 False。

参考来源

BerriAI/litellm #30198(原始 Issue)

BerriAI/litellm #30201(修复补丁)

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

celebrityanime
celebrityanime
文章: 7975

发表回复

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