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 <noreply@anthropic.com>
This commit is contained in:
Val 2026-02-25 12:11:20 +02:00
parent 66c49dbb9c
commit 37cb39f48e
3 changed files with 13 additions and 5 deletions

View file

@ -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 {}

View file

@ -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", {})

View file

@ -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