From 1b05b6e94066a58ec0b80dc1bb1f6e3b02fb5bc2 Mon Sep 17 00:00:00 2001 From: jesus Date: Fri, 16 Oct 2026 23:03:00 +0000 Subject: [PATCH] fix(model hub): serve custom tags and metadata on model_group_alias rows Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/proxy/proxy_server.py | 6 ++++- .../test_model_hub_custom_metadata.py | 20 +++++++++++++- .../public_v1/test_model_hub.py | 1 + tests/unit/proxy/test_proxy_utils.py | 27 +++++++++++++++++++ 4 files changed, 52 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 44095646f11..73a6f59facb 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -17064,7 +17064,11 @@ def get_model_group_info( ModelGroupInfoProxy.model_validate( { **_model_group_info.model_dump(), - **model_group_custom_fields(llm_router.get_model_list_of_routed_group(model)), + **model_group_custom_fields( + llm_router.get_model_list_of_routed_group( + resolve_model_group_alias(llm_router.model_group_alias, model) or model + ) + ), } ) ) diff --git a/tests/integration/management/test_model_hub_custom_metadata.py b/tests/integration/management/test_model_hub_custom_metadata.py index 721d5b16f96..6f007b6ebac 100644 --- a/tests/integration/management/test_model_hub_custom_metadata.py +++ b/tests/integration/management/test_model_hub_custom_metadata.py @@ -205,6 +205,7 @@ def _yaml_config( model_info: Mapping[str, object] = MappingProxyType( {"custom_tags": ["deprecated", "ptu"], "custom_metadata": LIFECYCLE_METADATA} ), + model_group_alias: Mapping[str, str] = MappingProxyType({}), ) -> Path: config: Final = yaml.safe_load(Path("tests/integration/proxy_config.yaml").read_text()) deployment: Final = { @@ -213,7 +214,12 @@ def _yaml_config( "model_info": dict(model_info), } path: Final = directory / "custom_fields.yaml" - path.write_text(yaml.safe_dump({**config, "model_list": [*config["model_list"], deployment]})) + router_settings: Final = {**config.get("router_settings", {}), "model_group_alias": dict(model_group_alias)} + path.write_text( + yaml.safe_dump( + {**config, "model_list": [*config["model_list"], deployment], "router_settings": router_settings} + ) + ) return path @@ -256,3 +262,15 @@ def test_yaml_custom_metadata_that_json_cannot_carry_is_dropped_without_breaking assert every_group.status_code == 200, every_group.text group: Final = _model_group(owned.gateway, model_name) assert _custom_fields(group) == (["ptu"], {"retirement_date": "2027-03-01"}), group + + +@pytest.mark.timeout(2 * graceful_stop_seconds() + 120) +def test_an_alias_group_serves_the_custom_tags_and_metadata_of_the_group_it_points_to( + gateway: Gateway, tmp_path: Path +) -> None: + model_name: Final = f"integration-yaml-{uuid.uuid4().hex}" + alias: Final = f"{model_name}-alias" + config: Final = _yaml_config(tmp_path, gateway.upstream_url, model_name, model_group_alias={alias: model_name}) + with owned_proxy_process(gateway, tmp_path, {}, config=config) as owned: + group: Final = _model_group(owned.gateway, alias) + assert _custom_fields(group) == (["deprecated", "ptu"], LIFECYCLE_METADATA), group diff --git a/tests/unit/proxy/public_endpoints/public_v1/test_model_hub.py b/tests/unit/proxy/public_endpoints/public_v1/test_model_hub.py index 8c164ec860b..b3636f948ae 100644 --- a/tests/unit/proxy/public_endpoints/public_v1/test_model_hub.py +++ b/tests/unit/proxy/public_endpoints/public_v1/test_model_hub.py @@ -24,6 +24,7 @@ class _FakeRouter: """Stands in for the running Router: `_get_model_group_info` only ever asks it these.""" infos: Mapping[str, ModelGroupInfo] + model_group_alias: Mapping[str, str] = MappingProxyType({}) def get_model_group_info(self, model_group: str) -> ModelGroupInfo | None: return self.infos.get(model_group) diff --git a/tests/unit/proxy/test_proxy_utils.py b/tests/unit/proxy/test_proxy_utils.py index 86240417f0b..e8ed0cf5134 100644 --- a/tests/unit/proxy/test_proxy_utils.py +++ b/tests/unit/proxy/test_proxy_utils.py @@ -1679,6 +1679,33 @@ def test_get_model_group_info_merges_custom_tags_and_metadata_across_deployments assert (group.custom_tags, group.custom_metadata) == (expected_tags, expected_metadata) + +@pytest.mark.parametrize( + "alias_target", + [pytest.param("gpt-4", id="string"), pytest.param({"model": "gpt-4", "hidden": False}, id="item")], +) +def test_get_model_group_info_serves_the_target_groups_custom_fields_on_an_alias_row( + alias_target: str | dict[str, object], +) -> None: + from litellm.proxy.proxy_server import get_model_group_info + + router: Final = Router( + model_list=[ + { + "model_name": "gpt-4", + "litellm_params": {"model": "gpt-4", "api_key": "fake"}, + "model_info": {"custom_tags": ["deprecated"], "custom_metadata": {"retirement_date": "2027-03-01"}}, + } + ], + model_group_alias={"gpt-alias": alias_target}, + ) + + groups: Final = get_model_group_info(llm_router=router, all_models_str=["gpt-4", "gpt-alias"], model_group=None) + + assert {group.model_group: (group.custom_tags, group.custom_metadata) for group in groups} == { + model_group: (("deprecated",), {"retirement_date": "2027-03-01"}) for model_group in ("gpt-4", "gpt-alias") + } + @pytest.fixture def mock_team_data(): return [