1. 什么时候该拆成多 Agent
出现以下任一信号,就该考虑 Multi-agent:
| 信号 | 说明 |
|---|---|
| 工具太多 | 单个 Agent 挂着十几二十个工具,工具选择准确率下降 |
| 上下文膨胀 | 单个 Agent 难以有效跟踪不断增长的历史与记忆 |
| 需要专业化 | 任务天然可分角色:规划器 / 研究员 / 数学专家 / 评审 |
拆分的收益不只是「快」,更重要的是每个 Agent 只看它该看的东西------这就是文档强调的「系统质量很大程度上取决于上下文工程」。
2. 两种基础协作模式
| 模式 | 工作原理 | 控制流 | 场景 |
|---|---|---|---|
| Tool Calling(Agent as Tool) | Supervisor 把其他 Agent 当工具调用,子 Agent 不直接面对用户,只执行并返回结果 | 集中式,所有路由经过调用方 | 任务编排、结构化工作流 |
| Handoffs(交接) | 当前 Agent 主动把控制权(和状态)移交给另一个 Agent,活跃 Agent 随之变更,用户接着与新的 Agent 交互 | 去中心化 | 跨领域对话、专家接管 |
怎么选:
| 问题 | Agent Tool | Handoffs |
|---|---|---|
| 需要集中控制工作流程? | ✅ | ❌ |
| 希望 Agent 直接与用户交互? | ❌ | ✅ |
| 专家之间复杂的类人对话? | 有限 | ✅ 强 |
两者可以混合:用 Handoffs 做 Agent 切换,同时每个 Agent 又把子 Agent 当工具调用。
3. 上下文工程:四个关键参数
这是 Multi-agent 的实操核心,决定每个子 Agent「看到什么」。
| 参数 | 作用 | 建议 |
|---|---|---|
instruction |
在当前 Agent 节点插入任务说明,支持 {input} / {outputKey} / {stateKey} 占位符 |
用占位符引用上游输出,别手动搬状态 |
outputKey |
把本 Agent 输出存进状态,供后续 {key} 引用 |
起有意义的名字 |
returnReasoningContents |
子 Agent 的中间推理是否回传父流程 | 默认关/设 false,减少上下文、提效 |
includeContents |
子 Agent 执行时是否带上父流程全部上下文 | 设 false 让子 Agent 专注自己的任务 |
java
ReactAgent reviewerAgent = ReactAgent.builder()
.name("reviewer_agent").model(chatModel)
.instruction("请对文章进行评审修正:\n{article},最终返回评审修正后的文章内容")
.includeContents(true) // 带上父流程上下文
.returnReasoningContents(true) // 回传推理过程(调试时开,生产建议关)
.outputKey("reviewed_article")
.build();
调试期:
includeContents=true+returnReasoningContents=true,看清全链路;生产期:两个都收窄,省 token、降干扰。
4. Instruction 占位符
| 占位符 | 含义 | 场景 |
|---|---|---|
{input} |
用户原始输入 | 第一个 Agent / 需要用户输入的 Agent |
{outputKey} |
引用其他 Agent 通过 outputKey 存的输出 |
顺序执行中下游引用上游 |
{stateKey} |
状态里的任意键值 | 访问任何状态数据 |
机制:执行时从 OverAllState 查值 → 转字符串 → 插入 instruction。
注意:占位符对应值不存在时,系统保留原始占位符文本 (不会报错,但模型会看到 {article} 这种字面量,容易生成怪结果)。
java
ReactAgent writerAgent = ReactAgent.builder()
.name("writer_agent").instruction("你是一个知名作家,请回答:{input}").outputKey("article").build();
ReactAgent reviewerAgent = ReactAgent.builder()
.name("reviewer_agent").instruction("请评审修正:{article},返回修改后的文章").outputKey("reviewed_article").build();
5. 五种编排 Agent
5.1 SequentialAgent(顺序)
java
SequentialAgent blogAgent = SequentialAgent.builder()
.name("blog_agent")
.description("先写文章,再交给评论员评论")
.subAgents(List.of(writerAgent, reviewerAgent))
.build();
Optional<OverAllState> result = blogAgent.invoke("帮我写一个100字左右的散文");
result.ifPresent(state -> {
state.value("article"); // 第一个 Agent 的输出
state.value("reviewed_article"); // 第二个 Agent 的输出
});
- 按
subAgents顺序执行,输出经outputKey落到状态。 - 默认共享消息历史 ;用
returnReasoningContents控制是否包含中间推理。 - 取出的值是
AssistantMessage,需instanceof判断后getText()。
5.2 ParallelAgent(并行)
java
ParallelAgent parallelAgent = ParallelAgent.builder()
.name("parallel_creative_agent")
.description("并行写散文、写诗、做总结")
.subAgents(List.of(proseWriterAgent, poemWriterAgent, summaryAgent))
.mergeOutputKey("merged_results")
.mergeStrategy(new ParallelAgent.DefaultMergeStrategy())
.build();
- 所有子 Agent 拿到同一份输入 (通常都用
{input}),结果再合并。 - 各自
outputKey独立可取,另有mergeOutputKey存合并结果。 - 自定义合并:实现
ParallelAgent.MergeStrategy:
java
public class CustomMergeStrategy implements ParallelAgent.MergeStrategy {
@Override
public Map<String, Object> merge(Map<String, Object> mergedState, OverAllState state) {
state.data().forEach((k, v) -> {
if (k.endsWith("_result")) { /* 累加到 all_results */ }
});
return mergedState;
}
}
5.3 LlmRoutingAgent(单次智能路由)
java
LlmRoutingAgent routingAgent = LlmRoutingAgent.builder()
.name("content_routing_agent")
.description("根据用户需求智能路由到合适的专家Agent")
.model(chatModel)
.subAgents(List.of(writerAgent, reviewerAgent, translatorAgent))
.build();
- 每次请求只路由到一个 Agent 就结束。
- 路由依据是子 Agent 的
description------描述写得含糊,路由就不准。 - 提升准确率的三个手段:清晰的 description、明确职责边界(写清「不处理什么」)、避免领域重叠。
- 可用
systemPrompt(SystemMessage,定义角色与决策规则,优先级更高)+instruction(UserMessage,补充场景指导)。
5.4 SupervisorAgent(多步循环监督)
与 LlmRoutingAgent 的关键区别:子 Agent 执行完回到监督者 ,监督者可以继续路由,直到返回 FINISH。
java
SupervisorAgent supervisorAgent = SupervisorAgent.builder()
.name("content_supervisor").model(chatModel)
.systemPrompt(SUPERVISOR_SYSTEM_PROMPT) // 决策规则 + 子Agent职责 + 「只返回名称或FINISH」
.instruction(SUPERVISOR_INSTRUCTION) // 可用占位符 {article_content} 读前序输出
.subAgents(List.of(translatorAgent, reviewerAgent))
.build();
| 特性 | LlmRoutingAgent | SupervisorAgent |
|---|---|---|
| 路由次数 | 单次 | 多步循环 |
| 子 Agent 完成后 | 直接结束 | 返回监督者继续决策 |
| 多步骤任务 | ❌ | ✅ |
| instruction 占位符 | ❌ | ✅ |
| 重试 | --- | 内置最多 2 次重试 |
可嵌套:SequentialAgent(subAgents = [写手Agent, SupervisorAgent]) ------ 先产出内容,再由监督者决定翻译还是评审。
5.5 自定义 FlowAgent
FlowAgent 是 SequentialAgent / ParallelAgent / LlmRoutingAgent 的基类,核心是实现 buildSpecificGraph():
java
public abstract class FlowAgent extends Agent {
protected List<Agent> subAgents;
protected CompileConfig compileConfig;
protected abstract StateGraph buildSpecificGraph(FlowGraphBuilder.FlowGraphConfig config) throws GraphStateException;
}
自定义套路:继承 FlowAgent + 继承 FlowAgentBuilder 写 Builder + 用 FlowGraphBuilder 构图。可做出 ConditionalAgent(Predicate 二分支)、CustomLoopAgent(exitCondition + maxIterations,如「质量分 ≥ 8 或最多迭代 5 次」)。
6. 混合模式(实战最常用)
ParallelAgent(并行收集:web_data + db_data → research_data)
↓
ReactAgent(分析:{research_data} → analysis_result)
↓
LlmRoutingAgent(按格式路由:pdf_report / html_report)
↓ 包一层
SequentialAgent hybridWorkflow
要点:FlowAgent 本身也是 Agent,所以能互相嵌套 ,subAgents() 里可以放 ReactAgent,也可以放另一个 FlowAgent。
7. 选型决策树
任务是固定几步流水线? → SequentialAgent
多个独立子任务可同时跑? → ParallelAgent(+ mergeStrategy)
需要按用户意图挑一个专家,一次就够? → LlmRoutingAgent
需要监督者多轮调度、可能反复调用不同专家? → SupervisorAgent
需要条件分支 / 循环迭代等自定义拓扑? → 继承 FlowAgent
以上都要? → 嵌套组合(Sequential 里放 Parallel / Supervisor)
8. 易混点 & 坑(7 条)
- Routing 与 Supervisor 别选错 :只要「一次跳转」用
LlmRoutingAgent;要「写完再翻、翻完再审」这种多步编排才用SupervisorAgent。反过来用会出现「任务只做了一半就结束」。 description就是路由的输入:子 Agent 的 description 写得像口号("专业Agent"),LLM 必然乱路由。要写清「能力 + 适用输入 + 不做什么」。- 占位符找不到值时不会报错 ,只会把
{article}原样塞给模型。上游outputKey拼错 = 静默降级。 returnReasoningContent(s)命名在文档里两种写法都有 (returnReasoningContent/returnReasoningContents),实际编码以你依赖版本的 API 为准。includeContents=false≠ 拿不到上游数据 :上游输出仍可通过{outputKey}引用,只是不把父流程全部消息历史带进来。这两件事要分开理解。- 并行 Agent 共享同一输入 :子 Agent 的 instruction 通常都用
{input};如果想让它们处理不同东西,得靠各自 instruction 加以区分。 - Supervisor 的
systemPrompt必须写死响应格式(「只返回 Agent 名称或 FINISH,不要解释」),否则模型输出一段分析文本会导致路由解析失败。
9. 本项目(xs-interview-agent)落地建议
现状:单条 ChatModel 直链,四步(解析简历 → 五维评分 → 出题 → 逐题评估)全靠 Service 方法串行拼装,所有上下文一次性塞进 prompt。
9.1 推荐拆法
| 角色 | Agent 类型 | 说明 |
|---|---|---|
| 简历解析 | ReactAgent(Tika 工具) | outputKey="resume_profile" |
| 五维评分 | ParallelAgent | 技术深度/项目STAR/匹配度/表达/潜力 五个子 Agent 并行打分 → mergeOutputKey="scores";天然解决上下文过长 |
| 出题 | ReactAgent | instruction="...{resume_profile}...{scores}",outputKey="questions" |
| 逐题评估 | ReactAgent | 按 question 循环 |
| 面试模式路由 | LlmRoutingAgent | 技术面 / HR 行为面 / 压力面三个专家,按用户选择或简历特征路由 |
| 总装 | SequentialAgent | [解析, 并行评分, 出题, 评估] |
9.2 骨架
java
ParallelAgent scoringAgent = ParallelAgent.builder()
.name("scoring_agent")
.description("并行完成简历五个维度的评分")
.subAgents(List.of(techDepthAgent, starAgent, matchAgent, expressionAgent, potentialAgent))
.mergeOutputKey("scores")
.mergeStrategy(new ParallelAgent.DefaultMergeStrategy())
.build();
SequentialAgent interviewFlow = SequentialAgent.builder()
.name("interview_flow")
.description("简历解析 → 并行评分 → 出题 → 评估")
.subAgents(List.of(profileAgent, scoringAgent, questionAgent, evaluationAgent))
.build();
Optional<OverAllState> state = interviewFlow.invoke(resumeText);
9.3 配套注意
- 每个子 Agent 设
includeContents=false,只靠{resume_profile}/{scores}传数据,避免每一轮都带上整份简历文本。 - 评分 Agent 用
outputType(见 ReactAgent 笔记)输出结构化分数,避免再手工cleanJsonResponse()剥围栏。 - 之前的
structureScore分值不一致问题,在「合并策略」处统一收敛:在CustomMergeStrategy.merge()里做min(100, Σ)兜底。 - 评分属于「可以并行、互不依赖」的典型场景,是本项目中收益最大的一处改造。
10. 30 秒答辩稿
Multi-agent 是把一个臃肿 Agent 拆成多个小而专的 Agent 协同工作。Spring AI Alibaba 有两种基础协作方式:Tool Calling 是集中式,把子 Agent 当工具调用,子 Agent 不面对用户;Handoffs 是去中心化,Agent 之间直接交棒,活跃 Agent 随之改变,用户可以继续和新 Agent 对话。上层提供了五种编排:SequentialAgent 顺序流水线、ParallelAgent 并行加合并策略、LlmRoutingAgent 单次智能路由、SupervisorAgent 多步循环调度直到 FINISH,以及继承 FlowAgent 自定义图拓扑,而且它们可以互相嵌套。真正决定效果的是上下文工程四个参数:instruction 配合占位符引用上游输出、outputKey 命名输出、returnReasoningContents 控制推理是否回传、includeContents 控制是否带父流程上下文------生产环境后两个都应收窄以省 token 降干扰。
11. 速查表
| 需求 | API |
|---|---|
| 顺序流水线 | SequentialAgent.builder().subAgents(List.of(a,b)).build() |
| 并行 + 合并 | ParallelAgent.builder().subAgents(...).mergeOutputKey(k).mergeStrategy(s).build() |
| 自定义合并 | 实现 ParallelAgent.MergeStrategy#merge(Map, OverAllState) |
| 单次路由 | LlmRoutingAgent.builder().model(chatModel).subAgents(...).build() |
| 多步监督 | SupervisorAgent.builder().model(chatModel).systemPrompt(sp).instruction(i).subAgents(...).build() |
| 自定义拓扑 | 继承 FlowAgent,实现 buildSpecificGraph(),配合 FlowGraphBuilder |
| 声明输出键 | .outputKey("xxx") → 下游用 {xxx} 引用 |
| 引用用户输入 | {input} |
| 引用任意状态 | {stateKey} |
| 控制推理回传 | .returnReasoningContents(true/false) |
| 控制父上下文 | .includeContents(true/false) |
| 取结果 | agent.invoke(x) → Optional<OverAllState> → state.value("key") |
| 结束监督循环 | 监督者返回 FINISH |