Spaces:
Build error
Build error
Garm commited on
Commit ·
51c9c1a
1
Parent(s): 3d4e512
Apply Ruff formatting to rebased stack
Browse files
headroom/proxy/savings_tracker.py
CHANGED
|
@@ -49,12 +49,7 @@ def _utc_now() -> datetime:
|
|
| 49 |
|
| 50 |
|
| 51 |
def _to_utc_iso(dt: datetime) -> str:
|
| 52 |
-
return (
|
| 53 |
-
dt.astimezone(timezone.utc)
|
| 54 |
-
.replace(microsecond=0)
|
| 55 |
-
.isoformat()
|
| 56 |
-
.replace("+00:00", "Z")
|
| 57 |
-
)
|
| 58 |
|
| 59 |
|
| 60 |
def _parse_timestamp(value: Any) -> datetime | None:
|
|
@@ -293,9 +288,7 @@ class SavingsTracker:
|
|
| 293 |
path: str | None = None,
|
| 294 |
max_history_points: int = DEFAULT_MAX_HISTORY_POINTS,
|
| 295 |
max_history_age_days: int = DEFAULT_MAX_HISTORY_AGE_DAYS,
|
| 296 |
-
display_session_inactivity_minutes: int = (
|
| 297 |
-
DEFAULT_DISPLAY_SESSION_INACTIVITY_MINUTES
|
| 298 |
-
),
|
| 299 |
) -> None:
|
| 300 |
self._path = Path(path or get_default_savings_storage_path())
|
| 301 |
self._max_history_points = max_history_points
|
|
@@ -472,9 +465,7 @@ class SavingsTracker:
|
|
| 472 |
)
|
| 473 |
total_before = session["tokens_saved"] + session["total_input_tokens"]
|
| 474 |
session["savings_percent"] = round(
|
| 475 |
-
(session["tokens_saved"] / total_before * 100)
|
| 476 |
-
if total_before > 0
|
| 477 |
-
else 0.0,
|
| 478 |
2,
|
| 479 |
)
|
| 480 |
session["last_activity_at"] = _to_utc_iso(timestamp_dt)
|
|
@@ -584,9 +575,7 @@ class SavingsTracker:
|
|
| 584 |
"lifetime": dict(self._state["lifetime"]),
|
| 585 |
"display_session": self._display_session_snapshot_locked(),
|
| 586 |
"display_session_policy": {
|
| 587 |
-
"rollover_inactivity_minutes": (
|
| 588 |
-
self._display_session_inactivity_minutes
|
| 589 |
-
),
|
| 590 |
},
|
| 591 |
"history": history,
|
| 592 |
"retention": {
|
|
@@ -645,13 +634,9 @@ class SavingsTracker:
|
|
| 645 |
if isinstance(lifetime_raw, dict):
|
| 646 |
lifetime_requests = _coerce_int(lifetime_raw.get("requests"))
|
| 647 |
lifetime_tokens_saved = _coerce_int(lifetime_raw.get("tokens_saved"))
|
| 648 |
-
lifetime_savings_usd = _coerce_float(
|
| 649 |
-
lifetime_raw.get("compression_savings_usd")
|
| 650 |
-
)
|
| 651 |
lifetime_input_tokens = _coerce_int(lifetime_raw.get("total_input_tokens"))
|
| 652 |
-
lifetime_input_cost_usd = _coerce_float(
|
| 653 |
-
lifetime_raw.get("total_input_cost_usd")
|
| 654 |
-
)
|
| 655 |
|
| 656 |
if normalized_history:
|
| 657 |
last = normalized_history[-1]
|
|
@@ -686,9 +671,7 @@ class SavingsTracker:
|
|
| 686 |
}
|
| 687 |
|
| 688 |
if normalized_history:
|
| 689 |
-
reference_time = (
|
| 690 |
-
_parse_timestamp(normalized_history[-1]["timestamp"]) or _utc_now()
|
| 691 |
-
)
|
| 692 |
original_state = self._state if hasattr(self, "_state") else None
|
| 693 |
self._state = state
|
| 694 |
try:
|
|
@@ -706,9 +689,7 @@ class SavingsTracker:
|
|
| 706 |
return
|
| 707 |
|
| 708 |
if self._max_history_age_days > 0:
|
| 709 |
-
cutoff = (reference_time or _utc_now()) - timedelta(
|
| 710 |
-
days=self._max_history_age_days
|
| 711 |
-
)
|
| 712 |
filtered = [
|
| 713 |
item
|
| 714 |
for item in history
|
|
|
|
| 49 |
|
| 50 |
|
| 51 |
def _to_utc_iso(dt: datetime) -> str:
|
| 52 |
+
return dt.astimezone(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
|
| 54 |
|
| 55 |
def _parse_timestamp(value: Any) -> datetime | None:
|
|
|
|
| 288 |
path: str | None = None,
|
| 289 |
max_history_points: int = DEFAULT_MAX_HISTORY_POINTS,
|
| 290 |
max_history_age_days: int = DEFAULT_MAX_HISTORY_AGE_DAYS,
|
| 291 |
+
display_session_inactivity_minutes: int = (DEFAULT_DISPLAY_SESSION_INACTIVITY_MINUTES),
|
|
|
|
|
|
|
| 292 |
) -> None:
|
| 293 |
self._path = Path(path or get_default_savings_storage_path())
|
| 294 |
self._max_history_points = max_history_points
|
|
|
|
| 465 |
)
|
| 466 |
total_before = session["tokens_saved"] + session["total_input_tokens"]
|
| 467 |
session["savings_percent"] = round(
|
| 468 |
+
(session["tokens_saved"] / total_before * 100) if total_before > 0 else 0.0,
|
|
|
|
|
|
|
| 469 |
2,
|
| 470 |
)
|
| 471 |
session["last_activity_at"] = _to_utc_iso(timestamp_dt)
|
|
|
|
| 575 |
"lifetime": dict(self._state["lifetime"]),
|
| 576 |
"display_session": self._display_session_snapshot_locked(),
|
| 577 |
"display_session_policy": {
|
| 578 |
+
"rollover_inactivity_minutes": (self._display_session_inactivity_minutes),
|
|
|
|
|
|
|
| 579 |
},
|
| 580 |
"history": history,
|
| 581 |
"retention": {
|
|
|
|
| 634 |
if isinstance(lifetime_raw, dict):
|
| 635 |
lifetime_requests = _coerce_int(lifetime_raw.get("requests"))
|
| 636 |
lifetime_tokens_saved = _coerce_int(lifetime_raw.get("tokens_saved"))
|
| 637 |
+
lifetime_savings_usd = _coerce_float(lifetime_raw.get("compression_savings_usd"))
|
|
|
|
|
|
|
| 638 |
lifetime_input_tokens = _coerce_int(lifetime_raw.get("total_input_tokens"))
|
| 639 |
+
lifetime_input_cost_usd = _coerce_float(lifetime_raw.get("total_input_cost_usd"))
|
|
|
|
|
|
|
| 640 |
|
| 641 |
if normalized_history:
|
| 642 |
last = normalized_history[-1]
|
|
|
|
| 671 |
}
|
| 672 |
|
| 673 |
if normalized_history:
|
| 674 |
+
reference_time = _parse_timestamp(normalized_history[-1]["timestamp"]) or _utc_now()
|
|
|
|
|
|
|
| 675 |
original_state = self._state if hasattr(self, "_state") else None
|
| 676 |
self._state = state
|
| 677 |
try:
|
|
|
|
| 689 |
return
|
| 690 |
|
| 691 |
if self._max_history_age_days > 0:
|
| 692 |
+
cutoff = (reference_time or _utc_now()) - timedelta(days=self._max_history_age_days)
|
|
|
|
|
|
|
| 693 |
filtered = [
|
| 694 |
item
|
| 695 |
for item in history
|
tests/test_proxy_savings_history.py
CHANGED
|
@@ -43,9 +43,7 @@ def _record_request(
|
|
| 43 |
def test_savings_tracker_helpers_normalize_inputs_and_paths(tmp_path, monkeypatch):
|
| 44 |
override_path = tmp_path / "custom-savings.json"
|
| 45 |
monkeypatch.setenv(HEADROOM_SAVINGS_PATH_ENV_VAR, str(override_path))
|
| 46 |
-
assert savings_tracker_module.get_default_savings_storage_path() == str(
|
| 47 |
-
override_path
|
| 48 |
-
)
|
| 49 |
|
| 50 |
monkeypatch.delenv(HEADROOM_SAVINGS_PATH_ENV_VAR, raising=False)
|
| 51 |
default_path = savings_tracker_module.get_default_savings_storage_path()
|
|
@@ -119,10 +117,7 @@ def test_savings_tracker_sanitizes_legacy_state_and_applies_retention(tmp_path):
|
|
| 119 |
"total_input_tokens": 0,
|
| 120 |
"total_input_cost_usd": 0.0,
|
| 121 |
}
|
| 122 |
-
assert (
|
| 123 |
-
snapshot["display_session"]
|
| 124 |
-
== savings_tracker_module._empty_display_session()
|
| 125 |
-
)
|
| 126 |
assert snapshot["history"] == [
|
| 127 |
{
|
| 128 |
"timestamp": "2026-03-27T09:00:00Z",
|
|
@@ -152,10 +147,7 @@ def test_non_dict_savings_state_resets_to_default(tmp_path):
|
|
| 152 |
"total_input_tokens": 0,
|
| 153 |
"total_input_cost_usd": 0.0,
|
| 154 |
}
|
| 155 |
-
assert (
|
| 156 |
-
snapshot["display_session"]
|
| 157 |
-
== savings_tracker_module._empty_display_session()
|
| 158 |
-
)
|
| 159 |
assert snapshot["history"] == []
|
| 160 |
|
| 161 |
|
|
@@ -254,9 +246,7 @@ def test_litellm_resolution_and_savings_estimation_fallbacks(monkeypatch):
|
|
| 254 |
) == pytest.approx(0.2)
|
| 255 |
|
| 256 |
fake_litellm.model_cost = {}
|
| 257 |
-
assert (
|
| 258 |
-
savings_tracker_module._estimate_compression_savings_usd("gpt-4o", 100) == 0.0
|
| 259 |
-
)
|
| 260 |
assert savings_tracker_module._estimate_input_cost_usd("gpt-4o", 100) == 0.0
|
| 261 |
|
| 262 |
monkeypatch.setattr(
|
|
@@ -264,19 +254,11 @@ def test_litellm_resolution_and_savings_estimation_fallbacks(monkeypatch):
|
|
| 264 |
"cost_per_token",
|
| 265 |
lambda **kwargs: (_ for _ in ()).throw(RuntimeError("boom")),
|
| 266 |
)
|
| 267 |
-
assert (
|
| 268 |
-
|
| 269 |
-
== "mystery-model"
|
| 270 |
-
)
|
| 271 |
-
assert (
|
| 272 |
-
savings_tracker_module._estimate_compression_savings_usd("mystery-model", 100)
|
| 273 |
-
== 0.0
|
| 274 |
-
)
|
| 275 |
|
| 276 |
monkeypatch.setattr(savings_tracker_module, "LITELLM_AVAILABLE", False)
|
| 277 |
-
assert (
|
| 278 |
-
savings_tracker_module._estimate_compression_savings_usd("gpt-4o", 100) == 0.0
|
| 279 |
-
)
|
| 280 |
assert savings_tracker_module._estimate_input_cost_usd("gpt-4o", 100) == 0.0
|
| 281 |
|
| 282 |
|
|
@@ -331,10 +313,7 @@ def test_display_session_rolls_after_inactivity_and_counts_zero_savings_requests
|
|
| 331 |
"_utc_now",
|
| 332 |
lambda: datetime(2026, 3, 27, 9, 45, tzinfo=timezone.utc),
|
| 333 |
)
|
| 334 |
-
assert (
|
| 335 |
-
tracker.snapshot()["display_session"]
|
| 336 |
-
== savings_tracker_module._empty_display_session()
|
| 337 |
-
)
|
| 338 |
|
| 339 |
tracker.record_request(
|
| 340 |
model="gpt-4o",
|
|
@@ -362,9 +341,7 @@ def test_display_session_rolls_after_inactivity_and_counts_zero_savings_requests
|
|
| 362 |
}
|
| 363 |
|
| 364 |
|
| 365 |
-
def test_savings_tracker_rollups_preserve_spend_and_input_history(
|
| 366 |
-
tmp_path, monkeypatch
|
| 367 |
-
):
|
| 368 |
path = tmp_path / "proxy_savings.json"
|
| 369 |
tracker = SavingsTracker(
|
| 370 |
path=str(path),
|
|
@@ -507,9 +484,7 @@ def test_savings_tracker_rollups_preserve_spend_and_input_history(
|
|
| 507 |
]
|
| 508 |
|
| 509 |
|
| 510 |
-
def test_stats_history_persists_across_restarts_and_stats_stays_compatible(
|
| 511 |
-
tmp_path, monkeypatch
|
| 512 |
-
):
|
| 513 |
savings_path = tmp_path / "proxy_savings.json"
|
| 514 |
monkeypatch.setenv("HEADROOM_SAVINGS_PATH", str(savings_path))
|
| 515 |
monkeypatch.setattr(
|
|
@@ -555,14 +530,13 @@ def test_stats_history_persists_across_restarts_and_stats_stays_compatible(
|
|
| 555 |
]
|
| 556 |
assert history_data["exports"]["available_series"][-2:] == ["weekly", "monthly"]
|
| 557 |
assert history_data["series"]["hourly"][0]["total_input_tokens_delta"] == 120
|
| 558 |
-
assert history_data["series"]["hourly"][0][
|
| 559 |
-
|
| 560 |
-
|
| 561 |
|
| 562 |
assert stats_data["display_session"] == history_data["display_session"]
|
| 563 |
assert (
|
| 564 |
-
stats_data["persistent_savings"]["display_session"]
|
| 565 |
-
== history_data["display_session"]
|
| 566 |
)
|
| 567 |
|
| 568 |
with TestClient(create_app(config)) as client:
|
|
@@ -584,9 +558,7 @@ def test_stats_history_persists_across_restarts_and_stats_stays_compatible(
|
|
| 584 |
assert updated["display_session"]["total_input_tokens"] == 240
|
| 585 |
assert updated["display_session"]["savings_percent"] == pytest.approx(18.64)
|
| 586 |
assert updated["series"]["daily"][0]["total_input_tokens_delta"] == 240
|
| 587 |
-
assert updated["series"]["daily"][0][
|
| 588 |
-
"total_input_cost_usd_delta"
|
| 589 |
-
] == pytest.approx(0.48)
|
| 590 |
|
| 591 |
persisted = json.loads(savings_path.read_text())
|
| 592 |
assert persisted["lifetime"]["tokens_saved"] == 55
|
|
|
|
| 43 |
def test_savings_tracker_helpers_normalize_inputs_and_paths(tmp_path, monkeypatch):
|
| 44 |
override_path = tmp_path / "custom-savings.json"
|
| 45 |
monkeypatch.setenv(HEADROOM_SAVINGS_PATH_ENV_VAR, str(override_path))
|
| 46 |
+
assert savings_tracker_module.get_default_savings_storage_path() == str(override_path)
|
|
|
|
|
|
|
| 47 |
|
| 48 |
monkeypatch.delenv(HEADROOM_SAVINGS_PATH_ENV_VAR, raising=False)
|
| 49 |
default_path = savings_tracker_module.get_default_savings_storage_path()
|
|
|
|
| 117 |
"total_input_tokens": 0,
|
| 118 |
"total_input_cost_usd": 0.0,
|
| 119 |
}
|
| 120 |
+
assert snapshot["display_session"] == savings_tracker_module._empty_display_session()
|
|
|
|
|
|
|
|
|
|
| 121 |
assert snapshot["history"] == [
|
| 122 |
{
|
| 123 |
"timestamp": "2026-03-27T09:00:00Z",
|
|
|
|
| 147 |
"total_input_tokens": 0,
|
| 148 |
"total_input_cost_usd": 0.0,
|
| 149 |
}
|
| 150 |
+
assert snapshot["display_session"] == savings_tracker_module._empty_display_session()
|
|
|
|
|
|
|
|
|
|
| 151 |
assert snapshot["history"] == []
|
| 152 |
|
| 153 |
|
|
|
|
| 246 |
) == pytest.approx(0.2)
|
| 247 |
|
| 248 |
fake_litellm.model_cost = {}
|
| 249 |
+
assert savings_tracker_module._estimate_compression_savings_usd("gpt-4o", 100) == 0.0
|
|
|
|
|
|
|
| 250 |
assert savings_tracker_module._estimate_input_cost_usd("gpt-4o", 100) == 0.0
|
| 251 |
|
| 252 |
monkeypatch.setattr(
|
|
|
|
| 254 |
"cost_per_token",
|
| 255 |
lambda **kwargs: (_ for _ in ()).throw(RuntimeError("boom")),
|
| 256 |
)
|
| 257 |
+
assert savings_tracker_module._resolve_litellm_model("mystery-model") == "mystery-model"
|
| 258 |
+
assert savings_tracker_module._estimate_compression_savings_usd("mystery-model", 100) == 0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
|
| 260 |
monkeypatch.setattr(savings_tracker_module, "LITELLM_AVAILABLE", False)
|
| 261 |
+
assert savings_tracker_module._estimate_compression_savings_usd("gpt-4o", 100) == 0.0
|
|
|
|
|
|
|
| 262 |
assert savings_tracker_module._estimate_input_cost_usd("gpt-4o", 100) == 0.0
|
| 263 |
|
| 264 |
|
|
|
|
| 313 |
"_utc_now",
|
| 314 |
lambda: datetime(2026, 3, 27, 9, 45, tzinfo=timezone.utc),
|
| 315 |
)
|
| 316 |
+
assert tracker.snapshot()["display_session"] == savings_tracker_module._empty_display_session()
|
|
|
|
|
|
|
|
|
|
| 317 |
|
| 318 |
tracker.record_request(
|
| 319 |
model="gpt-4o",
|
|
|
|
| 341 |
}
|
| 342 |
|
| 343 |
|
| 344 |
+
def test_savings_tracker_rollups_preserve_spend_and_input_history(tmp_path, monkeypatch):
|
|
|
|
|
|
|
| 345 |
path = tmp_path / "proxy_savings.json"
|
| 346 |
tracker = SavingsTracker(
|
| 347 |
path=str(path),
|
|
|
|
| 484 |
]
|
| 485 |
|
| 486 |
|
| 487 |
+
def test_stats_history_persists_across_restarts_and_stats_stays_compatible(tmp_path, monkeypatch):
|
|
|
|
|
|
|
| 488 |
savings_path = tmp_path / "proxy_savings.json"
|
| 489 |
monkeypatch.setenv("HEADROOM_SAVINGS_PATH", str(savings_path))
|
| 490 |
monkeypatch.setattr(
|
|
|
|
| 530 |
]
|
| 531 |
assert history_data["exports"]["available_series"][-2:] == ["weekly", "monthly"]
|
| 532 |
assert history_data["series"]["hourly"][0]["total_input_tokens_delta"] == 120
|
| 533 |
+
assert history_data["series"]["hourly"][0]["total_input_cost_usd_delta"] == pytest.approx(
|
| 534 |
+
0.24
|
| 535 |
+
)
|
| 536 |
|
| 537 |
assert stats_data["display_session"] == history_data["display_session"]
|
| 538 |
assert (
|
| 539 |
+
stats_data["persistent_savings"]["display_session"] == history_data["display_session"]
|
|
|
|
| 540 |
)
|
| 541 |
|
| 542 |
with TestClient(create_app(config)) as client:
|
|
|
|
| 558 |
assert updated["display_session"]["total_input_tokens"] == 240
|
| 559 |
assert updated["display_session"]["savings_percent"] == pytest.approx(18.64)
|
| 560 |
assert updated["series"]["daily"][0]["total_input_tokens_delta"] == 240
|
| 561 |
+
assert updated["series"]["daily"][0]["total_input_cost_usd_delta"] == pytest.approx(0.48)
|
|
|
|
|
|
|
| 562 |
|
| 563 |
persisted = json.loads(savings_path.read_text())
|
| 564 |
assert persisted["lifetime"]["tokens_saved"] == 55
|