fix(dispatcher): Map 节点错误传播 —— 并行子项失败不再静默(T4.E)
- writeSection 返回 (body, error):仅真·LLM 调用失败返 err;预算触顶/模型未配置 是主动降级(可见降级正文,err=nil)不计失败 - writeSections 返回 ([]section, failed):失败项 Body 带可见「撰写失败」标记 + 汇总失败数 - mapNode:全部子项失败 → 置 b.fatalErr(任务判 failed 而非静默 done-空); 部分失败 → trace span + 流式 ⚠️ 告警 - report handleReport:部分章节失败时流式提示,不再当全成功 - 3 单测:全失败/部分精确计数(=1 非 all-or-nothing)/mapNode 置 fatalErr Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -68,7 +68,7 @@ func (o *Orchestrator) mapNode(ctx context.Context, taskID string, n dsl.Node, b
|
||||
end(fmt.Sprintf("拆出 %d 项:%s", len(items), strings.Join(items, " / ")), nil)
|
||||
|
||||
o.emit(taskID, fmt.Sprintf("\n> 并行处理 %d 项…\n\n", len(items)))
|
||||
secs := o.writeSections(ctx, b.query, b.kb, items, tr) // 有界并发,trace 出 section:i 各项
|
||||
secs, failed := o.writeSections(ctx, b.query, b.kb, items, tr) // 有界并发,trace 出 section:i 各项
|
||||
b.sections = secs
|
||||
for _, s := range secs {
|
||||
chunk := "## " + s.Heading + "\n\n" + s.Body + "\n\n"
|
||||
@@ -76,6 +76,20 @@ func (o *Orchestrator) mapNode(ctx context.Context, taskID string, n dsl.Node, b
|
||||
b.answer += chunk
|
||||
b.refs = append(b.refs, s.Heading+":"+s.Body)
|
||||
}
|
||||
// 错误传播:并行子项不再静默丢失——汇总成败;全失败则置致命错,让任务判 failed 而非 done-空。
|
||||
if failed > 0 {
|
||||
endMap := tr.span("map:"+n.ID+":result", "plan", "并行结果汇总")
|
||||
if failed >= len(items) && len(items) > 0 {
|
||||
if b.fatalErr == nil {
|
||||
b.fatalErr = fmt.Errorf("并行 fan-out 全部 %d 项撰写失败", failed)
|
||||
}
|
||||
endMap(fmt.Sprintf("全部 %d 项失败", failed), b.fatalErr)
|
||||
} else {
|
||||
note := fmt.Sprintf("%d/%d 项失败(已标注),其余成功", failed, len(items))
|
||||
endMap(note, nil)
|
||||
o.emit(taskID, fmt.Sprintf("\n> ⚠️ %s\n\n", note))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// execToolNode 执行工具节点:调 MCP 工具,产出累计进黑板;失败降级不阻断。
|
||||
|
||||
Reference in New Issue
Block a user