fix(proxy): coalesce NULL rollup metrics in aggregated daily-activity (#30151)

This commit is contained in:
michelligabriele 2026-06-11 22:32:08 +02:00 committed by GitHub
parent a2c916fb45
commit 8e12d42ea7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 74 additions and 10 deletions

View File

@ -699,17 +699,23 @@ _GROUP_DATE_ENDPOINT_API_KEY = 30 # 0b0011110
def _record_to_spend_metrics(record: Any) -> SpendMetrics: def _record_to_spend_metrics(record: Any) -> SpendMetrics:
"""Build a SpendMetrics directly from one already-aggregated rollup row.""" """Build a SpendMetrics directly from one already-aggregated rollup row.
SUM() over zero rows is SQL NULL, so rollup rows (notably the grand-total
row, which Postgres emits even on an empty match) can carry None values.
"""
prompt_tokens = record.prompt_tokens or 0
completion_tokens = record.completion_tokens or 0
return SpendMetrics( return SpendMetrics(
spend=record.spend, spend=record.spend or 0.0,
prompt_tokens=record.prompt_tokens, prompt_tokens=prompt_tokens,
completion_tokens=record.completion_tokens, completion_tokens=completion_tokens,
total_tokens=record.prompt_tokens + record.completion_tokens, total_tokens=prompt_tokens + completion_tokens,
cache_read_input_tokens=record.cache_read_input_tokens, cache_read_input_tokens=record.cache_read_input_tokens or 0,
cache_creation_input_tokens=record.cache_creation_input_tokens, cache_creation_input_tokens=record.cache_creation_input_tokens or 0,
api_requests=record.api_requests, api_requests=record.api_requests or 0,
successful_requests=record.successful_requests, successful_requests=record.successful_requests or 0,
failed_requests=record.failed_requests, failed_requests=record.failed_requests or 0,
) )

View File

@ -585,3 +585,61 @@ async def test_aggregated_activity_preserves_metadata_for_deleted_keys():
assert key_data.metadata.key_alias == "toto-test-2" assert key_data.metadata.key_alias == "toto-test-2"
assert key_data.metadata.team_id == "69cd4b77-b095-4489-8c46-4f2f31d840a2" assert key_data.metadata.team_id == "69cd4b77-b095-4489-8c46-4f2f31d840a2"
assert key_data.metrics.spend == 10.0 assert key_data.metrics.spend == 10.0
@pytest.mark.asyncio
async def test_get_daily_activity_aggregated_empty_result_set():
"""Regression test for the empty-range 500.
When the date filter matches zero rows, Postgres still emits the
grand-total () grouping-set row with every SUM column NULL. The
endpoint must return an empty result set with zeroed totals, not
crash on None + None.
"""
mock_prisma = MagicMock()
mock_prisma.db = MagicMock()
mock_rows = [
{
"date": None,
"api_key": None,
"model": None,
"model_group": None,
"custom_llm_provider": None,
"mcp_namespaced_tool_name": None,
"endpoint": None,
"group_level": 127,
"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,
}
]
mock_prisma.db.query_raw = AsyncMock(return_value=mock_rows)
result = await get_daily_activity_aggregated(
prisma_client=mock_prisma,
table_name="litellm_dailyuserspend",
entity_id_field="user_id",
entity_id=None,
entity_metadata_field=None,
start_date="2026-06-16",
end_date="2026-06-16",
model=None,
api_key=None,
)
assert result.results == []
assert result.metadata.total_spend == 0.0
assert result.metadata.total_prompt_tokens == 0
assert result.metadata.total_completion_tokens == 0
assert result.metadata.total_tokens == 0
assert result.metadata.total_api_requests == 0
assert result.metadata.total_successful_requests == 0
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