관리자 사용량 근거를 정교화

This commit is contained in:
Yun Chan 2026-08-29 23:59:17 +09:00
parent ccdcfcd2f5
commit 707dba4f8f
10 changed files with 1136 additions and 209 deletions

View file

@ -5,7 +5,7 @@ from __future__ import annotations
import time
from datetime import datetime, timezone
from decimal import Decimal
from typing import Annotated, Literal, cast
from typing import Annotated, Iterable, Literal, cast
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, Query, status
@ -50,11 +50,14 @@ router = APIRouter(prefix="/admin", tags=["admin"])
AdminPrincipal = Annotated[Principal, Depends(require_admin_access())]
HealthStatus = Literal["ok", "degraded", "down"]
UsageBudgetStatus = Literal["disabled", "ok", "warn", "exceeded"]
UsageBudgetStatus = Literal["disabled", "ok", "warn", "exceeded", "indeterminate"]
UsageCostBasis = Literal[
"provider_estimate",
"provider_reported",
"reference_rate",
"reference_upper_bound",
"partial",
"partial_upper_bound",
"unavailable",
]
TicketCategory = Literal[
@ -69,8 +72,21 @@ TicketPriority = Literal["low", "normal", "high", "urgent"]
TicketStatus = Literal["open", "triaged", "in_progress", "resolved", "closed"]
NotificationDeliveryStatus = Literal["queued", "sending", "sent", "failed", "skipped"]
METERED_CLIENT_TURN_FILTER_SQL = """
SYNTHETIC_USAGE_SIGNATURES = frozenset({("e2e", "fake-client", 1, 1, 0.0)})
REPORTABLE_CLIENT_TURN_FILTER_SQL = """
speaker = 'client'
AND NOT (
LOWER(BTRIM(COALESCE(llm_provider, ''))) = 'e2e'
AND LOWER(BTRIM(COALESCE(model, ''))) = 'fake-client'
AND COALESCE(tokens_in, 0) = 1
AND COALESCE(tokens_out, 0) = 1
AND COALESCE(cost_usd, 0) = 0
)
"""
METERED_CLIENT_TURN_FILTER_SQL = f"""
{REPORTABLE_CLIENT_TURN_FILTER_SQL}
AND (
llm_provider IS NOT NULL OR model IS NOT NULL
OR tokens_in IS NOT NULL OR tokens_out IS NOT NULL
@ -78,28 +94,6 @@ METERED_CLIENT_TURN_FILTER_SQL = """
)
"""
USAGE_AGGREGATE_COLUMNS_SQL = """
COUNT(*) AS turns,
COUNT(*) FILTER (
WHERE COALESCE(tokens_in, 0) > 0 OR COALESCE(tokens_out, 0) > 0
) AS token_metered_turns,
COUNT(*) FILTER (
WHERE COALESCE(tokens_in, 0) <= 0 AND COALESCE(tokens_out, 0) <= 0
) AS token_unmetered_turns,
COALESCE(SUM(tokens_in), 0)::bigint AS tokens_in,
COALESCE(SUM(tokens_out), 0)::bigint AS tokens_out,
COALESCE(SUM(cost_usd), 0)::numeric AS cost_usd,
COUNT(*) FILTER (WHERE COALESCE(cost_usd, 0) <= 0) AS unpriced_turns,
COALESCE(
SUM(tokens_in) FILTER (WHERE COALESCE(cost_usd, 0) <= 0),
0
)::bigint AS unpriced_tokens_in,
COALESCE(
SUM(tokens_out) FILTER (WHERE COALESCE(cost_usd, 0) <= 0),
0
)::bigint AS unpriced_tokens_out
"""
SUPPORT_TICKET_DETAIL_FROM_SQL = """
SELECT
t.id,
@ -187,6 +181,7 @@ class AdminUsageDailyCost(BaseModel):
tokens_in: int
tokens_out: int
cost_usd: float
cost_basis: UsageCostBasis = "provider_reported"
class AdminUsageBudget(BaseModel):
@ -194,6 +189,7 @@ class AdminUsageBudget(BaseModel):
used_ratio: float
remaining_usd: float | None
status: UsageBudgetStatus
cost_basis: UsageCostBasis = "provider_reported"
class AdminUsageEvaluatorCache(BaseModel):
@ -221,6 +217,7 @@ class AdminUsageResponse(BaseModel):
cost_usd: float
recorded_cost_usd: float = 0.0
estimated_cost_usd: float = 0.0
cost_basis: UsageCostBasis = "provider_reported"
budget: AdminUsageBudget
evaluator_cache: AdminUsageEvaluatorCache
by_provider: list[AdminUsageBreakdown]
@ -458,6 +455,23 @@ def _safe_usage_int(value: object) -> int:
return 0
def _is_reportable_usage(
provider: str,
model: str,
tokens_in: int,
tokens_out: int,
cost_usd: float,
) -> bool:
signature = (
provider.strip().lower(),
model.strip().lower(),
max(0, tokens_in),
max(0, tokens_out),
max(0.0, cost_usd),
)
return signature not in SYNTHETIC_USAGE_SIGNATURES
def _usage_breakdown(
*,
provider: str,
@ -470,31 +484,41 @@ def _usage_breakdown(
stored_cost_usd: float,
unpriced_tokens_in: int,
unpriced_tokens_out: int,
priced_at: datetime | int | float | str,
) -> AdminUsageBreakdown:
"""저장된 공급자 비용 추정치와 공식 참조단가를 한 원장 행으로 정규화한다."""
reference_basis = provider_uses_reference_cost(provider)
fallback = estimate_reference_cost(
provider=provider,
model=model,
tokens_in=unpriced_tokens_in,
tokens_out=unpriced_tokens_out,
priced_at=priced_at,
)
rate_info = fallback or estimate_reference_cost(
provider=provider,
model=model,
tokens_in=tokens_in,
tokens_out=tokens_out,
)
rate_info = fallback
if rate_info is None and not (reference_basis and stored_cost_usd > 0):
rate_info = estimate_reference_cost(
provider=provider,
model=model,
tokens_in=tokens_in,
tokens_out=tokens_out,
priced_at=priced_at,
)
fallback_cost = fallback.cost_usd if fallback is not None else 0.0
effective_cost = max(0.0, stored_cost_usd) + fallback_cost
reference_basis = provider_uses_reference_cost(provider)
if reference_basis:
recorded_cost = 0.0
estimated_cost = effective_cost
basis: UsageCostBasis = (
"reference_rate" if rate_info is not None or effective_cost > 0 else "unavailable"
)
if fallback is not None and stored_cost_usd <= 0:
basis: UsageCostBasis = "reference_upper_bound"
else:
basis = (
"reference_rate"
if rate_info is not None or effective_cost > 0
else "unavailable"
)
else:
recorded_cost = max(0.0, stored_cost_usd)
estimated_cost = fallback_cost
@ -505,6 +529,17 @@ def _usage_breakdown(
else:
basis = "unavailable"
rate_label = rate_info.rate_label if rate_info is not None else None
rate_source_url = rate_info.source_url if rate_info is not None else None
if reference_basis and stored_cost_usd > 0:
if fallback is not None:
rate_label = "호출 시점 저장 추정값 + 미저장분 공식 참조단가 합산"
else:
rate_label = "호출 시점에 저장된 참조단가 추정값"
rate_source_url = None
elif fallback is not None:
rate_label = f"{fallback.rate_label} · 캐시 미보존 과거행은 전체 입력 기준"
return AdminUsageBreakdown(
provider=provider,
model=model,
@ -513,16 +548,120 @@ def _usage_breakdown(
token_unmetered_turns=max(0, token_unmetered_turns),
tokens_in=max(0, tokens_in),
tokens_out=max(0, tokens_out),
cost_usd=round(effective_cost, 6),
recorded_cost_usd=round(recorded_cost, 6),
estimated_cost_usd=round(estimated_cost, 6),
cost_usd=effective_cost,
recorded_cost_usd=recorded_cost,
estimated_cost_usd=estimated_cost,
cost_basis=basis,
rate_label=rate_info.rate_label if rate_info is not None else None,
rate_source_url=rate_info.source_url if rate_info is not None else None,
rate_label=rate_label,
rate_source_url=rate_source_url,
)
def _usage_budget(cost_usd: float) -> AdminUsageBudget:
def _merge_usage_breakdowns(
breakdowns: Iterable[AdminUsageBreakdown],
) -> list[AdminUsageBreakdown]:
grouped: dict[tuple[str, str], list[AdminUsageBreakdown]] = {}
for item in breakdowns:
grouped.setdefault((item.provider, item.model), []).append(item)
merged: list[AdminUsageBreakdown] = []
for (provider, model), items in grouped.items():
basis = _aggregate_cost_basis(item.cost_basis for item in items)
labels = {item.rate_label for item in items if item.rate_label}
source_urls = {item.rate_source_url for item in items if item.rate_source_url}
rate_label = next(iter(labels)) if len(labels) == 1 else None
if basis == "partial":
rate_label = "일부 호출 미산정 · 표시액은 산정 가능분 합계"
elif basis == "partial_upper_bound":
rate_label = "일부 호출 미산정 · 산정된 부분도 상한 추정"
elif len(labels) > 1:
rate_label = (
"기간별 공식 참조단가 상한 합산"
if basis == "reference_upper_bound"
else "기간별 공식 참조단가 합산"
)
merged.append(
AdminUsageBreakdown(
provider=provider,
model=model,
turns=sum(item.turns for item in items),
token_metered_turns=sum(item.token_metered_turns for item in items),
token_unmetered_turns=sum(item.token_unmetered_turns for item in items),
tokens_in=sum(item.tokens_in for item in items),
tokens_out=sum(item.tokens_out for item in items),
cost_usd=round(sum(item.cost_usd for item in items), 6),
recorded_cost_usd=round(
sum(item.recorded_cost_usd for item in items), 6
),
estimated_cost_usd=round(
sum(item.estimated_cost_usd for item in items), 6
),
cost_basis=cast(UsageCostBasis, basis),
rate_label=rate_label,
rate_source_url=(
next(iter(source_urls)) if len(source_urls) == 1 else None
),
)
)
return merged
def _aggregate_cost_basis(
bases: Iterable[UsageCostBasis],
) -> UsageCostBasis:
basis_set = set(bases)
if not basis_set:
return "provider_reported"
has_missing = bool(
basis_set & {"unavailable", "partial", "partial_upper_bound"}
)
has_upper_bound = bool(
basis_set & {"reference_upper_bound", "partial_upper_bound"}
)
has_known_amount = basis_set != {"unavailable"}
if has_missing:
if not has_known_amount:
return "unavailable"
return "partial_upper_bound" if has_upper_bound else "partial"
if has_upper_bound:
return "reference_upper_bound"
for candidate in (
"provider_estimate",
"provider_reported",
"reference_rate",
):
if candidate in basis_set:
return cast(UsageCostBasis, candidate)
return "unavailable"
def _scale_usage_breakdown(
breakdown: AdminUsageBreakdown,
multiplier: int,
) -> AdminUsageBreakdown:
count = max(0, multiplier)
return AdminUsageBreakdown(
provider=breakdown.provider,
model=breakdown.model,
turns=breakdown.turns * count,
token_metered_turns=breakdown.token_metered_turns * count,
token_unmetered_turns=breakdown.token_unmetered_turns * count,
tokens_in=breakdown.tokens_in * count,
tokens_out=breakdown.tokens_out * count,
cost_usd=breakdown.cost_usd * count,
recorded_cost_usd=breakdown.recorded_cost_usd * count,
estimated_cost_usd=breakdown.estimated_cost_usd * count,
cost_basis=breakdown.cost_basis,
rate_label=breakdown.rate_label,
rate_source_url=breakdown.rate_source_url,
)
def _usage_budget(
cost_usd: float,
cost_basis: UsageCostBasis,
) -> AdminUsageBudget:
limit = max(0.0, float(settings.admin_usage_budget_usd or 0.0))
if limit <= 0:
return AdminUsageBudget(
@ -530,18 +669,35 @@ def _usage_budget(cost_usd: float) -> AdminUsageBudget:
used_ratio=0.0,
remaining_usd=None,
status="disabled",
cost_basis=cost_basis,
)
used_ratio = max(0.0, cost_usd / limit)
status_value: UsageBudgetStatus = "ok"
if used_ratio >= 1.0:
remaining_usd: float | None = round(max(0.0, limit - cost_usd), 6)
status_value: UsageBudgetStatus
if cost_basis in {"partial_upper_bound", "unavailable"}:
status_value = "indeterminate"
remaining_usd = None
elif cost_basis == "partial":
if used_ratio >= 1.0:
status_value = "exceeded"
elif used_ratio >= 0.8:
status_value = "warn"
else:
status_value = "indeterminate"
elif cost_basis == "reference_upper_bound":
status_value = "ok" if used_ratio < 0.8 else "indeterminate"
elif used_ratio >= 1.0:
status_value = "exceeded"
elif used_ratio >= 0.8:
status_value = "warn"
else:
status_value = "ok"
return AdminUsageBudget(
limit_usd=round(limit, 6),
used_ratio=round(used_ratio, 4),
remaining_usd=round(max(0.0, limit - cost_usd), 6),
remaining_usd=remaining_usd,
status=status_value,
cost_basis=cost_basis,
)
@ -628,7 +784,7 @@ async def _usage_from_database(window_days: int) -> AdminUsageResponse:
total_row = await conn.fetchrow(
f"""
SELECT
COUNT(*) FILTER (WHERE speaker = 'client') AS total_turns,
COUNT(*) FILTER (WHERE {REPORTABLE_CLIENT_TURN_FILTER_SQL}) AS total_turns,
COUNT(*) FILTER (WHERE {METERED_CLIENT_TURN_FILTER_SQL}) AS metered_turns,
COUNT(*) FILTER (
WHERE {METERED_CLIENT_TURN_FILTER_SQL}
@ -639,58 +795,69 @@ async def _usage_from_database(window_days: int) -> AdminUsageResponse:
AND COALESCE(tokens_in, 0) <= 0
AND COALESCE(tokens_out, 0) <= 0
) AS token_unmetered_turns,
COALESCE(SUM(tokens_in) FILTER (WHERE speaker = 'client'), 0)::bigint AS tokens_in,
COALESCE(SUM(tokens_out) FILTER (WHERE speaker = 'client'), 0)::bigint AS tokens_out,
COALESCE(SUM(cost_usd) FILTER (WHERE speaker = 'client'), 0)::numeric AS cost_usd
COALESCE(
SUM(tokens_in) FILTER (WHERE {REPORTABLE_CLIENT_TURN_FILTER_SQL}), 0
)::bigint AS tokens_in,
COALESCE(
SUM(tokens_out) FILTER (WHERE {REPORTABLE_CLIENT_TURN_FILTER_SQL}), 0
)::bigint AS tokens_out,
COALESCE(
SUM(cost_usd) FILTER (WHERE {REPORTABLE_CLIENT_TURN_FILTER_SQL}), 0
)::numeric AS cost_usd
FROM app.turns
WHERE created_at >= now() - ($1::int * interval '1 day')
AND speaker = 'client'
""",
window_days,
)
rows = await conn.fetch(
usage_rows = await conn.fetch(
f"""
SELECT
to_char(
date_trunc('day', created_at AT TIME ZONE 'UTC'),
'YYYY-MM-DD'
) AS day,
COALESCE(llm_provider, 'unknown') AS provider,
COALESCE(model, 'unknown') AS model,
{USAGE_AGGREGATE_COLUMNS_SQL}
COALESCE(tokens_in, 0)::bigint AS tokens_in,
COALESCE(tokens_out, 0)::bigint AS tokens_out,
COALESCE(cost_usd, 0)::numeric AS cost_usd,
COUNT(*)::bigint AS matching_turns
FROM app.turns
WHERE created_at >= now() - ($1::int * interval '1 day')
AND {METERED_CLIENT_TURN_FILTER_SQL}
GROUP BY 1, 2
""",
window_days,
)
daily_rows = await conn.fetch(
f"""
SELECT
to_char(date_trunc('day', created_at), 'YYYY-MM-DD') AS day,
COALESCE(llm_provider, 'unknown') AS provider,
COALESCE(model, 'unknown') AS model,
{USAGE_AGGREGATE_COLUMNS_SQL}
FROM app.turns
WHERE created_at >= now() - ($1::int * interval '1 day')
AND {METERED_CLIENT_TURN_FILTER_SQL}
GROUP BY 1, 2, 3
ORDER BY 1, 2, 3
GROUP BY 1, 2, 3, 4, 5, 6
""",
window_days,
)
all_breakdowns = [
_usage_breakdown(
provider=str(row["provider"] or "unknown"),
model=str(row["model"] or "unknown"),
turns=_safe_usage_int(row["turns"]),
token_metered_turns=_safe_usage_int(row["token_metered_turns"]),
token_unmetered_turns=_safe_usage_int(row["token_unmetered_turns"]),
tokens_in=_safe_usage_int(row["tokens_in"]),
tokens_out=_safe_usage_int(row["tokens_out"]),
stored_cost_usd=_decimal_to_float(row["cost_usd"]),
unpriced_tokens_in=_safe_usage_int(row["unpriced_tokens_in"]),
unpriced_tokens_out=_safe_usage_int(row["unpriced_tokens_out"]),
# 요청별 입력 크기로 단가 구간이 갈리는 모델이 있어 동일 계량 signature만 묶는다.
dated_breakdowns: list[AdminUsageBreakdown] = []
for row in usage_rows:
tokens_in = _safe_usage_int(row["tokens_in"])
tokens_out = _safe_usage_int(row["tokens_out"])
stored_cost_usd = _decimal_to_float(row["cost_usd"])
is_token_metered = tokens_in > 0 or tokens_out > 0
matching_turns = max(1, _safe_usage_int(row["matching_turns"]))
dated_breakdowns.append(
_scale_usage_breakdown(
_usage_breakdown(
provider=str(row["provider"] or "unknown"),
model=str(row["model"] or "unknown"),
turns=1,
token_metered_turns=1 if is_token_metered else 0,
token_unmetered_turns=0 if is_token_metered else 1,
tokens_in=tokens_in,
tokens_out=tokens_out,
stored_cost_usd=stored_cost_usd,
unpriced_tokens_in=tokens_in if stored_cost_usd <= 0 else 0,
unpriced_tokens_out=tokens_out if stored_cost_usd <= 0 else 0,
priced_at=str(row["day"]),
),
matching_turns,
)
)
for row in rows
]
all_breakdowns = _merge_usage_breakdowns(dated_breakdowns)
all_breakdowns.sort(
key=lambda item: (
-item.cost_usd,
@ -700,33 +867,32 @@ async def _usage_from_database(window_days: int) -> AdminUsageResponse:
item.model,
)
)
recorded_cost = round(sum(item.recorded_cost_usd for item in all_breakdowns), 6)
estimated_cost = round(sum(item.estimated_cost_usd for item in all_breakdowns), 6)
recorded_cost = round(sum(item.recorded_cost_usd for item in dated_breakdowns), 6)
estimated_cost = round(sum(item.estimated_cost_usd for item in dated_breakdowns), 6)
total_cost = round(recorded_cost + estimated_cost, 6)
total_cost_basis = _aggregate_cost_basis(
item.cost_basis for item in dated_breakdowns
)
daily_buckets: dict[str, dict[str, int | float]] = {}
for row in daily_rows:
breakdown = _usage_breakdown(
provider=str(row["provider"] or "unknown"),
model=str(row["model"] or "unknown"),
turns=_safe_usage_int(row["turns"]),
token_metered_turns=_safe_usage_int(row["token_metered_turns"]),
token_unmetered_turns=_safe_usage_int(row["token_unmetered_turns"]),
tokens_in=_safe_usage_int(row["tokens_in"]),
tokens_out=_safe_usage_int(row["tokens_out"]),
stored_cost_usd=_decimal_to_float(row["cost_usd"]),
unpriced_tokens_in=_safe_usage_int(row["unpriced_tokens_in"]),
unpriced_tokens_out=_safe_usage_int(row["unpriced_tokens_out"]),
)
daily_buckets: dict[str, dict[str, int | float | list[UsageCostBasis]]] = {}
for row, breakdown in zip(usage_rows, dated_breakdowns, strict=True):
day = str(row["day"])
bucket = daily_buckets.setdefault(
day,
{"turns": 0, "tokens_in": 0, "tokens_out": 0, "cost_usd": 0.0},
{
"turns": 0,
"tokens_in": 0,
"tokens_out": 0,
"cost_usd": 0.0,
"cost_bases": [],
},
)
bucket["turns"] = int(bucket["turns"]) + breakdown.turns
bucket["tokens_in"] = int(bucket["tokens_in"]) + breakdown.tokens_in
bucket["tokens_out"] = int(bucket["tokens_out"]) + breakdown.tokens_out
bucket["cost_usd"] = float(bucket["cost_usd"]) + breakdown.cost_usd
cost_bases = cast(list[UsageCostBasis], bucket["cost_bases"])
cost_bases.append(breakdown.cost_basis)
return AdminUsageResponse(
source="database",
@ -746,7 +912,8 @@ async def _usage_from_database(window_days: int) -> AdminUsageResponse:
cost_usd=total_cost,
recorded_cost_usd=recorded_cost,
estimated_cost_usd=estimated_cost,
budget=_usage_budget(total_cost),
cost_basis=total_cost_basis,
budget=_usage_budget(total_cost, total_cost_basis),
evaluator_cache=_usage_evaluator_cache(),
by_provider=all_breakdowns[:12],
daily_cost=[
@ -756,6 +923,9 @@ async def _usage_from_database(window_days: int) -> AdminUsageResponse:
tokens_in=int(values["tokens_in"]),
tokens_out=int(values["tokens_out"]),
cost_usd=round(float(values["cost_usd"]), 6),
cost_basis=_aggregate_cost_basis(
cast(list[UsageCostBasis], values["cost_bases"])
),
)
for day, values in sorted(daily_buckets.items())
],
@ -1038,8 +1208,8 @@ def _usage_from_runtime_store(window_days: int) -> AdminUsageResponse:
cost_usd = 0.0
recorded_cost_usd = 0.0
estimated_cost_usd = 0.0
buckets: dict[tuple[str, str], dict[str, int | float]] = {}
daily_buckets: dict[str, dict[str, int | float]] = {}
turn_breakdowns: list[AdminUsageBreakdown] = []
daily_buckets: dict[str, dict[str, int | float | list[UsageCostBasis]]] = {}
for sess in store.list():
for turn in getattr(sess, "turns", []) or []:
@ -1048,12 +1218,20 @@ def _usage_from_runtime_store(window_days: int) -> AdminUsageResponse:
created_at = float(getattr(turn, "created_at", 0.0) or 0.0)
if created_at < window_start:
continue
total_turns += 1
provider = str(getattr(turn, "llm_provider", None) or "unknown")
model = str(getattr(turn, "model", None) or "unknown")
turn_tokens_in = _safe_usage_int(getattr(turn, "tokens_in", 0))
turn_tokens_out = _safe_usage_int(getattr(turn, "tokens_out", 0))
turn_cost = _decimal_to_float(getattr(turn, "cost_usd", 0.0))
if not _is_reportable_usage(
provider,
model,
turn_tokens_in,
turn_tokens_out,
turn_cost,
):
continue
total_turns += 1
is_metered = (
provider != "unknown"
or model != "unknown"
@ -1082,43 +1260,22 @@ def _usage_from_runtime_store(window_days: int) -> AdminUsageResponse:
stored_cost_usd=turn_cost,
unpriced_tokens_in=turn_tokens_in if turn_cost <= 0 else 0,
unpriced_tokens_out=turn_tokens_out if turn_cost <= 0 else 0,
priced_at=created_at,
)
turn_breakdowns.append(turn_breakdown)
cost_usd += turn_breakdown.cost_usd
recorded_cost_usd += turn_breakdown.recorded_cost_usd
estimated_cost_usd += turn_breakdown.estimated_cost_usd
key = (provider, model)
bucket = buckets.setdefault(
key,
{
"turns": 0,
"token_metered_turns": 0,
"token_unmetered_turns": 0,
"tokens_in": 0,
"tokens_out": 0,
"stored_cost_usd": 0.0,
"unpriced_tokens_in": 0,
"unpriced_tokens_out": 0,
},
)
bucket["turns"] = int(bucket["turns"]) + 1
if is_token_metered:
bucket["token_metered_turns"] = int(bucket["token_metered_turns"]) + 1
else:
bucket["token_unmetered_turns"] = int(bucket["token_unmetered_turns"]) + 1
bucket["tokens_in"] = int(bucket["tokens_in"]) + turn_tokens_in
bucket["tokens_out"] = int(bucket["tokens_out"]) + turn_tokens_out
bucket["stored_cost_usd"] = float(bucket["stored_cost_usd"]) + turn_cost
if turn_cost <= 0:
bucket["unpriced_tokens_in"] = (
int(bucket["unpriced_tokens_in"]) + turn_tokens_in
)
bucket["unpriced_tokens_out"] = (
int(bucket["unpriced_tokens_out"]) + turn_tokens_out
)
day = datetime.fromtimestamp(created_at, timezone.utc).strftime("%Y-%m-%d")
daily_bucket = daily_buckets.setdefault(
day,
{"turns": 0, "tokens_in": 0, "tokens_out": 0, "cost_usd": 0.0},
{
"turns": 0,
"tokens_in": 0,
"tokens_out": 0,
"cost_usd": 0.0,
"cost_bases": [],
},
)
daily_bucket["turns"] = int(daily_bucket["turns"]) + 1
daily_bucket["tokens_in"] = int(daily_bucket["tokens_in"]) + turn_tokens_in
@ -1126,22 +1283,12 @@ def _usage_from_runtime_store(window_days: int) -> AdminUsageResponse:
daily_bucket["cost_usd"] = (
float(daily_bucket["cost_usd"]) + turn_breakdown.cost_usd
)
daily_cost_bases = cast(
list[UsageCostBasis], daily_bucket["cost_bases"]
)
daily_cost_bases.append(turn_breakdown.cost_basis)
by_provider = [
_usage_breakdown(
provider=provider,
model=model,
turns=int(values["turns"]),
token_metered_turns=int(values["token_metered_turns"]),
token_unmetered_turns=int(values["token_unmetered_turns"]),
tokens_in=int(values["tokens_in"]),
tokens_out=int(values["tokens_out"]),
stored_cost_usd=float(values["stored_cost_usd"]),
unpriced_tokens_in=int(values["unpriced_tokens_in"]),
unpriced_tokens_out=int(values["unpriced_tokens_out"]),
)
for (provider, model), values in buckets.items()
]
by_provider = _merge_usage_breakdowns(turn_breakdowns)
by_provider.sort(
key=lambda item: (
-item.cost_usd,
@ -1153,6 +1300,9 @@ def _usage_from_runtime_store(window_days: int) -> AdminUsageResponse:
)
total_cost = round(cost_usd, 6)
total_cost_basis = _aggregate_cost_basis(
item.cost_basis for item in turn_breakdowns
)
return AdminUsageResponse(
source="server_session_registry",
durable=False,
@ -1167,7 +1317,8 @@ def _usage_from_runtime_store(window_days: int) -> AdminUsageResponse:
cost_usd=total_cost,
recorded_cost_usd=round(recorded_cost_usd, 6),
estimated_cost_usd=round(estimated_cost_usd, 6),
budget=_usage_budget(total_cost),
cost_basis=total_cost_basis,
budget=_usage_budget(total_cost, total_cost_basis),
evaluator_cache=_usage_evaluator_cache(),
by_provider=by_provider[:12],
daily_cost=[
@ -1177,6 +1328,9 @@ def _usage_from_runtime_store(window_days: int) -> AdminUsageResponse:
tokens_in=int(values["tokens_in"]),
tokens_out=int(values["tokens_out"]),
cost_usd=round(float(values["cost_usd"]), 6),
cost_basis=_aggregate_cost_basis(
cast(list[UsageCostBasis], values["cost_bases"])
),
)
for day, values in sorted(daily_buckets.items())
],