From fc01fdef1197cbad32ae67814db2b0bad43be4bf Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 8 Jul 2026 06:07:48 +0000 Subject: [PATCH] fix(router): honor allowed_fails_policy threshold of 0 in cooldown --- litellm/router_utils/cooldown_handlers.py | 8 +- ..._health_check_allowed_fails_integration.py | 87 +++++++++++++++++++ 2 files changed, 90 insertions(+), 5 deletions(-) diff --git a/litellm/router_utils/cooldown_handlers.py b/litellm/router_utils/cooldown_handlers.py index 2bc2ed998ca..802ebbfb3ba 100644 --- a/litellm/router_utils/cooldown_handlers.py +++ b/litellm/router_utils/cooldown_handlers.py @@ -372,12 +372,10 @@ def should_cooldown_based_on_allowed_fails_policy( - True if fails exceed the allowed limit (should cooldown) - False if fails are within the allowed limit (should not cooldown) """ - allowed_fails = ( - litellm_router_instance.get_allowed_fails_from_policy( - exception=original_exception, - ) - or litellm_router_instance.allowed_fails + policy_allowed_fails = litellm_router_instance.get_allowed_fails_from_policy( + exception=original_exception, ) + allowed_fails = policy_allowed_fails if policy_allowed_fails is not None else litellm_router_instance.allowed_fails cooldown_time = litellm_router_instance.cooldown_time or DEFAULT_COOLDOWN_TIME_SECONDS current_fails = litellm_router_instance.failed_calls.get_cache(key=deployment) or 0 diff --git a/tests/test_litellm/router_utils/test_health_check_allowed_fails_integration.py b/tests/test_litellm/router_utils/test_health_check_allowed_fails_integration.py index 64239f33966..2f7fe050f32 100644 --- a/tests/test_litellm/router_utils/test_health_check_allowed_fails_integration.py +++ b/tests/test_litellm/router_utils/test_health_check_allowed_fails_integration.py @@ -790,3 +790,90 @@ class TestSharedCacheTransientErrorFilter: unhealthy_ids = router.health_state_cache.get_unhealthy_deployment_ids() assert "deploy-1" in unhealthy_ids + + +class TestAllowedFailsPolicyZeroThreshold: + """ + Regression tests for allowed_fails_policy thresholds set to 0. + + A configured value of 0 must be honored (cooldown on the first matching + failure) instead of being treated as falsy and silently falling back to + the router-level allowed_fails default. + """ + + def test_zero_threshold_cooldowns_on_first_failure(self): + """RateLimitErrorAllowedFails=0 with default router allowed_fails should + cooldown on the first 429 (1 > 0), not fall back to litellm.allowed_fails=3.""" + from litellm.router_utils.cooldown_handlers import ( + should_cooldown_based_on_allowed_fails_policy, + ) + + router = Router( + model_list=[_make_model("deploy-1"), _make_model("deploy-2", "gpt-5")], + allowed_fails_policy=AllowedFailsPolicy(RateLimitErrorAllowedFails=0), + ) + assert router.allowed_fails == litellm.allowed_fails + + rate_exc = litellm.RateLimitError( + message="rate limited", model="gpt-4", llm_provider="openai" + ) + + result = should_cooldown_based_on_allowed_fails_policy( + litellm_router_instance=router, + deployment="deploy-1", + original_exception=rate_exc, + ) + assert result is True + + def test_zero_threshold_honored_over_nonzero_router_default(self): + """An explicit router allowed_fails must not override a policy value of 0.""" + from litellm.router_utils.cooldown_handlers import ( + should_cooldown_based_on_allowed_fails_policy, + ) + + router = Router( + model_list=[_make_model("deploy-1"), _make_model("deploy-2", "gpt-5")], + allowed_fails_policy=AllowedFailsPolicy(AuthenticationErrorAllowedFails=0), + allowed_fails=5, + ) + + auth_exc = litellm.AuthenticationError( + message="Invalid key", model="gpt-4", llm_provider="openai" + ) + + result = should_cooldown_based_on_allowed_fails_policy( + litellm_router_instance=router, + deployment="deploy-1", + original_exception=auth_exc, + ) + assert result is True + + def test_unmatched_exception_still_falls_back_to_router_default(self): + """When the policy returns None (no matching field), the router-level + allowed_fails default is still used as the fallback.""" + from litellm.router_utils.cooldown_handlers import ( + should_cooldown_based_on_allowed_fails_policy, + ) + + router = Router( + model_list=[_make_model("deploy-1"), _make_model("deploy-2", "gpt-5")], + allowed_fails_policy=AllowedFailsPolicy(RateLimitErrorAllowedFails=0), + allowed_fails=2, + ) + + generic_exc = Exception("some other error") + + for _ in range(2): + result = should_cooldown_based_on_allowed_fails_policy( + litellm_router_instance=router, + deployment="deploy-1", + original_exception=generic_exc, + ) + assert result is False + + result = should_cooldown_based_on_allowed_fails_policy( + litellm_router_instance=router, + deployment="deploy-1", + original_exception=generic_exc, + ) + assert result is True