From a95a166633b06b1f7c0eed53a156baed51bb361e Mon Sep 17 00:00:00 2001 From: Tanvir Alam Date: Mon, 28 Sep 2026 00:35:40 -0400 Subject: [PATCH] fix(router): keep deployment-id lookups in the healthy list _common_checks_available_deployment returns a single deployment dict when the model string is a deployment id. Both _get_healthy_deployments and _async_get_healthy_deployments treated that as "no healthy deployments" and skipped cooldown filtering, so retries/cooldown saw an empty list even when the deployment was up. Wrap the dict into a one-element list and continue the normal path. Fixes #43495 --- litellm/router.py | 8 +++- .../test_router_helper_utils.py | 40 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/litellm/router.py b/litellm/router.py index 1ef68e60440..a521529ac4d 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -8485,8 +8485,10 @@ class Router: _, _all_deployments = self._common_checks_available_deployment( model=model, ) + # Deployment-id lookup returns a single dict; wrap it so cooldown + # filtering still runs instead of reporting zero healthy copies. if isinstance(_all_deployments, dict): - return [] + _all_deployments = [_all_deployments] except Exception: pass @@ -8513,8 +8515,10 @@ class Router: _, _all_deployments = self._common_checks_available_deployment( model=model, ) + # Deployment-id lookup returns a single dict; wrap it so cooldown + # filtering still runs instead of reporting zero healthy copies. if isinstance(_all_deployments, dict): - return [], _all_deployments + _all_deployments = [_all_deployments] except Exception: pass diff --git a/tests/router_unit_tests/test_router_helper_utils.py b/tests/router_unit_tests/test_router_helper_utils.py index d3ad1d989c8..e50d7cdde1f 100644 --- a/tests/router_unit_tests/test_router_helper_utils.py +++ b/tests/router_unit_tests/test_router_helper_utils.py @@ -721,6 +721,46 @@ def test_get_healthy_deployments(model_list): ) assert len(deployments) > 0 +@pytest.mark.asyncio +async def test_get_healthy_deployments_by_deployment_id(): + """Routing by model_info.id must still return that deployment as healthy.""" + from unittest.mock import AsyncMock, patch + + router = Router( + model_list=[ + { + "model_name": "chat", + "litellm_params": {"model": "openai/gpt-4o-mini", "api_key": "sk-test"}, + "model_info": {"id": "deploy-1"}, + } + ] + ) + + with patch( + "litellm.router._get_cooldown_deployments", + return_value=[], + ), patch( + "litellm.router._async_get_cooldown_deployments", + new_callable=AsyncMock, + return_value=[], + ): + sync_healthy, sync_all = router._get_healthy_deployments( + model="deploy-1", parent_otel_span=None + ) + async_healthy, async_all = await router._async_get_healthy_deployments( + model="deploy-1", parent_otel_span=None + ) + + assert len(sync_healthy) == 1 + assert sync_healthy[0]["model_info"]["id"] == "deploy-1" + assert isinstance(sync_all, list) and len(sync_all) == 1 + + assert len(async_healthy) == 1 + assert async_healthy[0]["model_info"]["id"] == "deploy-1" + assert isinstance(async_all, list) and len(async_all) == 1 + + + @pytest.mark.parametrize("sync_mode", [True, False]) @pytest.mark.asyncio