feat(admin): 「自动评测」页做实 —— 接真评测数据,去 mock (P1)
审计 P1「admin 三页纯 mock」之一。此前 EvalsPage 是写死的质量趋势+编造的 错题本+虚构纠偏轨迹。现接 sundynix_eval 真数据(评测经 JetStream eval 流持久 落库,刚升级)。 - store/eval_query.go:EvalTrend(按天 avg 综合分/忠实度+低分计数)、EvalSummaryFor (ok/warn/poor/corrected 计数+均值)、PoorEvals(错题本,level in poor/warn + 评语+纠偏标记+租户名)。全 WithoutTenant 平台口径;忠实度均值只算 sources>0 (无来源的忠实度恒0会压低失真)。 - GET /admin/evals?days=(RequireAdmin);admin api.ts + EvalsPage 重写: 总览卡片(综合分/合格率/低分占比/纠偏采纳率)+质量&忠实度趋势(纯SVG折线+低分 背景条)+错题本(点行展开评语)。 - 诚实边界:纠偏「前后全文对照」后端未持久化,只存了 Reason/Corrected/各维度分, 故错题本展示评语+「已纠偏」标记,不再编造 before/after。 live:/admin/evals 返 57 次评测 avg=0.88、错题本10条、11天趋势;浏览器渲染 真数据(趋势线07-15真实下探)。go+tsc+41 vitest 全绿。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -570,3 +570,23 @@ func (h *Handler) AdminUsage(c *gin.Context) {
|
||||
}
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// AdminEvals: GET /api/v1/admin/evals?days= —— 自动评测观测(趋势 + 计数 + 错题本)。全平台口径。
|
||||
// 数据来自 sundynix_eval(评测经 JetStream eval 流持久落库);此前该页纯 mock。
|
||||
func (h *Handler) AdminEvals(c *gin.Context) {
|
||||
ctx := store.WithoutTenant(c.Request.Context())
|
||||
now := time.Now()
|
||||
days := 14
|
||||
if d, err := strconv.Atoi(c.Query("days")); err == nil && d > 0 && d <= 90 {
|
||||
days = d
|
||||
}
|
||||
from := now.AddDate(0, 0, -(days - 1)).Format("20060102")
|
||||
to := now.Format("20060102")
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"from": from,
|
||||
"to": to,
|
||||
"trend": h.db.EvalTrend(ctx, from, to),
|
||||
"summary": h.db.EvalSummaryFor(ctx, from, to),
|
||||
"poor": h.db.PoorEvals(ctx, 30),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -155,6 +155,7 @@ func New(db *store.Postgres, cache *store.Redis, bus *nats.Bus, blobStore *blob.
|
||||
admin.GET("/status", h.AdminStatus) // 服务状态:基建/服务探活 + MCP 工具注册
|
||||
admin.GET("/overview", h.AdminOverview) // 系统级聚合:全平台用户/任务/评测/模型态/提示词态/健康
|
||||
admin.GET("/usage", h.AdminUsage) // 用量/积分/成本:全平台按天趋势 + 租户排行 / 单租户余额
|
||||
admin.GET("/evals", h.AdminEvals) // 自动评测观测:质量趋势 + 计数 + 错题本(真数据)
|
||||
admin.POST("/migrate-kb-storage", h.MigrateKBStorage) // 增量3:存量 KB 三库 owner/kb→space/kb 重灌(一次性)
|
||||
admin.GET("/audit", h.AuditList) // 敏感操作审计流(倒序,翻页)
|
||||
admin.GET("/guardrail-events", h.GuardrailEvents) // 护栏命中安全事件流(倒序,翻页)
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
package store
|
||||
|
||||
import "context"
|
||||
|
||||
// 评测观测查询(admin「自动评测」页做实用;此前该页纯 mock)。全平台口径 → WithoutTenant。
|
||||
// 注:评测的「纠偏前后全文轨迹」后端未持久化,只存了 Reason(评语)/Corrected(是否已纠偏采纳)/
|
||||
// 各维度分,故错题本展示这些真数据,不含编造的 before/after 对照。
|
||||
|
||||
// EvalDay 是评测趋势按天一行。
|
||||
type EvalDay struct {
|
||||
Day string `json:"day"` // YYYYMMDD
|
||||
AvgOverall float64 `json:"avg_overall"` // 当日综合分均值 [0,1]
|
||||
AvgFaithful float64 `json:"avg_faithful"` // 当日忠实度均值(仅计有来源的评测)
|
||||
Count int64 `json:"count"`
|
||||
PoorCount int64 `json:"poor_count"` // 当日 poor 级条数(幻觉/低质趋势)
|
||||
}
|
||||
|
||||
// EvalTrend 按天聚合评测(from/to 为 YYYYMMDD)。avg_faithful 只算有检索来源的评测(sources>0),
|
||||
// 无来源的忠实度恒 0 会把均值压低失真。
|
||||
func (p *Postgres) EvalTrend(ctx context.Context, from, to string) []EvalDay {
|
||||
if p.db == nil {
|
||||
return nil
|
||||
}
|
||||
var out []EvalDay
|
||||
p.db.WithContext(ctx).Model(&Eval{}).
|
||||
Select("to_char(created_at,'YYYYMMDD') as day, "+
|
||||
"avg(overall) as avg_overall, "+
|
||||
"avg(case when sources > 0 then faithful end) as avg_faithful, "+
|
||||
"count(*) as count, "+
|
||||
"count(case when level = 'poor' then 1 end) as poor_count").
|
||||
Where("to_char(created_at,'YYYYMMDD') >= ? AND to_char(created_at,'YYYYMMDD') <= ?", from, to).
|
||||
Group("day").Order("day").Scan(&out)
|
||||
return out
|
||||
}
|
||||
|
||||
// EvalSummary 是评测总览计数。
|
||||
type EvalSummary struct {
|
||||
Total int64 `json:"total"`
|
||||
OK int64 `json:"ok"`
|
||||
Warn int64 `json:"warn"`
|
||||
Poor int64 `json:"poor"`
|
||||
Corrected int64 `json:"corrected"` // 经低分自动纠偏重生成后采纳的条数(恒温器闭环成效)
|
||||
AvgOverall float64 `json:"avg_overall"` // 区间综合分均值
|
||||
}
|
||||
|
||||
// EvalSummaryFor 区间内评测计数(from/to 为 YYYYMMDD)。
|
||||
func (p *Postgres) EvalSummaryFor(ctx context.Context, from, to string) EvalSummary {
|
||||
var s EvalSummary
|
||||
if p.db == nil {
|
||||
return s
|
||||
}
|
||||
p.db.WithContext(ctx).Model(&Eval{}).
|
||||
Select("count(*) as total, "+
|
||||
"count(case when level='ok' then 1 end) as ok, "+
|
||||
"count(case when level='warn' then 1 end) as warn, "+
|
||||
"count(case when level='poor' then 1 end) as poor, "+
|
||||
"count(case when corrected then 1 end) as corrected, "+
|
||||
"coalesce(avg(overall),0) as avg_overall").
|
||||
Where("to_char(created_at,'YYYYMMDD') >= ? AND to_char(created_at,'YYYYMMDD') <= ?", from, to).
|
||||
Scan(&s)
|
||||
return s
|
||||
}
|
||||
|
||||
// PoorEval 是错题本一行(低分评测 + 评语 + 纠偏标记;带租户名免前端二次查)。
|
||||
type PoorEval struct {
|
||||
TaskID string `json:"task_id"`
|
||||
TenantName string `json:"tenant_name"`
|
||||
Owner string `json:"owner"`
|
||||
Overall float64 `json:"overall"`
|
||||
Rule float64 `json:"rule"`
|
||||
LLM float64 `json:"llm"`
|
||||
Faithful float64 `json:"faithful"`
|
||||
Level string `json:"level"`
|
||||
Reason string `json:"reason"`
|
||||
Sources int `json:"sources"`
|
||||
Corrected bool `json:"corrected"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
}
|
||||
|
||||
// PoorEvals 最近的低分评测(level=poor/warn,错题本)。
|
||||
func (p *Postgres) PoorEvals(ctx context.Context, limit int) []PoorEval {
|
||||
if p.db == nil {
|
||||
return nil
|
||||
}
|
||||
if limit <= 0 || limit > 100 {
|
||||
limit = 30
|
||||
}
|
||||
var out []PoorEval
|
||||
p.db.WithContext(ctx).Table("sundynix_eval e").
|
||||
Select("e.task_id, coalesce(t.name,'') as tenant_name, e.owner, e.overall, e.rule, e.llm, "+
|
||||
"e.faithful, e.level, e.reason, e.sources, e.corrected, "+
|
||||
"to_char(e.created_at,'YYYY-MM-DD HH24:MI') as created_at").
|
||||
Joins("left join sundynix_tenant t on t.id = e.tenant_id").
|
||||
Where("e.level in ('poor','warn') AND e.deleted_at IS NULL").
|
||||
Order("e.created_at desc").Limit(limit).Scan(&out)
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user