mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
fix(proxy): anchor the tool spend clamp floor to UTC midnight
With end_date omitted the floor was end-anchored to now including its time-of-day, so an explicit start_date exactly 30 days back parsed as midnight, compared below the floor, and was invisibly clamped to a mid-day instant: up to a day of spend disappeared while the response start_date still printed the full calendar date. Anchoring the floor to today's UTC midnight makes every comparison in the window derivation date-pure. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
5fc4a058c1
commit
3ca9d551dc
2 changed files with 31 additions and 2 deletions
|
|
@ -232,7 +232,11 @@ async def get_tool_spend(
|
|||
|
||||
now = datetime.now(timezone.utc)
|
||||
end_day = _parse_day_start(end_date)
|
||||
window_floor = (end_day or now) - timedelta(days=TOOL_SPEND_MAX_WINDOW_DAYS)
|
||||
# Anchor the floor to a midnight so the clamp compares dates with dates:
|
||||
# parsed start_dates are midnight-aligned, and a floor carrying now's
|
||||
# time-of-day would invisibly truncate an explicit start_date to mid-day.
|
||||
today = now.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
window_floor = (end_day or today) - timedelta(days=TOOL_SPEND_MAX_WINDOW_DAYS)
|
||||
start_dt = _parse_day_start(start_date) or window_floor
|
||||
if start_dt < window_floor:
|
||||
start_dt = window_floor
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ imports these inside function bodies to avoid circular imports.
|
|||
|
||||
import os
|
||||
import sys
|
||||
from datetime import datetime, timezone
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Optional
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
|
|
@ -232,6 +232,31 @@ class TestToolManagementEndpoints:
|
|||
assert call.args[1] == datetime(2026, 6, 25, tzinfo=timezone.utc).isoformat()
|
||||
assert resp.json()["start_date"] == "2026-06-25"
|
||||
|
||||
def test_tool_spend_start_honored_when_end_date_omitted(self):
|
||||
# Regression: with end_date omitted the floor anchors to today's UTC
|
||||
# midnight, not now's time-of-day, so an explicit start_date exactly 30
|
||||
# days back is served from midnight rather than truncated to mid-day.
|
||||
prisma = MagicMock()
|
||||
prisma.db.query_raw = AsyncMock(return_value=[])
|
||||
floor_day = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(days=30)
|
||||
with patch("litellm.proxy.proxy_server.prisma_client", prisma):
|
||||
resp = self.client.get(f"/v1/tool/spend?start_date={floor_day.strftime('%Y-%m-%d')}")
|
||||
assert resp.status_code == 200
|
||||
for call in prisma.db.query_raw.await_args_list:
|
||||
assert call.args[1] == floor_day.isoformat()
|
||||
assert resp.json()["start_date"] == floor_day.strftime("%Y-%m-%d")
|
||||
|
||||
def test_tool_spend_clamp_without_end_date_lands_on_midnight(self):
|
||||
prisma = MagicMock()
|
||||
prisma.db.query_raw = AsyncMock(return_value=[])
|
||||
floor_day = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(days=30)
|
||||
with patch("litellm.proxy.proxy_server.prisma_client", prisma):
|
||||
resp = self.client.get("/v1/tool/spend?start_date=2020-01-01")
|
||||
assert resp.status_code == 200
|
||||
for call in prisma.db.query_raw.await_args_list:
|
||||
assert call.args[1] == floor_day.isoformat()
|
||||
assert resp.json()["start_date"] == floor_day.strftime("%Y-%m-%d")
|
||||
|
||||
def test_tool_spend_total_query_bounds_outer_spendlogs_scan(self):
|
||||
prisma = MagicMock()
|
||||
prisma.db.query_raw = AsyncMock(return_value=[])
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue