深入理解 AI Agent · 多 Agent 编排 #01:多 Agent 编排的四种核心模式
2026/8/28 17:39:41 网站建设 项目流程

📘 《深入理解 AI Agent》多 Agent 编排系列 · 第1篇

前篇回顾:Agent 基础篇 → RAG 篇 → MCP 篇 → 记忆篇 →编排篇


记忆系统解决了单个 Agent 的"remembering"问题,但任务复杂度一旦超过单 Agent 的能力边界,就需要多 Agent 协作——分工、协调、汇总。

多 Agent 编排要解决的核心问题:怎么让这些 Agent 按正确的顺序、正确的方式协同工作?

网上讲编排的文章大多是"串行、并行、条件、竞争"四种模式的概念罗列。但在 Dream-SaaS 落地的经验是:模式本身不复杂,复杂的是每种模式背后的坑。这篇直接聊问题。


1. 为什么单 Agent 不够用?

给一个 Agent 塞 10 个工具、写 2000 字 system prompt,当任务涉及"搜索 → 分析 → 写报告 → 生成图表 → 排版发布"这种多步骤流程时,单 Agent 会碰到三个硬伤:

🔴问题一:Prompt 膨胀
工具越多,Prompt 越长,模型注意力分散,指令遵循能力下降。实测中,当工具数从 3 个增加到 10 个时,指令遵循准确率从 92% 降至 71%。

🔴问题二:错误放大
单 Agent 链路中,一步错步步错,没有"回退"或"降级"的机会。上游 Agent 输出的微小偏差会在下游被逐步放大。

🔴问题三:无法并行
所有步骤串行执行,总延迟 = 各步骤延迟之和,用户体验差。5 个串行步骤,每步 3 秒,总延迟就是 15 秒。

解法是把任务拆分给多个专业 Agent,每个只负责一小块,通过编排层协调执行顺序和数据流。


2. 四种编排模式:每种模式的坑在哪

四种基础模式:串行、并行、条件分支、竞争。定义不展开,直接聊每种模式的坑点。

2.1 模式一:串行(Sequential)

Agent A → Agent B → Agent C → 结果

A 的输出作为 B 的输入,像流水线一样。

优势:逻辑清晰,每步输入输出可追踪。

⚠️坑点

  • 延迟累积:如果 A 耗时 2s、B 耗时 3s、C 耗时 5s,总延迟 = 10s。用户等不了。
  • 单点脆弱:任何一个环节失败,整条链路断裂。B 挂了,A 和 C 的结果全部作废。
  • 错误传播:A 输出一个小错误,经过 B 放大,到 C 已经面目全非——就像"传话游戏"。

工程建议:串行链路中,每个节点都应有超时控制输出校验。不要假设上游一定返回正确数据。Dream-SaaS 的内容生成链路中,每个节点之间加了一层轻量级校验:输出为空或格式异常时,直接触发降级逻辑,而不是让错误继续传播。

实际代码思路(Java)

public class SequentialPipeline { private final List<AgentNode> nodes; private final Duration timeoutPerNode; public String execute(String input) { String current = input; for (AgentNode node : nodes) { try { current = node.execute(current) .orTimeout(timeoutPerNode.toSeconds(), TimeUnit.SECONDS) .join(); // 输出校验:防止错误传播 if (current == null || current.isBlank()) { log.warn("节点 {} 返回空结果,触发降级", node.getName()); current = node.fallback(current); } } catch (CompletionException e) { log.error("节点 {} 执行失败: {}", node.getName(), e.getMessage()); throw new PipelineException(node.getName(), e); } } return current; } }

2.2 模式二:并行(Parallel)

┌→ Agent A ─┐ 输入 ─┼→ Agent B ─┼→ 合并 → 输出 └→ Agent C ─┘

多个 Agent 同时执行,最后合并结果。

优势:吞吐高、延迟低。

⚠️坑点

