fix(proxy): treat malformed cost-map token limits as absent on /v1/models - #33903
Conversation
…dels create_model_info_response cast cost-map max_input_tokens / max_output_tokens with unguarded int(). The surrounding try/except covers only the get_model_info lookup, so a deployment whose model_info carries a non-numeric limit (e.g. "128,000" or an empty string) raised inside the per-model listing loop and failed the entire GET /v1/models and /models response with a 500, taking healthy deployments down with it. A deployment's model_info is registered into litellm.model_cost verbatim, so the malformed value reaches the cost map and not just the router index. Router.get_configured_token_limits already coerced this safely for the deployment path; the cost-map path was missed, so the two together still regressed. Both now share coerce_token_limit in litellm_core_utils, which returns None for a malformed value so the listing omits that one limit instead of failing, matching the graceful degradation the endpoint had before the cost-map switch.
Greptile SummaryThis PR fixes a
Confidence Score: 5/5Safe to merge — the change is narrowly scoped to token-limit coercion, the shared helper is correctly implemented, and the router refactor is behaviorally equivalent to the code it replaces. The fix is minimal and targeted: coerce_token_limit correctly orders the bool-before-int check (bool subclasses int), adds OverflowError handling for inf/nan floats that the old router helper missed, and falls through cleanly for None and unsupported types. Both changed call sites now produce identical semantics to what they did for well-formed values. The new Router integration test covers the exact registration path described in the bug report, preventing a future regression through the real litellm.model_cost lookup. No files require special attention.
|
| Filename | Overview |
|---|---|
| litellm/litellm_core_utils/core_helpers.py | Adds coerce_token_limit() — a safe coercion helper that returns None for bools, non-numeric strings, and unsupported types. Bool check correctly precedes int check (since bool is a subclass of int) and OverflowError is caught for inf/nan floats. |
| litellm/proxy/utils.py | Replaces bare int() casts on cost-map token limits with coerce_token_limit(). The old casts were outside the surrounding try/except, so a malformed value like '128,000' raised ValueError mid-loop and failed the entire /v1/models response with a 500. |
| litellm/router.py | Removes the local _as_int helper from get_configured_token_limits and replaces it with the shared coerce_token_limit. Behavior is equivalent: None/bool → None, OverflowError now also caught (improvement for inf/nan floats). |
| tests/test_litellm/proxy/test_proxy_utils.py | Adds three new tests: parametrized bad-value coverage for the cost-map path, a mixed valid/malformed limit test, and a Router integration test that exercises the real litellm.model_cost registration path described in the bug report. litellm.model_cost is saved/restored in a try/finally for isolation. |
Reviews (1): Last reviewed commit: "fix(proxy): treat malformed cost-map tok..." | Re-trigger Greptile
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Relevant issues
Linear ticket
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Config used for both runs; the first deployment carries a non-numeric limit, the second is a healthy control that shows whether the whole listing survives
Before, at 595e724 (this branch's parent, tip of litellm_internal_staging at the time)
The proxy log shows the cast that failed, inside the per-model listing loop
After, at ab02127
The deployment with the malformed value is listed with its limits omitted, the healthy control keeps both of its limits, and the other 35 deployments come back instead of being lost to the 500
Same proxy still serves real traffic, hitting the live OpenAI API
Type
🐛 Bug Fix
Changes
create_model_info_responsereadmax_input_tokensandmax_output_tokensout of the cost map and cast both with a bareint(). The surrounding try/except covers only theget_model_infolookup, not the casts, so a deployment whosemodel_infocarries a non-numeric limit raised inside the per-model loop and failed the entireGET /v1/modelsand/modelsresponse with a 500. Every healthy deployment went down with it. Before the cost-map switch the same config returned 200 and simply omitted that deployment's limitsThe value gets there because a deployment's
model_infois registered intolitellm.model_costverbatim, so it reaches the cost map and not only the router index.Router.get_configured_token_limitsalready coerced this safely for the deployment path, so guarding one path and not the other still left the pair regressingBoth call sites now share
coerce_token_limitinlitellm_core_utils/core_helpers.py. It returnsNonefor anything that is not a usable number, so the listing omits that one limit and keeps serving, and it rejects bools sinceTrue/Falseis never a meaningful token limit. Coercion of well-formed values is unchanged, including numeric strings like"32000"Final Attestation