model usage: Requests counts only successful calls

The Requests column summed every spend-log row (COUNT(*)), so a model's
call count included refused/failed attempts — e.g. Metis showed 26 calls
when only 16 succeeded (the other 10 were 401s on /v1/systemone).

Count only rows whose status is not 'failure', and report the refused
count separately as 'refused' (per model and in totals) so a failure
cannot inflate use without disappearing from view. spend and tokens stay
summed over all rows: a call that failed after consuming tokens still
cost money.

Fixes the panel and dashboard model tables, which both read this
aggregation (panel via /admin/model-usage, dashboard via
/broker/model-leaderboard).
This commit is contained in:
inference-bot committed 2026-11-03 23:39:01 +00:00
1 parent 8bcb7ff049
commit c53112fbaf
1 file changed
+29 -8
+29 -8
View File
@@ -402,6 +402,13 @@ def aggregate_model_usage(days: int = 0, team_id: str | None = None) -> dict:
Prefers the `model_group` column, which carries our provider/model alias
(e.g. greenpt/glm-5.3-flash); falls back to `model` for pre-alias rows — matching
the logic the HTTP path used, expressed in SQL.
`requests` counts only metered calls that SUCCEEDED. Refused/failed attempts
(rows with status = 'failure', e.g. a 401 on the /v1/systemone decision
route) are excluded from that count and reported separately as `refused`,
so a failure cannot inflate a model's call count. `spend` and `tokens` stay
summed over every row: a call that failed after tokens were consumed still
cost money and still processed those tokens.
"""
if not litellm_db_ready():
raise RuntimeError("LiteLLM DB credentials not configured")
@@ -425,7 +432,8 @@ def aggregate_model_usage(days: int = 0, team_id: str | None = None) -> dict:
COALESCE(NULLIF(model_group, ''), NULLIF(model, ''), 'unknown') AS model,
COALESCE(SUM(spend), 0) AS spend,
COALESCE(SUM(total_tokens), 0) AS tokens,
COUNT(*) AS requests
COUNT(*) FILTER (WHERE status IS DISTINCT FROM 'failure') AS requests,
COUNT(*) FILTER (WHERE status = 'failure') AS refused
FROM "LiteLLM_SpendLogs"
{where_sql}
GROUP BY 1
@@ -442,7 +450,7 @@ def aggregate_model_usage(days: int = 0, team_id: str | None = None) -> dict:
models = [
{"model": r[0], "spend": float(r[1] or 0.0), "tokens": int(r[2] or 0),
"requests": int(r[3] or 0)}
"requests": int(r[3] or 0), "refused": int(r[4] or 0)}
for r in rows
]
return {
@@ -451,6 +459,7 @@ def aggregate_model_usage(days: int = 0, team_id: str | None = None) -> dict:
"total_spend": sum(m["spend"] for m in models),
"total_tokens": sum(m["tokens"] for m in models),
"total_requests": sum(m["requests"] for m in models),
"total_refused": sum(m["refused"] for m in models),
},
}
@@ -3345,8 +3354,10 @@ async def admin_model_usage(request: Request, days: int = 0):
"""Co-op-wide per-model usage from LiteLLM spend logs.
Authenticated by the X-Admin-Token header. Aggregates spend-log rows
(across ALL members) by model: total spend, tokens, and request count,
sorted by spend descending. Optional ?days=N limits to the last N days
(across ALL members) by model: total spend, tokens, and the count of
successful requests (refused/failed attempts are excluded and reported
separately as `refused`), sorted by request count descending. Optional
?days=N limits to the last N days
(0 or omitted = all-time). No per-member breakdown — this is co-op-level
so admins can see which models the membership actually uses.
@@ -3374,6 +3385,7 @@ async def admin_model_usage(request: Request, days: int = 0):
total_spend = 0.0
total_tokens = 0
total_requests = 0
total_refused = 0
try:
async with httpx.AsyncClient(timeout=30.0) as client:
r = await client.get(
@@ -3395,13 +3407,19 @@ async def admin_model_usage(request: Request, days: int = 0):
if started and started < cutoff:
continue
model = row.get("model_group") or row.get("model") or "unknown"
m = models.setdefault(model, {"spend": 0.0, "tokens": 0, "requests": 0})
m = models.setdefault(model, {"spend": 0.0, "tokens": 0, "requests": 0, "refused": 0})
m["spend"] += float(row.get("spend") or 0.0)
m["tokens"] += int(row.get("total_tokens") or 0)
m["requests"] += 1
# Refused/failed attempts are not served requests: keep them out
# of the request count (reported separately as `refused`).
if str(row.get("status") or "").lower() == "failure":
m["refused"] += 1
total_refused += 1
else:
m["requests"] += 1
total_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)
@@ -3416,6 +3434,7 @@ async def admin_model_usage(request: Request, days: int = 0):
"total_spend": total_spend,
"total_tokens": total_tokens,
"total_requests": total_requests,
"total_refused": total_refused,
},
}
@@ -3753,7 +3772,9 @@ async def broker_get_usage(email: str, days: int = 0) -> dict:
m = models.setdefault(model, {"spend": 0.0, "tokens": 0, "calls": 0})
m["spend"] += float(row.get("spend") or 0.0)
m["tokens"] += int(row.get("total_tokens") or 0)
m["calls"] += 1
# Refused/failed attempts are not served requests.
if str(row.get("status") or "").lower() != "failure":
m["calls"] += 1
total_tokens += int(row.get("total_tokens") or 0)
except Exception as e: # spend logs are best-effort; don't fail the whole view
logger.warning("spend/logs fetch failed: %s", e)