diff --git a/litellm/router.py b/litellm/router.py index 448d8f8390f..8cafe96fdbf 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -1169,11 +1169,16 @@ class Router: def _resolve_to_deployment_model_names(self, model: str) -> tuple[str, ...]: """ - The deployment `model_name`s behind a requested name: a - `model_group_alias` resolves to its target and a callable routing group - to its members, so lookups keyed by model group name reach the same - deployments the router would route the request to. Names that are - neither resolve to themselves. + The deployment `model_name`s behind a requested name, so lookups keyed + by a served name reach the deployments the router would route to: + + - a `model_group_alias` resolves to its target + - a callable routing group resolves to its member `model_name`s + - any other name resolves to itself + + Team-scoped deployments keep their own internal `model_name`, so they + are reached only when that name is requested, as is already the case + for a plain `model_name` lookup. """ resolved: Final = self._get_model_from_alias(model=model) or model group: Final = self.get_routing_group(resolved) diff --git a/tests/test_litellm/router_strategy/test_router_routing_groups.py b/tests/test_litellm/router_strategy/test_router_routing_groups.py index 3c99f3e6af3..d8fa0aabe89 100644 --- a/tests/test_litellm/router_strategy/test_router_routing_groups.py +++ b/tests/test_litellm/router_strategy/test_router_routing_groups.py @@ -1112,3 +1112,42 @@ async def test_group_call_reports_member_cooldown_time_when_every_member_is_cool await router.async_get_available_deployment(model="quality", request_kwargs={}) assert exc_info.value.cooldown_time == deployment_cooldown_time + + +def test_get_model_ids_for_a_group_does_not_reach_team_scoped_deployments(): + """ + Group resolution goes through the `model_name` index, and a team deployment is + indexed under its own internal `model_name` (its `team_public_model_name` lives + in a separate index), so resolving a group cannot pull in another team's + deployment ids. Unchanged from a plain `model_name` lookup. + """ + shared_deployment = { + "model_name": "shared-model", + "litellm_params": {"model": "openai/gpt-4o", "api_key": "sk-test-1", "api_base": "https://example.invalid"}, + "model_info": {"id": "shared-1"}, + } + team_deployments = [ + { + "model_name": f"shared-model_{team_id}_uuid", + "litellm_params": { + "model": "openai/gpt-4o", + "api_key": f"sk-test-{team_id}", + "api_base": "https://example.invalid", + }, + "model_info": { + "id": f"{team_id}-1", + "team_id": team_id, + "team_public_model_name": "shared-model", + }, + } + for team_id in ("team-a", "team-b") + ] + router = Router( + model_list=[shared_deployment, *team_deployments], + routing_groups=[{"group_name": "quality", "models": ["shared-model"], "routing_strategy": "simple-shuffle"}], + ) + + assert router.get_model_ids(model_name="quality") == ["shared-1"] + assert router.get_model_ids(model_name="shared-model") == ["shared-1"] + # the team deployments are still routable under their own names + assert router.get_model_ids(model_name="shared-model_team-a_uuid") == ["team-a-1"]