feat(memory): P1 长期记忆升级 —— 异步攒批 Consolidate + 软删 + importance/last_seen #1
+1
-1
@@ -187,7 +187,7 @@ RBAC 未做,暂以单管理员账号代理;概览口径必须是**系统级*
|
|||||||
- [x] Map 节点错误传播 + 汇总 ✅ —— writeSection 返回 (body,err) 仅真·LLM 失败计错(预算/无模型是主动降级不算);writeSections 汇总失败数、失败项 Body 带可见标记;mapNode 全失败→置 b.fatalErr(判 failed 非静默 done-空)、部分失败→trace+流式告警。report handleReport 同步提示部分失败。3 单测(全失败/部分精确计数/mapNode 置 fatalErr)。
|
- [x] Map 节点错误传播 + 汇总 ✅ —— writeSection 返回 (body,err) 仅真·LLM 失败计错(预算/无模型是主动降级不算);writeSections 汇总失败数、失败项 Body 带可见标记;mapNode 全失败→置 b.fatalErr(判 failed 非静默 done-空)、部分失败→trace+流式告警。report handleReport 同步提示部分失败。3 单测(全失败/部分精确计数/mapNode 置 fatalErr)。
|
||||||
- [ ] 熔断器接回 failover(harness/circuitbreaker.go 与 llm/failover.go 未接合,可能长卡备用)| M
|
- [ ] 熔断器接回 failover(harness/circuitbreaker.go 与 llm/failover.go 未接合,可能长卡备用)| M
|
||||||
- [x] coordinator 专家超时 ✅ —— specialistTool 加 timeout(默认 specialistTimeout=3min),InvokableRun 包 WithTimeout;超时作为"观察"跳过该专家(err=nil,lead 据其余综合,不中断协调)。2 单测(超时~50ms 跳过 / 正常不受影响)。
|
- [x] coordinator 专家超时 ✅ —— specialistTool 加 timeout(默认 specialistTimeout=3min),InvokableRun 包 WithTimeout;超时作为"观察"跳过该专家(err=nil,lead 据其余综合,不中断协调)。2 单测(超时~50ms 跳过 / 正常不受影响)。
|
||||||
- [ ] Branch else 兜底(compose_compiler.go:155 分支无"都不满足"收口)| M
|
- [x] Branch else 兜底 ✅ —— branchNode 识别 default/else 边,主选择(true/false)未命中时走它;trace 明确区分「走 default / 未匹配收口结束(END) / 正常选路」,杜绝静默落 END 被误当 bug。3 断言单测(default 命中/条件真走 true/无 default 仍空)。compose 层原有 len==0→END 收口保留。
|
||||||
- [ ] DSL 拓扑/节点-工具映射校验(gateway dsl/parser.go:23 TODO,现仅 JSON 格式校验)| M
|
- [ ] DSL 拓扑/节点-工具映射校验(gateway dsl/parser.go:23 TODO,现仅 JSON 格式校验)| M
|
||||||
|
|
||||||
### [ ] T4.F 健壮性 / 安全 / 性能收口(多为 S,可穿插着做)
|
### [ ] T4.F 健壮性 / 安全 / 性能收口(多为 S,可穿插着做)
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ func (o *Orchestrator) branchNode(n dsl.Node, b *board, outs []dsl.Edge, byID ma
|
|||||||
cond := cstr(n.Config, "condition")
|
cond := cstr(n.Config, "condition")
|
||||||
res := evalCondition(cond, b)
|
res := evalCondition(cond, b)
|
||||||
|
|
||||||
var truthy, falsy []string
|
var truthy, falsy, deflt []string
|
||||||
labeled := false
|
labeled := false
|
||||||
for _, e := range outs {
|
for _, e := range outs {
|
||||||
switch e.SourceHandle {
|
switch e.SourceHandle {
|
||||||
@@ -252,6 +252,8 @@ func (o *Orchestrator) branchNode(n dsl.Node, b *board, outs []dsl.Edge, byID ma
|
|||||||
case "false":
|
case "false":
|
||||||
falsy = append(falsy, e.Target)
|
falsy = append(falsy, e.Target)
|
||||||
labeled = true
|
labeled = true
|
||||||
|
case "default", "else": // 显式兜底边:主选择落空时走它
|
||||||
|
deflt = append(deflt, e.Target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,13 +282,28 @@ func (o *Orchestrator) branchNode(n dsl.Node, b *board, outs []dsl.Edge, byID ma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// else 兜底:主选择未命中任何下游时,走显式 default/else 边(若配了),避免悄悄落 END。
|
||||||
|
usedDefault := false
|
||||||
|
if len(chosen) == 0 && len(deflt) > 0 {
|
||||||
|
chosen = deflt
|
||||||
|
usedDefault = true
|
||||||
|
}
|
||||||
|
|
||||||
names := make([]string, 0, len(chosen))
|
names := make([]string, 0, len(chosen))
|
||||||
for _, id := range chosen {
|
for _, id := range chosen {
|
||||||
names = append(names, labelOf(byID[id], id))
|
names = append(names, labelOf(byID[id], id))
|
||||||
}
|
}
|
||||||
|
// 明确记录选路:走 default / 未匹配收口结束 / 正常选路,杜绝"静默落 END"被误当 bug。
|
||||||
|
dest := "[" + strings.Join(names, ", ") + "]"
|
||||||
|
switch {
|
||||||
|
case usedDefault:
|
||||||
|
dest = "default → " + dest
|
||||||
|
case len(chosen) == 0:
|
||||||
|
dest = "未匹配任何下游 → 收口结束(END)"
|
||||||
|
}
|
||||||
tr.info("branch:"+n.ID, "system", labelOf(n, "分支"),
|
tr.info("branch:"+n.ID, "system", labelOf(n, "分支"),
|
||||||
fmt.Sprintf("条件「%s」→ %v ⇒ 走 [%s](%s)",
|
fmt.Sprintf("条件「%s」→ %v ⇒ 走 %s(%s)",
|
||||||
firstNonEmpty(cond, "(空=真)"), res, strings.Join(names, ", "), mode))
|
firstNonEmpty(cond, "(空=真)"), res, dest, mode))
|
||||||
return chosen
|
return chosen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,6 +108,33 @@ func TestBranchNode_OrderFallback(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestBranchNode_DefaultFallback(t *testing.T) {
|
||||||
|
o := &Orchestrator{}
|
||||||
|
tr := &execTracer{}
|
||||||
|
byID := map[string]dsl.Node{"A": {ID: "A"}, "D": {ID: "D"}}
|
||||||
|
// 只有 true 边 + default 边,无 false 边。
|
||||||
|
outs := []dsl.Edge{
|
||||||
|
{Source: "br", Target: "A", SourceHandle: "true"},
|
||||||
|
{Source: "br", Target: "D", SourceHandle: "default"},
|
||||||
|
}
|
||||||
|
n := dsl.Node{ID: "br", Kind: "branch", Config: map[string]any{"condition": "1>2"}}
|
||||||
|
// 条件假 → true 未命中 → 走 default(D),而非悄悄落 END。
|
||||||
|
if got := o.branchNode(n, &board{}, outs, byID, tr); len(got) != 1 || got[0] != "D" {
|
||||||
|
t.Errorf("条件假且无 false 边应走 default(D), got %v", got)
|
||||||
|
}
|
||||||
|
// 条件真 → 正常走 true(A),default 不介入。
|
||||||
|
n.Config["condition"] = "2>1"
|
||||||
|
if got := o.branchNode(n, &board{}, outs, byID, tr); len(got) != 1 || got[0] != "A" {
|
||||||
|
t.Errorf("条件真应走 true(A), got %v", got)
|
||||||
|
}
|
||||||
|
// 无 default 边时,未命中仍返回空(兜底不凭空造边,交上层收口 END)。
|
||||||
|
noDef := []dsl.Edge{{Source: "br", Target: "A", SourceHandle: "true"}}
|
||||||
|
n.Config["condition"] = "1>2"
|
||||||
|
if got := o.branchNode(n, &board{}, noDef, byID, tr); len(got) != 0 {
|
||||||
|
t.Errorf("无 default 且未命中应为空, got %v", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSmallHelpers(t *testing.T) {
|
func TestSmallHelpers(t *testing.T) {
|
||||||
cfg := map[string]any{"s": " hi ", "n": 7, "b": true, "nil": nil}
|
cfg := map[string]any{"s": " hi ", "n": 7, "b": true, "nil": nil}
|
||||||
if cstr(cfg, "s") != "hi" {
|
if cstr(cfg, "s") != "hi" {
|
||||||
|
|||||||
Reference in New Issue
Block a user