mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
fix(proxy): stop mutating model list when expanding wildcards (#36398)
_get_wildcard_models mutated its unique_models parameter and removed only the first occurrence of each expanded wildcard, so a duplicate literal route (e.g. openai/*) could leak into /v1/models. Return an immutable _WildcardModels tuple and have get_complete_model_list build the result functionally, dropping every expanded literal regardless of duplicates. RED->GREEN: input-mutation regression fails on the old remove-in-place code, passes after. Full tests/test_litellm/proxy/auth suite green (1447 passed).
This commit is contained in:
parent
95b6cf50a7
commit
294efdcd39
2 changed files with 37 additions and 15 deletions
|
|
@ -232,14 +232,14 @@ def get_complete_model_list(
|
|||
model_access_groups_to_return.append(model)
|
||||
return model_access_groups_to_return
|
||||
|
||||
all_wildcard_models: Final = _get_wildcard_models(
|
||||
wildcard: Final = _get_wildcard_models(
|
||||
unique_models=unique_models,
|
||||
return_wildcard_routes=return_wildcard_routes,
|
||||
llm_router=llm_router,
|
||||
team_id=team_id,
|
||||
)
|
||||
|
||||
complete_model_list: Final = unique_models + all_wildcard_models
|
||||
complete_model_list: Final = list(wildcard.kept_models + wildcard.expanded_models)
|
||||
|
||||
return complete_model_list
|
||||
|
||||
|
|
@ -407,34 +407,34 @@ def _expand_wildcard_model(model: str, llm_router: Router | None, team_id: str |
|
|||
return _WildcardExpansion(concrete_models, True)
|
||||
|
||||
|
||||
class _WildcardModels(NamedTuple):
|
||||
kept_models: tuple[str, ...]
|
||||
expanded_models: tuple[str, ...]
|
||||
|
||||
|
||||
def _get_wildcard_models(
|
||||
unique_models: list[str],
|
||||
unique_models: Sequence[str],
|
||||
return_wildcard_routes: bool | None = False,
|
||||
llm_router: Router | None = None,
|
||||
team_id: str | None = None,
|
||||
) -> list[str]:
|
||||
) -> _WildcardModels:
|
||||
expansions: Final = {
|
||||
model: _expand_wildcard_model(model=model, llm_router=llm_router, team_id=team_id)
|
||||
for model in unique_models
|
||||
if _check_wildcard_routing(model=model)
|
||||
}
|
||||
|
||||
all_wildcard_models: Final = [
|
||||
expanded_models: Final = tuple(
|
||||
item
|
||||
for model, expansion in expansions.items()
|
||||
for item in [*([model] if return_wildcard_routes else []), *expansion.concrete_models]
|
||||
]
|
||||
)
|
||||
|
||||
# The literal pattern (e.g. "openai/*") is a routing directive, not a callable
|
||||
# model, so drop it from the listing once expanded. It is re-surfaced above only
|
||||
# when return_wildcard_routes is set (#13752).
|
||||
models_to_remove: Final = {
|
||||
literals_to_drop: Final = frozenset(
|
||||
model for model, expansion in expansions.items() if expansion.has_router_deployment or expansion.concrete_models
|
||||
}
|
||||
for model in models_to_remove:
|
||||
unique_models.remove(model)
|
||||
|
||||
return all_wildcard_models
|
||||
)
|
||||
kept_models: Final = tuple(model for model in unique_models if model not in literals_to_drop)
|
||||
return _WildcardModels(kept_models=kept_models, expanded_models=expanded_models)
|
||||
|
||||
|
||||
def get_all_fallbacks(
|
||||
|
|
|
|||
|
|
@ -853,3 +853,25 @@ def test_get_complete_model_list_omits_literal_wildcard_when_router_has_deployme
|
|||
)
|
||||
assert "openai/*" not in no_router_result
|
||||
assert any(m.startswith("openai/") and m != "openai/*" for m in no_router_result)
|
||||
|
||||
|
||||
def test_get_wildcard_models_does_not_mutate_input_and_drops_all_literals():
|
||||
"""Regression for #13752: _get_wildcard_models must not mutate the caller's list and
|
||||
must drop every expanded wildcard literal from kept_models, even when the same literal
|
||||
appears more than once, so no literal route leaks back into the listing."""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from litellm.proxy.auth.model_checks import _get_wildcard_models
|
||||
|
||||
router = MagicMock()
|
||||
router.get_model_list.return_value = [{"litellm_params": {"model": "openai/*"}}]
|
||||
|
||||
unique_models = ["openai/*", "openai/*"]
|
||||
original = list(unique_models)
|
||||
|
||||
result = _get_wildcard_models(unique_models=unique_models, llm_router=router)
|
||||
|
||||
assert unique_models == original
|
||||
assert "openai/*" not in result.kept_models
|
||||
assert result.expanded_models
|
||||
assert all(m != "openai/*" for m in result.expanded_models)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue