快速结论:这个报错通常发生在 LiteLLM 代理中:当使用 /key/update 或 /team/update 修改正在进行中请求的预算上限时,缓存被清除,请求结束时 _key_scope / _team_scope 因缓存未命中提前返回,导致该请求的费用没有写入窗口预算(如 LiteLLM_BudgetWindowSpend)。优先排查窗口预算与 LiteLLM_SpendLogs 是否出现差额,并确认更新操作是否恰好发生在请求执行期间。
适用环境:Issue 已确认:LiteLLM v1.100.0 可稳定复现(3/3),v1.106.6 代码路径未变(约 proxy_server.py L3389);使用含 budget_limits 的月度窗口键,以及约 10 秒的慢请求。其他版本、操作系统、Python、CUDA、显卡等未在 Issue 中给出。
最快修复方案:暂无确认的一步修复方案。Issue 中提出的修复思路(请求认证时快照窗口标识、缓存未命中时从 DB 回退读取等)仍在讨论与等待维护者确认策略,尚未合并验证。
注意事项:不能简单在请求结束时按缓存未命中读 DB,否则当管理员在请求进行中修改或删除窗口时,费用可能被错误归入新窗口或“复活”已删除窗口。Issue 还指出 key 更新会保留同长度窗口的 reset_at,而 team 更新(team_endpoints.py)会重新计算 reset_at,因此仅改上限的 team 更新也可能改变窗口标识。相关策略与回归矩阵尚未由报告者实测验证。
问题场景
用户在 LiteLLM 代理中为 key 配置了窗口预算(例如 budget_duration: "1mo" 与 max_budget),并发起一个较慢的请求(约 10 秒)。在请求尚未完成时,通过 /key/update 调整该 key 的 budget_limits。请求结束后,LiteLLM_SpendLogs 与 key 自身的 spend 列数值正确,但 LiteLLM_BudgetWindowSpend 中的窗口累计金额偏低,且在该窗口剩余时间内一直偏低。Redis 窗口计数器起初正确,但在约 60 秒 TTL 后按偏低的 DB 行重建,预算检查因此放行了本应拦截的请求。Issue 中的测试里真实花费为 $0.0338、上限为 $0.03,但下一个请求仍被允许。该问题影响所有在运行中 key 上修改上限的场景(如预算同步、审批流)。
报错原文
[Bug]: /key/update during an in-flight request drops that request's cost from the key's budget window spend
原因分析
最可能的原因是:/key/update 在更新数据后会调用 _delete_cache_key_object,将该 key 从 user_api_key_cache 中清除。正在进行的请求结束时,proxy_server.py 的 _key_scope 从该缓存读取 budget_limits,遇到缓存未命中便提前返回:
key_obj = await user_api_key_cache.async_get_cache(key=hashed_token)
if key_obj is None:
return
因此该请求的窗口计数递增与 _enqueue_window_spend_row_update 都没有执行。评论中的源码核对进一步指出:increment_spend_counters() → _key_scope 总是先创建普通 key 花费递增,但在缓存未命中时会在 _prepare_window_spend_counter_increment 和 _enqueue_window_spend_row_update 之前返回,这与“普通花费和 SpendLogs 正常、窗口行缺失”的稳定现象吻合,并非仅由 Redis TTL 导致。_team_scope 对 team_id:<id> 有相同的独立提前返回边界,team 窗口可能存在同类问题,但 Issue 中未测试。普通的 max_budget + budget_duration 路径不受影响。
环境排查
- 确认 LiteLLM 版本:v1.100.0 可复现;v1.106.6 代码路径未变,Issue 未确认修复。
- 确认是否使用了带窗口预算的
budget_limits(如budget_duration: "1mo")。 - 确认修改上限的操作是否发生在请求进行中(可通过日志时间线与请求耗时对照)。
- 确认
user_api_key_cache是否使用 Redis,以及其 TTL 设置;Issue 中观察到约 60 秒后计数器由 DB 重建。 - 对比
LiteLLM_SpendLogs汇总值与LiteLLM_BudgetWindowSpend中对应entity_id与window_duration的spend。 - 如涉及 team 预算,注意 Issue 指出 team 更新会重新计算
reset_at,但该场景尚未测试。
解决步骤
- 在运行中的 key 上复现问题时,先记录请求开始时间,并在请求进行中执行
/key/update修改budget_limits。 - 等待批量写入(Issue 中约 25 秒)后,查询
LiteLLM_BudgetWindowSpend中该 token 与窗口的spend,再查询LiteLLM_SpendLogs中该 token 的sum(spend),确认两者是否存在差额。 - 作为临时规避(可优先尝试):避免在请求进行中修改 key 或 team 的窗口预算上限,或将该类更新安排在无活跃请求的时间窗口执行。Issue 指出无请求进行时执行同样更新不会丢失花费,但此为规避思路,未作为正式修复验证。
- 修复方向需等待维护者确认窗口变更策略后再实现;Issue 中提到可考虑在认证时捕获窗口标识(
budget_duration、window_start、reset_at)并区分可变的上限金额(max_budget),或在缓存未命中时安全回退 DB 读取。评论建议用回归矩阵覆盖:仅改上限的 key 更新、仅改上限的 team 更新、进行中修改/删除窗口、无缓存但有效 key 与已删除/吊销 key、重复或迟到的完成回调至多递增一次。
验证方法
复现后对比两个查询结果:LiteLLM_BudgetWindowSpend 中对应窗口的 spend 应与 LiteLLM_SpendLogs 中该 key 的汇总花费一致,且差额不再等于进行中请求的费用。还需在 DB 批量写入完成后及 Redis TTL 过期后再次检查,确认强制执行的读取值仍与真实已提交窗口花费一致,而不是仅内存计数器正确。Issue 中报告的测试可作参考:真实花费 $0.0338 超过 $0.03 上限后,下一个请求应被拦截。
参考来源
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。
![[Bug]: `/v1/images/generations` requests refused by the router never reach failure callbacks (chat ones do)](https://www.chat-gpts.plus/wp-content/uploads/2026/11/47061-862ec916-768x403.jpg)
![[Feature]: Add an official QwenCloud migration path for the existing DashScope provider](https://www.chat-gpts.plus/wp-content/uploads/2026/11/36150-4b5d01c4-768x403.jpg)
