作者:互联网 时间: 2026-06-30 09:03:52
单 Agent 处理任何问题有一个根本限制:上下文会越来越长。

当你要求同一个 Agent 既搜索资料、又写代码、又做数学计算,历史消息越积越多,模型要在所有历史里找线索,注意力被稀释。而且,同一个系统 Prompt 很难同时把三个角色都交代清楚——"你是搜索员,也是程序员,也是数学家"——通常意味着什么都不精。
多 Agent 的核心价值就在这里:每个 Agent 只看自己需要的上下文,专注自己的职责。
Eino 源码注释里明确写着(agent_tool.go:69):
复制代码// NewAgentTool creates a tool that wraps an agent for invocation.
用法:把一个子 Agent 包装成工具,父 Agent 通过普通的工具调用来"雇用"它。这是 ADK 官方唯一推荐的 Multi-Agent 模式。
复制代码// 创建子 Agent
researchAgent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "research_agent",
Description: "the agent responsible to search the internet for info",
Instruction: "You are a research agent...",
Model: m,
ToolsConfig: adk.ToolsConfig{...},
})// 把子 Agent 变成一个工具
researchTool := adk.NewAgentTool(ctx, researchAgent)// 父 Agent 把这个"工具"加到自己的工具列表
parentAgent, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "project_manager",
Model: m,
ToolsConfig: adk.ToolsConfig{
ToolsNodeConfig: compose.ToolsNodeConfig{
Tools: []tool.BaseTool{researchTool, codeTool, reviewTool},
},
},
})
从外部看,父 Agent 还是正常的 ReAct 循环,只是"工具"恰好是另一个 Agent。父 Agent 不关心子 Agent 内部怎么工作——它只是调用了一个名叫 research_agent 的工具,传入 {"request": "find 2024 US GDP"}, 等待返回结果。
子 Agent 内部可能触发 Exit、Transfer、BreakLoop 等动作,但这些动作不会传到父 Agent(源码 agent_tool.go:90-93):
复制代码// Action Scoping:
// - Interrupted: Propagated via CompositeInterrupt (interrupt/resume works across boundaries)
// - Exit, TransferToAgent, BreakLoop: Ignored outside the agent tool
白话:子 Agent 想"结束任务",对它自己来说任务确实结束了,但父 Agent 只看到"这个工具调用完成了,返回了结果"。子 Agent 不能意外终止父 Agent 的执行流。唯一的例外是 Interrupted(中断等待人工确认),这个会通过 CompositeInterrupt 传递出去,让整个系统都知道需要暂停等待用户。
supervisor.New 是 AgentTool 之上的一个预制封装(源码在 adk/prebuilt/supervisor):
复制代码// eino-examples/adk/multiagent/supervisor/agent.go:192
supervisorAgent, err := supervisor.New(ctx, &supervisor.Config{
Supervisor: sv, // 主控 Agent
SubAgents: []adk.Agent{searchAgent, mathAgent}, // 子 Agent 列表
})
supervisor.New 自动把每个子 Agent 包装成 AgentTool,注入到主控 Agent 的工具列表里。你只需要定义主控 Agent 的 Instruction("你是项目经理,有两个下属……"),其余的工具调用由 ReAct 循环自动处理。
这是另一套机制——让模型自己决定"我要交给哪个 Agent"。
复制代码// eino-examples/adk/intro/transfer/transfer.go:35
a, err := adk.SetSubAgents(ctx, routerAgent, []adk.Agent{chatAgent, weatherAgent})
SetSubAgents 做了两件事:
transfer_to_agent(参数:agent_name)执行时,RouterAgent 的模型看到用户问题,自己判断该交给谁,然后调用 transfer_to_agent("WeatherAgent")——相当于给自己的下一步指路。
复制代码用户:"北京今天天气?"
RouterAgent → 判断:这是天气相关 → 调用 transfer_to_agent(agent_name="WeatherAgent")
WeatherAgent → 调用 get_weather(city="Beijing") → 返回结果
这个模式看起来自然,但 Eino 官方在源码里明确标注(utils.go:92-95):
复制代码// NOT RECOMMENDED: Agent transfer with full context sharing between agents has not
// proven to be more effective empirically. Consider using ChatModelAgent with AgentTool
// or DeepAgent instead for most multi-agent scenarios.
核心问题:全上下文共享。当 RouterAgent 的会话历史传给 WeatherAgent 时,WeatherAgent 要处理一堆它不需要的上下文(RouterAgent 的历史消息),既浪费 Token,又可能干扰判断。
ADK 在实现 Transfer 时做了一个折中(deterministic_transfer.go:166)——创建 IsolatedSession:
复制代码isolatedSession := &runSession{
Values: parentSession.Values, // 共享 session values(键值对)
valuesMtx: parentSession.valuesMtx,
// Events: 不继承(默认为空)
}
子 Agent 有独立的事件历史(不继承父 Agent 的全部消息),但共享 session.Values(通过 AddSessionValue/GetSessionValues 存取的键值对)。这样可以在两个 Agent 间传递少量结构化状态,同时避免把全部对话历史扔过去。
有时候移交目标不需要 AI 判断,就是固定的。AgentWithDeterministicTransferTo 用于这个场景:
复制代码// 执行完 agentA,固定移交给 agentB
wrappedA := adk.AgentWithDeterministicTransferTo(ctx, &adk.DeterministicTransferConfig{
Agent: agentA,
ToAgentNames: []string{"agentB"},
})
执行完 agentA 的全部逻辑后,框架自动追加两条消息(assistant 说"我要移交给 agentB" + tool 确认消息),然后触发 TransferToAgent 动作,Session 流转到 agentB。
白话:这是硬编码的流水线——A 完事了一定交给 B,不经过任何 AI 决策。适合固定流程("报告总结完毕,一定发给审阅 Agent"),不适合根据内容动态路由。
同样标注了 NOT RECOMMENDED,原因相同:全上下文共享。
| AgentTool(推荐) | SetSubAgents | DeterministicTransfer | |
|---|---|---|---|
| 路由决策者 | 父 Agent 的 LLM(工具调用) | Router Agent 的 LLM | 代码硬编码 |
| 上下文共享 | 隔离(只传 request 字符串) | 全部共享(IsolatedSession 折中) | 全部共享 |
| 子 Agent 动作边界 | Exit/Transfer 不传出 | 全部传出 | 全部传出 |
| 适合场景 | 绝大多数场景 | 特定路由式场景 | 固定流水线 |
| 官方立场 | 推荐 | ️ 不推荐 | ️ 不推荐 |
复制代码// eino-examples/adk/multiagent/supervisor/agent.go(精简)
func buildSupervisor(ctx context.Context) (adk.Agent, error) {
sv, _ := adk.NewChatModelAgent(ctx, &adk.ChatModelAgentConfig{
Name: "supervisor",
Instruction: `You are a supervisor managing two agents:
- a research agent: assign research-related tasks
- a math agent: assign math-related tasks
Do not do any work yourself.`,
Model: m,
Exit: &adk.ExitTool{}, // 主控完成后用 exit 工具退出
}) searchAgent, _ := buildSearchAgent(ctx) // 有 search 工具的 Agent
mathAgent, _ := buildMathAgent(ctx) // 有 add/multiply/divide 工具的 Agent // supervisor.New 内部自动 NewAgentTool 包装每个子 Agent
return supervisor.New(ctx, &supervisor.Config{
Supervisor: sv,
SubAgents: []adk.Agent{searchAgent, mathAgent},
})
}
运行时发生的事:
复制代码用户:"Find US and NY GDP in 2024. What % of US GDP was NY?"1. Supervisor 收到问题
2. Supervisor 决定:先搜索 → 调用 research_agent 工具,传入问题
3. research_agent 内部:搜索工具 → 返回 "US $29.18T, NY $2.297T"
4. Supervisor 收到搜索结果
5. Supervisor 决定:再计算 → 调用 math_agent 工具,传入数字
6. math_agent 内部:divide(2.297, 29.18) → 0.0787
7. Supervisor 汇总结果,调用 exit 工具,结束
主控 Agent 从未自己搜索或计算,只做"任务分配 + 汇总"。每个子 Agent 只看自己的 request,不知道其他 Agent 的存在。
当子 Agent(AgentTool 模式)完成任务后,父 Agent 的对话历史里只有:
{"tool": "research_agent", "input": {"request": "..."}}{"result": "US GDP was $29.18T..."}子 Agent 内部的全部过程(搜索了哪些网页、中间想了什么)不进入父 Agent 的上下文。这是 AgentTool 的设计意图:结果传递,过程隔离。
如果父 Agent 需要子 Agent 的中间事件(比如流式展示子 Agent 的思考过程),可以开启 EmitInternalEvents:
复制代码adk.ToolsConfig{
EmitInternalEvents: true, // 子 Agent 的事件实时推送给 Runner 的消费者
...
}
注意:这些内部事件只推给外部消费者(UI 显示),不记录在父 Agent 的 runSession 里——父 Agent 的历史依然只有工具调用和结果,不会因此膨胀。
AgentTool 是唯一被官方推荐的 Multi-Agent 模式,原因很简单:上下文隔离,边界清晰。父 Agent 通过 ReAct 工具调用驱动子 Agent,子 Agent 只看到自己的输入,不背负无关历史。supervisor.New 是在 AgentTool 之上的预制封装,适合"主控 + 多专家"的常见结构。SetSubAgents 和 DeterministicTransfer 虽然存在且功能可用,但源码里的 NOT RECOMMENDED 注释来自团队实测——在绝大多数场景下,AgentTool 效果更好,不要忽视这个提示。
下篇继续。
代码来源:cloudwego/eino · cloudwego/eino-examples