fix(router): ignore non-integer status codes when picking retry skips

CI's router_code_coverage gate wants every function in router.py called by
name from a test file with "router" in its name, and the new helper had no
direct caller, so the check-quality job failed on the first tip.

Covering it directly also turned up a hole. litellm._should_retry compares
the status code to 500, so a provider exception carrying a string status code
raises TypeError instead of answering. should_retry_this_error has the same
call, but the retry policy path skips it, which is exactly the path this
change enables, so the helper was the first to touch that value. Narrowing to
int leaves those exceptions on the old retry-in-place behavior.
This commit is contained in:
mateo-berri 2026-09-05 22:43:15 -07:00
parent 2e2fce5e58
commit 7c7810df42
2 changed files with 28 additions and 1 deletions

View file

@ -7467,7 +7467,7 @@ class Router:
) -> tuple[str, ...]:
failed_deployment_id: Final[str | None] = getattr(exception, "failed_deployment_id", None)
status_code: Final = getattr(exception, "status_code", None)
if not failed_deployment_id or status_code is None:
if not failed_deployment_id or not isinstance(status_code, int):
return ()
if litellm._should_retry(status_code): # pyright: ignore[reportPrivateUsage] # as in should_retry_this_error
return ()

View file

@ -13234,6 +13234,33 @@ async def test_router_retry_policy_400_retries_on_sibling_deployment(
assert response._hidden_params["additional_headers"]["x-litellm-attempted-retries"] == 1
@pytest.mark.parametrize(
"status_code,failed_deployment_id,already_skipped,healthy_deployment_ids,expected",
[
(400, "rejecting", None, ["rejecting", "accepting"], ("rejecting",)),
(403, "rejecting", None, ["rejecting", "accepting"], ("rejecting",)),
(400, "second", ("first",), ["first", "second", "third"], ("first", "second")),
(429, "rejecting", None, ["rejecting", "accepting"], ()),
(503, "rejecting", None, ["rejecting", "accepting"], ()),
(400, "rejecting", None, ["rejecting"], ()),
(400, None, None, ["rejecting", "accepting"], ()),
(None, "rejecting", None, ["rejecting", "accepting"], ()),
("400", "rejecting", None, ["rejecting", "accepting"], ()),
],
)
def test_router_deployment_ids_to_skip_on_retry(
status_code, failed_deployment_id, already_skipped, healthy_deployment_ids, expected
):
exception = Exception("upstream refused this request")
exception.status_code = status_code
exception.failed_deployment_id = failed_deployment_id
healthy_deployments = [{"model_info": {"id": deployment_id}} for deployment_id in healthy_deployment_ids]
assert (
litellm.Router._deployment_ids_to_skip_on_retry(exception, already_skipped, healthy_deployments) == expected
)
def _make_failure_logging_obj():
return LiteLLMLogging(
model="gpt-5.6",