fix(proxy): admit litellm_proxy/hosted_vllm in provider-endpoint discovery (#38617)

* fix(proxy): admit litellm_proxy/hosted_vllm in provider-endpoint discovery

get_provider_models gated all endpoint discovery on membership in the
static litellm.models_by_provider dict, which litellm_proxy and
hosted_vllm are intentionally absent from since their model list only
exists behind the provider's own endpoint. GET /v1/models returned the
literal wildcard string instead of the expanded model list for these
providers. Now falls through to ProviderConfigManager, which already
knows about them, before giving up.

* fix(tests): stop patching an SDK internal in the discovery regression test

TQ008 flagged patching litellm.proxy.auth.model_checks.get_valid_models.
Assert the gate's own return value instead of mocking past it.

* chore: drop redundant comment per repo convention

* test(proxy): cover litellm_proxy wildcard model discovery end to end

---------

Signed-off-by: mayuriphad <163738104+mayuriphad@users.noreply.github.com>
This commit is contained in:
Mayuri 2026-10-08 09:45:57 +05:30 • committed by GitHub
parent 03ba79d261
commit 9506f9e58b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 84 additions and 2 deletions

View file

@ -13,7 +13,7 @@ from litellm.router import Router
from litellm.router_utils.fallback_event_handlers import get_fallback_model_group
from litellm.types.router import CredentialLiteLLMParams, LiteLLM_Params
from litellm.types.utils import LlmProviders
from litellm.utils import get_valid_models
from litellm.utils import ProviderConfigManager, get_valid_models
_CREDENTIAL_LITELLM_PARAM_FIELDS = set(CredentialLiteLLMParams.model_fields)
@ -45,7 +45,14 @@ def get_provider_models(provider: str, litellm_params: LiteLLM_Params | None = N
if provider in litellm.models_by_provider:
provider_models: Final = get_valid_models(custom_llm_provider=provider, litellm_params=litellm_params)
return provider_models
return None
try:
llm_provider: Final = LlmProviders(provider)
except ValueError:
return None
if ProviderConfigManager.get_provider_model_info(model=None, provider=llm_provider) is None:
return None
return get_valid_models(custom_llm_provider=provider, litellm_params=litellm_params)
def _get_models_from_access_groups(

View file

@ -967,6 +967,81 @@ def test_get_complete_model_list_sentinel_only_grants_nothing():
assert result == []
def test_get_provider_models_admits_providers_without_a_static_catalog():
"""Providers without a static model list are no longer rejected up front.
With endpoint discovery off, get_valid_models falls back to the (empty)
static list, so the result is [] rather than None. Before the fix this
returned None and the wildcard was never expanded.
"""
import litellm
from litellm.proxy.auth.model_checks import get_provider_models
from litellm.types.router import LiteLLM_Params
assert "litellm_proxy" not in litellm.models_by_provider
assert "hosted_vllm" not in litellm.models_by_provider
result = get_provider_models(
"litellm_proxy",
litellm_params=LiteLLM_Params(
model="litellm_proxy/*",
api_base="http://upstream:4000",
api_key="sk-upstream",
),
)
assert result == []
def test_get_complete_model_list_discovers_litellm_proxy_wildcard_models(monkeypatch):
"""A litellm_proxy/* deployment lists the upstream proxy's models when endpoint discovery is on."""
import litellm
from litellm import Router
from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig
from litellm.proxy.auth.model_checks import get_complete_model_list
monkeypatch.setattr(litellm, "check_provider_endpoint", True)
captured = {}
def fake_get_models(self, api_key=None, api_base=None):
captured["api_key"] = api_key
captured["api_base"] = api_base
return ["gpt-4o", "claude-sonnet"]
monkeypatch.setattr(OpenAIGPTConfig, "get_models", fake_get_models)
router = Router(
model_list=[
{
"model_name": "litellm_proxy/*",
"litellm_params": {
"model": "litellm_proxy/*",
"api_base": "http://upstream:4000",
"api_key": "sk-upstream",
},
}
]
)
result = get_complete_model_list(
key_models=[],
team_models=[],
proxy_model_list=["litellm_proxy/*"],
user_model=None,
infer_model_from_keys=False,
llm_router=router,
)
assert captured == {"api_key": "sk-upstream", "api_base": "http://upstream:4000"}
assert "litellm_proxy/gpt-4o" in result
assert "litellm_proxy/claude-sonnet" in result
def test_get_provider_models_returns_none_for_an_unknown_provider():
from litellm.proxy.auth.model_checks import get_provider_models
assert get_provider_models("not-a-real-provider") is None
def test_transcribe_is_a_known_provider_for_wildcard_expansion():
import litellm
from litellm.proxy.auth.model_checks import (