主题
Multi-Agent 是「主从架构 + 三角色分工」——一个编排器当主,下面管三种角色:规划者拆解任务、执行者干活、检查者把关。它本质是给 ReAct 在「单 Agent 的 while 循环」之上,又加了两层协作:一层「先规划再分配」,一层「执行完要审查」。核心类是 AgentOrchestrator,入口是它的 run 方法。
Multi-Agent 协作流程
分三个阶段。
阶段一:规划(Planner)。 用户任务先交给规划者(SubAgent,角色 PLANNER),它输出一份 JSON 执行计划。编排器用 parsePlan 解析:和 Plan-and-Execute 一样的两遍解析——第一遍创建所有步骤并重编号成 step_1..N,第二遍根据 dependencies 建立依赖边。
阶段二:按依赖调度执行(Worker)。 这是一个 while 循环,每轮 getExecutableSteps 找出「依赖已全部完成」的步骤:
- 单个步骤 → 串行执行;
- 多个步骤 → 并行执行(
runBatchParallel,线程池上限 = Worker 数 = 2 个并发)。
每个步骤交给一个 Worker(角色 WORKER)去执行——注意只有 Worker 能用工具,规划者和检查者都只输出分析结果,不碰工具( shouldUseTools)。
阶段三:审查 + 重试(Reviewer)。 Worker 执行完,结果先交给检查者(REVIEWER)审查。检查者输出 approved 字段(JSON):
- 通过 → 步骤标记完成;
- 不通过 → 检查者还会输出
issues(问题反馈),编排器把「之前的执行结果 + 反馈原因」重新喂给 Worker 重试,最多重试 2 次(MAX_RETRIES_PER_STEP)。
所有步骤执行完,编排器汇总各步骤状态和结果,返回最终总结。
【主动抛深度点 1:审查的保守失败策略】 检查者的 approved 判断有个细节很值得讲:如果 Reviewer 输出的 JSON 解析失败、或者字段缺失,我不会默认放行,而是默认判「不通过」——因为「误放行一个有问题结果」的代价,远大于「多审查一次」。这是安全导向的设计:审查者的异常输出宁可不通过,也不让问题结果直接流下去。
【主动抛深度点 2:Worker 池化】 我只有 2 个 Worker 实例,但任务可能有多个步骤。并行执行时用 BlockingQueue 做 Worker 池化:一个步骤先 take 一个空闲 Worker 才开工,用完 offer 还回去。这保证同一个 Worker 不会被两个步骤并发占用——因为每个 SubAgent 有自己独立的对话历史,如果被并发复用,历史就会串。并行步骤的输出各自缓冲,最后按 step_id 顺序 flush,避免终端输出交错。
三角色怎么分工?为什么只有 Worker 能用工具?
规划者(PLANNER)负责把复杂任务拆成带依赖的子步骤;执行者(WORKER)负责真正调用工具干活(读文件、执行命令);检查者(REVIEWER)负责审查执行结果的质量。只有 Worker 能用工具,是因为规划和审查是「纯思考」任务,让它们碰工具既没必要又危险——审查者如果还能改文件,就失去「独立把关」的意义了。代码里 shouldUseTools 直接 return role == WORKER。
和 Plan-and-Execute 有什么区别?
节点粒度不同。Plan-and-Execute 的节点是「任务」,执行的是同一个 Agent 的不同上下文(PlanExecuteAgent 内部一个方法跑 ReAct);Multi-Agent 的节点是「步骤」,由独立的 SubAgent 执行,每个有独立角色、独立 system prompt、独立对话历史。而且 Multi-Agent 多了「审查 + 重试」这一环——Plan-and-Execute 只规划执行,没有 Reviewer 角色把关。一句话:Plan-and-Execute 是「一个 Agent + 显式 DAG」,Multi-Agent 是「多个角色 Agent + DAG 调度 + 审查闭环」。
每个子 Agent 内部还是 ReAct 吗?对话历史怎么隔离?
是。每个 SubAgent 的 execute 内部就是一个标准的 ReAct while 循环,和主 Agent 对称,也有预算兜底、上下文压缩、流式渲染。隔离靠两点:每个 SubAgent 有自己独立的 conversationHistory;执行完一个步骤后调 clearHistory,清空历史但保留第一条 system prompt,这样同一个 Worker 能干净地复用去执行下一个步骤。
Reviewer 审查不合格怎么重试?最多几次?重试拿什么当输入?
最多 2 次(MAX_RETRIES_PER_STEP = 2)。重试时不是原样重跑,而是把「之前的执行结果被审查拒绝,原因:{issues}」拼进上下文再喂给 Worker,让 Worker 带着「上次错在哪」重新做。如果重试到上限还没通过,就保留当前结果、标记「超过最大重试次数」,不无限循环。
为什么用主从架构,不用完全对等的多 Agent 协商?
可控性和确定性。主从架构里编排器是唯一的决策中枢,负责拆解、调度、汇总,流程是可预测的线性流水线;如果做成对等 Agent 自由协商,容易出现「多 Agent 来回拉扯、不收敛、谁也说服不了谁」的问题,成本和不确定性都高。对当前「执行确定性任务」的定位,主从 + 角色分工是性价比更高的选择,也更容易调试和审计。
多 Agent 编排具体是怎么做的?
Team 是主从结构。编排器是主,规划者、执行者、审查者是从。用户进 Team 之后,编排器先把任务交给规划者:规划者只出带依赖的步骤图,不调工具。
图解析完,编排器按依赖捞一批能跑的步骤。没有前置的可以并行,分给不同 Worker;有依赖的等前置完成。Worker 才有完整工具和独立对话历史,干的是单步,不是整张图。派活时会带上总目标和已完成前置的结果。并行时 Worker 是池化的,干完一步清掉自己的历史再还回去,避免上下步串台。
每步干完立刻审查,审查者也不动手,只看对不对。不过就带着问题打回给同一个步骤重做,次数有上限;还不过就保留当前结果继续,不把整单卡死。前置失败的后续步骤直接跳过,最后由编排器汇总。
和规划模式的差别是:规划是一张图给人审完,再由同一套执行器按图跑;Team 多了角色隔离和自动质检,适合需要交叉检查的复杂任务,代价是更重、更慢。
每个子 Agent 的区别是什么?
子 Agent 的差别主要在职责,落地是两件事:提示词不同,对话历史分开。规划者只拆步骤、不干活;Worker 只干被派到的那一步,能用工具;审查者只判断过不过。三者各看各的对话,Worker 之间也不共享聊天记录,所以不会在上下文里互相污染。
但不是谁都两眼一抹黑。编排器会按依赖,把已经完成的前置结果注入到当前这一步,Worker 能用结论,却看不到别人是怎么想出来的。
为什么这么设计?能不能所有的子 Agent 共享工具?
能共享,而且我们就是这么做的:三个角色共用同一套工具,没有按角色复制三份。但不能让所有子 Agent 都去调。
规划者和审查者在运行时拿不到工具,不是靠它们自觉。提示词只说明「你是谁」;真正卡住的是发请求时不携带工具,模型没有可调用的入口。只有 Worker 能动手。
这么设计是为了角色隔离。审查者如果也能改文件,发现 Worker 写错就自己改,再审自己改过的结果,等于既当运动员又当裁判,质检失效。执行权集中在 Worker,规划者和审查者只做判断:计划差找规划者,执行差找 Worker,漏检找审查者。
八股延伸|多 Agent 系统,Agent 之间如何协作?
多 Agent 系统,Agent 之间如何协作?
“主从架构,一个编排器(Orchestrator)加三种角色的 Sub-agent:规划者(Planner)、执行者(Worker)、审查者(Reviewer)。”
“协作流程分四个阶段。第一阶段,用户提交任务后,编排器把任务交给规划者。规划者的职责是把任务拆解成一组带依赖关系的子步骤,输出一个 JSON 格式的执行计划,每个步骤有 id、描述和依赖列表。”
“第二阶段,编排器解析执行计划,按依赖关系拓扑排序。同一批次内没有依赖关系的步骤可以并行执行。并行执行时,Workers 从一个 BlockingQueue 池子里获取,保证同一个 Worker 不会被两个步骤同时占用。每个并行步骤用独立的 PrintStream 缓冲输出,批次结束后按 step_id 顺序刷新到终端,避免多线程写同一个输出流造成内容交错。”
“第三阶段,每个步骤执行完后交给 Reviewer 审查。Reviewer 输出一个带 approved 字段的 JSON,编排器解析审批结果。未通过的步骤会带上 Reviewer 的反馈重新交给 Worker 执行,最多重试 2 次。”
“第四阶段,所有步骤完成后编排器汇总结果返回给用户。”
多 Agent 之间的上下文怎么管理?
“对话历史是隔离的,工具注册表是共享的。”
每个 Sub-agent 有自己独立的对话历史和系统提示词(根据角色不同使用不同的提示词模板),互不干扰。但它们共享同一个工具注册表——这意味着所有 Sub-agent 能调用的工具集合是一样的,只不过规划者和审查者不调工具,只有执行者才调。
“每个步骤执行完后,Worker 会清空自己的对话历史,只保留系统提示词。这是为了让 Worker 处理下一个步骤时不受上一个步骤的残余上下文干扰。”
任务状态如何去进行传递?
“任务状态通过一个不可变的步骤记录对象传递。”
每个步骤有 id、description、dependencies、result 和 status 五个字段。status 在 PENDING → RUNNING → COMPLETED/FAILED 之间流转。
编排器在分配下一批可执行步骤时,会检查每个 PENDING 步骤的 dependencies 是否全部处于 COMPLETED 状态。
“步骤之间的上下文传递靠编排器来组装:它把当前步骤所依赖的已完成步骤的结果拼成一段上下文,注入到 Worker 的任务描述里。这样 Worker 不需要看到所有步骤的历史,只看到和自己直接相关的前置结果。”
八股延伸|多 Agent 系统的工作流程和生成效果如何去做量化评估?
多 Agent 系统的工作流程和生成效果如何去做量化评估?
“三个维度。第一是任务完成率。所有步骤都达到 COMPLETED 状态算完成,有 FAILED 步骤算部分完成。这个指标反映的是端到端的可靠性。”
“第二是 Reviewer 通过率和重试率。如果大量步骤需要重试才能通过 Reviewer 审查,说明 Worker 的 prompt 或者工具组合有问题。单步最多重试 2 次是个硬上限,超过就强制保留当前结果。这个指标反映的是单步执行质量。”
“第三是 token 消耗和耗时。每个 Sub-agent 的每轮 LLM 调用都会通过预算机制记录 input token、output token 和 cached input token。编排器可以统计整个任务的总 token 消耗。并行执行的步骤可以通过比较批次耗时和串行估计耗时来评估并行加速比。”
“坦白说,多 Agent 系统的评估在业界还没有统一的标准。我目前主要靠 Reviewer 的审查机制做在线质量把控,离线评估还在探索中。”
八股延伸|有实现 Sub-agent 吗?怎么编排的?
有实现 Sub-agent 吗?怎么编排的?
“规划者(Planner)接收用户任务,拆解成一组带依赖关系的执行步骤。”
“比如用户说‘重构这个模块并写测试’,规划者会拆成:步骤 1 分析现有代码结构,步骤 2 执行重构(依赖步骤 1),步骤 3 写单元测试(依赖步骤 2),步骤 4 运行测试验证(依赖步骤 3)。”
“这些步骤按 DAG(有向无环图)组织。编排器做拓扑排序,找出所有依赖已满足的步骤,丢给执行者(Worker)并行跑。”
执行者工作池默认 2 个,用 BlockingQueue 管理,谁空闲谁接活,避免任务堆在一个执行者身上。
“每个步骤执行完,检查者(Reviewer)会审查结果,输出一个 JSON 结构,包含是否通过、问题列表、摘要。”
审查不通过的步骤会带着检查者的反馈重新执行,最多重试 2 次。
如果整体进度不到 50%,是 Plan-and-Execute 那条线的 PlanExecuteAgent 触发重新规划(Multi-Agent 这边是 reviewer 不通过带 issues 重试,最多 2 次),把已完成任务和失败原因喂回给规划者换一种拆解方式。
“每个执行者有独立的消息历史和 Skill 上下文缓冲区。并行执行时,输出各自写入独立的字节缓冲区,全部完成后再按原始顺序合并到终端,保证展示不会乱序。”
八股延伸|Sub-agent 也支持 Plan 模式吗?Skill 怎么调用?
Sub-agent 也支持 Plan 模式吗?Skill 怎么调用?
“可以。每个执行步骤有一个 mode 字段,值可以是 react 或 plan。”
规划者在拆解任务的时候可以判断某个步骤是否足够复杂,需要用 Plan 模式来执行。
“Plan 模式的流程是:先调规划者生成一份 JSON 格式的执行计划,每个步骤有 id、描述、类型和依赖列表。”
有一个简单的任务检测,如果用户输入不超过 30 个字、不包含‘然后’‘并且’‘再’这些多步骤线索词、且命中‘列出/查看/读取/执行/搜索’这类单步意图词,就跳过 LLM 规划,直接生成一个单步计划,省掉一次 LLM 调用。
“计划经过拓扑排序确定执行顺序。没有依赖关系的步骤按层级分批,同批内用线程池并行执行,有依赖的等上一批完成再跑。”
Sub-agent 是怎么调用 Skill 的?
老王紧跟着追问:“Skill 在 Sub-agent 里怎么工作?”
“系统提示词里注入了一份 Skill 索引,包含所有启用 Skill 的名称和描述,最多 20 个,总大小不超过 4KB。”
LLM 在处理任务时,如果判断当前任务和某个 Skill 的描述匹配,就会主动调用 load_skill 这个内置工具。
“加载后,Skill 的完整内容会写入一个上下文缓冲区。下一轮 LLM 请求时,这段内容自动注入到用户消息的前面。LLM 就相当于拿到了一份专家手册,按手册指引来执行任务。”
“有个关键设计:每个 Sub-agent 有独立的 Skill 缓冲区,用 LRU 策略最多缓存 3 个 Skill。”
因为 Skill 内容注入后会从缓冲区清空(drain 操作),如果多个 Sub-agent 共享同一个缓冲区,一个 Worker 的 drain 会把另一个 Worker 还没读到的内容清掉。
源码锚点(读码定位用,面试不主动讲)
AgentOrchestrator.java:305AgentOrchestrator.java:433SubAgent.java:189AgentOrchestrator.java:541SubAgent.java:316