Add /admin/model-usage endpoint (co-op per-model spend/tokens/requests)

This commit is contained in:
inference-bot committed 2026-09-26 15:31:07 -06:00
1 parent e92e29bac0
commit 810b878014
1 file changed
+53
+53
View File
@@ -1318,6 +1318,59 @@ async def admin_overview(request: Request):
}
@app.get("/admin/model-usage")
@limiter.limit("30/minute")
async def admin_model_usage(request: Request):
"""Co-op-wide per-model usage from LiteLLM spend logs.
Authenticated by the X-Admin-Token header. Aggregates every spend-log row
(across ALL members) by model: total spend, tokens, and request count,
sorted by spend descending. No per-member breakdown — this is co-op-level
so admins can see which models the membership actually uses.
"""
_verify_admin_token(request)
models = {}
total_spend = 0.0
total_tokens = 0
total_requests = 0
try:
async with httpx.AsyncClient(timeout=30.0) as client:
r = await client.get(
f"{LITELLM_BASE}/spend/logs",
headers={"Authorization": f"Bearer {LITELLM_MASTER_KEY}"},
)
r.raise_for_status()
rows = r.json()
if isinstance(rows, dict):
rows = rows.get("data", rows.get("logs", []))
for row in rows if isinstance(rows, list) else []:
model = row.get("model") or row.get("model_group") or "unknown"
m = models.setdefault(model, {"spend": 0.0, "tokens": 0, "requests": 0})
m["spend"] += float(row.get("spend") or 0.0)
m["tokens"] += int(row.get("total_tokens") or 0)
m["requests"] += 1
total_spend += float(row.get("spend") or 0.0)
total_tokens += int(row.get("total_tokens") or 0)
total_requests += 1
except Exception as e:
logger.warning("admin model-usage: spend/logs fetch failed: %s", e)
model_list = [
{"model": name, **stats}
for name, stats in sorted(models.items(), key=lambda kv: kv[1]["spend"], reverse=True)
]
return {
"models": model_list,
"totals": {
"total_spend": total_spend,
"total_tokens": total_tokens,
"total_requests": total_requests,
},
}
# --- Broker HTTP endpoints (called by the member dashboard) ---
# The dashboard is Cloudron-SSO-gated; it authenticates the member and passes
# their email with a shared secret. The portal trusts the email only because it