mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-25 05:54:53 +00:00
fix(proxy): drop fragment ids for a catch-all over a partial wildcard and route bare aliases through the deployment pattern
A * catch-all over a partial deployment now lists no catalog ids, a bare alias like my-cf/* over cloudflare/@cf/* names ids by the deployment pattern, an alias whose wildcard count differs from its deployment lists nothing, and /model/info rows show the model each listed id routes to
This commit is contained in:
parent
6ff82d13b0
commit
c096b69788
2 changed files with 85 additions and 16 deletions
|
|
@ -274,6 +274,8 @@ def _hydrate_litellm_credential_name(
|
|||
def _ids_routing_to_catalog_models(
|
||||
name_pattern: str, deployment_pattern: str, provider: str, catalog_ids: Sequence[str]
|
||||
) -> tuple[str, ...]:
|
||||
if name_pattern.count("*") != deployment_pattern.count("*"):
|
||||
return ()
|
||||
deployment_regex: Final = re.compile(PatternMatchRouter().pattern_to_regex(deployment_pattern))
|
||||
provider_id_prefix: Final = f"{provider}/"
|
||||
catalog_matches: Final = (
|
||||
|
|
@ -291,6 +293,13 @@ def _ids_routing_to_catalog_models(
|
|||
)
|
||||
|
||||
|
||||
def _deployment_pattern_naming_listed_ids(wildcard_suffix: str, litellm_params: LiteLLM_Params | None) -> str | None:
|
||||
if litellm_params is None or not _check_wildcard_routing(litellm_params.model):
|
||||
return None
|
||||
deployment_suffix: Final = litellm_params.model.partition("/")[2]
|
||||
return litellm_params.model if wildcard_suffix != "*" or deployment_suffix != "*" else None
|
||||
|
||||
|
||||
def get_known_models_from_wildcard(wildcard_model: str, litellm_params: LiteLLM_Params | None = None) -> list[str]:
|
||||
wildcard_model_to_expand: Final = (
|
||||
litellm_params.model
|
||||
|
|
@ -304,6 +313,9 @@ def get_known_models_from_wildcard(wildcard_model: str, litellm_params: LiteLLM_
|
|||
wildcard_provider_prefix, wildcard_suffix = wildcard_model_to_expand.split("/", 1)
|
||||
except ValueError: # safely fail
|
||||
return []
|
||||
deployment_pattern_naming_listed_ids: Final = _deployment_pattern_naming_listed_ids(wildcard_suffix, litellm_params)
|
||||
if deployment_pattern_naming_listed_ids is not None and wildcard_model == "*":
|
||||
return []
|
||||
|
||||
# Use provider from litellm_params when available, otherwise from wildcard prefix
|
||||
# (e.g., "openai" from "openai/*" - needed for BYOK where wildcard isn't in router)
|
||||
|
|
@ -321,16 +333,16 @@ def get_known_models_from_wildcard(wildcard_model: str, litellm_params: LiteLLM_
|
|||
|
||||
if wildcard_models is None:
|
||||
return []
|
||||
if wildcard_suffix != "*":
|
||||
if litellm_params is not None and _check_wildcard_routing(litellm_params.model):
|
||||
return list(
|
||||
_ids_routing_to_catalog_models(
|
||||
name_pattern=wildcard_model,
|
||||
deployment_pattern=litellm_params.model,
|
||||
provider=provider,
|
||||
catalog_ids=wildcard_models,
|
||||
)
|
||||
if deployment_pattern_naming_listed_ids is not None:
|
||||
return list(
|
||||
_ids_routing_to_catalog_models(
|
||||
name_pattern=wildcard_model,
|
||||
deployment_pattern=deployment_pattern_naming_listed_ids,
|
||||
provider=provider,
|
||||
catalog_ids=wildcard_models,
|
||||
)
|
||||
)
|
||||
if wildcard_suffix != "*":
|
||||
model_prefix: Final = wildcard_suffix.replace("*", "")
|
||||
provider_id_prefix: Final = f"{provider}/"
|
||||
filtered_wildcard_models: Final = [
|
||||
|
|
@ -363,6 +375,17 @@ def get_known_models_from_wildcard(wildcard_model: str, litellm_params: LiteLLM_
|
|||
return list(dict.fromkeys(suffix_appended_wildcard_models))
|
||||
|
||||
|
||||
def _deployment_model_serving(listed_id: str, name_pattern: str, deployment_model: str) -> str:
|
||||
listed_match: Final = (
|
||||
None if name_pattern == "*" else re.fullmatch(PatternMatchRouter().pattern_to_regex(name_pattern), listed_id)
|
||||
)
|
||||
if listed_match is None:
|
||||
return listed_id
|
||||
return PatternMatchRouter.set_deployment_model_name(
|
||||
matched_pattern=listed_match, litellm_deployment_litellm_model=deployment_model
|
||||
)
|
||||
|
||||
|
||||
def expand_wildcard_deployments_for_model_info(
|
||||
deployments: list[dict[str, object]],
|
||||
) -> list[dict[str, object]]:
|
||||
|
|
@ -411,7 +434,9 @@ def expand_wildcard_deployments_for_model_info(
|
|||
row["model_name"] = name
|
||||
params = row.get("litellm_params")
|
||||
if isinstance(params, dict):
|
||||
params["model"] = name
|
||||
params["model"] = _deployment_model_serving(
|
||||
name, name_pattern=model_name, deployment_model=litellm_model
|
||||
)
|
||||
expanded.append(row)
|
||||
|
||||
return expanded
|
||||
|
|
|
|||
|
|
@ -1284,11 +1284,13 @@ _ROUTE_CF_IDS: Final = {_ROUTE_CF_ID, _ROUTE_CF_SUFFIXED_ID}
|
|||
),
|
||||
pytest.param("cloudflare/team-*", "cloudflare/*", None, {*_ROUTE_CF_IDS, _ROUTE_HF_ID}, id="alias"),
|
||||
pytest.param("cloudflare/cf-*", "cloudflare/@cf/*", None, _ROUTE_CF_IDS, id="alias-over-partial-deployment"),
|
||||
pytest.param("*", "cloudflare/@cf/*", None, _ROUTE_CF_IDS, id="catch-all-over-partial-deployment"),
|
||||
pytest.param("my-cf/*", "cloudflare/@cf/*", None, _ROUTE_CF_IDS, id="bare-alias-over-partial-deployment"),
|
||||
pytest.param(
|
||||
"cloudflare/team-*", "cloudflare/@cf/*/*", None, set(), id="alias-with-fewer-wildcards-than-its-deployment"
|
||||
),
|
||||
pytest.param(
|
||||
"cloudflare/*", "cloudflare/*", "cloudflare/@cf/*", _ROUTE_CF_IDS, id="key-filter-over-provider-wildcard"
|
||||
),
|
||||
pytest.param("*", "gemini/gemini-partial-route-9*", None, {_ROUTE_GEMINI_ID}, id="catch-all-over-bare-id"),
|
||||
pytest.param("gemini/gemini-*", "gemini/*", None, {_ROUTE_GEMINI_ID}, id="alias-over-bare-id"),
|
||||
pytest.param(
|
||||
"gemini/*", "gemini/*", "gemini/gemini-partial-route-9*", {_ROUTE_GEMINI_ID}, id="key-filter-over-bare-id"
|
||||
|
|
@ -1337,15 +1339,57 @@ def test_every_listed_partial_wildcard_id_routes_to_the_catalog_model_it_names(
|
|||
infer_model_from_keys=False,
|
||||
llm_router=router,
|
||||
)
|
||||
model_info_names: Final = [
|
||||
str(row["model_name"])
|
||||
model_info_rows: Final = tuple(
|
||||
(str(row["model_name"]), TypeAdapter(LiteLLM_Params).validate_python(row["litellm_params"]).model)
|
||||
for row in expand_wildcard_deployments_for_model_info(
|
||||
[dict(deployment) for deployment in router.get_model_list() or []]
|
||||
)
|
||||
]
|
||||
)
|
||||
listed_v1_models: Final = routed_catalog_ids(v1_models)
|
||||
listed_model_info_names: Final = routed_catalog_ids(model_info_names)
|
||||
listed_model_info_names: Final = routed_catalog_ids([name for name, _ in model_info_rows])
|
||||
|
||||
assert listed_v1_models == sorted(reachable)
|
||||
assert [shown_model for _, shown_model in model_info_rows] == [routed_model(name) for name, _ in model_info_rows]
|
||||
if key_model is None:
|
||||
assert listed_model_info_names == sorted(reachable)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("deployment_model", ["cloudflare/@cf/*", "gemini/gemini-partial-route-9*"])
|
||||
def test_catch_all_over_a_partial_deployment_lists_no_catalog_ids(deployment_model: str) -> None:
|
||||
from litellm import Router
|
||||
from litellm.proxy.auth.model_checks import expand_wildcard_deployments_for_model_info, get_complete_model_list
|
||||
|
||||
router: Final = Router(model_list=[{"model_name": "*", "litellm_params": {"model": deployment_model}}])
|
||||
deployments: Final = [dict(deployment) for deployment in router.get_model_list() or []]
|
||||
with _registered_chat_models({_ROUTE_CF_ID: "cloudflare", _ROUTE_GEMINI_ID: "gemini"}):
|
||||
v1_models: Final = get_complete_model_list(
|
||||
key_models=[],
|
||||
team_models=[],
|
||||
proxy_model_list=["*"],
|
||||
user_model=None,
|
||||
infer_model_from_keys=False,
|
||||
llm_router=router,
|
||||
)
|
||||
model_info_rows: Final = expand_wildcard_deployments_for_model_info(deployments)
|
||||
|
||||
assert v1_models == []
|
||||
assert model_info_rows == deployments
|
||||
|
||||
|
||||
def test_catch_all_model_info_rows_show_the_listed_provider_model() -> None:
|
||||
from pydantic import TypeAdapter
|
||||
|
||||
from litellm.proxy.auth.model_checks import expand_wildcard_deployments_for_model_info
|
||||
from litellm.types.router import LiteLLM_Params
|
||||
|
||||
listed_id: Final = "openai/partial-route-catch-all-model"
|
||||
with _registered_chat_models({listed_id.removeprefix("openai/"): "openai"}):
|
||||
model_info_rows: Final = expand_wildcard_deployments_for_model_info(
|
||||
[{"model_name": "*", "litellm_params": {"model": "openai/*"}}]
|
||||
)
|
||||
|
||||
shown_models: Final = {
|
||||
str(row["model_name"]): TypeAdapter(LiteLLM_Params).validate_python(row["litellm_params"]).model
|
||||
for row in model_info_rows
|
||||
}
|
||||
assert shown_models[listed_id] == listed_id
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue