From f35f3a8a33a067f08d13206c43f2ed3e5ee72261 Mon Sep 17 00:00:00 2001 From: medisean <6093631+medisean@users.noreply.github.com> Date: Fri, 14 Aug 2026 12:44:47 +0800 Subject: [PATCH] fix: normalize Prometheus latency datetimes --- litellm/integrations/prometheus.py | 8 ++++++-- ...est_prometheus_overhead_with_guardrails.py | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/litellm/integrations/prometheus.py b/litellm/integrations/prometheus.py index a9056aaf4e1..e433c24be3c 100644 --- a/litellm/integrations/prometheus.py +++ b/litellm/integrations/prometheus.py @@ -8,7 +8,7 @@ import math import os import sys from collections.abc import Awaitable, Callable, Mapping, Sequence -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from typing import TYPE_CHECKING, Any, Final, Literal, cast import litellm @@ -3990,7 +3990,11 @@ class PrometheusLogger(CustomLogger): otherwise returns None. """ if isinstance(start_time, datetime) and isinstance(end_time, datetime): - return (end_time - start_time).total_seconds() + normalized_start: Final = ( + start_time if start_time.tzinfo is not None else start_time.replace(tzinfo=timezone.utc) + ) + normalized_end: Final = end_time if end_time.tzinfo is not None else end_time.replace(tzinfo=timezone.utc) + return (normalized_end - normalized_start).total_seconds() return None @staticmethod diff --git a/tests/test_litellm/integrations/test_prometheus_overhead_with_guardrails.py b/tests/test_litellm/integrations/test_prometheus_overhead_with_guardrails.py index 56c9702189e..e7af8f7b517 100644 --- a/tests/test_litellm/integrations/test_prometheus_overhead_with_guardrails.py +++ b/tests/test_litellm/integrations/test_prometheus_overhead_with_guardrails.py @@ -7,6 +7,7 @@ durations. During-call (moderation) guardrails run concurrently with the LLM call and are excluded so they don't inflate the overhead. """ +from datetime import datetime, timedelta, timezone from unittest.mock import MagicMock import pytest @@ -160,6 +161,25 @@ def test_get_guardrail_overhead_seconds_counts_single_pre_call_dict(): assert abs(PrometheusLogger._get_guardrail_overhead_seconds(payload) - 0.3) < 1e-6 +@pytest.mark.parametrize( + ("start_time", "end_time"), + ( + ( + datetime(2026, 8, 14, 12, 0, 0, tzinfo=timezone.utc).replace(tzinfo=None), + datetime(2026, 8, 14, 12, 0, 1, tzinfo=timezone.utc), + ), + ( + datetime(2026, 8, 14, 14, 0, 0, tzinfo=timezone(timedelta(hours=2))), + datetime(2026, 8, 14, 12, 0, 1, tzinfo=timezone.utc).replace(tzinfo=None), + ), + ), +) +def test_safe_duration_seconds_normalizes_mixed_timezone_awareness(start_time, end_time): + logger = PrometheusLogger() + + assert logger._safe_duration_seconds(start_time, end_time) == 1.0 + + def _patch_label_factory(monkeypatch): monkeypatch.setattr( "litellm.integrations.prometheus.prometheus_label_factory",