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
@@ -14,9 +14,14 @@ import (
|
||||
"github.com/Autumn-27/artex/db"
|
||||
"github.com/Autumn-27/artex/guard"
|
||||
"github.com/Autumn-27/artex/intercept"
|
||||
"github.com/Autumn-27/artex/llmrec"
|
||||
"github.com/Autumn-27/norma/llm"
|
||||
)
|
||||
|
||||
// judgeWorkerLane is the usage-ledger "worker" label for intercept fallback-judge
|
||||
// calls, so judge spend can be queried apart from the worker/planner/main lanes.
|
||||
const judgeWorkerLane = "judge"
|
||||
|
||||
// chatGuard returns a guard wired with the manager's interceptor, used for chat
|
||||
// conversations. Called once per applyLLM so a new LLM config always gets a fresh guard.
|
||||
func (s *Server) chatGuard() *guard.Guard {
|
||||
@@ -41,6 +46,9 @@ func (s *Server) wireInterceptReviewer() {
|
||||
if !ok {
|
||||
return intercept.Decision{ProfileID: profileID}, fmt.Errorf("裁判模型 profile %d 不可用", profileID)
|
||||
}
|
||||
// Tag this call's usage as the "judge" lane so the config page can report
|
||||
// how much the fallback approval has spent, separate from model profiles.
|
||||
ctx = llmrec.WithWorker(ctx, judgeWorkerLane)
|
||||
text, err := reviewCompletion(ctx, prov, prompt, input)
|
||||
if err != nil {
|
||||
return intercept.Decision{ProfileID: profileID}, err
|
||||
@@ -437,6 +445,23 @@ func (s *Server) interceptSetJudgeConfig(w http.ResponseWriter, r *http.Request)
|
||||
writeJSON(w, 200, map[string]any{"ok": true})
|
||||
}
|
||||
|
||||
// interceptJudgeUsage returns the fallback judge's cumulative token spend plus a
|
||||
// recent daily series, for the config page. ?days bounds the daily series (default 30).
|
||||
func (s *Server) interceptJudgeUsage(w http.ResponseWriter, r *http.Request) {
|
||||
pg := s.m.PG()
|
||||
if pg == nil {
|
||||
writeJSON(w, 200, db.JudgeUsage{Daily: []db.JudgeDayUsage{}})
|
||||
return
|
||||
}
|
||||
days := atoiDefault(r.URL.Query().Get("days"), 30)
|
||||
usage, err := pg.JudgeUsageStats(days)
|
||||
if err != nil {
|
||||
writeErr(w, 500, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, 200, usage)
|
||||
}
|
||||
|
||||
// --- helpers ---
|
||||
|
||||
type interceptRuleReq struct {
|
||||
|
||||
@@ -930,6 +930,7 @@ func (s *Server) Handler() http.Handler {
|
||||
mux.HandleFunc("PUT /api/intercept/tool-config", s.interceptSetToolConfig)
|
||||
mux.HandleFunc("GET /api/intercept/judge", s.interceptGetJudgeConfig)
|
||||
mux.HandleFunc("PUT /api/intercept/judge", s.interceptSetJudgeConfig)
|
||||
mux.HandleFunc("GET /api/intercept/judge/usage", s.interceptJudgeUsage) // 兜底审批累计 token 用量
|
||||
|
||||
// /api/* goes through CORS + JWT; everything else is served by the embedded
|
||||
// frontend (public — auth is enforced client-side and on the API). With the
|
||||
|
||||
Reference in new issue
Block a user