diff --git a/litellm/router_strategy/least_busy.py b/litellm/router_strategy/least_busy.py index 819fedde991..1379aa49a4a 100644 --- a/litellm/router_strategy/least_busy.py +++ b/litellm/router_strategy/least_busy.py @@ -32,7 +32,7 @@ class LeastBusyLoggingHandler(CustomLogger): pass else: model_group = kwargs["litellm_params"]["metadata"].get("model_group", None) - id = kwargs["litellm_params"].get("model_info", {}).get("id", None) + id = (kwargs["litellm_params"].get("model_info") or {}).get("id", None) if model_group is None or id is None: return elif isinstance(id, int): @@ -54,7 +54,7 @@ class LeastBusyLoggingHandler(CustomLogger): else: model_group = kwargs["litellm_params"]["metadata"].get("model_group", None) - id = kwargs["litellm_params"].get("model_info", {}).get("id", None) + id = (kwargs["litellm_params"].get("model_info") or {}).get("id", None) if model_group is None or id is None: return elif isinstance(id, int): @@ -81,7 +81,7 @@ class LeastBusyLoggingHandler(CustomLogger): pass else: model_group = kwargs["litellm_params"]["metadata"].get("model_group", None) - id = kwargs["litellm_params"].get("model_info", {}).get("id", None) + id = (kwargs["litellm_params"].get("model_info") or {}).get("id", None) if model_group is None or id is None: return elif isinstance(id, int): @@ -109,7 +109,7 @@ class LeastBusyLoggingHandler(CustomLogger): else: model_group = kwargs["litellm_params"]["metadata"].get("model_group", None) - id = kwargs["litellm_params"].get("model_info", {}).get("id", None) + id = (kwargs["litellm_params"].get("model_info") or {}).get("id", None) if model_group is None or id is None: return elif isinstance(id, int): @@ -136,7 +136,7 @@ class LeastBusyLoggingHandler(CustomLogger): pass else: model_group = kwargs["litellm_params"]["metadata"].get("model_group", None) - id = kwargs["litellm_params"].get("model_info", {}).get("id", None) + id = (kwargs["litellm_params"].get("model_info") or {}).get("id", None) if model_group is None or id is None: return elif isinstance(id, int): diff --git a/litellm/router_strategy/lowest_cost.py b/litellm/router_strategy/lowest_cost.py index 67bdbfbe0ac..f2d1af5f61e 100644 --- a/litellm/router_strategy/lowest_cost.py +++ b/litellm/router_strategy/lowest_cost.py @@ -28,7 +28,7 @@ class LowestCostLoggingHandler(CustomLogger): else: model_group = kwargs["litellm_params"]["metadata"].get("model_group", None) - id = kwargs["litellm_params"].get("model_info", {}).get("id", None) + id = (kwargs["litellm_params"].get("model_info") or {}).get("id", None) if model_group is None or id is None: return elif isinstance(id, int): @@ -106,7 +106,7 @@ class LowestCostLoggingHandler(CustomLogger): else: model_group = kwargs["litellm_params"]["metadata"].get("model_group", None) - id = kwargs["litellm_params"].get("model_info", {}).get("id", None) + id = (kwargs["litellm_params"].get("model_info") or {}).get("id", None) if model_group is None or id is None: return elif isinstance(id, int): diff --git a/litellm/router_strategy/lowest_tpm_rpm.py b/litellm/router_strategy/lowest_tpm_rpm.py index 89ad7526f20..6114276b647 100644 --- a/litellm/router_strategy/lowest_tpm_rpm.py +++ b/litellm/router_strategy/lowest_tpm_rpm.py @@ -38,7 +38,7 @@ class LowestTPMLoggingHandler(CustomLogger): else: model_group = kwargs["litellm_params"]["metadata"].get("model_group", None) - id = kwargs["litellm_params"].get("model_info", {}).get("id", None) + id = (kwargs["litellm_params"].get("model_info") or {}).get("id", None) if model_group is None or id is None: return elif isinstance(id, int): diff --git a/tests/test_litellm/router_strategy/test_lowest_cost.py b/tests/test_litellm/router_strategy/test_lowest_cost.py new file mode 100644 index 00000000000..22251da495a --- /dev/null +++ b/tests/test_litellm/router_strategy/test_lowest_cost.py @@ -0,0 +1,51 @@ +from datetime import datetime +from unittest.mock import patch + +import pytest + +from litellm.caching.caching import DualCache +from litellm.router_strategy.lowest_cost import LowestCostLoggingHandler +from litellm.types.utils import ModelResponse, Usage + + +def _kwargs(model_info: object) -> dict: + return { + "litellm_params": { + "metadata": {"model_group": "gpt-3.5-turbo"}, + "model_info": model_info, + }, + "response_cost": 0.01, + } + + +@pytest.mark.asyncio +@pytest.mark.parametrize("model_info", [None, {}]) +async def test_log_success_event_without_deployment_id_does_not_raise(model_info): + """ + litellm_params carries an explicit `model_info: None` for calls that aren't routed + through a router deployment; the handler must no-op instead of blowing up on + `None.get("id")` and spamming exception logs (issue #35459) + """ + handler = LowestCostLoggingHandler(router_cache=DualCache(), routing_args={}) + response_obj = ModelResponse(usage=Usage(prompt_tokens=25, completion_tokens=25, total_tokens=50)) + now = datetime.now() + + with patch("litellm.router_strategy.lowest_cost.verbose_logger.exception") as mock_exception: + handler.log_success_event(_kwargs(model_info), response_obj, now, now) + await handler.async_log_success_event(_kwargs(model_info), response_obj, now, now) + + mock_exception.assert_not_called() + assert handler.router_cache.get_cache("gpt-3.5-turbo_map") is None + + +@pytest.mark.asyncio +async def test_log_success_event_records_cost_for_routed_deployment(): + handler = LowestCostLoggingHandler(router_cache=DualCache(), routing_args={}) + response_obj = ModelResponse(usage=Usage(prompt_tokens=25, completion_tokens=25, total_tokens=50)) + now = datetime.now() + + await handler.async_log_success_event(_kwargs({"id": "deployment-1"}), response_obj, now, now) + + cost_map = handler.router_cache.get_cache("gpt-3.5-turbo_map") + assert cost_map is not None + assert "deployment-1" in cost_map