From f35382de562e1041cc418ff10b31cc4ce2b6aa8a Mon Sep 17 00:00:00 2001 From: ryder777777 Date: Thu, 22 Oct 2026 15:33:20 +0000 Subject: [PATCH 1/2] fix(router): fallback for ModelNotMappedError in get_router_model_info (#47922) Complexity-router compaction calls get_router_model_info for every candidate deployment. On 1.106.0 with LITELLM_ENABLE_MCP_STDIO=true, deployed MCP stdio models are not in model_cost, so litellm.get_model_info raises ModelNotMappedError and crashes /chat/completions via context_compaction._budget. Wrap get_model_info in try/except: on ModelNotMappedError return a minimal fallback ModelMapInfo with null windows and zero costs so compaction treats the model as uncompactable instead of crashing the request. --- litellm/router.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/litellm/router.py b/litellm/router.py index da04e59f18a..052498f43d6 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -12198,7 +12198,27 @@ class Router: else: model_info_name = model - model_info: Final = litellm.get_model_info(model=model_info_name) + try: + model_info: Final = litellm.get_model_info(model=model_info_name) + except Exception as e: + if e.__class__.__name__ == "ModelNotMappedError" or "not mapped" in str(e).lower(): + verbose_router_logger.warning( + "Model '%s' not mapped in model_cost; returning fallback info for compaction/budget checks: %s", + model_info_name, + e, + ) + fallback: ModelMapInfo = { + "key": model_info_name, + "max_tokens": None, + "max_input_tokens": None, + "max_output_tokens": None, + "input_cost_per_token": 0, + "output_cost_per_token": 0, + "litellm_provider": custom_llm_provider or "", + "mode": "chat", + } + return fallback + raise if model_info is None: return model_info From d5951c174df762df0e8b669dd724af6c15e35c9d Mon Sep 17 00:00:00 2001 From: ryder777777 Date: Fri, 23 Oct 2026 21:08:33 +0000 Subject: [PATCH 2/2] fix(router): annotate fallback as Final[ModelMapInfo] and add unit coverage (#47922) --- litellm/router.py | 2 +- .../test_router_model_not_mapped_fallback.py | 48 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 tests/unit/test_router_model_not_mapped_fallback.py diff --git a/litellm/router.py b/litellm/router.py index 052498f43d6..3795c426582 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -12207,7 +12207,7 @@ class Router: model_info_name, e, ) - fallback: ModelMapInfo = { + fallback: Final[ModelMapInfo] = { "key": model_info_name, "max_tokens": None, "max_input_tokens": None, diff --git a/tests/unit/test_router_model_not_mapped_fallback.py b/tests/unit/test_router_model_not_mapped_fallback.py new file mode 100644 index 00000000000..95161dc879c --- /dev/null +++ b/tests/unit/test_router_model_not_mapped_fallback.py @@ -0,0 +1,48 @@ +from typing import Final +from unittest.mock import patch + +import pytest + +import litellm +from litellm.exceptions import ModelNotMappedError +from litellm.router import Router + + +def test_get_router_model_info_returns_fallback_on_model_not_mapped_error() -> None: + router: Final = Router( + model_list=[ + { + "model_name": "mcp-stdio-group", + "litellm_params": { + "model": "openai/unmapped-mcp-stdio-model", + "api_key": "test-key", + }, + } + ] + ) + + with patch.object( + litellm, + "get_model_info", + side_effect=ModelNotMappedError(message="Model 'unmapped-mcp-stdio-model' is not mapped", model="unmapped-mcp-stdio-model"), + ): + info: Final = router.get_router_model_info(model="mcp-stdio-group") + + assert info is not None + assert info["max_tokens"] is None + assert info["max_input_tokens"] is None + assert info["max_output_tokens"] is None + assert info["input_cost_per_token"] == 0 + assert info["output_cost_per_token"] == 0 + assert info["litellm_provider"] == "openai" + assert info["mode"] == "chat" + + +def test_get_router_model_info_reraises_unrelated_exceptions() -> None: + router: Final = Router(model_list=[]) + + with ( + patch.object(litellm, "get_model_info", side_effect=RuntimeError("unexpected failure")), + pytest.raises(RuntimeError, match="unexpected failure"), + ): + router.get_router_model_info(model="any-model")