  • 资源争用:5 个 Agent 同时调用 LLM API,触发限流,反而比串行还慢。
  • 结果不一致:A 说"答案是 X",B 说"答案是 Y",怎么合并?简单拼接?投票?让 LLM 仲裁?
  • 部分失败:3 个 Agent 中 1 个失败了,是返回 2 个的部分结果,还是整体回滚?

真实踩坑:Dream-SaaS 的"多源知识检索"功能,最初设计 4 个 Agent 并行查询不同数据源。上线后发现,并发数超过 3 时,DeepSeek API 的限流导致大量 429 错误。最终方案是引入信号量控制并发数+指数退避重试,并在合并层做容错处理——允许部分 Agent 失败,只要核心数据源成功即可。

并发控制示例

public class ParallelOrchestrator { private final Semaphore concurrencyLimit; private final ExecutorService executor; public ParallelResult execute(List<AgentTask> tasks) { List<CompletableFuture<AgentResult>> futures = tasks.stream() .map(task -> CompletableFuture.supplyAsync(() -> { try { concurrencyLimit.acquire(); // 控制并发数 return executeWithRetry(task, 3); // 指数退避重试 } catch (InterruptedException e) { Thread.currentThread().interrupt(); return AgentResult.failed(e.getMessage()); } finally { concurrencyLimit.release(); } }, executor)) .toList(); // 容错合并:只要核心数据源成功即可 List<AgentResult> results = futures.stream() .map(CompletableFuture::join) .toList(); long coreSuccess = results.stream() .filter(r -> r.isCoreSource() && r.isSuccess()) .count(); if (coreSuccess == 0) { throw new OrchestrationException("所有核心数据源均失败"); } return mergeResults(results); } private AgentResult executeWithRetry(AgentTask task, int maxRetries) { for (int i = 0; i <= maxRetries; i++) { try { return task.agent().execute(task.input()); } catch (RateLimitException e) { if (i == maxRetries) throw e; long backoff = (long) Math.pow(2, i) * 1000; // 指数退避 sleep(backoff); } } throw new UnreachableException(); } }

2.3 模式三:条件分支(Conditional)

┌→ Agent A(路径 1) 输入 → 路由器 ─┼→ Agent B(路径 2) └→ Agent C(路径 3)

根据条件判断路由到不同 Agent。典型场景:意图识别后分发。

优势:职责分离,Prompt 精准。

⚠️坑点

  • LLM 的不确定性:路由器用 LLM 实现时,同一个问题问两次可能路由到不同 Agent。例如用户问"帮我查一下文档里的代码示例",第一次路由到 RAG Agent(关键词命中"文档"),第二次可能路由到 Code Review Agent(关键词命中"代码")。
  • 边界模糊:用户的问题可能同时涉及多个分支,需要设计优先级或组合路由策略。
  • 无默认路径:路由条件未覆盖所有情况时,请求会"掉到地上"。必须有兜底的 default route。

工程建议:路由层的设计原则是**"能不调 LLM 就不调"**。Dream-SaaS 的 Supervisor 设计采用三层判定策略:

第一层:规则匹配(关键词命中,延迟 < 1ms)→ 如包含"代码审查"直接走 Review Agent
第二层:启发式判断(基于特征,延迟 < 50ms)→ 如检测到代码片段 + 长度 > 50 行
第三层:LLM 分类(兜底,延迟 500ms+)→ 只有前两层无法确定时才调用 LLM

这种设计让 80% 的请求在前两层完成路由,显著降低延迟和不确定性。

三层路由实现

public class ThreeLayerRouter { private final List<RuleMatcher> rules; // 第一层 private final List<HeuristicMatcher> heuristics; // 第二层 private final LLMAgent llmClassifier; // 第三层 public AgentRoute route(String input) { // 第一层:规则匹配 for (RuleMatcher rule : rules) { if (rule.matches(input)) { log.debug("规则命中: {} -> {}", rule.pattern(), rule.targetAgent()); return rule.targetAgent(); } } // 第二层:启发式判断 for (HeuristicMatcher h : heuristics) { if (h.matches(input)) { log.debug("启发式命中: {} -> {}", h.feature(), h.targetAgent()); return h.targetAgent(); } } // 第三层:LLM 分类(兜底) try { String classification = llmClassifier.classify(input); return AgentRegistry.resolve(classification); } catch (Exception e) { // 必须有 default route,不能让请求"掉到地上" log.warn("LLM 分类失败,使用默认路由: {}", e.getMessage()); return AgentRegistry.defaultAgent(); } } }

2.4 模式四:竞争(Race)

┌→ Agent A ─┐ 输入 ─┼→ Agent B ─┼→ 取最快/最优 → 输出 └→ Agent C ─┘

多个 Agent 同时执行相同任务,取最快或最优的结果。

优势:低延迟、高可靠、可 A/B 测试。

⚠️坑点

  • 资源浪费:3 个 Agent 都在跑,但只有 1 个的结果会被用到,其他 2 个的算力和 API 调用费都浪费了。
  • 败者清理:取到最快结果后,其余 Agent 还在跑吗?要不要主动取消?如何取消?
  • 结果质量:最快的不一定是最好的。如果 Agent A 100ms 返回了一个"不知道",Agent B 2s 后返回了正确答案,你取哪个?

容易被忽略的问题:竞争模式中的**"败者清理"。Java 中用CompletableFuture.anyOf()拿到最快返回的 Future,但其他 Future 还在后台执行,继续占用线程和 LLM 资源。Dream-SaaS 实现了一个竞争清理器**:胜者确定后,遍历所有败者 Future,调用cancel(true)并记录取消原因到日志,方便后续分析。

public class RaceOrchestrator { @SuppressWarnings("unchecked") public AgentResult race(List<AgentTask> tasks) { CompletableFuture<AgentResult>[] futures = tasks.stream() .map(t -> CompletableFuture.supplyAsync( () -> t.agent().execute(t.input()), executor)) .toArray(CompletableFuture[]::new); try { // 取最快结果 AgentResult winner = CompletableFuture.anyOf(futures).join(); // 败者清理:取消所有未完成的 Future for (CompletableFuture<AgentResult> future : futures) { if (!future.isDone()) { boolean cancelled = future.cancel(true); log.info("取消败者任务: cancelled={}", cancelled); } } // 质量校验:最快的结果可能质量不够 if (!winner.meetsQualityThreshold()) { log.warn("最快结果质量不达标,等待次优结果"); return waitForQualityResult(futures, winner); } return winner; } catch (CompletionException e) { throw new RaceException("所有竞争任务均失败", e); } } }

3. DAG 调度:编排模式的"集大成者"

实际项目中更常见的是混合编排——先条件路由,再并行执行,最后串行汇总。描述这种复杂拓扑的最佳方式是DAG(有向无环图)

┌→ Agent A ─┐ START → 路由 ─┼→ Agent B ─┼→ 合并 → Agent D → END └→ Agent C ─┘

DAG 的好处是声明式:你只需要定义"谁依赖谁",调度器自动计算执行顺序。但有几个问题值得注意:

3.1 静态 DAG vs 动态 DAG

传统的 DAG 是编译时确定的——你画好图,执行时就按这个拓扑走。但在 Agent 场景中,图的结构可能需要在运行时动态决定

举个例子:Agent A 分析用户需求后,可能决定需要调用 Agent B 和 C,也可能只需要 B,甚至可能发现需要新增一个 Agent D。这种动态图生成的需求,传统 DAG 调度器搞不定。

主流框架的解法

框架策略特点
LangGraph有状态的有向图,允许循环节点可根据状态动态决定下一节点(条件边),甚至回退。代价是需要处理"无限循环"——必须设置最大步数或收敛检测
Spring AI Alibaba StateGraphDAG + 条件边保留 DAG 结构约束,但通过条件边(Conditional Edge)支持运行时动态路由。"静态拓扑 + 动态路由"的折中,约束更强但可预测性更好

LangGraph 的循环机制

from langgraph.graph import StateGraph, END workflow = StateGraph(AgentState) workflow.add_node("analyze", analyze_node) workflow.add_node("execute", execute_node) workflow.add_node("review", review_node) # 条件边:review 节点可以回退到 execute workflow.add_conditional_edges( "review", lambda state: "execute" if state["needs_revision"] else END ) # 必须设置最大步数防止无限循环 app = workflow.compile() result = app.invoke(input, config={"recursion_limit": 10})

Spring AI Alibaba StateGraph 的条件边

StateGraph graph = new StateGraph("supervisor", stateFactory) .addNode("intent", intentRouterNode) .addNode("rag", ragAgentNode) .addNode("code_review", codeReviewNode) // 条件边:根据运行时状态动态路由 .addConditionalEdges("intent", state -> { String intent = state.get("intent"); return switch (intent) { case "knowledge" -> "rag"; case "code" -> "code_review"; default -> "rag"; // 默认路由 }; }) .addEdge("rag", END) .addEdge("code_review", END);

两者的核心区别:LangGraph 允许review → execute → review这种循环(本质是有向图),而 StateGraph 要求拓扑无环,只能通过条件边在"同一层的不同节点"之间做选择。前者表达力更强,后者更容易推理和调试。

3.2 状态传递

DAG 中节点之间的状态传递有两种方式:

方式说明适用场景
显式传递节点输出作为下一个节点的输入参数串行链路、简单 DAG
共享状态所有节点读写同一个全局状态对象复杂 DAG、需要跨分支共享数据

显式传递更安全,但不够灵活;共享状态更灵活,但容易出竞态条件——两个并行节点同时修改同一个状态字段,结果不可预测。

3.3 竞态条件及解法

当并行分支共享同一个状态对象时,两个节点可能同时读写同一字段,导致数据不一致。

三种常见解法

方案原理优点缺点
不可变状态每次更新返回新对象,不修改原对象(Redux reducer 模式)天然线程安全内存开销大,状态对象多时 GC 压力上升
细粒度锁对状态字段加ReentrantLocksynchronized简单直接可能成为性能瓶颈,死锁风险
原子更新AtomicReference<Map>ConcurrentHashMap原子操作性能好实现复杂,复合操作需要 CAS 循环

Dream-SaaS 的方案是共享状态 + 增量更新 + 键隔离

// 每个节点返回状态增量,而非修改全局状态 public Map<String, Object> execute(Map<String, Object> sharedState) { Map<String, Object> delta = new HashMap<>(); delta.put("analysisResult", performAnalysis(sharedState.get("input"))); delta.put("confidence", 0.95); return delta; // 框架负责合并 } // 并行分支的约束:只写各自隔离的状态键 // Agent A 只写 "rag_result" 键 // Agent B 只写 "code_result" 键 // 合并阶段由汇总节点统一处理,避免并行写入冲突

Spring AI Alibaba 的 StateGraph 采用的就是这种模式:每个节点返回一个Map<String, Object>作为状态增量,框架负责合并到全局状态。Dream-SaaS 在此基础上增加了约束:并行分支只写各自隔离的状态键,从设计上杜绝了竞态条件的发生。


4. 选型决策框架

面对具体任务,怎么选编排模式?下面是一个从实际项目中总结的决策框架

第一步:任务可拆分吗? ├─ 不可拆分 → 单 Agent,不需要编排 └─ 可拆分 → 进入第二步 第二步:子任务之间有依赖吗? ├─ 无依赖 → 并行模式 └─ 有依赖 → 进入第三步 第三步:依赖是线性的还是分支的? ├─ 线性(A → B → C)→ 串行模式 └─ 分支(A 的输出决定走 B 还是 C)→ 条件分支模式 第四步:需要冗余或低延迟吗? ├─ 需要(如多模型择优、容灾)→ 竞争模式 └─ 不需要 → 用前面的组合即可

核心逻辑:从简单到复杂,能用简单模式就不上复杂模式。编排越复杂,调试越困难,故障点越多。

反模式警告:不要为了"架构感"而过度编排。见过有项目把简单的"检索 → 生成"流程搞成 5 个 Agent 的 DAG,每个 Agent 都要调 LLM,总延迟从 3s 变成 12s,用户直接流失。编排是为了解决问题,不是炫技。


5. 总结

模式适用场景主要风险缓解措施
串行线性流程,步骤间有强依赖延迟累积、错误传播超时控制 + 输出校验 + 降级逻辑
并行无依赖的独立子任务资源争用、结果不一致信号量限流 + 容错合并
条件分支意图路由、任务分发LLM 不确定性、边界模糊三层路由(规则→启发式→LLM)
竞争低延迟要求、多模型择优资源浪费、败者未清理竞争清理器 + 质量阈值
DAG 混合复杂多步骤任务动态图、竞态条件条件边 + 键隔离 + 增量状态

关键结论:

  • 编排模式没有银弹,每种都有适用场景和对应的坑
  • LLM 的不确定性会沿着编排链路传染,路由层、判断层都需要兜底机制
  • 动态 DAG 的表达力强,但实现复杂度不容小觑
  • 从简单到复杂,编排是手段不是目的

下篇预告:《深入理解 AI Agent(六):Agent 之间怎么"说话"——通信、状态与冲突解决》


📘深入理解 AI Agent · 编排篇(上)

作者:宋哥 | Java 后端 → AI Agent 工程师

项目:Dream-SaaS · 多 Agent 协作平台

有问题评论区见,欢迎交流~

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询