Agent Transfer:让 AI 把任务交给更合适的 AI

系列「企业级 AI Agent 实现拆解」E19 篇。上一篇 E18 讲了 ADK 的基础用法:ChatModelAgent、Runner、Interrupt/Resume。这篇聚焦 Multi-Agent 协作的核心问题------一个 Agent 怎么把任务交给另一个 Agent。ADK 提供了三种机制,但只有一种被官方推荐,另外两种留有明确的"NOT RECOMMENDED"注释。

读完这篇你会知道

  • 为什么需要 Agent Transfer:单 Agent 处理不了哪些场景
  • 三种协作机制:AgentTool(推荐)、SetSubAgents(不推荐)、Supervisor(不推荐)
  • AgentTool 的边界隔离:子 Agent 的 Exit/Transfer 动作为什么不传出来
  • Transfer 模式下对话历史怎么迁移:IsolatedSession 的设计
  • 确定性移交:DeterministicTransfer 的用法
  • 三种机制的实测对比

一、为什么一个 Agent 不够

单 Agent 处理任何问题有一个根本限制:上下文会越来越长

当你要求同一个 Agent 既搜索资料、又写代码、又做数学计算,历史消息越积越多,模型要在所有历史里找线索,注意力被稀释。而且,同一个系统 Prompt 很难同时把三个角色都交代清楚------"你是搜索员,也是程序员,也是数学家"------通常意味着什么都不精。

多 Agent 的核心价值就在这里:每个 Agent 只看自己需要的上下文,专注自己的职责


二、方案一(推荐):把 Agent 包装成工具

Eino 源码注释里明确写着(agent_tool.go:69):

arduino 复制代码
// NewAgentTool creates a tool that wraps an agent for invocation.

用法:把一个子 Agent 包装成工具,父 Agent 通过普通的工具调用来"雇用"它。这是 ADK 官方唯一推荐的 Multi-Agent 模式

go 复制代码
// 创建子 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):

arduino 复制代码
// 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 预制模式

supervisor.New 是 AgentTool 之上的一个预制封装(源码在 adk/prebuilt/supervisor):

go 复制代码
// 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 循环自动处理。


三、方案二(不推荐):LLM 驱动的 Agent 切换

这是另一套机制------让模型自己决定"我要交给哪个 Agent"。

go 复制代码
// eino-examples/adk/intro/transfer/transfer.go:35
a, err := adk.SetSubAgents(ctx, routerAgent, []adk.Agent{chatAgent, weatherAgent})

SetSubAgents 做了两件事:

  1. 把子 Agent 的名字和描述告诉 Router Agent(注入到系统 Prompt)
  2. 给 Router Agent 加一个内置工具 transfer_to_agent(参数:agent_name

执行时,RouterAgent 的模型看到用户问题,自己判断该交给谁,然后调用 transfer_to_agent("WeatherAgent")------相当于给自己的下一步指路。

ini 复制代码
用户:"北京今天天气?"
RouterAgent → 判断:这是天气相关 → 调用 transfer_to_agent(agent_name="WeatherAgent")
WeatherAgent → 调用 get_weather(city="Beijing") → 返回结果

这个模式看起来自然,但 Eino 官方在源码里明确标注(utils.go:92-95):

less 复制代码
// 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

go 复制代码
isolatedSession := &runSession{
    Values:    parentSession.Values,   // 共享 session values(键值对)
    valuesMtx: parentSession.valuesMtx,
    // Events: 不继承(默认为空)
}

子 Agent 有独立的事件历史(不继承父 Agent 的全部消息),但共享 session.Values(通过 AddSessionValue/GetSessionValues 存取的键值对)。这样可以在两个 Agent 间传递少量结构化状态,同时避免把全部对话历史扔过去。


四、方案三(不推荐):确定性移交

有时候移交目标不需要 AI 判断,就是固定的。AgentWithDeterministicTransferTo 用于这个场景:

go 复制代码
// 执行完 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 不传出 全部传出 全部传出
适合场景 绝大多数场景 特定路由式场景 固定流水线
官方立场 ✅ 推荐 ⚠️ 不推荐 ⚠️ 不推荐

六、完整示例:Supervisor 模式(AgentTool 推荐路径)

go 复制代码
// 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},
    })
}

运行时发生的事:

bash 复制代码
用户:"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 调用请求:{"tool": "research_agent", "input": {"request": "..."}}
  • Tool 返回结果:{"result": "US GDP was $29.18T..."}

子 Agent 内部的全部过程(搜索了哪些网页、中间想了什么)不进入父 Agent 的上下文 。这是 AgentTool 的设计意图:结果传递,过程隔离

如果父 Agent 需要子 Agent 的中间事件(比如流式展示子 Agent 的思考过程),可以开启 EmitInternalEvents

go 复制代码
adk.ToolsConfig{
    EmitInternalEvents: true,  // 子 Agent 的事件实时推送给 Runner 的消费者
    ...
}

注意:这些内部事件只推给外部消费者(UI 显示),不记录在父 Agent 的 runSession 里------父 Agent 的历史依然只有工具调用和结果,不会因此膨胀。


小结

AgentTool 是唯一被官方推荐的 Multi-Agent 模式,原因很简单:上下文隔离,边界清晰 。父 Agent 通过 ReAct 工具调用驱动子 Agent,子 Agent 只看到自己的输入,不背负无关历史。supervisor.New 是在 AgentTool 之上的预制封装,适合"主控 + 多专家"的常见结构。SetSubAgentsDeterministicTransfer 虽然存在且功能可用,但源码里的 NOT RECOMMENDED 注释来自团队实测------在绝大多数场景下,AgentTool 效果更好,不要忽视这个提示。

下篇继续。


代码来源:cloudwego/eino · cloudwego/eino-examples

相关推荐
universeplayer1 小时前
天天用 Claude Code 和 Codex,但你比过它们在你自己的活上谁更强吗?我写了个工具让它们同台开打
ai编程·claude·cursor
后端小肥肠2 小时前
Codex + Obsidian 做人生副本视频:输入主题文案,直通剪映草稿
人工智能·aigc·agent
花椒技术2 小时前
Agent 不只会聊天:我们如何用 CLI 整理业务能力入口
agent·ai编程·mcp
DigitalOcean3 小时前
在云端运行 Codex —— DigitalOcean Codex 插件正式推出
agent
FanetheDivine4 小时前
学习Agent开发6 langgraph速览
agent·ai编程
coderhuo4 小时前
惊呆了:AI改了三个字节,修好了一个跑不起来的adb
ai编程
threerocks5 小时前
什么?我连 A2A、MCP 都没学会,现在又来了 AG-UI、A2UI.
前端·aigc·ai编程
Coffeeee6 小时前
两个例子,帮你快速理解什么是Token
人工智能·程序员·ai编程
饼干哥哥6 小时前
用AI全自动剪辑,日更 100条爆款视频——HyperFrames、Remotion、Git使用入门
人工智能·机器学习·ai编程