From fc35c5e228832abbb43b1f3c0292753a2ea4edcd Mon Sep 17 00:00:00 2001 From: Krrish Dholakia Date: Tue, 30 Jun 2026 17:39:09 +0000 Subject: [PATCH] fix: remove wildcard routes from /v1/models response Wildcard routes like bedrock/* were leaking into the /v1/models response because _get_wildcard_models only removed them from unique_models in the fallback branches (no router or no deployment), but not when the router had a matching deployment. Now wildcards are always removed from the base list; they are only re-added to the result when return_wildcard_routes=True is explicitly passed. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/proxy/auth/model_checks.py | 11 +-- .../proxy/auth/test_model_checks.py | 68 +++++++++++++++++++ 2 files changed, 71 insertions(+), 8 deletions(-) diff --git a/litellm/proxy/auth/model_checks.py b/litellm/proxy/auth/model_checks.py index 0f3b8b5a412..028ba67470a 100644 --- a/litellm/proxy/auth/model_checks.py +++ b/litellm/proxy/auth/model_checks.py @@ -384,10 +384,11 @@ def _get_wildcard_models( all_wildcard_models = [] for model in unique_models: if _check_wildcard_routing(model=model): - if return_wildcard_routes: # will add the wildcard route to the list eg: anthropic/*. + if return_wildcard_routes: all_wildcard_models.append(model) - ## get litellm params from model + models_to_remove.add(model) + if llm_router is not None: model_list = llm_router.get_model_list(model_name=model, team_id=team_id) if model_list: @@ -400,18 +401,12 @@ def _get_wildcard_models( ) all_wildcard_models.extend(wildcard_models) else: - # Router has no deployment for this wildcard (e.g., BYOK team models) - # Fall back to expanding from known provider models wildcard_models = get_known_models_from_wildcard(wildcard_model=model, litellm_params=None) if wildcard_models: - models_to_remove.add(model) all_wildcard_models.extend(wildcard_models) else: - # get all known provider models wildcard_models = get_known_models_from_wildcard(wildcard_model=model, litellm_params=None) - if wildcard_models: - models_to_remove.add(model) all_wildcard_models.extend(wildcard_models) for model in models_to_remove: diff --git a/tests/test_litellm/proxy/auth/test_model_checks.py b/tests/test_litellm/proxy/auth/test_model_checks.py index e30bfb15938..372d00e3742 100644 --- a/tests/test_litellm/proxy/auth/test_model_checks.py +++ b/tests/test_litellm/proxy/auth/test_model_checks.py @@ -686,3 +686,71 @@ def test_expand_wildcard_invalid_litellm_params_passthrough(): # Even if LiteLLM_Params construction fails the deployment should survive result = expand_wildcard_deployments_for_model_info([deployment]) assert result == [deployment] + + +def test_get_complete_model_list_excludes_wildcard_routes_by_default(): + """ + Regression: wildcard routes like bedrock/* must NOT appear in the model + list when return_wildcard_routes=False (the default for /v1/models). + Previously, wildcards with a router deployment were never removed from + unique_models, leaking them into the response. + """ + from litellm import Router + from litellm.proxy.auth.model_checks import get_complete_model_list + + router = Router( + model_list=[ + { + "model_name": "bedrock/*", + "litellm_params": {"model": "bedrock/*"}, + }, + { + "model_name": "gpt-4", + "litellm_params": {"model": "openai/gpt-4"}, + }, + ] + ) + + result = get_complete_model_list( + key_models=[], + team_models=[], + proxy_model_list=["bedrock/*", "gpt-4"], + user_model=None, + infer_model_from_keys=False, + return_wildcard_routes=False, + llm_router=router, + ) + + assert "bedrock/*" not in result + assert "gpt-4" in result + + +def test_get_complete_model_list_includes_wildcard_routes_when_requested(): + """ + When return_wildcard_routes=True the wildcard pattern should appear in the + result (once), alongside the expanded models. + """ + from litellm import Router + from litellm.proxy.auth.model_checks import get_complete_model_list + + router = Router( + model_list=[ + { + "model_name": "bedrock/*", + "litellm_params": {"model": "bedrock/*"}, + }, + ] + ) + + result = get_complete_model_list( + key_models=[], + team_models=[], + proxy_model_list=["bedrock/*"], + user_model=None, + infer_model_from_keys=False, + return_wildcard_routes=True, + llm_router=router, + ) + + assert "bedrock/*" in result + assert result.count("bedrock/*") == 1