104 lines
3.6 KiB
Python
104 lines
3.6 KiB
Python
"""调用可观测面板后端 API(媒体/LLM 调用归因)。
|
||
路由前缀: /app/api/admin/observability
|
||
数据来源: token_usage_logs(Phase 2.2 已加 trace_id 列,可经 X-Trace-Id 关联请求链路)。
|
||
"""
|
||
from datetime import datetime, timedelta
|
||
|
||
from fastapi import APIRouter, Query, Depends
|
||
from ...database import get_db
|
||
from ..auth import get_current_user
|
||
from sqlalchemy import text
|
||
from sqlalchemy.orm import Session
|
||
|
||
router = APIRouter()
|
||
|
||
|
||
@router.get("/summary")
|
||
async def observability_summary(
|
||
days: int = Query(30, description="统计天数"),
|
||
db: Session = Depends(get_db),
|
||
user=Depends(get_current_user),
|
||
):
|
||
"""媒体/LLM 调用归因:总览 + 按模型 + 按用户 + 每日趋势 + 最近调用(trace_id 下钻)。"""
|
||
since = datetime.now() - timedelta(days=days)
|
||
|
||
totals = db.execute(text("""
|
||
SELECT COUNT(*),
|
||
COALESCE(SUM(total_tokens), 0),
|
||
COALESCE(SUM(cost), 0),
|
||
COUNT(DISTINCT user_id)
|
||
FROM token_usage_logs WHERE created_at >= :since
|
||
"""), {"since": since}).fetchone()
|
||
|
||
by_model = db.execute(text("""
|
||
SELECT model_name, COUNT(*), COALESCE(SUM(total_tokens), 0), COALESCE(SUM(cost), 0)
|
||
FROM token_usage_logs WHERE created_at >= :since
|
||
GROUP BY model_name ORDER BY COALESCE(SUM(total_tokens), 0) DESC
|
||
"""), {"since": since}).fetchall()
|
||
|
||
by_user = db.execute(text("""
|
||
SELECT t.user_id, u.username, COUNT(*), COALESCE(SUM(t.total_tokens), 0), COALESCE(SUM(t.cost), 0)
|
||
FROM token_usage_logs t LEFT JOIN users u ON u.id = t.user_id
|
||
WHERE t.created_at >= :since
|
||
GROUP BY t.user_id, u.username ORDER BY COALESCE(SUM(t.total_tokens), 0) DESC LIMIT 20
|
||
"""), {"since": since}).fetchall()
|
||
|
||
daily = db.execute(text("""
|
||
SELECT DATE(created_at) AS d, COALESCE(SUM(total_tokens), 0), COALESCE(SUM(cost), 0), COUNT(*)
|
||
FROM token_usage_logs WHERE created_at >= :since
|
||
GROUP BY DATE(created_at) ORDER BY d
|
||
"""), {"since": since}).fetchall()
|
||
|
||
recent = db.execute(text("""
|
||
SELECT trace_id, model_name, total_tokens, cost, created_at
|
||
FROM token_usage_logs WHERE created_at >= :since
|
||
ORDER BY created_at DESC LIMIT 50
|
||
"""), {"since": since}).fetchall()
|
||
|
||
return {
|
||
"totals": {
|
||
"calls": int(totals[0] or 0),
|
||
"tokens": int(totals[1] or 0),
|
||
"cost": float(totals[2] or 0),
|
||
"users": int(totals[3] or 0),
|
||
},
|
||
"by_model": [
|
||
{
|
||
"model_name": m[0] or "unknown",
|
||
"calls": int(m[1] or 0),
|
||
"tokens": int(m[2] or 0),
|
||
"cost": float(m[3] or 0),
|
||
}
|
||
for m in by_model
|
||
],
|
||
"by_user": [
|
||
{
|
||
"user_id": u[0],
|
||
"username": u[1] or u[0],
|
||
"calls": int(u[2] or 0),
|
||
"tokens": int(u[3] or 0),
|
||
"cost": float(u[4] or 0),
|
||
}
|
||
for u in by_user
|
||
],
|
||
"daily": [
|
||
{
|
||
"date": str(d[0]),
|
||
"tokens": int(d[1] or 0),
|
||
"cost": float(d[2] or 0),
|
||
"calls": int(d[3] or 0),
|
||
}
|
||
for d in daily
|
||
],
|
||
"recent": [
|
||
{
|
||
"trace_id": r[0],
|
||
"model_name": r[1] or "unknown",
|
||
"tokens": int(r[2] or 0),
|
||
"cost": float(r[3] or 0),
|
||
"created_at": r[4].strftime("%Y-%m-%d %H:%M:%S") if r[4] else None,
|
||
}
|
||
for r in recent
|
||
],
|
||
}
|