From c551a5c44abacea6d777cdd7bedde075a9dd75c9 Mon Sep 17 00:00:00 2001 From: mateo Date: Thu, 20 Aug 2026 20:59:38 +0000 Subject: [PATCH] fix(proxy): treat explicit zero non-token prices as priced A deployment that overrides any cost_per field, including at zero, now counts as priced so it is not blocked as unpriced Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/proxy/auth/auth_checks.py | 13 +++++++---- .../proxy/auth/test_auth_checks.py | 23 ++++++++++++++++++- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index 3639ef245cf..5ad61f93d4f 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -300,15 +300,20 @@ def _entry_has_priced_metric(entry: Mapping[str, object]) -> bool: return False +def _entry_declares_price(entry: Mapping[str, object]) -> bool: + return any("cost_per" in key for key in entry) + + def _model_group_has_pricing(model: str, llm_router: "Router") -> bool: """ - Check every deployment behind a model group for a positive price on any billed - metric (tokens, characters, seconds, pages, images, queries, ...), so models that - are billed by a non-token metric are not treated as unpriced. + A model group counts as priced when a deployment overrides any *cost_per* field in its + litellm_params, even at zero, or when its resolved model info carries a positive price on + any billed metric (tokens, characters, seconds, pages, images, queries, ...), so models + billed by a non-token metric are not treated as unpriced. """ for deployment in llm_router.get_model_list(model_name=model) or []: litellm_params = deployment.get("litellm_params") or {} - if _entry_has_priced_metric(litellm_params): + if _entry_declares_price(litellm_params): return True model_id = (deployment.get("model_info") or {}).get("id") diff --git a/tests/test_litellm/proxy/auth/test_auth_checks.py b/tests/test_litellm/proxy/auth/test_auth_checks.py index a078e041aa7..fbdd9a42750 100644 --- a/tests/test_litellm/proxy/auth/test_auth_checks.py +++ b/tests/test_litellm/proxy/auth/test_auth_checks.py @@ -5246,7 +5246,7 @@ def test_model_has_no_cost_mapping_non_token_price_from_litellm_params_is_false( { "model_name": "custom-tts", "litellm_params": { - "model": UNPRICED_UNDERLYING_MODEL, + "model": f"{UNPRICED_UNDERLYING_MODEL}-per-second", "api_key": "sk-test", "input_cost_per_second": 0.0001, }, @@ -5257,6 +5257,27 @@ def test_model_has_no_cost_mapping_non_token_price_from_litellm_params_is_false( assert model_has_no_cost_mapping(model="custom-tts", llm_router=router) is False +@pytest.mark.parametrize("cost_field", ["input_cost_per_second", "input_cost_per_token"]) +def test_model_has_no_cost_mapping_explicit_zero_price_is_false(cost_field): + from litellm.proxy.auth.auth_checks import model_has_no_cost_mapping + from litellm.router import Router + + router = Router( + model_list=[ + { + "model_name": "free-group", + "litellm_params": { + "model": f"{UNPRICED_UNDERLYING_MODEL}-{cost_field}", + "api_key": "sk-test", + cost_field: 0, + }, + } + ] + ) + + assert model_has_no_cost_mapping(model="free-group", llm_router=router) is False + + async def _run_common_checks( model: Optional[str], llm_router: Optional["Router"], route: str = "/chat/completions" ) -> bool: