深入理解 AI Agent · 多 Agent 编排 #01:多 Agent 编排的四种核心模式

📘 《深入理解 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)

bash 复制代码
Agent A → Agent B → Agent C → 结果

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

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

⚠️ 坑点

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

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

实际代码思路(Java)

bash 复制代码
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)

bash 复制代码
        ┌→ 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 失败,只要核心数据源成功即可。

并发控制示例

bash 复制代码
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)

bash 复制代码
           ┌→ 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% 的请求在前两层完成路由,显著降低延迟和不确定性。

三层路由实现

bash 复制代码
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)

bash 复制代码
        ┌→ 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) 并记录取消原因到日志,方便后续分析。

bash 复制代码
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(有向无环图)

bash 复制代码
            ┌→ 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 StateGraph DAG + 条件边 保留 DAG 结构约束,但通过条件边(Conditional Edge)支持运行时动态路由。"静态拓扑 + 动态路由"的折中,约束更强但可预测性更好

LangGraph 的循环机制

bash 复制代码
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 的条件边

bash 复制代码
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 的方案是共享状态 + 增量更新 + 键隔离

bash 复制代码
// 每个节点返回状态增量,而非修改全局状态
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. 选型决策框架

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

bash 复制代码
第一步:任务可拆分吗?
├─ 不可拆分 → 单 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 协作平台

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

相关推荐
GIR7201 小时前
重构全球关节腔内注射物行业竞争格局:市场占有率、销量排名及主要竞争对手分析
大数据·人工智能
阿图灵1 小时前
OpenCV 阈值与模糊:全局/自适应阈值、Canny 边缘检测与三种模糊
图像处理·人工智能·python·opencv·计算机视觉·边缘检测
AI编程实验室1 小时前
AI 前端项目验收:Playwright 截图、响应式视口、控制台错误与交互回归
ai编程
Web3_Basketball1 小时前
看完这篇,GLM-5.3-Flash原生多模态实战你也能上手
ai编程
故七月1 小时前
让AI“看见”好服务——生成式引擎优化(GEO)的底层逻辑与产业实践
大数据·人工智能·机器学习
用户5274675614211 小时前
Agent 之间别互抄聊天记录:把交接做成可验证的 Artifact Envelope
人工智能
刘立军1 小时前
插件化与扩展点:引导 AI 模块化插拔开发,功能解耦便于迭代
人工智能·后端·架构
腾视科技-AIoT1 小时前
让安全驾驶有“AI”相伴|腾视科技DMS视频监控一体机,守护每一次出行
大数据·人工智能·科技·安全·行车记录仪·ainas·腾视科技
欧特克_Glodon1 小时前
OpenCV计算机视觉开发入门与实践<二十一>:灰度直方图清晰化图像
c++·人工智能·opencv·计算机视觉