From b224b15b9d24c15590a81c3c4bb5399d40b7d9e0 Mon Sep 17 00:00:00 2001 From: milan Date: Fri, 21 Aug 2026 02:41:55 +0000 Subject: [PATCH 1/2] feat(model_hub): surface model_info.description in model group info and Model Hub UI Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/router.py | 6 +++++ litellm/types/router.py | 1 + tests/test_litellm/test_router.py | 25 +++++++++++++++++++ .../src/components/AIHub/ModelHubTable.tsx | 6 +++++ .../components/AIHub/ModelHubTableColumns.tsx | 1 + .../components/PublicModelHubTableColumns.tsx | 1 + .../src/components/public_model_hub.tsx | 6 +++++ ui/litellm-dashboard/src/lib/http/schema.d.ts | 2 ++ 8 files changed, 48 insertions(+) diff --git a/litellm/router.py b/litellm/router.py index e9eeab53934..90968800460 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -9334,6 +9334,9 @@ class Router: model_litellm_params = model.get("litellm_params", {}) model_info_dict = model.get("model_info", {}) + _raw_description = model_info_dict.get("description") + _deployment_description: str | None = _raw_description if isinstance(_raw_description, str) else None + # get model tpm _deployment_tpm: int | None = None if _deployment_tpm is None: @@ -9415,6 +9418,7 @@ class Router: **{ "model_group": user_facing_model_group_name, "providers": [llm_provider], + "description": _deployment_description, **model_info, } ) @@ -9428,6 +9432,8 @@ class Router: # supports_function_calling == True if llm_provider not in model_group_info.providers: model_group_info.providers.append(llm_provider) + if model_group_info.description is None and _deployment_description is not None: + model_group_info.description = _deployment_description if ( model_info.get("max_input_tokens", None) is not None and model_info["max_input_tokens"] is not None diff --git a/litellm/types/router.py b/litellm/types/router.py index 99a4603ae49..b33e387c508 100644 --- a/litellm/types/router.py +++ b/litellm/types/router.py @@ -637,6 +637,7 @@ class ModelGroupInfo(BaseModel): supports_function_calling: bool = Field(default=False) supported_openai_params: list[str] | None = Field(default=[]) configurable_clientside_auth_params: CONFIGURABLE_CLIENTSIDE_AUTH_PARAMS = None + description: str | None = None def __init__(self, **data) -> None: for field_name, field_type in get_type_hints(self.__class__).items(): diff --git a/tests/test_litellm/test_router.py b/tests/test_litellm/test_router.py index 9894fcef163..765e479e378 100644 --- a/tests/test_litellm/test_router.py +++ b/tests/test_litellm/test_router.py @@ -1626,6 +1626,31 @@ def test_model_group_info_cost_none_when_db_model_info_has_no_cost(): assert result.output_cost_per_token is None +def test_model_group_info_description_from_model_info(): + """ + model_info.description set in the config should surface on ModelGroupInfo, + including when only a later deployment in the group carries it. + """ + router = litellm.Router( + model_list=[ + { + "model_name": "gpt-4", + "litellm_params": {"model": "gpt-4", "api_key": "fake"}, + "model_info": {}, + }, + { + "model_name": "gpt-4", + "litellm_params": {"model": "gpt-4", "api_key": "fake2"}, + "model_info": {"description": "State-of-the-art language model."}, + }, + ] + ) + + result = router.get_model_group_info("gpt-4") + assert result is not None + assert result.description == "State-of-the-art language model." + + @pytest.mark.parametrize( "value,expected", [ diff --git a/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.tsx b/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.tsx index 299104b271b..5fadbe3300d 100644 --- a/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.tsx +++ b/ui/litellm-dashboard/src/components/AIHub/ModelHubTable.tsx @@ -647,6 +647,12 @@ const ModelHubTable: React.FC = ({ accessToken, publicPage, ))} + {selectedModel.description && ( +
+

Description:

+

{selectedModel.description}

+
+ )} diff --git a/ui/litellm-dashboard/src/components/AIHub/ModelHubTableColumns.tsx b/ui/litellm-dashboard/src/components/AIHub/ModelHubTableColumns.tsx index 9f74771f3b1..b025bcf62aa 100644 --- a/ui/litellm-dashboard/src/components/AIHub/ModelHubTableColumns.tsx +++ b/ui/litellm-dashboard/src/components/AIHub/ModelHubTableColumns.tsx @@ -31,6 +31,7 @@ export interface ModelHubData { supports_function_calling: boolean; supported_openai_params?: string[]; is_public_model_group: boolean; + description?: string; [key: string]: any; } diff --git a/ui/litellm-dashboard/src/components/PublicModelHubTableColumns.tsx b/ui/litellm-dashboard/src/components/PublicModelHubTableColumns.tsx index ab0ed976149..f3673df8025 100644 --- a/ui/litellm-dashboard/src/components/PublicModelHubTableColumns.tsx +++ b/ui/litellm-dashboard/src/components/PublicModelHubTableColumns.tsx @@ -24,6 +24,7 @@ export interface ModelGroupInfo { health_status?: string; health_response_time?: number; health_checked_at?: string; + description?: string; [key: string]: any; } diff --git a/ui/litellm-dashboard/src/components/public_model_hub.tsx b/ui/litellm-dashboard/src/components/public_model_hub.tsx index 171b7992325..cd10068b9e6 100644 --- a/ui/litellm-dashboard/src/components/public_model_hub.tsx +++ b/ui/litellm-dashboard/src/components/public_model_hub.tsx @@ -918,6 +918,12 @@ const PublicModelHub: React.FC = ({ accessToken, isEmbedded })} + {selectedModel.description && ( +
+

Description:

+

{selectedModel.description}

+
+ )} {/* Wildcard Routing Note */} diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts index fff48e14ecf..39b3255efb3 100644 --- a/ui/litellm-dashboard/src/lib/http/schema.d.ts +++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts @@ -29481,6 +29481,8 @@ export interface components { ModelGroupInfoProxy: { /** Configurable Clientside Auth Params */ configurable_clientside_auth_params?: (string | components["schemas"]["ConfigurableClientsideParamsCustomAuth-Output"])[] | null; + /** Description */ + description?: string | null; /** Health Checked At */ health_checked_at?: string | null; /** Health Response Time */ From 342b93fe0a92f32b8c2fb4ccf61441cb0a2cf3b1 Mon Sep 17 00:00:00 2001 From: milan Date: Fri, 21 Aug 2026 03:27:35 +0000 Subject: [PATCH 2/2] refactor(router): aggregate model group description without in-place mutation Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/router.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/litellm/router.py b/litellm/router.py index 90968800460..d65c13d57b2 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -9313,6 +9313,10 @@ class Router: model_list: Final = self.get_model_list(model_name=model_group) if model_list is None: return None + model_group_description: Final[str | None] = next( + (d for d in (m.get("model_info", {}).get("description") for m in model_list) if isinstance(d, str)), + None, + ) for model in model_list: is_match = False if ( @@ -9334,9 +9338,6 @@ class Router: model_litellm_params = model.get("litellm_params", {}) model_info_dict = model.get("model_info", {}) - _raw_description = model_info_dict.get("description") - _deployment_description: str | None = _raw_description if isinstance(_raw_description, str) else None - # get model tpm _deployment_tpm: int | None = None if _deployment_tpm is None: @@ -9418,7 +9419,7 @@ class Router: **{ "model_group": user_facing_model_group_name, "providers": [llm_provider], - "description": _deployment_description, + "description": model_group_description, **model_info, } ) @@ -9432,8 +9433,6 @@ class Router: # supports_function_calling == True if llm_provider not in model_group_info.providers: model_group_info.providers.append(llm_provider) - if model_group_info.description is None and _deployment_description is not None: - model_group_info.description = _deployment_description if ( model_info.get("max_input_tokens", None) is not None and model_info["max_input_tokens"] is not None