feat(agent): 多智能体协同 v1 —— coordinator 节点(Eino×Anthropic 融合)
LLM 自主在 agent 间路由/委派:orchestrator=ReAct(Eino 出机器),编排认知按 Anthropic orchestrator-worker 配方(出脑子)。专家=包成工具的子 agent(agent-as-tool),lead 给 每个专家写定制简报(brief)后并行派发、综合。方案见 MULTI_AGENT.md。 为什么 agent-as-tool 而非 Eino host:host 的 specialist 拿原始输入(preHandler return state.msgs),传不了 lead 写的定制简报,而定制简报正是 Anthropic 多智能体的 精髓。agent-as-tool 让 orchestrator 自己 emit 工具调用、参数 brief 即简报。 = OpenAI agent.as_tool() / Anthropic 研究系统的 orchestrator-worker。 - coordinator.go: specialistTool(react.Agent/ChatModel 包成 InvokableTool,入参 brief, 精炼返回) + parseSpecialists/buildSpecialists(带工具→react,不带→ChatModel,MCP 工具 按 spec.tools 过滤) + runCoordinator(lead 提示词=Anthropic 配方) + leadOrchestratorPrompt。 - 双路接入 execDSLNode(compose)+ runGraph(graph.go)的 case coordinator。 - 护栏:禁套娃(专家是内联叶子)/ MaxStep / 专家 I/O 计入共享 Budget / 降级(无 ToolCallingModel 或 0 专家 → runAgent)。 - streamAgentReply:抽出 runReactAgent 与 runCoordinator 共用的流式回流尾段。 - 复用即得:evaluator-optimizer=harness 低分纠偏;成本天花板=预算护栏;上下文隔离= 专家独立 react.Agent;观测=每次派发落 agent 轨迹。 测试:parseSpecialists / agent-as-tool 包装(brief 透传+精炼返回+失败作观察) / 降级。 live 验证(真 deepseek):两专家**并行派发**、lead 给各自写**不同定制简报**、最终 **综合**(非拼接)成稿,评测 1.00。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -225,7 +225,12 @@ func (o *Orchestrator) runReactAgent(ctx context.Context, taskID string, b *boar
|
||||
return
|
||||
}
|
||||
defer sr.Close()
|
||||
o.streamAgentReply(ctx, taskID, b, sr, tr, node, "ReAct 智能体", t0)
|
||||
}
|
||||
|
||||
// streamAgentReply 把一个 agent / 协调者的流式输出回流到 sink:跨分片脱敏 + 计输出 token +
|
||||
// 落产出(供下游接力) + 轨迹收尾。runReactAgent 与 runCoordinator 共用,杜绝两路尾段漂移。
|
||||
func (o *Orchestrator) streamAgentReply(ctx context.Context, taskID string, b *board, sr *schema.StreamReader[*schema.Message], tr *execTracer, node, label string, t0 time.Time) {
|
||||
chunks := 0
|
||||
var produced strings.Builder // 本节点产出(供下游 agent 接力)
|
||||
red := harness.NewStreamRedactor() // 输出护栏:跨分片脱敏,杜绝密钥被切断而漏检
|
||||
@@ -243,7 +248,7 @@ func (o *Orchestrator) runReactAgent(ctx context.Context, taskID string, b *boar
|
||||
break
|
||||
}
|
||||
if rerr != nil {
|
||||
tr.emit(node, "model", "error", "ReAct 智能体", rerr.Error(), time.Since(t0).Milliseconds())
|
||||
tr.emit(node, "model", "error", label, rerr.Error(), time.Since(t0).Milliseconds())
|
||||
return
|
||||
}
|
||||
if chunk.Content == "" {
|
||||
@@ -256,6 +261,6 @@ func (o *Orchestrator) runReactAgent(ctx context.Context, taskID string, b *boar
|
||||
bud.AddComplete(produced.String()) // 成本护栏:计入输出 token
|
||||
}
|
||||
o.recordAgentOutput(b, produced.String())
|
||||
tr.emit(node, "model", "end", "ReAct 智能体",
|
||||
tr.emit(node, "model", "end", label,
|
||||
fmt.Sprintf("%d 段输出 / %d 字", chunks, len([]rune(produced.String()))), time.Since(t0).Milliseconds())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user