fix: coalesce null aggregates in update_metrics for no-spend keys (#29945)

This commit is contained in:
Harshith Gujjeti 2026-06-17 07:17:24 -04:00 • committed by GitHub
parent 8fbe3dc537
commit a40009eaaf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 65 additions and 10 deletions

View file

@ -35,16 +35,25 @@ _PRISMA_TO_PG_TABLE: Dict[str, str] = {
def update_metrics(existing_metrics: SpendMetrics, record: Any) -> SpendMetrics:
"""Update metrics with new record data."""
existing_metrics.spend += record.spend
existing_metrics.prompt_tokens += record.prompt_tokens
existing_metrics.completion_tokens += record.completion_tokens
existing_metrics.total_tokens += record.prompt_tokens + record.completion_tokens
existing_metrics.cache_read_input_tokens += record.cache_read_input_tokens
existing_metrics.cache_creation_input_tokens += record.cache_creation_input_tokens
existing_metrics.api_requests += record.api_requests
existing_metrics.successful_requests += record.successful_requests
existing_metrics.failed_requests += record.failed_requests
"""Update metrics with new record data.
Rollup rows can carry None for numeric fields when SUM() spans zero rows
(e.g. a key with no spend), so coalesce to 0 before accumulating to avoid
a TypeError. Mirrors the handling in ``_record_to_spend_metrics``.
"""
prompt_tokens = record.prompt_tokens or 0
completion_tokens = record.completion_tokens or 0
existing_metrics.spend += record.spend or 0.0
existing_metrics.prompt_tokens += prompt_tokens
existing_metrics.completion_tokens += completion_tokens
existing_metrics.total_tokens += prompt_tokens + completion_tokens
existing_metrics.cache_read_input_tokens += record.cache_read_input_tokens or 0
existing_metrics.cache_creation_input_tokens += (
record.cache_creation_input_tokens or 0
)
existing_metrics.api_requests += record.api_requests or 0
existing_metrics.successful_requests += record.successful_requests or 0
existing_metrics.failed_requests += record.failed_requests or 0
return existing_metrics

View file

@ -1,5 +1,6 @@
import os
import sys
from types import SimpleNamespace
from unittest.mock import AsyncMock, MagicMock
import pytest
@ -12,10 +13,13 @@ from litellm.proxy.management_endpoints.common_daily_activity import (
_adjust_dates_for_timezone,
_build_aggregated_sql_query,
_is_user_agent_tag,
_record_to_spend_metrics,
get_api_key_metadata,
get_daily_activity,
get_daily_activity_aggregated,
update_metrics,
)
from litellm.types.proxy.management_endpoints.common_daily_activity import SpendMetrics
@pytest.mark.asyncio
@ -810,3 +814,45 @@ async def test_get_daily_activity_aggregated_empty_result_set():
assert result.metadata.total_failed_requests == 0
assert result.metadata.total_cache_read_input_tokens == 0
assert result.metadata.total_cache_creation_input_tokens == 0
def _no_spend_record():
"""A rollup row for a key with no spend, where SUM() returns NULL (None)."""
return SimpleNamespace(
spend=None,
prompt_tokens=None,
completion_tokens=None,
cache_read_input_tokens=None,
cache_creation_input_tokens=None,
api_requests=None,
successful_requests=None,
failed_requests=None,
)
def test_record_to_spend_metrics_handles_none_values():
"""Keys with no spend produce NULL aggregates; treat them as zero, not a crash."""
metrics = _record_to_spend_metrics(_no_spend_record())
assert metrics.spend == 0
assert metrics.prompt_tokens == 0
assert metrics.completion_tokens == 0
assert metrics.total_tokens == 0
assert metrics.api_requests == 0
assert metrics.successful_requests == 0
assert metrics.failed_requests == 0
assert metrics.cache_read_input_tokens == 0
assert metrics.cache_creation_input_tokens == 0
def test_update_metrics_handles_none_values():
"""update_metrics should coalesce NULL aggregates instead of raising TypeError."""
metrics = update_metrics(SpendMetrics(), _no_spend_record())
assert metrics.spend == 0
assert metrics.prompt_tokens == 0
assert metrics.completion_tokens == 0
assert metrics.total_tokens == 0
assert metrics.api_requests == 0
assert metrics.successful_requests == 0
assert metrics.failed_requests == 0
assert metrics.cache_read_input_tokens == 0
assert metrics.cache_creation_input_tokens == 0