refactor(proxy): rename _is_access_group_only_string + add custom-alias test

Address review feedback on #25550 fix:
- Rename `_is_access_group_only_string` to `_is_unresolvable_model_identifier`
  to reflect that the helper drops any unresolvable identifier (stale access
  group OR removed proxy model name), not just access-group strings. Update
  docstring and comment at the call site accordingly.
- Add `test_get_complete_model_list_keeps_custom_proxy_alias` to lock in the
  proxy_model_list-membership branch — preservation of a custom enterprise
  alias that is in proxy_model_list but not in litellm.model_list_set.

No behavior change.
This commit is contained in:
Dmitriy Alergant 2026-04-29 16:03:49 -04:00
parent d373512a2b
commit 0b3de67edb
No known key found for this signature in database
2 changed files with 38 additions and 18 deletions

View file

@ -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,

View file

@ -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