| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132 |
- """LLM billing query service for the admin dashboard."""
- from __future__ import annotations
- from datetime import datetime, timedelta
- from typing import Any
- from zoneinfo import ZoneInfo
- from supply_infra.db.repositories.llm_usage_repo import LlmUsageRepository
- from supply_infra.db.session import get_session
- _SHANGHAI = ZoneInfo("Asia/Shanghai")
- def _today() -> datetime:
- return datetime.now(_SHANGHAI)
- def _biz_dt(day: datetime) -> str:
- return day.strftime("%Y%m%d")
- def _shift_biz_dt(biz_dt: str, days: int) -> str:
- base = datetime.strptime(biz_dt, "%Y%m%d").replace(tzinfo=_SHANGHAI)
- return _biz_dt(base + timedelta(days=days))
- def _format_biz_dt_label(biz_dt: str) -> str:
- return f"{biz_dt[0:4]}-{biz_dt[4:6]}-{biz_dt[6:8]}"
- def _serialize_event(row: Any) -> dict[str, Any]:
- return {
- "id": row.id,
- "biz_dt": row.biz_dt,
- "provider": row.provider,
- "model": row.model,
- "agent_name": row.agent_name,
- "run_id": row.run_id,
- "iteration": row.iteration,
- "prompt_tokens": row.prompt_tokens,
- "completion_tokens": row.completion_tokens,
- "total_tokens": row.total_tokens,
- "cost_usd": float(row.cost_usd) if row.cost_usd is not None else None,
- "created_at": row.created_at.isoformat(sep=" ", timespec="seconds")
- if row.created_at
- else None,
- }
- def get_billing_overview(
- *,
- start_dt: str | None = None,
- end_dt: str | None = None,
- recent_limit: int = 50,
- ) -> dict[str, Any]:
- """Aggregate LLM cost for a date range with agent/model breakdowns."""
- today = _today()
- today_dt = _biz_dt(today)
- end = end_dt or today_dt
- start = start_dt or _shift_biz_dt(end, -29)
- if start > end:
- start, end = end, start
- yesterday_dt = _shift_biz_dt(today_dt, -1)
- week_start = _shift_biz_dt(today_dt, -6)
- with get_session() as session:
- repo = LlmUsageRepository(session)
- daily = repo.summarize_daily(start_dt=start, end_dt=end)
- by_agent = repo.breakdown_by_agent(start_dt=start, end_dt=end)
- by_model = repo.breakdown_by_model(start_dt=start, end_dt=end)
- recent = repo.list_recent_events(start_dt=start, end_dt=end, limit=recent_limit)
- # Fill missing days so charts are continuous.
- daily_map = {item["biz_dt"]: item for item in daily}
- filled_daily: list[dict[str, Any]] = []
- cursor = start
- while cursor <= end:
- item = daily_map.get(
- cursor,
- {
- "biz_dt": cursor,
- "call_count": 0,
- "prompt_tokens": 0,
- "completion_tokens": 0,
- "total_tokens": 0,
- "total_cost_usd": 0.0,
- "missing_cost_count": 0,
- },
- )
- filled_daily.append({**item, "label": _format_biz_dt_label(cursor)})
- cursor = _shift_biz_dt(cursor, 1)
- range_cost = sum(item["total_cost_usd"] for item in filled_daily)
- range_calls = sum(item["call_count"] for item in filled_daily)
- range_tokens = sum(item["total_tokens"] for item in filled_daily)
- today_item = daily_map.get(today_dt)
- yesterday_item = daily_map.get(yesterday_dt)
- week_cost = sum(
- item["total_cost_usd"]
- for item in filled_daily
- if week_start <= item["biz_dt"] <= today_dt
- )
- return {
- "timezone": "Asia/Shanghai",
- "currency": "USD",
- "range": {
- "start_dt": start,
- "end_dt": end,
- "total_cost_usd": range_cost,
- "call_count": range_calls,
- "total_tokens": range_tokens,
- },
- "summary": {
- "today_biz_dt": today_dt,
- "today_cost_usd": float(today_item["total_cost_usd"]) if today_item else 0.0,
- "today_call_count": int(today_item["call_count"]) if today_item else 0,
- "yesterday_cost_usd": float(yesterday_item["total_cost_usd"])
- if yesterday_item
- else 0.0,
- "week_cost_usd": week_cost,
- "range_cost_usd": range_cost,
- },
- "daily": filled_daily,
- "by_agent": by_agent,
- "by_model": by_model,
- "recent_events": [_serialize_event(row) for row in recent],
- }
|