feat(harness): 评测闭环 —— 评测结果落库 + 分级 + 告警 + 可查(测温计→恒温器)
此前 eval 只打日志、不闭环。现在: - 分级:evalLevel 据综合分+忠实度 → ok(≥0.75) / warn(≥0.5 或忠实<0.6) / poor(<0.5);poor 出 slog.Warn 告警。 - 落库:dispatcher 评完经 NATS(SubjectEval) 广播 EvalEvent → 网关订阅写 PG(新表 sundynix_eval, 按 task_id upsert)。沿用任务状态回写那套(dispatcher 无 DB,经 bus→gateway 落库)。 - 可查:GET /api/v1/tasks/:id/eval 返回 overall/rule/llm/faithful/level/flags/reason/sources。 - 契约 EvalEvent + EvalOK/Warn/Poor;bus PublishEval/SubscribeEval;dispatcher EvalSink(NewOrchestrator 第9参)。 验证:三模块 build+vet+test 全绿;live RAG 任务评测落库,端点返回 overall~1.0 / level=ok / faithful=1 / sources=1。 剩:桌面端质量面板、低分自动重试(P3)。project_analysis 勾掉该项。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,11 @@ type ApprovalWaiter interface {
|
||||
WaitApproval(ctx context.Context, taskID string, timeout time.Duration) (*contract.ApprovalDecision, error)
|
||||
}
|
||||
|
||||
// EvalSink 回写自动化评测结果供网关落库(由 NATS bus 实现;可为 nil → 仅打日志不落库)。
|
||||
type EvalSink interface {
|
||||
PublishEval(ev *contract.EvalEvent) error
|
||||
}
|
||||
|
||||
// errRejected 是审批节点拒绝(或超时)时图执行返回的哨兵错误:它是合法终态而非故障,
|
||||
// Handle 据此判 rejected 并优雅收尾(不计熔断失败)。
|
||||
var errRejected = errors.New("approval rejected")
|
||||
@@ -82,16 +87,17 @@ type Orchestrator struct {
|
||||
exec ExecSink
|
||||
status StatusSink // 任务生命周期状态回写(可为 nil)
|
||||
approval ApprovalWaiter // HITL 审批等待(可为 nil → 审批节点自动放行)
|
||||
evalSink EvalSink // 评测结果回写落库(可为 nil → 仅打日志)
|
||||
|
||||
turnMu sync.Mutex // 保护 turns(攒批计数,多任务 goroutine 共享)
|
||||
turns map[string]int // sessionID → 累计轮次,用于每 N 轮触发 consolidate
|
||||
}
|
||||
|
||||
// NewOrchestrator 持有依赖;图按任务的 DSL 在 Handle 内动态编译。
|
||||
// exec 为执行可视化事件出口(可为 nil,则不发轨迹事件);eval 为自动化评测(可为 nil);
|
||||
// status 为任务生命周期状态回写出口(可为 nil);approval 为 HITL 审批等待(可为 nil)。
|
||||
func NewOrchestrator(pool LLM, breaker *harness.CircuitBreaker, eval *harness.Evaluator, sink TokenSink, tools ToolCaller, exec ExecSink, status StatusSink, approval ApprovalWaiter) (*Orchestrator, error) {
|
||||
return &Orchestrator{pool: pool, breaker: breaker, eval: eval, sink: sink, tools: tools, exec: exec, status: status, approval: approval}, nil
|
||||
// exec 为执行可视化事件出口(可为 nil);eval 为自动化评测(可为 nil);status 为状态回写(可为 nil);
|
||||
// approval 为 HITL 审批等待(可为 nil);evalSink 为评测结果落库出口(可为 nil)。
|
||||
func NewOrchestrator(pool LLM, breaker *harness.CircuitBreaker, eval *harness.Evaluator, sink TokenSink, tools ToolCaller, exec ExecSink, status StatusSink, approval ApprovalWaiter, evalSink EvalSink) (*Orchestrator, error) {
|
||||
return &Orchestrator{pool: pool, breaker: breaker, eval: eval, sink: sink, tools: tools, exec: exec, status: status, approval: approval, evalSink: evalSink}, nil
|
||||
}
|
||||
|
||||
// setStatus 回写一次任务状态流转(status 为 nil 时静默跳过)。
|
||||
@@ -203,8 +209,33 @@ func (o *Orchestrator) evaluate(t *contract.Task, input, output string, sources
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
r := o.eval.Score(ctx, input, output, sources)
|
||||
log.Printf("[eval] task %s 综合 %.2f(规则 %.2f / LLM %.2f / 忠实 %.2f,来源 %d)flags=%v %s",
|
||||
t.ID, r.Overall, r.Rule, r.LLM, r.Faithful, len(sources), r.Flags, r.Reason)
|
||||
level := evalLevel(r)
|
||||
log.Printf("[eval] task %s 综合 %.2f(规则 %.2f / LLM %.2f / 忠实 %.2f,来源 %d)level=%s flags=%v %s",
|
||||
t.ID, r.Overall, r.Rule, r.LLM, r.Faithful, len(sources), level, r.Flags, r.Reason)
|
||||
if level == contract.EvalPoor { // 低质量告警(可观测 + 后续可挂报警)
|
||||
slog.Warn("eval poor quality", "task_id", t.ID, "overall", r.Overall, "faithful", r.Faithful, "flags", r.Flags)
|
||||
}
|
||||
// 闭环:评测结果落库(供 UI 查询 / 质量趋势 / 门控)。
|
||||
if o.evalSink != nil {
|
||||
if err := o.evalSink.PublishEval(&contract.EvalEvent{
|
||||
TaskID: t.ID, Overall: r.Overall, Rule: r.Rule, LLM: r.LLM, Faithful: r.Faithful,
|
||||
Level: level, Flags: r.Flags, Reason: r.Reason, Sources: len(sources), TS: time.Now().UnixMilli(),
|
||||
}); err != nil {
|
||||
log.Printf("[eval] 回写评测结果失败 task=%s: %v", t.ID, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// evalLevel 据综合分 + 忠实度把评测分级(闭环门控/告警用)。
|
||||
func evalLevel(r harness.Result) string {
|
||||
switch {
|
||||
case r.Overall < 0.5:
|
||||
return contract.EvalPoor
|
||||
case r.Overall < 0.75 || (r.Faithful > 0 && r.Faithful < 0.6):
|
||||
return contract.EvalWarn
|
||||
default:
|
||||
return contract.EvalOK
|
||||
}
|
||||
}
|
||||
|
||||
// fetchMemory 经 MCP memory_get 工具召回用户常驻画像。
|
||||
|
||||
@@ -81,6 +81,11 @@ func (s *Subscriber) PublishTaskStatus(taskID, status, detail string) error {
|
||||
})
|
||||
}
|
||||
|
||||
// PublishEval 让 Subscriber 满足 eino.EvalSink,把自动化评测结果回写给网关落库。
|
||||
func (s *Subscriber) PublishEval(ev *contract.EvalEvent) error {
|
||||
return s.inner.PublishEval(ev)
|
||||
}
|
||||
|
||||
// WaitApproval 让 Subscriber 满足 eino.ApprovalWaiter,阻塞等待审批节点的人工决定。
|
||||
func (s *Subscriber) WaitApproval(ctx context.Context, taskID string, timeout time.Duration) (*contract.ApprovalDecision, error) {
|
||||
return s.inner.WaitApproval(ctx, taskID, timeout)
|
||||
|
||||
Reference in New Issue
Block a user