diff --git a/litellm/proxy/auth/model_checks.py b/litellm/proxy/auth/model_checks.py index 6f91b1693d2..50f319d0121 100644 --- a/litellm/proxy/auth/model_checks.py +++ b/litellm/proxy/auth/model_checks.py @@ -64,30 +64,26 @@ def _get_models_from_access_groups( return all_models -def _is_access_group_only_string( +def _is_unresolvable_model_identifier( model: str, proxy_model_list: List[str], model_access_groups: Dict[str, List[str]], ) -> bool: """ - Identify entries in a key/team `models` list that are access-group strings - rather than real model identifiers. - - Issue #25550: when a virtual key was created with `models=["foo"]` against - an access group "foo" that has since been removed (or that was never - defined on any model), "foo" remains in the key but is no longer in - `model_access_groups`. The current expansion logic only strips entries - that ARE in the access-groups dict, so the stale string passes through - /v1/models as if it were a model name. - - A string is treated as access-group-only when none of the markers of a - real model identifier are present: - - it is in proxy_model_list (a configured proxy model group) + A string from a key/team `models` list resolves to a real model id when + any of these are true: + - it is in proxy_model_list (a configured proxy model name, including + custom enterprise aliases not present in litellm.model_list_set) - it is in model_access_groups (an active access group; expanded above) - it is in litellm.model_list_set (a known base model id) - it contains "/" (provider-qualified route, e.g. `openai/gpt-4o`) - it contains "*" (wildcard route, expanded by `_get_wildcard_models`) - it starts with "ft:" (OpenAI fine-tune id) + + Anything else is unresolvable and would otherwise leak into /v1/models as + if it were a real model. The most common case (issue #25550) is a stale + access-group label whose group was removed; identifiers for proxy models + that have since been removed from config are filtered for the same reason. """ if model in proxy_model_list: return False @@ -249,14 +245,16 @@ def get_complete_model_list( valid_models = get_valid_models() append_unique(valid_models) - # Drop stale access-group strings carried in key/team `models` (issue #25550). - # Only filter the key/team paths — the proxy-admin path above is sourced - # from authoritative state (proxy_model_list, model_access_groups keys). + # Drop unresolvable identifiers carried in key/team `models` — most + # commonly stale access-group labels (issue #25550), but also names of + # proxy models that have since been removed from config. Only filter the + # key/team paths; the proxy-admin path above is sourced from authoritative + # state (proxy_model_list, model_access_groups keys). if key_models or team_models: unique_models = [ m for m in unique_models - if not _is_access_group_only_string( + if not _is_unresolvable_model_identifier( model=m, proxy_model_list=proxy_model_list, model_access_groups=model_access_groups, diff --git a/tests/test_litellm/proxy/auth/test_model_checks.py b/tests/test_litellm/proxy/auth/test_model_checks.py index f2f30a88738..efbe628100f 100644 --- a/tests/test_litellm/proxy/auth/test_model_checks.py +++ b/tests/test_litellm/proxy/auth/test_model_checks.py @@ -338,6 +338,28 @@ def test_get_complete_model_list_keeps_known_base_model(): assert result == ["gpt-4o-mini"] +def test_get_complete_model_list_keeps_custom_proxy_alias(): + """ + A custom enterprise proxy model name (e.g. 'internal-assistant') that is + listed in proxy_model_list but is NOT a known LiteLLM base model id must + survive the filter. This locks in the proxy_model_list-membership branch + of _is_unresolvable_model_identifier — the most common preservation path + in real deployments. + """ + from litellm.proxy.auth.model_checks import get_complete_model_list + + result = get_complete_model_list( + key_models=["internal-assistant"], + team_models=[], + proxy_model_list=["internal-assistant"], + user_model=None, + infer_model_from_keys=False, + model_access_groups={}, + ) + + assert result == ["internal-assistant"] + + def test_get_complete_model_list_keeps_finetune_id(): """OpenAI fine-tune ids (`ft:...`) must survive the filter.""" from litellm.proxy.auth.model_checks import get_complete_model_list