mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-13 03:59:12 +00:00
fix(focus): re-send the last two whole days so late-flushed spend lands
LiteLLM writes DailyUserSpend in batches, so spend dated yesterday can be flushed after the 00:05 UTC daily export. That export only covered the previous UTC midnight onward, and the next day's export moved past it, so the tail of each day was never sent. Start the window at UTC midnight two days back: the following run re-sends yesterday whole, overwriting its per-day object with the complete day.
This commit is contained in:
parent
32bf19bbef
commit
b9134f2d73
2 changed files with 41 additions and 4 deletions
|
|
@ -256,9 +256,9 @@ class TernaryLogger(FocusLogger):
|
|||
)
|
||||
|
||||
def _compute_time_window(self, now: datetime) -> FocusTimeWindow:
|
||||
"""Snap the window start to the previous UTC midnight so each push carries whole days."""
|
||||
"""Start at UTC midnight two days back so each push re-sends whole days, including spend flushed late."""
|
||||
now_utc: Final = now.astimezone(timezone.utc)
|
||||
start_time: Final = (now_utc - timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
start_time: Final = (now_utc - timedelta(days=2)).replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
return FocusTimeWindow(start_time=start_time, end_time=now_utc, frequency=self.frequency)
|
||||
|
||||
def _ensure_engine(self) -> FocusExportEngine:
|
||||
|
|
|
|||
|
|
@ -94,9 +94,46 @@ def test_compute_time_window_is_day_aligned_and_carries_whole_days():
|
|||
logger = TernaryLogger(api_key="k", connection_id="c", base_url="http://localhost:8080")
|
||||
now = datetime(2026, 9, 4, 13, 47, 5, tzinfo=timezone.utc)
|
||||
window = logger._compute_time_window(now)
|
||||
assert window.start_time == datetime(2026, 9, 3, 0, 0, 0, tzinfo=timezone.utc)
|
||||
assert window.start_time == datetime(2026, 9, 2, 0, 0, 0, tzinfo=timezone.utc)
|
||||
assert window.end_time == now
|
||||
assert window.start_time.hour == 0 and window.start_time.minute == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_daily_export_resends_yesterday_whole_including_spend_flushed_after_midnight():
|
||||
"""LiteLLM flushes spend in batches, so a 2026-10-07 row can be written at 2026-10-08 00:10, after that
|
||||
day's 00:05 export. The next daily run (2026-10-09 00:05) must still upload 2026-10-07 complete."""
|
||||
from datetime import datetime, timezone
|
||||
|
||||
rows = pl.DataFrame(
|
||||
{
|
||||
"date": ["2026-10-07", "2026-10-07", "2026-10-08"],
|
||||
"updated_at": [
|
||||
datetime(2026, 10, 7, 12, 0, tzinfo=timezone.utc),
|
||||
datetime(2026, 10, 8, 0, 10, tzinfo=timezone.utc),
|
||||
datetime(2026, 10, 8, 9, 0, tzinfo=timezone.utc),
|
||||
],
|
||||
"spend": [1.0, 2.0, 3.0],
|
||||
}
|
||||
)
|
||||
logger = TernaryLogger(api_key="k", connection_id="c", base_url="http://localhost:8080")
|
||||
window = logger._compute_time_window(datetime(2026, 10, 9, 0, 5, tzinfo=timezone.utc))
|
||||
|
||||
engine = MagicMock()
|
||||
|
||||
async def fake_get_usage_data(*, limit, start_time_utc, end_time_utc):
|
||||
return rows.filter((pl.col("updated_at") >= start_time_utc) & (pl.col("updated_at") <= end_time_utc))
|
||||
|
||||
engine._database.get_usage_data = fake_get_usage_data
|
||||
captured = {}
|
||||
|
||||
async def fake_deliver(data, _window):
|
||||
captured["data"] = data
|
||||
|
||||
engine._deliver_enriched = fake_deliver
|
||||
await _TernaryExportEngine.export_window(engine, window=window, limit=None)
|
||||
|
||||
sent = captured["data"]
|
||||
assert sent.filter(pl.col("date") == "2026-10-07")["spend"].sum() == 3.0
|
||||
|
||||
|
||||
def test_drop_days_before_removes_older_days():
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue