From 8a87909a3fd5638c5f59e3d71aac0dbe64b0dcf3 Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 15:37:33 +0000 Subject: [PATCH 1/2] fix(router): keep deployments when provider budget has duration but no max_budget --- litellm/router_strategy/budget_limiter.py | 27 ++++++------ .../test_budget_limiter_hotpath.py | 41 +++++++++++++++++++ 2 files changed, 55 insertions(+), 13 deletions(-) diff --git a/litellm/router_strategy/budget_limiter.py b/litellm/router_strategy/budget_limiter.py index 067f38ab11c..f723dc05ece 100644 --- a/litellm/router_strategy/budget_limiter.py +++ b/litellm/router_strategy/budget_limiter.py @@ -222,20 +222,21 @@ class RouterBudgetLimiting(CustomLogger): provider = self._get_llm_provider_for_deployment(deployment) if provider in provider_configs: config = provider_configs[provider] - if config.max_budget is None: - continue - current_spend = spend_map.get(f"provider_spend:{provider}:{config.budget_duration}", 0.0) - self._track_provider_remaining_budget_prometheus( - provider=provider, - spend=current_spend, - budget_limit=config.max_budget, - ) + if config.max_budget is not None: + current_spend = spend_map.get(f"provider_spend:{provider}:{config.budget_duration}", 0.0) + self._track_provider_remaining_budget_prometheus( + provider=provider, + spend=current_spend, + budget_limit=config.max_budget, + ) - if config.max_budget and current_spend >= config.max_budget: - debug_msg = f"Exceeded budget for provider {provider}: {current_spend} >= {config.max_budget}" - deployment_above_budget_info += f"{debug_msg}\n" - is_within_budget = False - continue + if current_spend >= config.max_budget: + debug_msg = ( + f"Exceeded budget for provider {provider}: {current_spend} >= {config.max_budget}" + ) + deployment_above_budget_info += f"{debug_msg}\n" + is_within_budget = False + continue # Check deployment budget if self.deployment_budget_config and is_within_budget: diff --git a/tests/test_litellm/router_strategy/test_budget_limiter_hotpath.py b/tests/test_litellm/router_strategy/test_budget_limiter_hotpath.py index 36fa38bacb5..83646189e69 100644 --- a/tests/test_litellm/router_strategy/test_budget_limiter_hotpath.py +++ b/tests/test_litellm/router_strategy/test_budget_limiter_hotpath.py @@ -141,6 +141,47 @@ async def test_async_filter_deployments_resolves_provider_once_per_deployment( assert provider_resolution_calls == len(healthy_deployments) +@pytest.mark.asyncio +async def test_async_filter_deployments_keeps_provider_without_max_budget( + disable_budget_sync, monkeypatch +): + provider_budget = RouterBudgetLimiting( + dual_cache=DualCache(), + provider_budget_config={ + "openai": BudgetConfig(budget_duration="1d"), + }, + ) + + healthy_deployments = [ + { + "model_name": "gpt-4o-mini", + "litellm_params": {"model": "openai/gpt-4o-mini"}, + "model_info": {"id": "deployment-1"}, + }, + { + "model_name": "gpt-4o-mini", + "litellm_params": {"model": "openai/gpt-4o-mini"}, + "model_info": {"id": "deployment-2"}, + }, + ] + + monkeypatch.setattr( + provider_budget, + "_get_llm_provider_for_deployment", + lambda deployment: "openai", + ) + + filtered_deployments = await provider_budget.async_filter_deployments( + model="gpt-4o-mini", + healthy_deployments=healthy_deployments, + messages=[], + request_kwargs={}, + parent_otel_span=None, + ) + + assert filtered_deployments == healthy_deployments + + @pytest.mark.asyncio async def test_async_filter_deployments_does_not_recompute_provider_when_resolved_none( disable_budget_sync, monkeypatch From 1ce6d571d706d01a285efe97d1a7c0808b1d5bbd Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 15:54:57 +0000 Subject: [PATCH 2/2] test(router): cover provider over-budget filtering path --- .../test_budget_limiter_hotpath.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/tests/test_litellm/router_strategy/test_budget_limiter_hotpath.py b/tests/test_litellm/router_strategy/test_budget_limiter_hotpath.py index 83646189e69..f6088c47905 100644 --- a/tests/test_litellm/router_strategy/test_budget_limiter_hotpath.py +++ b/tests/test_litellm/router_strategy/test_budget_limiter_hotpath.py @@ -182,6 +182,45 @@ async def test_async_filter_deployments_keeps_provider_without_max_budget( assert filtered_deployments == healthy_deployments +@pytest.mark.asyncio +async def test_async_filter_deployments_filters_provider_over_max_budget( + disable_budget_sync, monkeypatch +): + provider_budget = RouterBudgetLimiting( + dual_cache=DualCache(), + provider_budget_config={ + "openai": BudgetConfig(budget_duration="1d", max_budget=100.0), + }, + ) + + await provider_budget.dual_cache.async_set_cache( + key="provider_spend:openai:1d", value=150.0 + ) + + healthy_deployments = [ + { + "model_name": "gpt-4o-mini", + "litellm_params": {"model": "openai/gpt-4o-mini"}, + "model_info": {"id": "deployment-1"}, + }, + ] + + monkeypatch.setattr( + provider_budget, + "_get_llm_provider_for_deployment", + lambda deployment: "openai", + ) + + with pytest.raises(ValueError, match="Exceeded budget for provider openai"): + await provider_budget.async_filter_deployments( + model="gpt-4o-mini", + healthy_deployments=healthy_deployments, + messages=[], + request_kwargs={}, + parent_otel_span=None, + ) + + @pytest.mark.asyncio async def test_async_filter_deployments_does_not_recompute_provider_when_resolved_none( disable_budget_sync, monkeypatch