ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Agent 间 Transfer 交接:用户在不同 Agent 间无缝切换(第93篇-E79)

Agent 间 Transfer 交接:用户在不同 Agent 间无缝切换(第93篇-E79) 上一篇 拆了三种 ADK prebuilt 模式其中 Supervisor 用 transfer 机制让 agent 之间交接任务。第91篇 讲 Host MultiAgent 时host 把任务交给 specialist 也是一种交接。这篇聚焦一个问题Agent 之间怎么交接三个具体问题transfer 是怎么发生的从 LLM 调 tool 到 flowAgent 启动子 agent 的完整链路交接时上下文怎么传rewriteMessage 重写历史 session 共享为什么 transfer 被标注 NOT RECOMMENDEDAgentTool 又怎么做一Transfer 的完整链路从 LLM 决策到 flowAgent 启动第一步ChatModelAgent 自动注入 transfer_to_agent 工具chatmodel.go:874-927的prepareExecContext在每个 agent 启动时运行// chatmodel.go:881-891transferToAgents:a.subAgentsifa.parentAgent!nil!a.disallowTransferToParent{transferToAgentsappend(transferToAgents,a.parentAgent)}iflen(transferToAgents)0{transferInstruction:genTransferToAgentInstruction(ctx,transferToAgents)instructionconcatInstructions(instruction,transferInstruction)toolsNodeConf.Toolsappend(toolsNodeConf.Tools,transferToAgent{})returnDirectly[TransferToAgentToolName]true}三个关键动作收集可 transfer 的 agentsubAgents parentAgent如果没禁止 transfer 回 parent注入 instruction告诉 LLM 有哪些 agent 可以 transfer 到以及什么时候该 transfer注册 transfer_to_agent 工具让 LLM 能通过 tool call 发起 transfer设置 ReturnDirectlytransfer 工具调用后直接返回不再经过 LLM第二步LLM 决定 transferinstruction.go:28-42定义了 transfer 的 instruction中英双语可用的其他 agent - Agent 名字: MathAgent Agent 描述: 执行数学计算 决策规则 - 如果根据你的职责描述你最适合回答这个问题ANSWER - 如果根据其职责描述另一个 agent 更适合调用 transfer_to_agent 函数并传入该 agent 的名称 当进行移交时只输出函数调用不要输出其他任何内容LLM 看到这段 instruction 后如果判断自己不合适会调用transfer_to_agent工具参数agent_name填目标 agent 名。第三步transfer_to_agent 工具执行chatmodel.go:661-677的transferToAgent.InvokableRunfunc(tta transferToAgent)InvokableRun(ctx context.Context,argumentsInJSONstring,_...tool.Option)(string,error){params:transferParams{}sonic.UnmarshalString(argumentsInJSON,params)// 关键通过 SendToolGenAction 把 TransferToAgentAction 附加到工具事件errSendToolGenAction(ctx,TransferToAgentToolName,NewTransferToAgentAction(params.AgentName))returntransferToAgentToolOutput(params.AgentName),nil}SendToolGenActionreact.go:274-285把TransferToAgentAction存到 Eino 图状态中当工具事件发出时这个 action 会被附加到事件上。第四步flowAgent 处理 transferflow.go:481-582的flowAgent.run()是 transfer 的核心处理逻辑// flow.go:554-581vardestNamestringiflastAction!nil{iflastAction.TransferToAgent!nil{destNamelastAction.TransferToAgent.DestAgentName}}ifdestName!{agentToRun:a.getAgent(ctxForSubAgents,destName)ifagentToRunnil{generator.Send(AgentEvent{Err:fmt.Errorf(transfer failed: agent %s not found,destName)})return}subAIter:agentToRun.Run(ctxForSubAgents,nil,...)for{subEvent,ok_:subAIter.Next()if!ok_{break}generator.Send(subEvent)}}流程agent 运行完 → 检查最后一个 action 是否有TransferToAgent→ 通过getAgent()在 subAgents 和 parentAgent 中查找 → 找到后调用agentToRun.Run()启动目标 agent。getAgent去哪儿找flow.go:174-186func(a*flowAgent)getAgent(ctx context.Context,namestring)*flowAgent{for_,subAgent:rangea.subAgents{ifsubAgent.Name(ctx)name{returnsubAgent}}ifa.parentAgent!nila.parentAgent.Name(ctx)name{returna.parentAgent}returnnil}先在 subAgents 里找再在 parentAgent 里找。这意味着一个 agent 可以 transfer 到它的子 agent下属或父 agent上级但不能 transfer 到 sibling同级。二交接时上下文怎么传rewriteMessage Session 共享transfer 的核心问题是目标 agent 怎么知道之前发生了什么Session 共享所有 agent 共享同一个runSessionflow.go:352-396。每个 agent 产出的事件都写入同一个 session后续 agent 通过genAgentInputflow.go:275-328从 session 读取事件历史来构建自己的输入。rewriteMessage标注来源flow.go:188-253的rewriteMessage是关键funcrewriteMessage(msg Message,agentNamestring)Message{varsb strings.Builder sb.WriteString(For context:)ifmsg.Roleschema.Assistant{ifmsg.Content!{sb.WriteString(fmt.Sprintf( [%s] said: %s.,agentName,msg.Content))}// tool call 也标注fori:rangemsg.ToolCalls{f:msg.ToolCalls[i].Function sb.WriteString(fmt.Sprintf( [%s] called tool: %s with arguments: %s.,agentName,f.Name,f.Arguments))}}elseifmsg.Roleschema.Toolmsg.Content!{sb.WriteString(fmt.Sprintf( [%s] %s tool returned result: %s.,agentName,msg.ToolName,msg.Content))}rewritten:schema.UserMessage(sb.String())returnrewritten}作用把非本 agent 产出的消息重写为 “For context: [AgentX] said: …” 格式注入到目标 agent 的输入中。目标 agent 就知道这是别人说的不是我的记忆。genAgentInput构建目标 agent 的输入flow.go:275-328func(a*flowAgent)genAgentInput(ctx context.Context,runCtx*runContext,skipTransferMessagesbool)(*AgentInput,error){input:deepCopyAgentInput(runCtx.RootInput)events:runCtx.Session.getEvents()historyEntries:make([]*HistoryEntry,0)// 先放用户原始输入for_,m:rangeinput.Messages{historyEntriesappend(historyEntries,HistoryEntry{IsUserInput:true,Message:m})}// 再放 session 中的所有事件for_,event:rangeevents{// 跳过 transfer 消息如果 skipTransferMessagestrueifskipTransferMessagesevent.Action!nilevent.Action.TransferToAgent!nil{continue}msg,err:getMessageFromWrappedEvent(event)historyEntriesappend(historyEntries,HistoryEntry{AgentName:event.AgentName,Message:msg})}// 调用 historyRewriter 重写messages,err:a.historyRewriter(ctx,historyEntries)input.Messagesmessagesreturninput,nil}buildDefaultHistoryRewriterflow.go:330-350对每条非用户输入的消息调用genMsg如果消息来源 agent 不是当前 agent就调用rewriteMessage标注来源。这就是 NOT RECOMMENDED 的原因源码中多处标注 NOT RECOMMENDED。flow.go:70-77的SetSubAgentsNOT 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.“经验证明这个方向不如另一个方向”。具体问题完整上下文共享每 transfer 一次目标 agent 的上下文就多一段 rewrite 后的历史。多轮 transfer 后上下文膨胀很快。缺乏隔离目标 agent 能看到所有前置 agent 的完整历史包括不该它关心的信息。消息重写损失信息rewriteMessage 把 assistant/tool 消息重写为 user 消息可能丢失角色语义。三AgentWithDeterministicTransferTo限制 transfer 方向deterministic_transfer.go:43-54funcAgentWithDeterministicTransferTo(_context.Context,config*DeterministicTransferConfig)Agent{ifra,ok:config.Agent.(ResumableAgent);ok{returnresumableAgentWithDeterministicTransferTo{agent:ra,toAgentNames:config.ToAgentNames,}}returnagentWithDeterministicTransferTo{agent:config.Agent,toAgentNames:config.ToAgentNames,}}这个包装器限制被包装的 agent 只能 transfer 到toAgentNames列表中的 agent。deterministic_transfer.go:139-164的forwardEventsAndAppendTransferfuncforwardEventsAndAppendTransfer(iter*AsyncIterator[*AgentEvent],generator*AsyncGenerator[*AgentEvent],toAgentNames[]string){varlastEvent*AgentEventfor{event,ok:iter.Next()if!ok{break}generator.Send(event)lastEventevent}// 如果最后一个事件没有 Exit 或 Interrupted自动发出 transfer 事件iflastEvent!nillastEvent.Action!nil(lastEvent.Action.Interrupted!nil||lastEvent.Action.Exit){return}sendTransferEvents(generator,toAgentNames)}sendTransferEvents286-301给每个toAgentNames中的 agent 生成一对 assistanttool 消息模拟 transfer 调用。Supervisor 模式用这个机制每个子 agent 被AgentWithDeterministicTransferTo包装toAgentNames只有 supervisor 的名字。子 agent 完成后自动 transfer 回 supervisor不能 transfer 到其他子 agent。四AgentTool更好的替代方案agent_tool.go:93-103的NewAgentTool把 agent 包装为 toolfuncNewAgentTool(_context.Context,agent Agent,options...AgentToolOption)tool.BaseTool{returnagentTool{agent:agent,fullChatHistoryAsInput:opts.fullChatHistoryAsInput,inputSchema:opts.agentInputSchema,}}agent_tool.go:154-280的InvokableRun展示了 key 差异独立 session内部 agent 有独立的 checkpoint storebridgeStore不共享父 agent 的 sessionAction 隔离Exit, TransferToAgent, BreakLoop被忽略不传播到父 agentagent_tool.go:352-356独立 checkpoint每个 agent tool 有自己的 checkpoint中断恢复时独立处理// agent_tool.go:352-356// - Exit, TransferToAgent, BreakLoop: Ignored outside the agent tool; these actions only affect// the inner agents execution and do not propagate to the parent agent对比 transfer 和 AgentTool维度TransferAgentTool上下文共享 sessionrewrite 历史独立 session工具参数传 request隔离性差完整上下文共享好独立 checkpoint子 agent action传播到父 agent隔离在工具内部上下文膨胀每轮 transfer 追加历史每次调用独立不累积推荐程度NOT RECOMMENDED推荐五Host MultiAgent 的 HandOff 回调E91 和 E92 提到的 Host MultiAgent 也有交接概念但用的是回调机制不是 ADK 的 transfer。callback.go:31-33typeMultiAgentCallbackinterface{OnHandOff(ctx context.Context,info*HandOffInfo)context.Context}typeHandOffInfostruct{ToAgentNamestringArgumentstring}ConvertCallbackHandlerscallback.go:42-89监听 ChatModel 的 OnEnd 事件当 host LLM 产出 tool call 时每个 tool call 对应一个 specialist触发OnHandOff回调。用户可以在回调中记录日志、修改上下文等。Host MultiAgent 的 HandOff 和 ADK 的 transfer 是两套不同的机制Host HandOffhost 把 specialist 当 tool 调host 的 Graph 负责汇总结果。交接发生在 host 一个 agent 内部。ADK Transferagent 之间通过transfer_to_agent工具和TransferToAgentAction传递控制权。交接发生在 agent 之间。小结问题答案关键源码transfer 怎么发生LLM 调 transfer_to_agent → SendToolGenAction → flowAgent.run 处理 TransferToAgentchatmodel.go:661-677,flow.go:554-581上下文怎么传Session 共享 rewriteMessage 标注来源 genAgentInput 构建输入flow.go:188-253, 275-328为什么 NOT RECOMMENDED完整上下文共享 → 膨胀快 隔离差 消息重写损失语义flow.go:70-77DeterministicTransfer 怎么限制包装 agent子 agent 完成后自动 transfer 回指定列表deterministic_transfer.go:43-54, 139-164AgentTool 怎么替代独立 session Action 隔离 独立 checkpointagent_tool.go:93-103, 154-280Host HandOff 和 ADK Transfer 的区别Host 内部回调 vs Agent 间控制权转移callback.go:31-89几个设计判断Transfer 就是共享 session 重写历史。目标 agent 能看到前面所有 agent 说了什么但每条消息被标注了来源。这不是隔离而是透明的上下文传递——问题恰恰出在这里。AgentTool 是独立 session 工具参数。子 agent 看不到父 agent 的完整历史只通过request参数接收任务描述。隔离性更好但也意味着子 agent 无法利用父 agent 的上下文做推理——需要父 agent 在 request 参数中写清楚。NOT RECOMMENDED 不代表不能用。Supervisor 模式用 transfer 实现简单的路由——router 分析用户意图transfer 到合适的专家 agent。如果 agent 层级简单一层 router 若干专家上下文膨胀不是大问题。但复杂场景下 AgentTool 更好。下一篇E94讲 Deep Agent 的文件系统工具链——read/write/glob/grep/shell 五个工具怎么设计以及 filesystem 中间件的实现。
返回列表