feat(memory): P1 长期记忆升级 —— 异步攒批 Consolidate + 软删 + importance/last_seen #1
+2
-1
@@ -112,7 +112,8 @@ Harness = 围绕 LLM 的可靠性 / 安全 / 质量治理层。4 个组件均为
|
|||||||
|
|
||||||
**已知短板 / 优化清单**(按性价比,逐项推进):
|
**已知短板 / 优化清单**(按性价比,逐项推进):
|
||||||
|
|
||||||
- [ ] **P1 评测闭环**:eval 结果落库 + 阈值门控(低分标记/触发重试)+ 桌面端质量面板。现状只打日志、不闭环。
|
- [x] **P1 评测闭环** ✅:eval 结果经 NATS 落 PG(sundynix_eval)+ 分级(ok/warn/poor) + poor 告警 +
|
||||||
|
`GET /tasks/:id/eval` 可查。dispatcher 评→广播→网关落库。剩:桌面端质量面板、低分自动重试(P3)。
|
||||||
- [x] **P1 RAG 忠实度评测** ✅:检索原文喂给 judge,一次评质量+忠实度,未被来源支持的说法进 Flags。
|
- [x] **P1 RAG 忠实度评测** ✅:检索原文喂给 judge,一次评质量+忠实度,未被来源支持的说法进 Flags。
|
||||||
综合分(有来源)=0.3规则+0.35质量+0.35忠实。runGraph 透传 refs → evaluate。live 实测忠实 1.00/来源 1,单测覆盖。
|
综合分(有来源)=0.3规则+0.35质量+0.35忠实。runGraph 透传 refs → evaluate。live 实测忠实 1.00/来源 1,单测覆盖。
|
||||||
- [ ] **P2 输出脱敏增强**:滑动窗口跨片检测(现流式逐片会漏跨 token 的密钥)+ PII 模式(手机号/邮箱/身份证)。
|
- [ ] **P2 输出脱敏增强**:滑动窗口跨片检测(现流式逐片会漏跨 token 的密钥)+ PII 模式(手机号/邮箱/身份证)。
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ func main() {
|
|||||||
go sub.FetchModelConfigWithRetry(context.Background(), pool.SetConfig)
|
go sub.FetchModelConfigWithRetry(context.Background(), pool.SetConfig)
|
||||||
|
|
||||||
// sub 同时作为 Token 回流(TokenSink)、MCP 工具调用(ToolCaller)、执行事件(ExecSink)、
|
// sub 同时作为 Token 回流(TokenSink)、MCP 工具调用(ToolCaller)、执行事件(ExecSink)、
|
||||||
// 任务状态回写(StatusSink)与 HITL 审批等待(ApprovalWaiter)出口。
|
// 任务状态回写(StatusSink)、HITL 审批等待(ApprovalWaiter)与评测落库(EvalSink)出口。
|
||||||
orch, err := eino.NewOrchestrator(pool, breaker, eval, sub, sub, sub, sub, sub)
|
orch, err := eino.NewOrchestrator(pool, breaker, eval, sub, sub, sub, sub, sub, sub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("[dispatcher] build eino graph: %v", err)
|
log.Fatalf("[dispatcher] build eino graph: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,11 @@ type ApprovalWaiter interface {
|
|||||||
WaitApproval(ctx context.Context, taskID string, timeout time.Duration) (*contract.ApprovalDecision, error)
|
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 是审批节点拒绝(或超时)时图执行返回的哨兵错误:它是合法终态而非故障,
|
// errRejected 是审批节点拒绝(或超时)时图执行返回的哨兵错误:它是合法终态而非故障,
|
||||||
// Handle 据此判 rejected 并优雅收尾(不计熔断失败)。
|
// Handle 据此判 rejected 并优雅收尾(不计熔断失败)。
|
||||||
var errRejected = errors.New("approval rejected")
|
var errRejected = errors.New("approval rejected")
|
||||||
@@ -82,16 +87,17 @@ type Orchestrator struct {
|
|||||||
exec ExecSink
|
exec ExecSink
|
||||||
status StatusSink // 任务生命周期状态回写(可为 nil)
|
status StatusSink // 任务生命周期状态回写(可为 nil)
|
||||||
approval ApprovalWaiter // HITL 审批等待(可为 nil → 审批节点自动放行)
|
approval ApprovalWaiter // HITL 审批等待(可为 nil → 审批节点自动放行)
|
||||||
|
evalSink EvalSink // 评测结果回写落库(可为 nil → 仅打日志)
|
||||||
|
|
||||||
turnMu sync.Mutex // 保护 turns(攒批计数,多任务 goroutine 共享)
|
turnMu sync.Mutex // 保护 turns(攒批计数,多任务 goroutine 共享)
|
||||||
turns map[string]int // sessionID → 累计轮次,用于每 N 轮触发 consolidate
|
turns map[string]int // sessionID → 累计轮次,用于每 N 轮触发 consolidate
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewOrchestrator 持有依赖;图按任务的 DSL 在 Handle 内动态编译。
|
// NewOrchestrator 持有依赖;图按任务的 DSL 在 Handle 内动态编译。
|
||||||
// exec 为执行可视化事件出口(可为 nil,则不发轨迹事件);eval 为自动化评测(可为 nil);
|
// exec 为执行可视化事件出口(可为 nil);eval 为自动化评测(可为 nil);status 为状态回写(可为 nil);
|
||||||
// status 为任务生命周期状态回写出口(可为 nil);approval 为 HITL 审批等待(可为 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) (*Orchestrator, error) {
|
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}, nil
|
return &Orchestrator{pool: pool, breaker: breaker, eval: eval, sink: sink, tools: tools, exec: exec, status: status, approval: approval, evalSink: evalSink}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// setStatus 回写一次任务状态流转(status 为 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)
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
r := o.eval.Score(ctx, input, output, sources)
|
r := o.eval.Score(ctx, input, output, sources)
|
||||||
log.Printf("[eval] task %s 综合 %.2f(规则 %.2f / LLM %.2f / 忠实 %.2f,来源 %d)flags=%v %s",
|
level := evalLevel(r)
|
||||||
t.ID, r.Overall, r.Rule, r.LLM, r.Faithful, len(sources), r.Flags, r.Reason)
|
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 工具召回用户常驻画像。
|
// 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,阻塞等待审批节点的人工决定。
|
// WaitApproval 让 Subscriber 满足 eino.ApprovalWaiter,阻塞等待审批节点的人工决定。
|
||||||
func (s *Subscriber) WaitApproval(ctx context.Context, taskID string, timeout time.Duration) (*contract.ApprovalDecision, error) {
|
func (s *Subscriber) WaitApproval(ctx context.Context, taskID string, timeout time.Duration) (*contract.ApprovalDecision, error) {
|
||||||
return s.inner.WaitApproval(ctx, taskID, timeout)
|
return s.inner.WaitApproval(ctx, taskID, timeout)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
@@ -64,6 +65,19 @@ func main() {
|
|||||||
log.Printf("[gateway] subscribe task status: %v", err)
|
log.Printf("[gateway] subscribe task status: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 评测闭环:订阅 dispatcher 回写的自动化评测结果,落 PG 供 UI 查询 / 质量趋势 / 门控。
|
||||||
|
if _, err := bus.SubscribeEval(func(ev *contract.EvalEvent) {
|
||||||
|
flags, _ := json.Marshal(ev.Flags)
|
||||||
|
if err := db.SaveEval(context.Background(), &store.Eval{
|
||||||
|
TaskID: ev.TaskID, Overall: ev.Overall, Rule: ev.Rule, LLM: ev.LLM, Faithful: ev.Faithful,
|
||||||
|
Level: ev.Level, Flags: string(flags), Reason: ev.Reason, Sources: ev.Sources,
|
||||||
|
}); err != nil {
|
||||||
|
log.Printf("[gateway] 落库评测 %s 失败: %v", ev.TaskID, err)
|
||||||
|
}
|
||||||
|
}); err != nil {
|
||||||
|
log.Printf("[gateway] subscribe eval: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
r := router.New(db, cache, bus, blobStore)
|
r := router.New(db, cache, bus, blobStore)
|
||||||
addr := envOr("GATEWAY_ADDR", ":8080")
|
addr := envOr("GATEWAY_ADDR", ":8080")
|
||||||
log.Printf("[gateway] listening on %s", addr)
|
log.Printf("[gateway] listening on %s", addr)
|
||||||
|
|||||||
@@ -89,6 +89,21 @@ func (h *Handler) TaskStatus(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, gin.H{"task_id": id, "status": status, "detail": detail})
|
c.JSON(http.StatusOK, gin.H{"task_id": id, "status": status, "detail": detail})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TaskEval: GET /api/v1/tasks/:id/eval —— 取一次任务的自动化评测结果(综合/质量/忠实度/分级/flags)。
|
||||||
|
func (h *Handler) TaskEval(c *gin.Context) {
|
||||||
|
e := h.db.GetEval(c.Request.Context(), c.Param("id"))
|
||||||
|
if e == nil {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "尚无评测结果(任务未完成或评测进行中)"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var flags []string
|
||||||
|
_ = json.Unmarshal([]byte(e.Flags), &flags)
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"task_id": e.TaskID, "overall": e.Overall, "rule": e.Rule, "llm": e.LLM,
|
||||||
|
"faithful": e.Faithful, "level": e.Level, "flags": flags, "reason": e.Reason, "sources": e.Sources,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ApproveTask: POST /api/v1/tasks/:id/approve {approved, node?, note?} —— 人工审批决定(HITL)。
|
// ApproveTask: POST /api/v1/tasks/:id/approve {approved, node?, note?} —— 人工审批决定(HITL)。
|
||||||
// 把决定经 NATS 发给 dispatcher,解除审批节点的阻塞(批准放行 / 拒绝中止)。
|
// 把决定经 NATS 发给 dispatcher,解除审批节点的阻塞(批准放行 / 拒绝中止)。
|
||||||
func (h *Handler) ApproveTask(c *gin.Context) {
|
func (h *Handler) ApproveTask(c *gin.Context) {
|
||||||
|
|||||||
@@ -69,6 +69,11 @@ func (b *Bus) PublishApproval(dec *contract.ApprovalDecision) error {
|
|||||||
return b.inner.PublishApproval(dec)
|
return b.inner.PublishApproval(dec)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SubscribeEval 订阅 dispatcher 回写的自动化评测结果(落 PG)。
|
||||||
|
func (b *Bus) SubscribeEval(onEvent func(*contract.EvalEvent)) (func() error, error) {
|
||||||
|
return b.inner.SubscribeEval(onEvent)
|
||||||
|
}
|
||||||
|
|
||||||
// ServeConfig 让网关作为配置控制面,响应某 kind 的配置请求。
|
// ServeConfig 让网关作为配置控制面,响应某 kind 的配置请求。
|
||||||
func (b *Bus) ServeConfig(kind string, provide func() *contract.ModelConfig) (func() error, error) {
|
func (b *Bus) ServeConfig(kind string, provide func() *contract.ModelConfig) (func() error, error) {
|
||||||
return b.inner.ServeConfig(kind, provide)
|
return b.inner.ServeConfig(kind, provide)
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ func New(db *store.Postgres, cache *store.Redis, bus *nats.Bus, blobStore *blob.
|
|||||||
p.POST("/tasks", h.SubmitTask) // 解析 DSL 并 Publish 到 NATS(带已验证 uid)
|
p.POST("/tasks", h.SubmitTask) // 解析 DSL 并 Publish 到 NATS(带已验证 uid)
|
||||||
p.GET("/tasks/:id", h.TaskStatus) // 任务生命周期状态(UI 轮询 submitted/running/done/failed/timeout/waiting/rejected)
|
p.GET("/tasks/:id", h.TaskStatus) // 任务生命周期状态(UI 轮询 submitted/running/done/failed/timeout/waiting/rejected)
|
||||||
p.POST("/tasks/:id/approve", h.ApproveTask) // HITL 人工审批决定(批准/拒绝)
|
p.POST("/tasks/:id/approve", h.ApproveTask) // HITL 人工审批决定(批准/拒绝)
|
||||||
|
p.GET("/tasks/:id/eval", h.TaskEval) // 自动化评测结果(综合/质量/忠实度/分级)
|
||||||
p.PUT("/memory", h.SetMemory) // 偏好记忆登记(→ mcp-go memory_upsert)
|
p.PUT("/memory", h.SetMemory) // 偏好记忆登记(→ mcp-go memory_upsert)
|
||||||
p.GET("/memory", h.ListMemory) // 列出当前用户偏好(记忆面板)
|
p.GET("/memory", h.ListMemory) // 列出当前用户偏好(记忆面板)
|
||||||
p.DELETE("/memory", h.DeleteMemory) // 软删一条偏好(?key=)
|
p.DELETE("/memory", h.DeleteMemory) // 软删一条偏好(?key=)
|
||||||
|
|||||||
@@ -22,3 +22,19 @@ type Task struct {
|
|||||||
Status string `gorm:"size:32"` // submitted / running / done / failed / timeout
|
Status string `gorm:"size:32"` // submitted / running / done / failed / timeout
|
||||||
Detail string `gorm:"type:text"` // 失败/超时原因等(状态机回写)
|
Detail string `gorm:"type:text"` // 失败/超时原因等(状态机回写)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Eval 是一次任务的自动化评测结果(dispatcher 评完经 NATS 回写,每任务一条,按 task_id upsert)。
|
||||||
|
type Eval struct {
|
||||||
|
BaseModel
|
||||||
|
TaskID string `gorm:"uniqueIndex;size:64"`
|
||||||
|
Overall float64 // 综合分 [0,1]
|
||||||
|
Rule float64 // 规则分
|
||||||
|
LLM float64 // LLM 质量分
|
||||||
|
Faithful float64 // RAG 忠实度分(0=无来源未评)
|
||||||
|
Level string `gorm:"size:16"` // ok / warn / poor
|
||||||
|
Flags string `gorm:"type:text"` // 命中问题(JSON 数组字符串)
|
||||||
|
Reason string `gorm:"type:text"` // 评语
|
||||||
|
Sources int // 检索来源数
|
||||||
|
}
|
||||||
|
|
||||||
|
func (Eval) TableName() string { return "sundynix_eval" }
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"gorm.io/driver/postgres"
|
"gorm.io/driver/postgres"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
"gorm.io/gorm/schema"
|
"gorm.io/gorm/schema"
|
||||||
|
|
||||||
"github.com/sundynix/sundynix-shared/contract"
|
"github.com/sundynix/sundynix-shared/contract"
|
||||||
@@ -65,7 +66,7 @@ func OpenPostgres(dsn string) *Postgres {
|
|||||||
migrateLegacyIntIDs(db)
|
migrateLegacyIntIDs(db)
|
||||||
migrateDocLinkToID(db)
|
migrateDocLinkToID(db)
|
||||||
|
|
||||||
if err := db.AutoMigrate(&User{}, &Task{}, &LLMModel{}, &KB{}, &Doc{}, &Agent{}, &DocLink{}, &Pricing{}); err != nil {
|
if err := db.AutoMigrate(&User{}, &Task{}, &Eval{}, &LLMModel{}, &KB{}, &Doc{}, &Agent{}, &DocLink{}, &Pricing{}); err != nil {
|
||||||
log.Printf("[store] postgres AutoMigrate 失败,降级运行: %v", err)
|
log.Printf("[store] postgres AutoMigrate 失败,降级运行: %v", err)
|
||||||
return &Postgres{}
|
return &Postgres{}
|
||||||
}
|
}
|
||||||
@@ -144,6 +145,31 @@ func (p *Postgres) GetTaskStatus(ctx context.Context, id string) (status, detail
|
|||||||
return t.Status, t.Detail
|
return t.Status, t.Detail
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveEval 落库一条评测结果(按 task_id upsert:重评覆盖)。
|
||||||
|
func (p *Postgres) SaveEval(ctx context.Context, e *Eval) error {
|
||||||
|
if p.db == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return p.db.WithContext(ctx).Clauses(clause.OnConflict{
|
||||||
|
Columns: []clause.Column{{Name: "task_id"}},
|
||||||
|
DoUpdates: clause.AssignmentColumns([]string{
|
||||||
|
"overall", "rule", "llm", "faithful", "level", "flags", "reason", "sources", "updated_at",
|
||||||
|
}),
|
||||||
|
}).Create(e).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEval 取一条任务的评测结果(不存在返回 nil)。
|
||||||
|
func (p *Postgres) GetEval(ctx context.Context, taskID string) *Eval {
|
||||||
|
if p.db == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var e Eval
|
||||||
|
if err := p.db.WithContext(ctx).Where("task_id = ?", taskID).First(&e).Error; err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &e
|
||||||
|
}
|
||||||
|
|
||||||
// CountTasks 返回已提交任务数(降级模式返回 0)。
|
// CountTasks 返回已提交任务数(降级模式返回 0)。
|
||||||
func (p *Postgres) CountTasks(ctx context.Context) (int64, error) {
|
func (p *Postgres) CountTasks(ctx context.Context) (int64, error) {
|
||||||
if p.db == nil {
|
if p.db == nil {
|
||||||
|
|||||||
@@ -338,6 +338,31 @@ func (b *Bus) SubscribeTaskStatus(onEvent func(*contract.TaskStatusEvent)) (unsu
|
|||||||
return sub.Unsubscribe, nil
|
return sub.Unsubscribe, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 自动化评测结果回写(core NATS pub-sub)----
|
||||||
|
|
||||||
|
// PublishEval 广播一次评测结果(dispatcher 调用)。
|
||||||
|
func (b *Bus) PublishEval(ev *contract.EvalEvent) error {
|
||||||
|
data, err := json.Marshal(ev)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return b.nc.Publish(contract.SubjectEval, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubscribeEval 订阅评测结果(网关调用,落 PG)。
|
||||||
|
func (b *Bus) SubscribeEval(onEvent func(*contract.EvalEvent)) (unsub func() error, err error) {
|
||||||
|
sub, err := b.nc.Subscribe(contract.SubjectEval, func(m *nats.Msg) {
|
||||||
|
var ev contract.EvalEvent
|
||||||
|
if json.Unmarshal(m.Data, &ev) == nil {
|
||||||
|
onEvent(&ev)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("subscribe eval: %w", err)
|
||||||
|
}
|
||||||
|
return sub.Unsubscribe, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ---- 人工审批(HITL,core NATS pub-sub)----
|
// ---- 人工审批(HITL,core NATS pub-sub)----
|
||||||
|
|
||||||
// PublishApproval 广播一次人工审批决定(网关在收到 UI 的批准/拒绝后调用)。
|
// PublishApproval 广播一次人工审批决定(网关在收到 UI 的批准/拒绝后调用)。
|
||||||
|
|||||||
@@ -37,8 +37,32 @@ const (
|
|||||||
// 人工审批(HITL)决定回传前缀:实际 sundynix.approval.<task_id>。
|
// 人工审批(HITL)决定回传前缀:实际 sundynix.approval.<task_id>。
|
||||||
// core NATS pub-sub:UI 点批准/拒绝 → 网关发到此 → dispatcher 解除审批节点的阻塞。
|
// core NATS pub-sub:UI 点批准/拒绝 → 网关发到此 → dispatcher 解除审批节点的阻塞。
|
||||||
SubjectApproval = "sundynix.approval"
|
SubjectApproval = "sundynix.approval"
|
||||||
|
|
||||||
|
// 自动化评测结果回写:dispatcher 评完经此广播,网关订阅落 PG 并供 UI 查询。core NATS pub-sub。
|
||||||
|
SubjectEval = "sundynix.eval.task"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 评测质量分级(据综合分 + 忠实度阈值,闭环门控/告警用)。
|
||||||
|
const (
|
||||||
|
EvalOK = "ok" // 综合 ≥ 0.75 且无忠实度风险
|
||||||
|
EvalWarn = "warn" // 0.5 ≤ 综合 < 0.75,或忠实度偏低
|
||||||
|
EvalPoor = "poor" // 综合 < 0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
// EvalEvent 是一次自动化评测的结果(经 SubjectEval 回流给网关落库)。
|
||||||
|
type EvalEvent struct {
|
||||||
|
TaskID string `json:"task_id"`
|
||||||
|
Overall float64 `json:"overall"` // 综合分 [0,1]
|
||||||
|
Rule float64 `json:"rule"` // 规则分
|
||||||
|
LLM float64 `json:"llm"` // LLM 质量分
|
||||||
|
Faithful float64 `json:"faithful"` // RAG 忠实度分(0=无来源未评)
|
||||||
|
Level string `json:"level"` // ok / warn / poor
|
||||||
|
Flags []string `json:"flags,omitempty"` // 命中问题(规则 + 未被来源支持)
|
||||||
|
Reason string `json:"reason,omitempty"` // 评语
|
||||||
|
Sources int `json:"sources,omitempty"` // 检索来源数
|
||||||
|
TS int64 `json:"ts"` // unix 毫秒
|
||||||
|
}
|
||||||
|
|
||||||
// 任务生命周期状态机:submitted(网关建任务)→ running(dispatcher 开跑)
|
// 任务生命周期状态机:submitted(网关建任务)→ running(dispatcher 开跑)
|
||||||
// → done / failed / timeout(dispatcher 收尾)。
|
// → done / failed / timeout(dispatcher 收尾)。
|
||||||
// HITL:执行到审批节点 → waiting(等人工决定)→ 批准回 running / 拒绝→rejected。
|
// HITL:执行到审批节点 → waiting(等人工决定)→ 批准回 running / 拒绝→rejected。
|
||||||
|
|||||||
Reference in New Issue
Block a user