feat(intercept): 模型兜底审批单独计量 token 并在配置页展示
兜底审批(LLM judge)调模型判定漏网命令,其 token 消耗此前无法单独查看: usage 行要么无 worker 归属、要么被并进复用的模型 profile 桶里。 - llmrec 增加 WithWorker 上下文覆盖,reviewer 调用前标注 worker='judge', 让审批调用的计量行可被单独筛出(复用现有 llm_usage.worker 列,无需迁移)。 - db.JudgeUsageStats 返回累计总量 + 近 N 天每日序列(WHERE worker='judge')。 - 新增 GET /api/intercept/judge/usage。 - 配置页「模型兜底审批」开关下方新增用量卡片:调用次数 / 输入 / 输出 / 缓存读写 五项统计 + 近 30 天每日消耗迷你柱图 + 刷新。
This commit is contained in:
1 parent
d0033724a6
commit
86729b65dc
8 files changed
+242
-1
No files matched your search
@@ -259,6 +259,70 @@ ORDER BY day`, days)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// JudgeDayUsage is one UTC-day token bucket for the intercept fallback judge,
|
||||
// for the config page's recent-spend sparkline.
|
||||
type JudgeDayUsage struct {
|
||||
Date string `json:"date"` // YYYY-MM-DD (UTC)
|
||||
Calls int `json:"calls"`
|
||||
InputTokens int `json:"input_tokens"`
|
||||
OutputTokens int `json:"output_tokens"`
|
||||
}
|
||||
|
||||
// JudgeUsage is the cumulative token spend of the intercept fallback judge
|
||||
// (worker='judge' rows in the always-on ledger), plus a recent daily series.
|
||||
type JudgeUsage struct {
|
||||
Calls int `json:"calls"`
|
||||
InputTokens int `json:"input_tokens"`
|
||||
OutputTokens int `json:"output_tokens"`
|
||||
CacheReadTokens int `json:"cache_read_tokens"`
|
||||
CacheWriteTokens int `json:"cache_write_tokens"`
|
||||
Daily []JudgeDayUsage `json:"daily"`
|
||||
}
|
||||
|
||||
// JudgeUsageStats returns the fallback judge's all-time token totals and a
|
||||
// per-day series over the past `days` days (default 30). Sourced from the
|
||||
// always-on llm_usage ledger, so it is accurate across pool rotation and
|
||||
// interrupted/failed judge calls. days only bounds the daily series; totals are
|
||||
// all-time.
|
||||
func (d *DB) JudgeUsageStats(days int) (JudgeUsage, error) {
|
||||
if days <= 0 {
|
||||
days = 30
|
||||
}
|
||||
var u JudgeUsage
|
||||
err := d.QueryRow(`
|
||||
SELECT COUNT(*), COALESCE(SUM(input_tokens),0), COALESCE(SUM(output_tokens),0),
|
||||
COALESCE(SUM(cache_read),0), COALESCE(SUM(cache_write),0)
|
||||
FROM llm_usage
|
||||
WHERE worker = 'judge'`).Scan(&u.Calls, &u.InputTokens, &u.OutputTokens,
|
||||
&u.CacheReadTokens, &u.CacheWriteTokens)
|
||||
if err != nil {
|
||||
return JudgeUsage{}, err
|
||||
}
|
||||
rows, err := d.Query(`
|
||||
SELECT to_char(ts AT TIME ZONE 'UTC', 'YYYY-MM-DD') AS day,
|
||||
COUNT(*), COALESCE(SUM(input_tokens),0), COALESCE(SUM(output_tokens),0)
|
||||
FROM llm_usage
|
||||
WHERE worker = 'judge' AND ts >= now() - ($1 * interval '1 day')
|
||||
GROUP BY day
|
||||
ORDER BY day`, days)
|
||||
if err != nil {
|
||||
return JudgeUsage{}, err
|
||||
}
|
||||
defer rows.Close()
|
||||
u.Daily = []JudgeDayUsage{}
|
||||
for rows.Next() {
|
||||
var day JudgeDayUsage
|
||||
if err := rows.Scan(&day.Date, &day.Calls, &day.InputTokens, &day.OutputTokens); err != nil {
|
||||
return JudgeUsage{}, err
|
||||
}
|
||||
u.Daily = append(u.Daily, day)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return JudgeUsage{}, err
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// ParseExpID turns the exploration-id segment parsed from a session string into an
|
||||
// int64 (0 when empty/non-numeric, e.g. chat sessions keyed by conversation id).
|
||||
func ParseExpID(s string) int64 {
|
||||
|
||||
Reference in new issue
Block a user