一.深入理解 Alibaba ReactAgent 原理
核心原理
ReactAgent 将模型调用、工具执行和流程控制组织成一张 Graph,由图执行引擎根据状态和条件边驱动循环。
用户问题
↓
Model:调用大模型
↓
是否需要调用工具?
├── 否 → 输出答案,结束
└── 是 → Tool:执行工具
↓
保存工具结果
↓
返回Model
↓
基于工具结果继续处理
可以将其理解为:Graph 负责调度,节点负责执行,State 负责保存信息,条件边负责决定下一步。
以下方法名和调用关系按本节源码示例整理,具体实现随版本可能有所变化。
调试入口
ToolCallback weatherTool = FunctionToolCallback
.builder("weather", new WeatherQueryTool())
.description("查询天气")
.inputType(String.class)
.build();
ReactAgent agent = ReactAgent.builder()
.name("demo_agent")
.model(chatModel)
.tools(weatherTool)
.hooks(new LoggingHook())
.systemPrompt("你是一个助手。")
.build();
System.out.println(
agent.call("查询南京天气").getText()
);
执行过程:
用户:查询南京天气
↓
模型返回weather工具调用请求
↓
工具查询天气并返回结果
↓
模型结合工具结果生成回答
核心调用链
ReactAgent.call()
↓
doInvoke()
↓
Graph.invoke()
↓
runner.run()
↓
mainGraphExecutor.execute()
↓
nodeExecutor.execute()
↓
action.apply()
各层职责:
| 组件 | 职责 |
|---|---|
| ReactAgent | 构建 Agent 并提供调用入口 |
| Graph | 定义节点、边和执行流程 |
| Runner / Executor | 调度并执行节点 |
| Node Action | 执行当前节点的具体逻辑 |
| OverAllState | 保存消息、工具结果等运行状态 |
| 条件边 | 根据状态决定下一个节点 |
节点如何执行
节点执行器会从 GraphRunnerContext 中取得当前节点及其对应的 Action。
获取当前节点
↓
获取节点Action
↓
处理需要的中断或恢复逻辑
↓
执行action.apply()
↓
处理节点结果并更新状态
↓
根据边选择下一个节点
action.apply() 并不一定对应某个固定实现类,也可能对应构建 Graph 时注册的 Lambda。
为什么追踪不到固定的实现类
AsyncNodeActionWithConfig 是函数式接口,可以接收符合其签名的 Lambda 或实现对象。
因此,调试时需要回到节点注册的位置,查看传入的具体执行逻辑。
initGraph注册节点
↓
绑定节点执行逻辑
↓
Graph运行到该节点
↓
调用注册时传入的Action
node_async() 用于将同步节点逻辑适配为异步接口,让图引擎统一处理返回结果。
注意:异步接口不等于多个节点一定并行执行,节点顺序仍由 Graph 的边决定。
ReactAgent 的核心节点
ReactAgent 在初始化 Graph 时,注册两个核心节点:
model → AgentLlmNode
tool → AgentToolNode
概念性伪代码:
// 将模型节点和工具节点注册到Graph
graph.addNode("model", node_async(llmNode));
graph.addNode("tool", node_async(toolNode));
// 再配置节点之间的跳转规则
AgentLlmNode:模型节点
模型节点负责执行一次大模型调用。
主要过程:
读取State中的消息和上下文
↓
构建模型请求
↓
调用ChatModel
↓
获得ChatResponse
↓
将模型输出写入State
模型输出可能包含:
- 最终回答
- 工具调用请求
tool_calls
例如,模型要求调用天气工具:
{
"tool_calls": [
{
"name": "weather",
"arguments": {
"city": "南京"
}
}
]
}
这里是结构示意,实际参数格式取决于工具定义。
AgentToolNode:工具节点
工具节点负责执行模型提出的工具调用。
读取最近一次AssistantMessage
↓
提取tool_calls
↓
找到对应工具
↓
传入参数并执行工具
↓
封装ToolResponseMessage
↓
将工具结果写入State
工具执行结果会成为下一次模型调用的上下文。
模型:请调用weather查询南京天气
工具:南京,多云,气温26℃
模型:根据工具结果组织最终回答
模型负责决定调用哪个工具,工具节点负责真正执行。
OverAllState:全局状态
OverAllState 用于在节点之间共享运行信息。
例如:
用户问题
模型输出
工具调用请求
工具返回结果
其他流程控制信息
消息会随着循环不断补充:
UserMessage:查询南京天气
↓
AssistantMessage:请求调用weather
↓
ToolResponseMessage:天气查询结果
↓
AssistantMessage:最终回答
因此,下一轮模型能够看到上一轮工具的结果,而不是从头开始。
循环如何实现
ReAct 循环由 Graph 的条件边实现。
本节源码中的相关入口包括:
initGraph()
↓
setupHookEdges()
↓
setupToolRouting()
关键是两组路由:
Model → Tools / Loop / Exit
Tools → Model / Exit
Model 执行后的路由
模型节点执行后,路由逻辑检查当前状态,决定:
-
进入工具节点
-
继续下一轮模型调用
-
结束执行
Model执行完成
↓
检查模型输出和控制状态
├── 需要工具 → Tool
├── 需要继续 → Loop
└── 可以结束 → Exit
普通工具调用场景下:
存在tool_calls → 执行工具
没有tool_calls → 返回模型答案
实际路由还可能受到 Hook 等控制逻辑影响。
Tool 执行后的路由
工具节点完成后,默认返回模型节点,让模型读取工具结果并继续处理。
Tool执行完成
↓
检查是否直接返回
├── 是 → Exit
└── 否 → Model
按本节源码描述,如果本轮所有已执行工具均满足 return_direct=true,路由会直接结束,而不再交给模型加工。
普通工具:
Model → Tool → Model → 最终回答
直接返回工具:
Model → Tool → 结束
Hooks 和 Interceptors 的位置
两者都是扩展机制,但关注点不同。
| 扩展机制 | 作用位置 | 典型用途 |
|---|---|---|
| Hooks | Agent 生命周期节点 | 开始、结束、每轮前后的状态处理 |
| Interceptors | 模型或工具调用边界 | 改写请求、修改响应、重试、缓存 |
理解源码时:
Hooks:
关注扩展逻辑在Graph流程中的执行位置。
Interceptors:
关注模型或工具节点内部的调用如何被包装。
不能仅根据"记录日志"判断是哪一种,两者都可以记录日志。
完整执行示例
用户输入:
查询南京天气。
执行步骤:
-
用户消息进入
OverAllState。 -
Graph 调度
AgentLlmNode。 -
模型返回
weather工具调用请求。 -
条件边将流程转到
AgentToolNode。 -
工具执行,并将结果写入状态。
-
若不满足直接返回条件,流程回到模型节点。
-
模型结合天气结果生成答案。
-
路由判断可以结束,返回结果。
用户问题
↓
State保存用户消息
↓
Model生成tool_calls
↓
Tool执行weather
↓
State保存工具结果
↓
Model生成最终答案
↓
Exit
重点总结
-
ReactAgent 的底层是 Graph 驱动的执行流程,可以理解为一套状态机。
-
四个核心角色:
Graph:定义整体流程
Node:执行具体操作
State:共享消息和运行数据
条件边:决定下一步执行哪个节点 -
两个核心节点:
AgentLlmNode → 调用大模型
AgentToolNode → 执行工具 -
ReAct 的循环由条件边完成:
Model → Tool → Model → ...
-
工具结果会写入状态,下一轮模型基于这些结果继续处理。
-
action.apply()执行的是构建 Graph 时注册的逻辑,不一定存在一个固定实现类。 -
node_async()用于适配异步执行接口,不代表所有节点都会并行运行。 -
普通工具执行后返回模型;满足
return_direct条件时可以直接结束。 -
Hooks 关注生命周期节点,Interceptors 关注具体调用的执行行为。
三. Agent常用架构:Human-in-the-Loop
HITL 简介
Human-in-the-Loop(HITL,人在回路)是在 Agent 执行流程中引入人工审核,让人在关键节点批准、修改或拒绝 Agent 的操作。
Agent自动执行
↓
准备执行需要审批的操作
↓
暂停并保存状态
↓
人工审核
├── 批准 → 执行原操作
├── 修改 → 按修改后的参数执行
└── 拒绝 → 不执行该操作,反馈给模型
HITL 本质上是一种流程控制机制,通过中断、状态保存和恢复控制 Agent 的行为。
适用场景
- 删除文件、修改重要资源
- 执行数据库修改或运维指令
- 下单、转账、封禁账号等外部操作
- 需要人工确认的业务审批
- 信息不足、决策存在较大不确定性的任务
是否需要审批应根据业务规则配置,不是所有工具都必须人工确认。
Spring AI Alibaba 的实现
本节使用 HumanInTheLoopHook 实现工具审批。
主要包含三个阶段:
配置中断
↓
响应中断
↓
人工反馈并恢复执行
核心组件:
| 组件 | 作用 |
|---|---|
HumanInTheLoopHook |
声明受控工具,处理中断和人工反馈 |
ToolConfig |
配置工具审批说明 |
MemorySaver |
在内存中保存执行检查点 |
threadId |
关联同一次执行的暂停与恢复 |
InterruptionMetadata |
保存待审核工具的信息 |
ToolFeedback |
表示针对工具调用的人工反馈 |
RunnableConfig |
传递执行标识和人工反馈 |
以下代码按课程中的 API 写法整理。
第一步:配置中断
指定哪些工具需要审批,并为 Agent 配置检查点保存器。
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.agent.hook.hip.HumanInTheLoopHook;
import com.alibaba.cloud.ai.graph.agent.hook.hip.ToolConfig;
import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver;
import java.util.List;
// 保存执行检查点,支持暂停与恢复
MemorySaver memorySaver = new MemorySaver();
// 配置需要审批的工具
HumanInTheLoopHook humanInTheLoopHook =
HumanInTheLoopHook.builder()
.approvalOn(
"write_file",
ToolConfig.builder()
.description("文件写入操作需要审批")
.build()
)
.approvalOn(
"execute_sql",
ToolConfig.builder()
.description("SQL执行操作需要审批")
.build()
)
.build();
// chatModel和工具回调由项目提前定义
ReactAgent agent = ReactAgent.builder()
.name("approval_agent")
.model(chatModel)
.tools(writeFileTool, executeSqlTool, readDataTool)
.hooks(List.of(humanInTheLoopHook))
.saver(memorySaver)
.build();
这里:
write_file → 需要人工审批
execute_sql → 需要人工审批
readDataTool → 未在该Hook中配置审批
approvalOn() 中的名字必须与工具注册名称一致。
第二步:执行并接收中断
调用时提供 threadId,用于关联检查点和后续恢复。
import com.alibaba.cloud.ai.graph.RunnableConfig;
import com.alibaba.cloud.ai.graph.NodeOutput;
import com.alibaba.cloud.ai.graph.action.InterruptionMetadata;
import java.util.Optional;
String threadId = "user-session-123";
RunnableConfig config = RunnableConfig.builder()
.threadId(threadId)
.build();
Optional<NodeOutput> result = agent.invokeAndGetOutput(
"删除数据库中的旧记录",
config
);
if (result.isPresent()
&& result.get() instanceof InterruptionMetadata interruption) {
for (InterruptionMetadata.ToolFeedback feedback
: interruption.toolFeedbacks()) {
System.out.println("工具:" + feedback.getName());
System.out.println("参数:" + feedback.getArguments());
System.out.println("说明:" + feedback.getDescription());
}
}
示例中断信息:
工具:execute_sql
参数:{"query":"DELETE FROM records WHERE ..."}
说明:SQL执行操作需要审批
此时工具尚未执行,Agent 处于可恢复的暂停状态,并不是执行失败。
第三步:人工反馈并恢复
用户完成审批后,将反馈通过相同的 threadId 传回。
下面代码演示"用户已明确批准全部待审核操作"的情况:
InterruptionMetadata.Builder feedbackBuilder =
InterruptionMetadata.builder()
.nodeId(interruption.node())
.state(interruption.state());
for (InterruptionMetadata.ToolFeedback toolFeedback
: interruption.toolFeedbacks()) {
InterruptionMetadata.ToolFeedback approvedFeedback =
InterruptionMetadata.ToolFeedback.builder(toolFeedback)
.result(
InterruptionMetadata.ToolFeedback
.FeedbackResult.APPROVED
)
.build();
feedbackBuilder.addToolFeedback(approvedFeedback);
}
InterruptionMetadata approvalMetadata =
feedbackBuilder.build();
RunnableConfig resumeConfig = RunnableConfig.builder()
.threadId(threadId)
.addMetadata(
RunnableConfig.HUMAN_FEEDBACK_METADATA_KEY,
approvalMetadata
)
.build();
Optional<NodeOutput> resumedResult =
agent.invokeAndGetOutput("", resumeConfig);
if (resumedResult.isPresent()) {
if (resumedResult.get() instanceof InterruptionMetadata) {
System.out.println("执行再次暂停,需要继续人工审批");
} else {
System.out.println("执行结果:" + resumedResult.get());
}
}
注意:
- 批准动作应来自真实用户或审批系统。
- 恢复时使用原来的
threadId和检查点存储。 - 恢复后可能再次触发审批,不能认为有返回值就一定执行完成。
三种人工决策
| 决策 | 处理方式 |
|---|---|
APPROVED |
保留原工具调用,允许执行 |
EDITED |
使用人工修改后的参数执行 |
REJECTED |
不执行该工具,将拒绝信息反馈给模型 |
例如:
模型计划:
删除90天前的数据。
人工批准:
按原条件执行。
人工修改:
改成删除180天前的数据。
人工拒绝:
取消删除,要求先查询记录数量。
拒绝某个工具调用,不一定意味着整个 Agent 结束;模型可以根据拒绝原因继续回答或重新规划。
为什么需要检查点
暂停后,框架需要知道:
-
当前执行到了哪个节点
-
用户和模型已经说了什么
-
模型准备调用哪些工具
-
哪些工具尚未执行
-
恢复时应该从哪里继续
触发中断
↓
保存Graph执行状态
↓
等待人工反馈
↓
通过threadId找到检查点
↓
恢复原执行流程
MemorySaver 保存的是执行检查点,与仅保存聊天消息的对话记忆不同。
对话记忆:
让模型知道之前聊了什么。
执行检查点:
让框架知道流程停在哪里、如何继续。
MemorySaver 是内存实现,应用重启后不能依靠它恢复原来的执行状态。
HumanInTheLoopHook 的执行位置
该 Hook 位于:
模型输出之后
↓
HumanInTheLoopHook
↓
工具真正执行之前
对应生命周期位置:
@HookPositions(HookPosition.AFTER_MODEL)
此时模型已经生成 tool_calls,但工具还没有执行,所以可以展示具体工具和参数供人审批。
这也说明:Hook 同样可以控制流程是否继续,功能不局限于记录日志。
interrupt:判断是否中断
节点执行器在节点的 action.apply() 之前检查中断条件。
interrupt()
↓
判断是否暂停
├── 暂停 → 返回中断信息
└── 放行 → action.apply()
两种返回值:
// 不触发中断,继续执行
Optional.empty();
// 触发中断,将控制权交还调用方
Optional.of(interruptionMetadata);
首次执行时:
读取最近的AssistantMessage
↓
检查是否包含Tool Call
↓
检查工具是否命中approvalOn
↓
命中受控工具
↓
构造ToolFeedback和InterruptionMetadata
↓
暂停Graph
恢复执行时:
读取HUMAN_FEEDBACK_METADATA_KEY
↓
校验人工反馈
├── 不合法或不完整 → 再次中断
└── 合法 → 放行节点
放行表示允许进入反馈处理逻辑,并不意味着所有工具都已获批准。
afterModel:处理人工反馈
恢复后,afterModel() 读取人工反馈,并调整之前尚未执行的工具调用。
读取人工反馈
↓
定位原AssistantMessage中的Tool Call
↓
逐个匹配审批结果
├── APPROVED → 保留调用
├── EDITED → 替换参数
└── REJECTED → 生成拒绝的工具响应
↓
更新State中的消息
↓
Graph继续执行
按原文中的实现,旧消息会被标记删除,再加入调整后的消息,避免后续继续执行未经批准的原始调用。
完整原理
Model生成工具调用
↓
HumanInTheLoopHook.interrupt()
↓
发现受控工具,返回InterruptionMetadata
↓
Graph暂停并保存检查点
↓
外部系统展示工具名称、参数和审批说明
↓
用户批准、修改或拒绝
↓
相同threadId + 人工反馈恢复Graph
↓
interrupt()校验反馈并放行
↓
afterModel()调整工具调用
↓
执行批准后的工具,或向模型反馈拒绝结果
↓
Agent继续运行
重点总结
-
HITL 是在 Agent 的关键执行节点引入人工审核,本质是流程控制。
-
审批发生在模型已生成工具调用、工具尚未执行的时候。
-
三个使用步骤:
配置受控工具 → 接收中断 → 提交人工反馈并恢复
-
approvalOn()指定需要审批的工具,InterruptionMetadata提供待审批的工具名称、参数和说明。 -
暂停和恢复依赖检查点及相同的
threadId;聊天记录本身不足以恢复执行流程。 -
两个核心方法:
interrupt:
判断是否暂停,并校验恢复时的人工反馈。afterModel:
根据批准、修改、拒绝结果调整工具调用。 -
示例中的自动构造
APPROVED只是演示,真实流程需要接入人工审批结果。 -
Agent 恢复后仍可能再次中断,需要继续处理后续审批。
四. Agent常用架构:Multi-Agent
Multi-Agent 简介
Multi-Agent(多智能体)是将一个复杂任务交给多个具有不同职责的 Agent,通过分工与协作完成。
复杂任务
↓
任务拆分
↓
多个专业Agent分别处理
↓
协调和汇总结果
↓
完成任务
例如:
研究Agent → 搜集资料
写作Agent → 撰写报告
审核Agent → 检查内容
多个 Agent 可以使用同一个模型,也可以分别使用不同模型。区别主要体现在提示词、工具、上下文和职责上。
为什么需要多智能体
单个 Agent 承担过多职责时,可能出现:
- 上下文过长,关键信息容易被忽略。
- 工具太多,模型容易选错工具。
- 任务不够聚焦,专业任务处理效果下降。
- 独立子任务串行执行,整体耗时较长。
- 无法针对不同任务灵活选择模型。
多智能体通过专业化分工、上下文隔离和任务编排缓解这些问题,但会增加调用成本和协调开销。
常见协作模式
| 模式 | 核心方式 | 谁负责控制流程 |
|---|---|---|
| SubAgents | 主 Agent 将子 Agent 当作工具调用 | 主 Agent |
| Handoff | 当前 Agent 把任务和控制权交给另一个 Agent | 当前接管的 Agent |
| Group Chat | 多个 Agent 围绕共享消息协作 | 群聊管理器 |
| 自定义工作流 | 按业务规则组织串行、并行、条件和循环 | 工作流引擎 |
这些模式可以组合使用,不是互斥关系。
SubAgents:子智能体模式
主 Agent 将其他子 Agent 封装成工具,需要时调用它们,再接收结果并继续处理。
用户
↓
主Agent
├── 调用研究Agent → 返回研究结果
├── 调用写作Agent → 返回文章
└── 汇总结果
↓
回复用户
职责划分:
- 主 Agent:分配任务、选择子 Agent、组织最终结果。
- 子 Agent:专注完成某一类任务,并向主 Agent 返回结果。
典型模式下,子 Agent 不直接与用户对话。主 Agent 可以只传入必要信息,避免每个子 Agent 都接收完整对话历史。
主Agent的完整上下文
↓
提取子任务所需信息
↓
子Agent在独立上下文中执行
↓
返回精简结果
子 Agent 是否保存记忆取决于配置,并不是必须无状态。
Spring AI Alibaba 示例
以下沿用课程中的 API,chatModel 为项目已有模型实例。
import com.alibaba.cloud.ai.graph.agent.ReactAgent;
import com.alibaba.cloud.ai.graph.agent.AgentTool;
// 创建写作子Agent
ReactAgent writerAgent = ReactAgent.builder()
.name("writer_agent")
.model(chatModel)
.description("根据主题撰写文章")
.instruction("""
你是一名作家。
请根据收到的主题和要求完成写作,
返回文章正文。
""")
.build();
// 主Agent将子Agent作为工具调用
ReactAgent blogAgent = ReactAgent.builder()
.name("blog_agent")
.model(chatModel)
.instruction("""
根据用户给定的主题完成文章。
使用写作工具处理写作任务,
根据结果回复用户。
""")
.tools(
AgentTool.getFunctionToolCallback(writerAgent)
)
.build();
System.out.println(
blogAgent.call("写一篇100字左右的秋日散文").getText()
);
核心是:
AgentTool.getFunctionToolCallback(writerAgent)
它将子 Agent 转换成主 Agent 可调用的工具。
主Agent决定调用工具
↓
实际执行子Agent
↓
子Agent返回结果
↓
主Agent继续处理
Handoff:交接模式
Handoff 是当前 Agent 发现另一个 Agent 更适合处理任务时,将任务及控制权转交过去。
用户问题
↓
接待Agent
↓
识别为售后问题
↓
交接给售后Agent
↓
售后Agent继续处理
与 SubAgents 的主要区别:
SubAgents:
调用子Agent → 子Agent返回结果 → 主Agent继续负责。
Handoff:
转交给另一个Agent → 新Agent接管后续流程。
交接时通常需要传递:
- 用户目标
- 已完成的工作
- 必要的历史信息
- 当前任务状态
- 后续需要处理的问题
不一定要传递全部历史,重点是让接管方具备继续执行所需的信息。
Handoff 协作示例
研究Agent
↓ 资料收集完成
写作Agent
↓ 初稿完成
审核Agent
├── 通过 → 结束
└── 需要修改 → 交回写作Agent
↓
修改后再次审核
交接也可以回到前面的 Agent,不一定是单向流程。
写作Agent发现资料不足
↓
交回研究Agent补充资料
↓
再由写作Agent继续撰写
需要设置终止条件或最大轮次,避免反复交接。
Group Chat:群聊模式
多个 Agent 围绕共享的任务和消息进行协作,由群聊管理器决定下一位发言者。
共享任务和消息
↓
群聊管理器选择下一位Agent
↓
被选中的Agent执行并发言
↓
结果加入共享消息
↓
管理器选择下一位或结束
例如:
产品Agent:提出需求
技术Agent:分析实现方案
测试Agent:提出风险和测试建议
产品Agent:根据反馈调整需求
课程描述的是轮流发言的群聊模式,同一时刻由被选中的 Agent 工作。
与 Handoff 的区别:
Handoff:
当前Agent决定交给谁。
Group Chat:
管理器决定接下来由谁处理。
共享消息有利于协作,但讨论轮次过多也可能导致上下文膨胀。
自定义工作流
当固定模式无法满足业务需求时,可以自行编排多个 Agent。
常用流程包括:
串行:
研究Agent → 写作Agent → 审核Agent
并行:
┌→ 技术分析Agent ─┐
任务 ──┼→ 成本分析Agent ─┼→ 汇总Agent
└→ 风险分析Agent ─┘
条件路由:
用户问题
├── 技术问题 → 技术Agent
├── 财务问题 → 财务Agent
└── 售后问题 → 售后Agent
循环:
生成Agent → 审核Agent
↑ ↓
└── 不通过 ┘
↓
通过后结束
这些流程也可以结合 HITL,在关键步骤加入人工审核。
模式选择
| 需求 | 适合的模式 |
|---|---|
| 统一入口,由主 Agent 分派专业任务 | SubAgents |
| 不同专家接管不同阶段的任务 | Handoff |
| 多角色围绕同一任务讨论 | Group Chat |
| 流程固定,需要精确控制执行顺序 | 自定义工作流 |
| 多个子任务彼此独立 | 并行工作流 |
| 需要人工批准后继续 | 工作流结合 HITL |
注意事项
- 每个 Agent 的职责和输出要求应明确。
- 传递必要上下文,避免反复复制全部历史。
- 独立任务可以并行,有依赖的任务需要按顺序执行。
- 设置最大轮次、超时和终止条件。
- 共享状态需要明确由谁更新,避免结果相互覆盖。
- 多 Agent 不一定更快、更准确,需要评估实际效果和成本。
重点总结
-
多智能体是在单智能体基础上增加任务分工与协作。
-
多个 Agent 不要求使用不同模型,可以通过不同提示词、工具和上下文实现专业化。
-
四种常见模式:
SubAgents:主Agent调用子Agent,结果返回主Agent。
Handoff:当前Agent将控制权交给另一个Agent。
Group Chat:管理器决定下一个发言的Agent。
自定义工作流:按业务规则编排多个Agent。 -
SubAgents 的关键是"调用后返回",Handoff 的关键是"交接后接管"。
-
多智能体可以采用串行、并行、条件路由和循环,也可以加入人工审核。
-
多智能体增加了协调成本,应在任务足够复杂、分工能够带来收益时使用。
SubAgents和Group Chat的区别?
因为它们都有一个"负责人"选择接下来由谁工作,所以看起来很像。
区别主要在协作方式:
-
SubAgents 像领导派活:主 Agent 单独布置任务,子 Agent 做完向主 Agent 汇报。子 Agent 通常只看到自己的任务,不知道其他子 Agent 在做什么。
-
Group Chat 像开会讨论:协调者安排谁发言,各 Agent 能参考共享讨论记录,接着前面其他 Agent 的意见继续处理。
SubAgents:
主Agent → A做调研 → 结果交回主Agent
主Agent → B写文章 → 结果交回主AgentGroup Chat:
协调者安排A发言 → A提出调研结果
协调者安排B发言 → B根据A的结果写文章
协调者安排C发言 → C根据前面的讨论提出修改意见
一个主要通过主 Agent 分发任务、收集结果;另一个主要通过共享讨论协作。 实际框架也可以混合这两种方式。
五. 使用AutoGen构建代码生成器
AutoGen 简介
AutoGen 是微软发起的开源多智能体框架,主要通过多个 Agent 的对话与协作完成复杂任务。
本例通过不同角色实现代码生成和验证:
用户提出需求
↓
产品经理明确需求
↓
工程师编写代码
↓
审查员检查代码
↓
执行器运行测试
↓
根据结果修改或结束
本文按照课程中的经典 autogen.GroupChat API 整理,其他版本的接口可能不同。
智能体分工
| 智能体 | 作用 |
|---|---|
ProductManager |
明确需求、检查功能是否符合要求 |
SoftwareEngineer |
编写代码和测试用例 |
CodeReviewer |
审查代码,结合执行结果判断是否通过 |
UserProxyAgent |
发起任务、执行代码、反馈运行结果 |
GroupChatManager |
协调群聊,选择下一位发言者 |
前三个角色使用 AssistantAgent 创建,通过不同的系统提示词区分职责。
GroupChat:定义群聊
GroupChat 保存参与者、共享消息和调度配置。
groupchat = autogen.GroupChat(
agents=[
user_proxy,
product_manager,
software_engineer,
code_reviewer,
],
messages=[],
max_round=12,
speaker_selection_method="auto",
)
主要参数:
| 参数 | 含义 |
|---|---|
agents |
参与群聊的 Agent |
messages |
群聊消息记录 |
max_round |
最大群聊轮数 |
speaker_selection_method |
下一位发言者的选择方式 |
allow_repeat_speaker |
是否允许连续选择同一个 Agent |
send_introductions |
是否发送成员介绍 |
发言选择方式:
auto → 由模型根据上下文选择
round_robin → 按固定顺序轮流发言
random → 随机选择
自定义函数 → 根据业务规则选择
auto 不保证一定按照"产品经理 → 工程师 → 审查员"的顺序执行。
GroupChatManager:协调群聊
GroupChatManager 使用群聊配置组织对话:
接收消息
↓
根据策略选择下一位Agent
↓
传递上下文并触发响应
↓
收集新消息
↓
继续调度或结束
创建方式:
manager = autogen.GroupChatManager(
groupchat=groupchat,
llm_config=llm_config,
)
区别:
GroupChat:
保存成员、消息和调度规则。
GroupChatManager:
根据规则实际组织和推进对话。
模型配置
各角色可以共享同一个模型配置,通过系统提示词承担不同职责。
import os
config_list = [
{
"model": os.environ["LLM_MODEL"],
"api_key": os.environ["LLM_API_KEY"],
"base_url": os.environ["LLM_BASE_URL"],
}
]
llm_config = {
"config_list": config_list
}
其中,模型名称、API Key 和接口地址需要与实际提供服务的平台对应。
创建专业 Agent
import autogen
product_manager = autogen.AssistantAgent(
name="ProductManager",
system_message=(
"你是一名产品经理。"
"明确用户需求、边界条件和验收标准。"
"不要凭空增加用户未要求的功能。"
),
llm_config=llm_config,
)
software_engineer = autogen.AssistantAgent(
name="SoftwareEngineer",
system_message=(
"你是一名Python工程师。"
"根据需求编写完整可执行代码和测试用例。"
"使用python代码块输出代码。"
"收到错误反馈后修改代码并重新提交验证。"
),
llm_config=llm_config,
)
code_reviewer = autogen.AssistantAgent(
name="CodeReviewer",
system_message=(
"你是一名代码审查员。"
"检查代码是否满足需求,是否存在明显错误。"
"未执行测试时,不要宣称代码已通过验证。"
"代码可供验证时,请User执行完整代码和测试。"
"若执行失败,要求SoftwareEngineer修改。"
"只有执行结果符合验收要求时,"
"才单独回复TERMINATE。"
),
llm_config=llm_config,
)
仅靠审查员阅读代码不等于测试通过,需要执行环境提供实际结果。
UserProxyAgent:执行代码
课程示例中的 UserProxyAgent 同时负责发起任务和运行生成的代码。
user_proxy = autogen.UserProxyAgent(
name="User",
human_input_mode="NEVER",
max_consecutive_auto_reply=10,
code_execution_config={
"work_dir": "coding",
"use_docker": False,
},
is_termination_msg=lambda message: (
(message.get("content") or "").strip()
== "TERMINATE"
),
)
参数说明:
human_input_mode="NEVER":运行时不等待人工输入。max_consecutive_auto_reply=10:限制连续自动回复次数。work_dir="coding":代码执行工作目录。use_docker=False:在本机执行,没有 Docker 隔离。is_termination_msg:判断消息是否为终止信号。
本例配置会执行模型生成的代码,适合受控的演示环境;需要隔离执行时应配置对应的执行环境。
组建群聊并启动任务
groupchat = autogen.GroupChat(
agents=[
user_proxy,
product_manager,
software_engineer,
code_reviewer,
],
messages=[],
max_round=12,
speaker_selection_method="auto",
)
manager = autogen.GroupChatManager(
groupchat=groupchat,
llm_config=llm_config,
)
user_proxy.initiate_chat(
manager,
message=(
"请实现一个Python函数:"
"输入字符串列表,返回最长的字符串。"
"如果多个字符串长度相同,返回第一个。"
"空列表应抛出ValueError。"
"请提供完整代码,并使用断言测试正常情况、"
"相同长度情况和空列表情况。"
"审查后由User执行验证,确认通过后再结束。"
),
)
运行已有兼容依赖环境中的脚本:
uv run main.py
生成代码示例
def find_longest_string(strings):
if not strings:
raise ValueError("Input list cannot be empty")
longest = strings[0]
for item in strings:
if len(item) > len(longest):
longest = item
return longest
if __name__ == "__main__":
# 正常情况
assert find_longest_string(
["apple", "hi", "banana", "cat"]
) == "banana"
# 长度相同时,保留第一个
assert find_longest_string(
["apple", "banana", "orange"]
) == "banana"
# 空列表应抛出异常
try:
find_longest_string([])
except ValueError:
pass
else:
raise AssertionError("空列表没有抛出ValueError")
print("所有测试通过")
使用 > 而不是 >=,可以保留第一个最长字符串:
if len(item) > len(longest):
longest = item
执行与反馈闭环
SoftwareEngineer生成代码
↓
CodeReviewer审查
├── 有问题 → 返回工程师修改
└── 可验证 → UserProxy执行
↓
返回执行结果
↓
审查结果是否符合要求
├── 否 → 修改并重试
└── 是 → TERMINATE
执行器返回的信息通常包括:
exitcode: 0
Code output:
所有测试通过
退出码为 0 只说明程序没有以错误状态退出,是否符合需求还需要结合测试断言和输出判断。
终止条件
本例有两类结束条件:
任务通过验证 → 返回TERMINATE
达到最大轮数或自动回复限制 → 停止继续对话
达到轮次上限不等于任务成功,需要区分"验证通过"和"因限制停止"。
提示词中的"由谁宣布通过"属于行为约定;如果业务必须严格控制结束条件,应增加确定性的流程判断。
重点总结
-
AutoGen 通过多个角色共享消息、轮流协作,实现代码生成任务。
-
各角色分工:
产品经理 → 明确需求
工程师 → 编写代码
审查员 → 检查代码和验证结果
用户代理 → 执行代码并反馈结果
管理器 → 调度下一位发言者 -
GroupChat定义群聊配置,GroupChatManager负责推进对话。 -
speaker_selection_method="auto"表示由模型选择下一位发言者,不保证固定顺序。 -
UserProxyAgent将生成代码接入执行环境,形成"生成 → 执行 → 反馈 → 修改"的闭环。 -
use_docker=False表示本机执行,不具备 Docker 隔离。 -
代码看起来正确、程序正常退出,都不能代替测试和需求验收。
-
TERMINATE用于主动结束,最大轮数用于防止对话无限进行。
六. Spring AI Alibaba中的多智能体支持
核心思路
Spring AI Alibaba 可以通过两种方式组织多个 Agent:
- Agent Tool:将子 Agent 包装成工具,由主 Agent 调用。(就是subAgent!!!)
- FlowAgent:通过 Graph 编排多个 Agent,实现顺序、并行、路由和监督等流程。(就是Hand Off!!!)
课程将后一类放在 Handoff 下介绍,但严格区分时,顺序和并行属于工作流编排,不一定是 Agent 自主决定交接控制权。
以下代码按课程中的 API 写法整理,chatModel 为项目已有模型实例。
Agent Tool:将Agent作为工具
核心方法:
AgentTool.getFunctionToolCallback(agent)
作用:
ReactAgent
↓
包装成ToolCallback
↓
注册到主Agent的tools中
↓
主Agent根据用户需求调用
创建专业子Agent
// 写作Agent
ReactAgent writerAgent = ReactAgent.builder()
.name("writer_agent")
.model(chatModel)
.description("专门负责文章创作和内容生成")
.instruction("你是一名专业作家,擅长各类文章创作。")
.build();
// 翻译Agent
ReactAgent translatorAgent = ReactAgent.builder()
.name("translator_agent")
.model(chatModel)
.description("专门负责文本翻译")
.instruction("你是一名专业翻译,请准确翻译收到的文本。")
.build();
// 总结Agent
ReactAgent summarizerAgent = ReactAgent.builder()
.name("summarizer_agent")
.model(chatModel)
.description("专门负责内容总结和关键信息提炼")
.instruction("你是一名总结专家,请提炼收到内容的核心信息。")
.build();
其中:
name:Agent 名称。description:说明能力,帮助主 Agent 选择工具。instruction:指导子 Agent 如何完成任务。
创建主Agent
ReactAgent multiToolAgent = ReactAgent.builder()
.name("multi_tool_coordinator")
.model(chatModel)
.instruction("""
你可以使用写作、翻译和总结工具。
根据用户需求选择工具,并将前一步的结果
作为后一步所需的输入,完成整个任务。
""")
.tools(
AgentTool.getFunctionToolCallback(writerAgent),
AgentTool.getFunctionToolCallback(translatorAgent),
AgentTool.getFunctionToolCallback(summarizerAgent)
)
.build();
Optional<OverAllState> result = multiToolAgent.invoke(
"请写一篇关于AI的文章,然后翻译成英文,最后给出摘要"
);
典型执行流程:
用户需求
↓
主Agent调用写作工具
↓
writerAgent返回文章
↓
主Agent调用翻译工具
↓
translatorAgent返回英文
↓
主Agent调用总结工具
↓
summarizerAgent返回摘要
↓
主Agent组织最终回答
工具调用顺序由主 Agent 决定;如果必须严格保证顺序,可以使用顺序编排。
Agent Tool 实现原理
按课程源码,getFunctionToolCallback() 使用 MethodToolCallback 包装子 Agent 的执行方法。
ReactAgent
↓
提取Agent名称和描述
↓
创建MethodToolCallback
↓
绑定AgentToolExecutor.executeAgent()
↓
主Agent调用该工具
↓
executeAgent()调用子Agent底层Graph.invoke()
↓
执行结果返回主Agent
本质上仍然是调用子 Agent,只是将调用入口转换成模型能够选择的工具。
FlowAgent:多Agent流程编排
FlowAgent 将多个 Agent 组合成一个执行流程。
课程介绍了四种实现:
| 类型 | 调度方式 | 适用场景 |
|---|---|---|
SequentialAgent |
按预定顺序执行 | 写作后审核、翻译后总结 |
ParallelAgent |
多个子 Agent 并行执行 | 多角度分析、独立任务 |
LlmRoutingAgent |
模型选择一个子 Agent | 按问题类型分流 |
SupervisorAgent |
监督者反复选择下一步 | 动态、多步骤协作 |
SequentialAgent:顺序执行
多个 Agent 按预先定义的顺序依次执行。
输入 → 写作Agent → 审核Agent → 输出
ReactAgent reviewerAgent = ReactAgent.builder()
.name("reviewer_agent")
.model(chatModel)
.description("审核文章并给出修改建议")
.instruction("请检查文章的逻辑、表达和内容质量。")
.build();
SequentialAgent blogAgent = SequentialAgent.builder()
.name("blog_agent")
.description("先写文章,再交给审核Agent检查")
.subAgents(List.of(writerAgent, reviewerAgent))
.build();
关键点:
- 执行顺序由
subAgents列表确定。 - 后续 Agent 使用前面产生的结果。
- 实际输入、输出的传递需要结合框架的状态字段配置。
ParallelAgent:并行执行
多个 Agent 并行处理任务,再收集并合并结果。
┌→ 散文Agent ─┐
输入 ─────┼→ 诗歌Agent ─┼→ 合并结果 → 输出
└→ 总结Agent ─┘
// proseWriterAgent、poemWriterAgent、summaryAgent需提前创建
ParallelAgent parallelAgent = ParallelAgent.builder()
.name("parallel_creative_agent")
.description("并行执行散文、诗歌和总结任务")
.mergeOutputKey("merged_results")
.subAgents(
List.of(
proseWriterAgent,
poemWriterAgent,
summaryAgent
)
)
.mergeStrategy(
new ParallelAgent.DefaultMergeStrategy()
)
.build();
参数说明:
subAgents:参与并行执行的 Agent。mergeStrategy:合并各个 Agent 的输出。mergeOutputKey:合并结果保存到状态中的字段名。
并行适合彼此独立的任务。如果翻译必须等待文章生成,就不适合将这两步直接并行。
LlmRoutingAgent:路由选择
由模型根据用户需求选择合适的子 Agent。
用户问题
↓
路由Agent
├── 写作需求 → writerAgent
├── 审核需求 → reviewerAgent
└── 翻译需求 → translatorAgent
↓
输出
LlmRoutingAgent routingAgent = LlmRoutingAgent.builder()
.name("content_routing_agent")
.description("根据用户需求选择合适的专家Agent")
.model(chatModel)
.subAgents(
List.of(
writerAgent,
reviewerAgent,
translatorAgent
)
)
.build();
子 Agent 的描述需要清晰,便于路由模型判断职责。
SupervisorAgent:监督者协调
监督者根据当前任务和执行结果,动态选择下一个 Agent。
子 Agent 执行完成后返回监督者,由监督者决定继续分配任务还是结束。
用户任务
↓
监督者
├── 选择写作Agent → 返回文章 ─┐
├── 选择翻译Agent → 返回译文 ─┤
↑ │
└──────── 接收结果 ──────────┘
↓
判断任务完成 → 输出
SupervisorAgent supervisorAgent = SupervisorAgent.builder()
.name("content_supervisor")
.description("协调写作和翻译,直到完成用户任务")
.model(chatModel)
.subAgents(
List.of(writerAgent, translatorAgent)
)
.build();
与路由模式的区别:
LlmRoutingAgent:
选择一个合适的Agent处理请求。
SupervisorAgent:
选择Agent → 接收结果 → 再次决定下一步。
Agent Tool和Supervisor的区别
两者都可以由一个主控角色协调多个子 Agent,区别主要在组织方式:
Agent Tool:
将子Agent包装成工具,通过工具调用机制执行。
SupervisorAgent:
将子Agent纳入多Agent流程,由监督者进行路由调度。
不能仅通过"有一个主 Agent"来区分,需要看底层使用的是工具调用还是流程路由。
底层实现:Graph
这些编排方式最终都依靠 Graph 的节点和边实现。
Agent → 节点或子图
执行顺序 → 普通边
动态选择 → 条件边
中间结果 → 共享状态
顺序模式:
START → Agent A → Agent B → END
并行模式:
┌→ Agent A ─┐
START ───┤ ├→ 合并 → END
└→ Agent B ─┘
路由模式:
START → 路由判断
├→ Agent A → END
└→ Agent B → END
监督者模式:
START → Supervisor → 子Agent
↑ ↓
└── 结果 ──┘
↓
END
因此,多智能体编排主要是在构建不同的图结构,再交给 Graph 引擎执行。
代码编写流程
准备ChatModel
↓
创建各个专业子Agent
配置name、description、instruction、tools
↓
选择协作方式
├── Agent Tool → 包装成工具,注册到主Agent
└── FlowAgent → 配置subAgents和编排方式
↓
调用组合后的Agent
↓
获取最终结果
重点总结
-
Agent Tool 将子 Agent 转换成工具:
AgentTool.getFunctionToolCallback(childAgent)
-
工具执行时,最终会调用子 Agent 的 Graph,结果再返回主 Agent。
-
四种流程编排:
SequentialAgent → 按顺序执行
ParallelAgent → 并行执行并合并
LlmRoutingAgent → 选择合适的子Agent
SupervisorAgent → 根据结果循环调度 -
路由模式侧重一次选择,监督者模式支持多步骤协调。
-
顺序、并行等是编排方式,不应全部理解为"当前 Agent 自主交接控制权"。
-
底层原理一致:将 Agent 组织成节点或子图,通过边定义执行顺序,并使用状态传递结果。