diff --git a/litellm/proxy/auth/model_checks.py b/litellm/proxy/auth/model_checks.py index 8fecad2b462..9e4f75e0a8e 100644 --- a/litellm/proxy/auth/model_checks.py +++ b/litellm/proxy/auth/model_checks.py @@ -527,23 +527,11 @@ def _routed_deployment_model(name_pattern: str, listed_name: str, deployment_mod ) -def _names_listed_under_wildcard( - deployment: Mapping[str, object], - wildcard_pattern: str, - litellm_params: LiteLLM_Params | None, - routed_models: Callable[[str, str | None], frozenset[str]] | None, - deployed_by_name: Callable[[str, str | None], bool], +def _names_no_deployment_serves_by_name( + deployment: Mapping[str, object], names: Sequence[str], deployed_by_name: Callable[[str, str | None], bool] ) -> tuple[str, ...]: team_id: Final = _deployment_team_id(deployment) - return tuple( - name - for name in get_known_models_from_wildcard( - wildcard_model=wildcard_pattern, - litellm_params=litellm_params, - routes_to_model=_routes_to_model(routed_models, team_id), - ) - if not deployed_by_name(name, team_id) - ) + return tuple(name for name in names if not deployed_by_name(name, team_id)) def expand_wildcard_deployments_for_model_info( @@ -584,14 +572,16 @@ def expand_wildcard_deployments_for_model_info( except Exception: expanded.append(deployment) continue - expanded_names = _names_listed_under_wildcard( - deployment, wildcard_pattern, litellm_params, routed_models, deployed_by_name + expanded_names = get_known_models_from_wildcard( + wildcard_model=wildcard_pattern, + litellm_params=litellm_params, + routes_to_model=_routes_to_model(routed_models, _deployment_team_id(deployment)), ) if not expanded_names: expanded.append(deployment) continue - for name in expanded_names: + for name in _names_no_deployment_serves_by_name(deployment, expanded_names, deployed_by_name): row = copy.deepcopy(deployment) row["model_name"] = name params = row.get("litellm_params") diff --git a/tests/unit/proxy/auth/test_model_checks.py b/tests/unit/proxy/auth/test_model_checks.py index 5629873c044..05ea7e22164 100644 --- a/tests/unit/proxy/auth/test_model_checks.py +++ b/tests/unit/proxy/auth/test_model_checks.py @@ -1394,7 +1394,11 @@ def test_catch_all_wildcard_model_info_keeps_only_the_concrete_row_for_a_deploye @contextmanager def _openrouter_catalog_beside_a_named_deployment( - monkeypatch: pytest.MonkeyPatch, wildcard_name: str, named: str + monkeypatch: pytest.MonkeyPatch, + wildcard_name: str, + named: str, + catalog: tuple[str, ...] = ("openai/gpt-5.4-mini", "anthropic/claude-haiku-5-5"), + wildcard_key: str | None = None, ) -> Generator[Router, None, None]: import litellm @@ -1402,15 +1406,16 @@ def _openrouter_catalog_beside_a_named_deployment( monkeypatch.delenv("OPENROUTER_API_BASE", raising=False) with respx.mock(assert_all_called=False) as respx_mock: respx_mock.get("https://openrouter.ai/api/v1/models/user").mock( - return_value=httpx.Response( - 200, json={"data": [{"id": "openai/gpt-5.4-mini"}, {"id": "anthropic/claude-haiku-5-5"}]} - ) + return_value=httpx.Response(200, json={"data": [{"id": catalog_id} for catalog_id in catalog]}) ) yield Router( model_list=[ { "model_name": wildcard_name, - "litellm_params": {"model": "openrouter/*", "api_key": f"sk-or-wildcard-{wildcard_name}"}, + "litellm_params": { + "model": "openrouter/*", + "api_key": wildcard_key or f"sk-or-wildcard-{wildcard_name}", + }, }, { "model_name": named, @@ -1450,6 +1455,29 @@ def test_provider_wildcard_lists_a_name_a_named_deployment_serves_once( ] +@pytest.mark.parametrize( + ("wildcard_name", "named"), + [("openrouter/*", "openrouter/openai/gpt-5.4-mini"), ("or-team/*", "or-team/openai/gpt-5.4-mini")], +) +def test_provider_wildcard_whose_whole_catalog_is_deployed_by_name_shows_only_the_named_row( + monkeypatch: pytest.MonkeyPatch, wildcard_name: str, named: str +) -> None: + from litellm.proxy.auth.model_checks import expand_wildcard_deployments_for_model_info + + with _openrouter_catalog_beside_a_named_deployment( + monkeypatch, + wildcard_name, + named, + catalog=("openai/gpt-5.4-mini",), + wildcard_key=f"sk-or-one-model-{wildcard_name}", + ) as router: + rows: Final = expand_wildcard_deployments_for_model_info( + [dict(deployment) for deployment in router.get_model_list() or ()], llm_router=router + ) + + assert [(row["model_name"], row["litellm_params"]["api_key"]) for row in rows] == [(named, "sk-or-named")] + + @respx.mock def test_custom_prefix_that_starts_a_discovered_id_still_prefixes_every_listed_model( monkeypatch: pytest.MonkeyPatch,