From 37cb39f48ee605d7dd46b47359989178bb474e33 Mon Sep 17 00:00:00 2001 From: Val Date: Wed, 25 Feb 2026 12:11:20 +0200 Subject: [PATCH] fix(router): prevent NoneType errors in routing callbacks and timedelta serialization Three related bugs in the routing/metrics stack: 1. `get_litellm_metadata_from_kwargs` (core_helpers.py) returns None when `kwargs["litellm_params"]` exists but is explicitly None. Python's `.get(key, default)` only uses default when key is MISSING, not when value is None. Fix: use `or {}` pattern. 2. `LowestLatencyLoggingHandler` (lowest_latency.py) stores `datetime.timedelta` objects directly as `final_value`, which fails JSON serialization when written to Redis cache. Fix: convert to float seconds via `.total_seconds()` before storage. 3. `PrometheusLogger.set_llm_deployment_success_metrics` (prometheus.py) crashes with NoneType when `_litellm_params` or `_metadata` is None (downstream of bug 1). Fix: defensive `or {}` on both assignments. Co-Authored-By: Claude Opus 4.6 --- litellm/integrations/prometheus.py | 4 ++-- litellm/litellm_core_utils/core_helpers.py | 2 +- litellm/router_strategy/lowest_latency.py | 12 ++++++++++-- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/litellm/integrations/prometheus.py b/litellm/integrations/prometheus.py index 08db77e8571..0220676f5da 100644 --- a/litellm/integrations/prometheus.py +++ b/litellm/integrations/prometheus.py @@ -1969,8 +1969,8 @@ class PrometheusLogger(CustomLogger): return api_base = standard_logging_payload["api_base"] - _litellm_params = request_kwargs.get("litellm_params", {}) or {} - _metadata = get_litellm_metadata_from_kwargs(request_kwargs) + _litellm_params = request_kwargs.get("litellm_params") or {} + _metadata = get_litellm_metadata_from_kwargs(request_kwargs) or {} litellm_model_name = request_kwargs.get("model", None) llm_provider = _litellm_params.get("custom_llm_provider", None) _model_info = _metadata.get("model_info") or {} diff --git a/litellm/litellm_core_utils/core_helpers.py b/litellm/litellm_core_utils/core_helpers.py index 7c8e2ebeaff..76d2d1fed72 100644 --- a/litellm/litellm_core_utils/core_helpers.py +++ b/litellm/litellm_core_utils/core_helpers.py @@ -163,7 +163,7 @@ def get_litellm_metadata_from_kwargs(kwargs: dict): Return `litellm_metadata` if it exists, otherwise return `metadata` """ - litellm_params = kwargs.get("litellm_params", {}) + litellm_params = kwargs.get("litellm_params") or {} if litellm_params: metadata = litellm_params.get("metadata", {}) litellm_metadata = litellm_params.get("litellm_metadata", {}) diff --git a/litellm/router_strategy/lowest_latency.py b/litellm/router_strategy/lowest_latency.py index 0449a843bd2..07f38965dac 100644 --- a/litellm/router_strategy/lowest_latency.py +++ b/litellm/router_strategy/lowest_latency.py @@ -87,7 +87,11 @@ class LowestLatencyLoggingHandler(CustomLogger): kwargs.get("completion_start_time", end_time) - start_time ) - final_value: Union[float, timedelta] = response_ms + # Always convert to float seconds for Redis JSON serialization + if isinstance(response_ms, timedelta): + final_value: Union[float, timedelta] = response_ms.total_seconds() + else: + final_value: Union[float, timedelta] = float(response_ms) if response_ms else 0.0 time_to_first_token: Optional[float] = None total_tokens = 0 @@ -308,7 +312,11 @@ class LowestLatencyLoggingHandler(CustomLogger): kwargs.get("completion_start_time", end_time) - start_time ) - final_value: Union[float, timedelta] = response_ms + # Always convert to float seconds for Redis JSON serialization + if isinstance(response_ms, timedelta): + final_value: Union[float, timedelta] = response_ms.total_seconds() + else: + final_value: Union[float, timedelta] = float(response_ms) if response_ms else 0.0 total_tokens = 0 time_to_first_token: Optional[float] = None