From 46efa0e5f869e8248fb520f9adbdebc9d3dbc901 Mon Sep 17 00:00:00 2001 From: mubashir1osmani Date: Mon, 27 Apr 2026 17:57:04 -0400 Subject: [PATCH] add tests, handle errors gracefully --- litellm/proxy/utils.py | 16 ++-- tests/test_litellm/proxy/test_proxy_utils.py | 89 ++++++++++++++++++++ 2 files changed, 100 insertions(+), 5 deletions(-) diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 4c7fd29e878..3021bc713ad 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -5719,11 +5719,17 @@ def create_model_info_response( "owned_by": provider, } - if include_model_info and llm_router is not None: - try: - group_info = llm_router.get_model_group_info(model_group=model_id) - except Exception: - group_info = None + if include_model_info: + group_info = None + if llm_router is not None: + try: + group_info = llm_router.get_model_group_info(model_group=model_id) + except Exception as e: + verbose_proxy_logger.exception( + "failed to fetch model group info for %s: %s", + model_id, + e, + ) model_info["model_info"] = ( group_info.model_dump(exclude_none=True) if group_info is not None else {} ) diff --git a/tests/test_litellm/proxy/test_proxy_utils.py b/tests/test_litellm/proxy/test_proxy_utils.py index 2605eadba7a..b1cea0d4bfc 100644 --- a/tests/test_litellm/proxy/test_proxy_utils.py +++ b/tests/test_litellm/proxy/test_proxy_utils.py @@ -264,3 +264,92 @@ def test_enrich_http_exception_callback_without_guardrail_name_noop(): exc = HTTPException(status_code=400, detail={"error": "x"}) _enrich_http_exception_with_guardrail_context(exc, StubCallback()) assert exc.detail == {"error": "x"} + + +def _router_with_group_info(group_info): + router = MagicMock() + router.get_model_group_info.return_value = group_info + return router + + +def test_create_model_info_response_include_model_info(): + """`include_model_info=True` merges router group info under `model_info`. + + When false (default), the key must be absent — preserving the OpenAI + /v1/models response shape. + """ + from litellm.proxy.utils import create_model_info_response + from litellm.types.router import ModelGroupInfo + + # Default: no model_info key, no router lookup. + router = _router_with_group_info(None) + response = create_model_info_response( + model_id="gpt-4o", provider="openai", llm_router=router + ) + assert "model_info" not in response + assert response["id"] == "gpt-4o" + assert response["owned_by"] == "openai" + router.get_model_group_info.assert_not_called() + + # Flag on: pricing/tokens/mode/supports_* flow through. + group_info = ModelGroupInfo( + model_group="gpt-4o", + providers=["openai"], + max_input_tokens=128000, + max_output_tokens=16384, + input_cost_per_token=0.0000025, + output_cost_per_token=0.00001, + mode="chat", + tpm=30000, + rpm=500, + supports_function_calling=True, + supports_vision=True, + ) + router = _router_with_group_info(group_info) + response = create_model_info_response( + model_id="gpt-4o", + provider="openai", + include_model_info=True, + llm_router=router, + ) + router.get_model_group_info.assert_called_once_with(model_group="gpt-4o") + info = response["model_info"] + assert info["mode"] == "chat" + assert info["max_input_tokens"] == 128000 + assert info["max_output_tokens"] == 16384 + assert info["input_cost_per_token"] == 0.0000025 + assert info["output_cost_per_token"] == 0.00001 + assert info["tpm"] == 30000 + assert info["rpm"] == 500 + assert info["supports_function_calling"] is True + assert info["supports_vision"] is True + + # Unknown / wildcard model: empty dict, not missing key. + response = create_model_info_response( + model_id="openai/*", + provider="openai", + include_model_info=True, + llm_router=_router_with_group_info(None), + ) + assert response["model_info"] == {} + + # Router error must not bubble — endpoint should still return a 200. + failing = MagicMock() + failing.get_model_group_info.side_effect = RuntimeError("boom") + response = create_model_info_response( + model_id="gpt-4o", + provider="openai", + include_model_info=True, + llm_router=failing, + ) + assert response["model_info"] == {} + + # No router available: still emit `model_info: {}` so callers get a + # consistent response shape regardless of proxy state. + response = create_model_info_response( + model_id="gpt-4o", + provider="openai", + include_model_info=True, + llm_router=None, + ) + assert response["model_info"] == {}