mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
fix(spend): coerce Decimal and numeric-string aggregates in the benchmarks read path
This commit is contained in:
parent
7eeafa908e
commit
798e54ddc7
2 changed files with 41 additions and 1 deletions
|
|
@ -10,6 +10,7 @@ from __future__ import annotations
|
|||
from collections.abc import Mapping, Sequence
|
||||
from dataclasses import astuple, dataclass, fields
|
||||
from datetime import date, datetime, time, timedelta, timezone
|
||||
from decimal import Decimal
|
||||
from functools import reduce
|
||||
from typing import TYPE_CHECKING, Final
|
||||
|
||||
|
|
@ -136,7 +137,16 @@ def _per(numerator: float, denominator: float) -> float:
|
|||
|
||||
|
||||
def _number(value: object) -> float:
|
||||
return float(value) if isinstance(value, (int, float)) else 0.0
|
||||
"""Postgres aggregates surface through the driver as int, float, Decimal or a numeric
|
||||
string depending on the column type (SUM over BIGINT and EXTRACT both yield NUMERIC)."""
|
||||
if isinstance(value, (int, float, Decimal)):
|
||||
return float(value)
|
||||
if not isinstance(value, str):
|
||||
return 0.0
|
||||
try:
|
||||
return float(value)
|
||||
except ValueError:
|
||||
return 0.0
|
||||
|
||||
|
||||
def _counters_from_row(row: Mapping[str, object]) -> _Counters:
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ is exercised here without a database.
|
|||
|
||||
import datetime as dt
|
||||
from dataclasses import fields
|
||||
from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
|
|
@ -233,6 +234,35 @@ class TestTotals:
|
|||
assert response.groups == ()
|
||||
assert response.totals.turns == 0
|
||||
|
||||
def test_decimal_and_string_aggregates_survive_the_driver(self):
|
||||
"""SUM over BIGINT and EXTRACT(EPOCH) yield NUMERIC, which the driver may hand
|
||||
back as Decimal or a numeric string; neither may read as zero traffic."""
|
||||
response = build_response(
|
||||
rows=[
|
||||
_row(
|
||||
"a",
|
||||
sessions=Decimal("2"),
|
||||
turns=Decimal("64"),
|
||||
total_tokens=Decimal("10000"),
|
||||
total_session_seconds="7200.5",
|
||||
)
|
||||
],
|
||||
start_date=dt.date(2026, 8, 1),
|
||||
end_date=dt.date(2026, 8, 3),
|
||||
)
|
||||
assert response.totals.turns == 64
|
||||
assert response.totals.total_tokens == 10_000
|
||||
assert response.totals.avg_tokens_per_session == pytest.approx(5000.0)
|
||||
assert response.totals.avg_session_seconds == pytest.approx(3600.25)
|
||||
|
||||
def test_a_malformed_aggregate_reads_as_zero_rather_than_failing_the_page(self):
|
||||
response = build_response(
|
||||
rows=[_row("a", total_tokens="not-a-number")],
|
||||
start_date=dt.date(2026, 8, 1),
|
||||
end_date=dt.date(2026, 8, 3),
|
||||
)
|
||||
assert response.totals.total_tokens == 0
|
||||
|
||||
def test_group_identity_is_carried_through(self):
|
||||
response = build_response(
|
||||
rows=[_row("claude-auto")], start_date=dt.date(2026, 8, 1), end_date=dt.date(2026, 8, 3)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue