feat(proxy): add model_list_return_wildcard_routes setting for /v1/models

general_settings.model_list_return_wildcard_routes: true makes /v1/models list
wildcard routes such as openai/* for every caller, the same as passing
return_wildcard_routes=true. An explicit return_wildcard_routes=false on the
request still leaves them out
This commit is contained in:
shrey kharbanda 2026-09-28 12:54:40 -07:00
parent 9fd78ff6f4
commit b841d9a6c2
4 changed files with 95 additions and 3 deletions

View file

@ -2856,6 +2856,14 @@ class ConfigGeneralSettings(LiteLLMPydanticObjectBase):
"hidden model can still be called."
),
)
model_list_return_wildcard_routes: bool | None = Field(
None,
description=(
"When true, `/models` lists wildcard routes such as `openai/*` next to the models they "
"expand to, for every caller, without needing `return_wildcard_routes=true` per request. "
"A request can still pass `return_wildcard_routes=false` to leave them out."
),
)
alerting: list | None = Field(
None,
description="List of alerting integrations - e.g. `alerting: ['slack', 'webhook', 'email']`. 'slack' posts Slack-format messages to any Slack-compatible webhook (Slack, Rocket.Chat, Mattermost); 'webhook' posts structured JSON budget alerts to WEBHOOK_URL",

View file

@ -11349,7 +11349,7 @@ async def _deployment_hidden_by_listing_callbacks(deployment: Deployment, user_a
async def model_list(
request: Request = None, # pyright: ignore[reportArgumentType] # FastAPI always injects the Request; the None default only serves direct in-process callers
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
return_wildcard_routes: bool | None = False,
return_wildcard_routes: bool | None = None,
team_id: str | None = None,
include_model_access_groups: bool | None = False,
only_model_access_groups: bool | None = False,
@ -11364,6 +11364,10 @@ async def model_list(
This is just for compatibility with openai projects like aider.
Query Parameters:
- return_wildcard_routes: When true, also list wildcard routes (e.g. `openai/*`)
next to the models they expand to. Defaults to
`general_settings.model_list_return_wildcard_routes`, which is
false unless set; pass `false` to leave them out regardless.
- include_metadata: Include additional metadata in the response with fallback information
- fallback_type: Type of fallbacks to include ("general", "context_window", "content_policy")
Defaults to "general" when include_metadata=true
@ -11452,6 +11456,12 @@ async def model_list(
hidden_names: Final = blocked_names | unhealthy_names
include_wildcard_routes: Final = (
settings.get("model_list_return_wildcard_routes") is True
if return_wildcard_routes is None
else return_wildcard_routes
)
# If scope=expand and user has admin privileges, return all proxy models
if should_expand_scope:
# Get all proxy models as if user is a proxy admin
@ -11475,7 +11485,7 @@ async def model_list(
proxy_model_list=proxy_model_list,
user_model=None,
infer_model_from_keys=False,
return_wildcard_routes=return_wildcard_routes or False,
return_wildcard_routes=include_wildcard_routes,
llm_router=llm_router,
model_access_groups=model_access_groups,
include_model_access_groups=include_model_access_groups or False,
@ -11535,7 +11545,7 @@ async def model_list(
team_id=team_id,
include_model_access_groups=include_model_access_groups or False,
only_model_access_groups=only_model_access_groups or False,
return_wildcard_routes=return_wildcard_routes or False,
return_wildcard_routes=include_wildcard_routes,
user_api_key_cache=user_api_key_cache,
)

View file

@ -3108,3 +3108,64 @@ def test_get_litellm_model_info(data):
):
get_litellm_model_info(model=model)
get_info_mock.assert_called_once_with(data["expected"])
@pytest.fixture
def openai_wildcard_router(monkeypatch: pytest.MonkeyPatch) -> litellm.Router:
router = litellm.Router(
model_list=[
{"model_name": "gpt-4o", "litellm_params": {"model": "openai/gpt-4o", "api_key": "sk-fake"}},
{"model_name": "openai/*", "litellm_params": {"model": "openai/*", "api_key": "sk-fake"}},
]
)
monkeypatch.setattr(litellm.proxy.proxy_server, "llm_router", router)
monkeypatch.setattr(litellm.proxy.proxy_server, "llm_model_list", router.model_list)
monkeypatch.setattr(litellm.proxy.proxy_server, "prisma_client", None)
monkeypatch.setattr(litellm.proxy.proxy_server, "user_model", None)
return router
async def _listed_model_ids(
monkeypatch: pytest.MonkeyPatch,
general_settings: dict[str, object],
user_role: LitellmUserRoles,
**query: str | bool,
) -> list[str]:
monkeypatch.setattr(litellm.proxy.proxy_server, "general_settings", general_settings)
response = await litellm.proxy.proxy_server.model_list(
user_api_key_dict=UserAPIKeyAuth(api_key="sk-test", user_role=user_role), **query
)
return [model["id"] for model in response["data"]]
@pytest.mark.parametrize(
"user_role, query",
[(LitellmUserRoles.INTERNAL_USER, {}), (LitellmUserRoles.PROXY_ADMIN, {"scope": "expand"})],
)
@pytest.mark.asyncio
async def test_model_list_return_wildcard_routes_setting_matches_query_param(
openai_wildcard_router, monkeypatch, user_role, query
):
requested = await _listed_model_ids(monkeypatch, {}, user_role, return_wildcard_routes=True, **query)
from_setting = await _listed_model_ids(monkeypatch, {"model_list_return_wildcard_routes": True}, user_role, **query)
assert "openai/*" in from_setting, from_setting
assert from_setting == requested
@pytest.mark.parametrize(
"general_settings, query",
[
({}, {}),
({"model_list_return_wildcard_routes": "false"}, {}),
({"model_list_return_wildcard_routes": True}, {"return_wildcard_routes": False}),
],
)
@pytest.mark.asyncio
async def test_model_list_leaves_out_wildcard_routes_unless_enabled(
openai_wildcard_router, monkeypatch, general_settings, query
):
listed = await _listed_model_ids(monkeypatch, general_settings, LitellmUserRoles.INTERNAL_USER, **query)
assert "gpt-4o" in listed, listed
assert "openai/*" not in listed, listed

View file

@ -9779,6 +9779,10 @@ export interface paths {
* This is just for compatibility with openai projects like aider.
*
* Query Parameters:
* - return_wildcard_routes: When true, also list wildcard routes (e.g. `openai/*`)
* next to the models they expand to. Defaults to
* `general_settings.model_list_return_wildcard_routes`, which is
* false unless set; pass `false` to leave them out regardless.
* - include_metadata: Include additional metadata in the response with fallback information
* - fallback_type: Type of fallbacks to include ("general", "context_window", "content_policy")
* Defaults to "general" when include_metadata=true
@ -20059,6 +20063,10 @@ export interface paths {
* This is just for compatibility with openai projects like aider.
*
* Query Parameters:
* - return_wildcard_routes: When true, also list wildcard routes (e.g. `openai/*`)
* next to the models they expand to. Defaults to
* `general_settings.model_list_return_wildcard_routes`, which is
* false unless set; pass `false` to leave them out regardless.
* - include_metadata: Include additional metadata in the response with fallback information
* - fallback_type: Type of fallbacks to include ("general", "context_window", "content_policy")
* Defaults to "general" when include_metadata=true
@ -28379,6 +28387,11 @@ export interface components {
* @description When true, `/models`, `/v1/models/{id}` and `/model/info` hide models whose backing deployments are all unhealthy, for every caller, without needing `healthy_only=true` per request. Requires `background_health_checks: true`, and keeps deployment health state cached without turning on `enable_health_check_routing`, so routing is unaffected. With no health state nothing is hidden. Hiding is presentation-only, a hidden model can still be called.
*/
model_list_healthy_only?: boolean | null;
/**
* Model List Return Wildcard Routes
* @description When true, `/models` lists wildcard routes such as `openai/*` next to the models they expand to, for every caller, without needing `return_wildcard_routes=true` per request. A request can still pass `return_wildcard_routes=false` to leave them out.
*/
model_list_return_wildcard_routes?: boolean | null;
/**
* Otel
* @description [BETA] OpenTelemetry support - this might change, use with caution.