mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
fix(router): honor ServiceUnavailableErrorRetries and InternalServerErrorRetries in retry policy
This commit is contained in:
parent
e4fd790f1c
commit
902a93d10d
6 changed files with 145 additions and 0 deletions
|
|
@ -8,7 +8,9 @@ from litellm.exceptions import (
|
|||
AuthenticationError,
|
||||
BadRequestError,
|
||||
ContentPolicyViolationError,
|
||||
InternalServerError,
|
||||
RateLimitError,
|
||||
ServiceUnavailableError,
|
||||
Timeout,
|
||||
)
|
||||
from litellm.types.router import RetryPolicy
|
||||
|
|
@ -26,6 +28,8 @@ def get_num_retries_from_retry_policy(
|
|||
TimeoutErrorRetries: Optional[int] = None
|
||||
RateLimitErrorRetries: Optional[int] = None
|
||||
ContentPolicyViolationErrorRetries: Optional[int] = None
|
||||
InternalServerErrorRetries: Optional[int] = None
|
||||
ServiceUnavailableErrorRetries: Optional[int] = None
|
||||
"""
|
||||
# if we can find the exception then in the retry policy -> return the number of retries
|
||||
|
||||
|
|
@ -48,6 +52,10 @@ def get_num_retries_from_retry_policy(
|
|||
and retry_policy.ContentPolicyViolationErrorRetries is not None
|
||||
):
|
||||
return retry_policy.ContentPolicyViolationErrorRetries
|
||||
if isinstance(exception, ServiceUnavailableError) and retry_policy.ServiceUnavailableErrorRetries is not None:
|
||||
return retry_policy.ServiceUnavailableErrorRetries
|
||||
if isinstance(exception, InternalServerError) and retry_policy.InternalServerErrorRetries is not None:
|
||||
return retry_policy.InternalServerErrorRetries
|
||||
if isinstance(exception, BadRequestError) and retry_policy.BadRequestErrorRetries is not None:
|
||||
return retry_policy.BadRequestErrorRetries
|
||||
|
||||
|
|
|
|||
|
|
@ -95,6 +95,7 @@ class RetryPolicy(BaseModel):
|
|||
RateLimitErrorRetries: Optional[int] = None
|
||||
ContentPolicyViolationErrorRetries: Optional[int] = None
|
||||
InternalServerErrorRetries: Optional[int] = None
|
||||
ServiceUnavailableErrorRetries: Optional[int] = None
|
||||
|
||||
|
||||
class UpdateRouterConfig(BaseModel):
|
||||
|
|
|
|||
102
tests/test_litellm/router_utils/test_get_retry_from_policy.py
Normal file
102
tests/test_litellm/router_utils/test_get_retry_from_policy.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import litellm
|
||||
from litellm.router_utils.get_retry_from_policy import (
|
||||
get_num_retries_from_retry_policy,
|
||||
)
|
||||
from litellm.types.router import RetryPolicy
|
||||
|
||||
|
||||
def _service_unavailable_error() -> litellm.ServiceUnavailableError:
|
||||
return litellm.ServiceUnavailableError(
|
||||
message="model is down",
|
||||
llm_provider="openai",
|
||||
model="gpt-5.6",
|
||||
)
|
||||
|
||||
|
||||
def _internal_server_error() -> litellm.InternalServerError:
|
||||
return litellm.InternalServerError(
|
||||
message="upstream 500",
|
||||
llm_provider="openai",
|
||||
model="gpt-5.6",
|
||||
)
|
||||
|
||||
|
||||
def test_service_unavailable_error_retries_honored():
|
||||
policy = RetryPolicy(ServiceUnavailableErrorRetries=0)
|
||||
|
||||
assert (
|
||||
get_num_retries_from_retry_policy(
|
||||
exception=_service_unavailable_error(),
|
||||
retry_policy=policy,
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
def test_service_unavailable_error_retries_nonzero():
|
||||
policy = RetryPolicy(ServiceUnavailableErrorRetries=4)
|
||||
|
||||
assert (
|
||||
get_num_retries_from_retry_policy(
|
||||
exception=_service_unavailable_error(),
|
||||
retry_policy=policy,
|
||||
)
|
||||
== 4
|
||||
)
|
||||
|
||||
|
||||
def test_internal_server_error_retries_honored():
|
||||
policy = RetryPolicy(InternalServerErrorRetries=0)
|
||||
|
||||
assert (
|
||||
get_num_retries_from_retry_policy(
|
||||
exception=_internal_server_error(),
|
||||
retry_policy=policy,
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
def test_service_unavailable_not_covered_by_internal_server_error_retries():
|
||||
policy = RetryPolicy(InternalServerErrorRetries=0)
|
||||
|
||||
assert (
|
||||
get_num_retries_from_retry_policy(
|
||||
exception=_service_unavailable_error(),
|
||||
retry_policy=policy,
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
def test_internal_server_error_not_covered_by_service_unavailable_retries():
|
||||
policy = RetryPolicy(ServiceUnavailableErrorRetries=0)
|
||||
|
||||
assert (
|
||||
get_num_retries_from_retry_policy(
|
||||
exception=_internal_server_error(),
|
||||
retry_policy=policy,
|
||||
)
|
||||
is None
|
||||
)
|
||||
|
||||
|
||||
def test_service_unavailable_error_retries_from_dict_policy():
|
||||
assert (
|
||||
get_num_retries_from_retry_policy(
|
||||
exception=_service_unavailable_error(),
|
||||
retry_policy={"ServiceUnavailableErrorRetries": 0},
|
||||
)
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
def test_service_unavailable_error_retries_from_model_group_policy():
|
||||
assert (
|
||||
get_num_retries_from_retry_policy(
|
||||
exception=_service_unavailable_error(),
|
||||
model_group="gpt-5.6",
|
||||
model_group_retry_policy={"gpt-5.6": RetryPolicy(ServiceUnavailableErrorRetries=1)},
|
||||
)
|
||||
== 1
|
||||
)
|
||||
|
|
@ -6574,3 +6574,34 @@ def test_model_info_is_active_for_environment_matrix(monkeypatch):
|
|||
monkeypatch.delenv("LITELLM_ENVIRONMENT")
|
||||
with pytest.raises(ValueError, match="LITELLM_ENVIRONMENT"):
|
||||
model_info_is_active_for_environment(model_info={"supported_environments": ["production"]})
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("policy_retries,expected_calls", [(0, 1), (1, 2)])
|
||||
async def test_router_retry_policy_service_unavailable_retries(policy_retries, expected_calls):
|
||||
from litellm.types.router import RetryPolicy
|
||||
|
||||
router = litellm.Router(
|
||||
model_list=[
|
||||
{
|
||||
"model_name": "gpt-5.6",
|
||||
"litellm_params": {"model": "openai/gpt-5.6", "api_key": "fake-key"},
|
||||
}
|
||||
],
|
||||
retry_policy=RetryPolicy(ServiceUnavailableErrorRetries=policy_retries),
|
||||
disable_cooldowns=True,
|
||||
)
|
||||
|
||||
error = litellm.ServiceUnavailableError(
|
||||
message="model is down",
|
||||
llm_provider="openai",
|
||||
model="gpt-5.6",
|
||||
)
|
||||
with patch.object(litellm, "acompletion", AsyncMock(side_effect=error)) as mock_acompletion:
|
||||
with pytest.raises(litellm.ServiceUnavailableError):
|
||||
await router.acompletion(
|
||||
model="gpt-5.6",
|
||||
messages=[{"role": "user", "content": "hi"}],
|
||||
)
|
||||
|
||||
assert mock_acompletion.call_count == expected_calls
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ const retryPolicyMap: Record<string, string> = {
|
|||
"RateLimitError (429)": "RateLimitErrorRetries",
|
||||
"ContentPolicyViolationError (400)": "ContentPolicyViolationErrorRetries",
|
||||
"InternalServerError (500)": "InternalServerErrorRetries",
|
||||
"ServiceUnavailableError (503)": "ServiceUnavailableErrorRetries",
|
||||
};
|
||||
|
||||
const ModelRetrySettingsTab = ({
|
||||
|
|
|
|||
2
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
2
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
|
|
@ -30947,6 +30947,8 @@ export interface components {
|
|||
InternalServerErrorRetries?: number | null;
|
||||
/** Ratelimiterrorretries */
|
||||
RateLimitErrorRetries?: number | null;
|
||||
/** Serviceunavailableerrorretries */
|
||||
ServiceUnavailableErrorRetries?: number | null;
|
||||
/** Timeouterrorretries */
|
||||
TimeoutErrorRetries?: number | null;
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue