From ee71d69db0a762c1d99293b6836d172cafc745ce Mon Sep 17 00:00:00 2001 From: Dantuluri Surya Narayana Raju Date: Sun, 17 May 2026 20:18:34 +0530 Subject: [PATCH] fix(router): use .get() for 'hidden' and 'model' keys in dict-style model_group_alias entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RouterModelGroupAliasItem is a TypedDict with required fields 'model' and 'hidden', but TypedDict is a type-hint only — Python does not validate or inject keys at runtime. Any user whose config omits the 'hidden' key (natural, since it is only needed to hide a group) gets a plain dict without it: model_group_alias: 'gpt-4-alias': {model: gpt-3.5-turbo} # no hidden key This caused a KeyError on every request because: - get_model_group_info() is called by set_response_headers on every router completion (confirmed by source comment) - get_model_list_from_model_alias() is called during Router.__init__ via set_model_list -> get_model_names Both call sites used item['hidden'] and item['model'] directly. Fix: switch to item.get('hidden') and item.get('model', '') in both locations. item.get('hidden') returns None when the key is absent, and None is not True, so the visibility logic is preserved correctly. Adds regression test covering three cases: - dict alias without 'hidden' key (no KeyError, returns info) - dict alias with hidden=True (returns None) - dict alias with hidden=False (returns info) --- litellm/router.py | 8 +-- .../test_router_helper_utils.py | 50 +++++++++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/litellm/router.py b/litellm/router.py index fac48b45fb3..fc1b29497f3 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -8697,10 +8697,10 @@ class Router: if isinstance(item, str): _router_model_group = item elif isinstance(item, dict): - if item["hidden"] is True: + if item.get("hidden") is True: return None else: - _router_model_group = item["model"] + _router_model_group = item.get("model", "") else: return None @@ -9299,10 +9299,10 @@ class Router: _router_model_name: str = model_value elif isinstance(model_value, dict): _model_value = RouterModelGroupAliasItem(**model_value) # type: ignore - if _model_value["hidden"] is True: + if _model_value.get("hidden") is True: continue else: - _router_model_name = _model_value["model"] + _router_model_name = _model_value.get("model", "") else: continue diff --git a/tests/router_unit_tests/test_router_helper_utils.py b/tests/router_unit_tests/test_router_helper_utils.py index 65d9d6b925d..5c0550b0571 100644 --- a/tests/router_unit_tests/test_router_helper_utils.py +++ b/tests/router_unit_tests/test_router_helper_utils.py @@ -977,6 +977,56 @@ def test_update_settings(model_list): assert router.allowed_fails == 20 +def test_get_model_group_info_alias_dict_without_hidden_key(model_list): + """Regression test: get_model_group_info must not raise KeyError when a + dict-style model_group_alias entry omits the 'hidden' key. + + RouterModelGroupAliasItem declares 'hidden: bool' as a TypedDict field, but + TypedDict is a type-hint only — Python does NOT inject default values at + runtime. Any user who writes: + + model_group_alias: + "gpt-4-alias": {"model": "gpt-3.5-turbo"} + + gets a plain dict without 'hidden', causing item["hidden"] to raise + KeyError on every request (via set_response_headers -> get_model_group_info). + """ + alias_model_list = list(model_list) + [ + { + "model_name": "gpt-3.5-turbo", + "litellm_params": {"model": "openai/gpt-3.5-turbo", "api_key": "sk-fake"}, + } + ] + router = Router( + model_list=alias_model_list, + model_group_alias={ + "gpt-4-alias": {"model": "gpt-3.5-turbo"}, + }, + ) + + # Must not raise KeyError — 'hidden' key is absent from the alias dict + result = router.get_model_group_info(model_group="gpt-4-alias") + assert result is not None + + # Alias with hidden=True must still return None + router2 = Router( + model_list=alias_model_list, + model_group_alias={ + "secret-alias": {"model": "gpt-3.5-turbo", "hidden": True}, + }, + ) + assert router2.get_model_group_info(model_group="secret-alias") is None + + # Alias with hidden=False (explicit) must return info + router3 = Router( + model_list=alias_model_list, + model_group_alias={ + "visible-alias": {"model": "gpt-3.5-turbo", "hidden": False}, + }, + ) + assert router3.get_model_group_info(model_group="visible-alias") is not None + + def test_common_checks_available_deployment(model_list): """Test if the 'common_checks_available_deployment' function is working correctly""" router = Router(model_list=model_list)