☰
多智能体编排层架构选型:自研、框架还是混合架构?
2026/10/20 21:29:27 网站建设 项目流程

做多智能体系统的人,迟早都会撞上一堵墙:编排层到底自己写,还是直接上通用框架?这个问题的纠结程度,不亚于当年选 MVC 还是微服务。我所在的项目组在做一个多智能体协作平台时,前前后后折腾了三轮——先自研、再框架、最后被迫走向混合架构,期间踩过的坑、推翻的重构、还有深夜排查的线上事故,值得完整记录一次。

这篇文章就以这次架构演进为主线,把自研编排与通用框架各自的利弊、混合架构的划分逻辑、落地时的关键细节全部摊开讲。不吹某个框架万能,也不鼓励什么都手搓。适合正在做多智能体编排、或者正准备从原型走向生产环境的技术团队参考。

1. 多智能体系统的架构选型全景

1.1 先想清楚:编排层到底解决什么问题

很多团队一上来就讨论“用框架还是不用框架”,但实际上这个问题的前提是先搞清楚编排层要干什么。多智能体系统不是把几个大模型 API 串起来就行,它本质上是让多个具备不同专长的 Agent 在同一个任务上下文里协作,这就需要一套机制来回答四个问题:

  • 谁先执行、谁后执行,任务怎么拆解成有依赖关系的子任务
  • 每个 Agent 的输入从哪里来、输出写到哪里
  • 中途失败了怎么重试、重试粒度是整条链路还是局部节点
  • 每个 Agent 的中间结果如何追溯排查、如何保证不污染

这套机制的集合就是编排层。它不是可有可无的胶水代码,而是多智能体系统的骨架。骨架一旦选错,业务跑起来就会到处疼。

我见过不少团队把编排层理解成“工作流引擎”,这是不够的。传统工作流引擎面对的是确定性的任务依赖,节点执行结果可预期;而多智能体编排面对的是大模型输出这种随机性极强的执行单元,同一个输入两次调用可能给出不同结果,而且结果是否符合预期很难提前验证。所以编排层必须额外承担“不确定性管理”的职责,这恰恰是通用工作流引擎覆盖不到的地方。

1.2 自研编排的优势与适用边界

自己写编排,最直接的好处是掌控感。系统里每个执行步骤怎么调度、上下文怎么传递、异常怎么处理,全都由你的代码说了算。Agent 之间的交互模式在业务场景里往往非常特殊,可能包括串行、并行、条件跳转、嵌套调用甚至动态生成子任务,通用框架外层的编排能力很难完全覆盖这种多样性。

另一个优势是排障链路短。出问题的时候,自研代码的堆栈是你能读懂的,状态流转是你能推断的。多智能体系统里最痛苦的问题就是“这个 Agent 为什么吃了那个 Agent 的输出却没有产出预期结果”,如果编排层是黑盒,排查起来就是灾难。

但自研也有明显的适用边界。如果你的场景里只需要两三个 Agent、任务流程基本固定、没有复杂依赖关系,那自己写一个二三十行的调度循环完全够用,没必要引入重型框架。反过来,如果任务依赖特别复杂,动态规划子任务的频率很高,仍坚持全自研,很容易把精力耗在不同 Agent 之间的通信协议、状态持久化方案、可观测性埋点这类通用能力上,而这些能力框架通常已经帮你踩过坑了。

1.3 通用框架的强项与隐性成本

通用框架的价值不在于帮你省掉那几百行调度代码,而在于它沉淀了社区对不同 Agent 协作模式的抽象。比如 Agent 会话管理、工具调用的标准协议、消息传递的规范格式,这些设计都是经过大量场景验证过的。直接用框架,相当于站在别人的肩膀上。

但框架的隐性成本往往被低估。首当其冲的就是学习成本——不是学会 API 怎么用,而是理解它的抽象模型。多智能体框架的核心抽象通常分为图模型和对话模型两类,它们的思维方式和适用场景完全不同。团队里每个人对这套模型的理解深度参差,写出来的代码风格就会漂移。

更麻烦的是框架升级。任何活跃的通用框架都在快速演进,接口说变就变,废弃特性说删就删。把核心业务编排逻辑大量依赖框架 API 之后,你会发现自己被框架版本绑架了——想升级但又怕破坏现有流程。

2. 自研路线的实战复盘:能跑,但越跑越痛

2.1 前期实现看起来一切正常

我们的多智能体系统最初就是纯自研路线。核心思路很直白:用一张 DAG 图描述 Agent 之间的依赖关系,然后写一个调度器去逐个执行节点。每个节点内部调用大模型或工具,节点之间通过一个共享的上下文对象传递数据。

这种方案在链路短、Agent 数量少的时候跑得很顺。比如一个“调研+总结”的链路,只有两个节点,调度器先把调研任务发给 AgentA,拿到结果之后塞给 AgentB 做总结,上下文对象里存两份数据就完事了。代码逻辑简洁清晰,当时的结论是“自研完全没问题”。

真正的问题是从链路复杂度上升到五六个节点开始的。随着业务方提出的场景越来越复杂,比如一个销售线索处理链路,涉及客户信息抽取、行业分析、内容生成、多轮审核几个环节,我们开始感受到那根支撑系统的骨架在吱吱作响。起初接到的报障内容都还不难解决,调整下提示词、修修补补上下文传递的逻辑就能应付过去。但很快我们就发现自己太乐观了——骨架承重不足,塌陷是必然的。

2.2 第一个崩溃点:状态管理

自研到中期,最先顶不住的是状态管理。我们的调度器在内存里维护整张 DAG 的执行状态,包括每个节点是 pending、running、success 还是 failed。任务量小的时候内存状态没压力,但一旦执行到中途进程重启或发布新版本,所有进行中的任务状态全部丢失,Agent 的执行结果也一并蒸发。

这个问题在开发环境可能一周触发一次,但到了生产环境就变成了事故。回滚代码后对账发现,线上有十几个执行到一半的任务彻底变成了幽灵任务——调度器不知道它们跑到哪了,用户也不清楚该不该重新发起。当时我在排查日志时意识到,内存状态撑不起生产环境。

解决方案是引入数据库存储状态。我们用一张表记录任务实例,一行对应一个 DAG 节点,字段包括节点 ID、状态、输入输出快照、执行时间。调度器执行每个节点前先查一次库,执行完再更新状态。这套方案能解决崩溃恢复问题,但新问题马上来了:数据库操作变成了瓶颈。一个节点执行完要更新好几张表,多节点并行时数据库压力骤增,而且状态更新和节点执行的原子性很难保证,容易出现节点已执行但状态没更新的情况。

2.3 第二个崩溃点:重试与超时

大模型 API 调用的不稳定,是自研编排压力测试中第一个现形的。供应商接口时不时给你来一个超时,或返回一段格式不合法的 JSON,这些状况在“单 Agent 原型”时期完全不需要考虑——报错就重新跑一次,不会造成大问题。但在多智能体编排场景下,这个问题被急剧放大:一个前置节点失败,整个 DAG 的后续节点全部阻塞,如果重试错误地针对整条链路,所有节点的 API 调用全部重复一遍,费用和时延双双失控。

我们必须实现节点级重试。考虑一个简单的策略:单个节点最多重试 3 次,重试间隔按 1s、2s、4s 递增。问题在于,有些 Agent 的副作用是不可逆的——比如一个负责发邮件的 Agent,你重试一次可能就发了两封。大模型 Agent 的核心动作是文本生成,重试不会产生真实世界的副作用,但如果 Agent 调用了工具(比如发 HTTP 请求、写数据库),重试的幂等性完全取决于工具本身的设计。我们被迫给每个 Agent 节点加了“是否幂等”的标记,不幂等的节点不重试或只重试一次。

超时问题的处理同样头疼。大模型 API 的响应时间波动区间能从 5 秒拉到 3 分钟,而且不同供应商的波动特性还不一样。固定超时时间必然误伤慢请求,不设超时又会被某个坏掉的调用拖垮整个任务。后面我们改成了分层超时策略,在动态调整超时值和收缩重试范围之间反复调参,这套逻辑已经相当于在自研一个微型容错框架了。

2.4 第三个崩溃点:上下文丢失与调试

上下文传递问题则更加隐蔽。我们的调度器设计了一个共享上下文对象,每个 Agent 节点在运行时里读输入、写输出。链路一长,上下文对象里开始堆积大量历史数据——前面所有节点的完整输出都堆在里面,导致两种后果:一是大模型输入 Token 数量急剧膨胀,费用飙升;二是上下文太长之后,部分模型会出现明显的“注意力稀释”现象,在无关紧要的历史细节上纠缠,却忽略了真正关键的任务指令。

这时候你不得不上上下文压缩。但压缩时机怎么选、压缩策略用什么(摘要是提取关键信息还是按对话轮次截断),都需要反复调优。更要命的是,链路中任何一个节点的上下文出现错乱,你都得拿着日志一行行对照 DAG 里的数据流,手工判断哪个 Agent 读到了不该读的数据。这种排查方式效率实在太低了,状态全在内存里,没有形成可观测的痕迹,这也是自研最需要警惕的成本陷阱。

到这一步,我们清晰意识到自研编排的上限——状态管理、容错机制、上下文治理、可观测性,这些不只是功能点,而是系统工程,每一项都要投入大量时间打磨。而业务方根本不会因为你在自研容错框架就多给你三个月工期。

3. 通用框架的试水:效率和黑盒的双刃剑

3.1 迁移初期的效率提升

在自研路线进入瓶颈期后,某位同行在交流群里分享的选型建议让我们转向了通用框架。从表面上看,框架确实把自研期间踩过的那些坑都预填了。首先是 Agent 之间的消息传递协议和会话管理直接可用,不需要自己设计一套上下文命名规范。其次是工具调用的统一标准,多模态工具不用开发者重复适配,插件式管理非常直观。最关键的是,框架提供了受控的容错机制,超时重试不用自己实现,配置下参数就能运行起来。

迁移初期确实感受到了效率跃升。以前自研时每次新增一个 Agent 都要写一遍“读上下文—调用模型—解析结果—写回上下文”的模板代码,在框架里只要继承一个基类、实现几个接口,就能被框架自动调用。一个原本需要两天的需求,一天就搞定了。当时团队信心很高,觉得这次选型选对了。

3.2 框架黑盒带来的问题

这种信心在第一次处置线上故障时彻底崩塌。某天一个生产链路突然大面积失败,Agent 之间的消息不知道哪里断了,框架日志也没有显式的报错堆栈。我们拿着框架的调试日志翻来覆去查了快两个小时,发现实际执行路径和我们以为的指令顺序对不上。原因很快就找到了——框架会在内部对 Agent 的输入模板做一些隐式的预置和后处理,这与我们自定义的指令模板产生了叠加效果。这个问题放在自研代码里一眼就能看到,但在框架里你需要重新阅读源码才能定位。

另一个令人难以适应的问题在动态流程扩展上体现得很明显。我们的业务场景要求支持运行时根据中间结果动态生成新的 Agent 子任务,这相当于让系统有“自治能力”,调度路径不是一个静态图。通用框架更擅长在静态图里定义协作模式,动态分发的实现细节往往没有文档明确,需要开发者自己去翻源码研究扩展点。框架越是“开箱即用”,动态扩展的空间就越是需要探索。

3.3 版本升级和二次开发的痛苦

通用框架的隐形债务在版本升级时集中兑现。从旧版本升到新版本后,不兼容的 API 改动直接导致大量代码需要跟着调整。框架为了保持抽象纯净,选择不暴露底层细节,但当我们需要依赖某个底层特性时,只能二次开发框架源码。自己维护一个框架分支的代价非常大——每次上游更新都要做差异对比、合并冲突、回归测试,这比重新实现一个编排器还耗时。

后来我们统计了一下,引入框架的两个月里,真正花在业务逻辑上的时间大约只占一半,另一半全花在搞懂框架机制、做框架适配和二次开发上了。就在我们处在“退回自研还是改造框架”的纠结时刻,一次偶然的架构评审改变了方向——可以尝试拆分:把“流程怎么走”和“Agent 怎么执行”分开,各用各的优势实现。

4. 混合架构:把灵活性和效率分开管理

4.1 混合架构的分层设计

所谓混合架构,核心原则是把编排和执行解耦。我们把系统分成两层:

  • 编排层:负责定义任务 DAG、管理节点状态、处理重试和超时、控制上下文流转。这一层由自己掌控,用数据库做状态持久化。
  • Agent 执行层:负责单个 Agent 的内部逻辑,如指令模板、多轮对话、工具调用。这一层交给通用框架托管,利用框架在多模态消息和 Agent 生命周期管理上的成熟设计。

它们之间的交互通过一个极薄的中介接口完成。编排层只知道“我要调用某个 Agent,传入一段结构化输入,拿到一段结构化输出”,并不关心 Agent 内部是用了什么提示词模板、调用了几个中间模型步骤。Agent 执行层的内部滚动更新完全由框架管理,两层之间的依赖被隔离在中介接口的两侧。

听起来不过是“自己写个壳,里面套框架”对不对?但关键在于这个壳和里面那层都做了正确的事:编排层解决“协作状态和异常传播”,执行层解决“单点智能的实现效率”,两者互不越界。

4.2 自研编排层的核心代码示例

编排层的核心是一个轻量的任务执行状态机。我用 Python 描述一下设计骨架,细节不复杂但层与层之间的划分清晰:

class Orchestrator: def __init__(self, repo, agent_gateway): self.repo = repo # 数据库状态仓库 self.agent_gateway = agent_gateway # 通往执行层的中介 self.executor = ThreadPoolExecutor(max_workers=8) def run_task(self, task: Task): dag = self._load_dag(task.dag_id) self.repo.create_task_instance(task.task_id, dag.nodes) self._schedule_ready_nodes(task, dag) def _schedule_ready_nodes(self, task, dag): ready_nodes = dag.get_ready_nodes() for node in ready_nodes: self.executor.submit(self._execute_node, task, node) def _execute_node(self, task, node): try: # 1. 标记节点开始执行 self.repo.mark_running(task.task_id, node.node_id) # 2. 通过中介接口调用 Agent 执行层 result = self.agent_gateway.run_agent( agent_name=node.agent_name, input_data=self._assemble_node_input(task, node) ) # 3. 校验结果格式,写回状态仓库 validated = self._validate_node_output(node, result) self.repo.mark_success(task.task_id, node.node_id, validated) except NonIdempotentFailure as exc: # 幂等保护:不重试,直接终态 self.repo.mark_failed(task.task_id, node.node_id, str(exc)) except RetryableFailure as exc: self._schedule_retry(task, node, exc) # 4. 节点完成后重新检查 DAG 中有无新就绪节点 self._schedule_ready_nodes(task, dag)

这段代码里最容易被忽略的是_assemble_node_input这个函数。它负责从上下文存储中挑选出当前节点真正需要的输入字段,而不是把全链路数据一股脑塞给 Agent。混合架构下上下文治理依然要自己管好,这里我们采用了“字段白名单”的策略,每个节点声明自己需要哪些上游字段,编排器按需装配,从源头避免上下文膨胀。

重试逻辑也单独封装成独立模块,这一次的重试设计比全自研时期更细致。实现了两种重试模式的灵活配置——幂等节点快速重试、非幂等节点单次确认不重试。超时策略采用级联式设计,三层超时保护:单次工具调用超时、单轮 Agent 执行超时、整条链路累计超时。层与层之间互不干扰。

4.3 框架Agent执行层的配置实例

执行层使用某个开源框架的多智能体协作能力,具体名称框架选型时有多个候选,这里用某通用 Agent 框架指代。为了让读者理解配置形态,我把执行层的一个典型 Agent 配置展示出来。这个 Agent 是一个“客户画像分析器”,输入结构化客户资料,输出标签化的画像结果。

agent: name: customer_profile_analyzer model: provider: openai_compatible model_name: gpt-4o-mini temperature: 0.3 max_tokens: 2048 instruction: |- 你是一个客户画像分析专家。根据输入的客户资料, 输出以下 JSON 字段: - customer_type: 高价值/中价值/低价值 - keywords: 关键词数组 - suggestion: 一句营销建议 只输出 JSON,不要输出其他内容。 tools: - name: search_database enabled: true memory: type: local_summary max_turns: 4

执行层的 Agent 定义让团队成员可以快速声明一个智能体应该具备的完整能力:模型参数、指令模板、工具权限、记忆策略都在一个文件里清晰表述。框架自带的记忆管理会自动摘要历史对话,避免单轮调用的上下文越积越长。这一点比自研时代自己写上下文压缩省心得多。

中介接口的代码隐去了框架相关的细节,编排层对执行层的调用稳定为三个方法:

class AgentGateway: def run_agent(self, agent_name, input_data) -> dict: # 通过框架的会话管理创建一个一次性会话 session = self.framework.create_session(agent_name) response = session.run(input_data) return self._parse_structured_response(response)

不需要在编排层暴露框架的消息协议和会话生命周期,这些由网关内部消化。以后如果要替换执行层框架,只需要改写网关内部实现,编排层完全感知不到。

4.4 关键参数为什么这么设置

上面贴出的配置和代码里,有几个参数是我们反复调出来的,背后都有原因。

max_workers=8是基于下游 API 速率限制估算的。假设单个供应商接口账号并发配额是 10 QPS,一次 Agent 调用平均耗时 20 秒,每条链路平均有 5 个可并行节点,8 个并发 worker 能把单任务延迟控制在可接受范围内,同时不会打满配额导致其他任务排队等待。这个参数不是越大越好,要根据供应商限流和单节点耗时来计算。

temperature=0.3是平衡“稳定输出结构化字段”和“保留语义丰富性”的结果。这个 Agent 的核心任务是提取和分类,不需要创造性,温度太高会让 JSON 字段取值不稳定,太低又会出现关键词过于干瘪的问题。我们在 0.2 到 0.5 之间做了几轮对比,0.3 在字段准确率和关键词可用性上取得了平衡点。

max_turns=4是防止多轮自省循环失控。框架允许 Agent 在单次任务内多轮调用模型,但如果 Agent 一直判断“信息不足”就会无限循环。限制 4 轮可以强制收敛。我们的评估标准是:90% 以上的正常任务在 3 轮以内就能完成画像分析,留 1 轮富余量作为缓冲。

5. 落地混合架构的避坑清单与可复用经验

5.1 状态一致性和幂等设计

混合架构下状态一致性比纯自研或纯框架都更难把握,因为状态横跨了两个技术体系。我们踩过的一个大坑:编排层数据库事务把节点标记为“成功”,但执行层框架内部还没把会话状态落盘,结果系统重启后执行层从旧会话恢复时,工具被重复调用了两次。最终我们给中介接口加了“会话提交确认”机制,框架会话结束并持久化完成后,编排层才允许把节点标记为终态。

幂等设计这里必须说得更具体。给每个 Agent 调用分配一个全局唯一的 invocation_id,从编排层传入执行层,再由执行层传给所有工具调用。工具实现中要以 invocation_id 为幂等键,同一 ID 的操作重复执行时直接返回第一次的结果,不产生副作用。这个设计之前完全没有意识到,直到第一次线上邮箱重复发送事故发生后,才意识到多智能体系统里“调一次和调一百次效果相同”不是默认行为,是要刻意设计的。

5.2 超时、重试、熔断的取舍

混合架构中,编排层和执行层分别有自己的超时和重试机制,如果两层配置不当可能出现重试风暴。一个典型的相互叠加场景是:编排层超时设为 30 秒,执行层内部单轮模型调用超时设为 10 秒、最多重试 3 次,那么最坏情况下执行层要花掉 30 秒,正好卡在编排层的超时边界上,一旦网络波动就可能导致编排层提前放弃。

正确的做法是让两层超时留有梯度。我们用“执行层单轮超时 10 秒、最多 3 次重试,整层预算 40 秒;编排层节点超时 45 秒,链路总超时 300 秒”这套参数。层级越高,预算越宽松,才能避免上层提前掐断下层的合法重试。

熔断器建议加在网关层,维度是“目标 Agent 的失败率”,例如 1 分钟内失败率超过 50% 就熔断 30 秒。熔断期间直接返回一个“暂时不可用”的响应,编排层把它映射为可降级的后置策略,而不是重试。多智能体系统里最容易忽略的是熔断后的降级动作,业务方需要明确当某个 Agent 不可用时能否跳过该环节、用静态数据替代,还是彻底终止任务。这个决策要在编排流程设计时和业务方一起敲定,等事故发生了才讨论就来不及了。

5.3 观测性:让架构“可以看到”

混合架构的观测性容易产生断层。体现在:编排层记录了节点状态和流转时间,但丢失了 Agent 内部的思考过程;执行层有详尽的提示词输入输出日志,但不知道属于哪个任务链路。如果不能把两层日志串联起来,排查问题时只能在两个系统间来回跳转。

我们的做法是引入一个贯穿两层的 trace_id,从编排层创建任务实例时生成,通过网关传入执行层会话,最终注入到所有外部调用和日志记录中。在日志系统里用 trace_id 作为查询主键,能够一次性拉出完整链路:哪个节点调用了哪个 Agent、内部模型调用的输入输出是什么、工具调用结果如何、耗时多少。

另外建议把每个 Agent 节点的输入输出快照存到独立的存储中,保留 30 天。这个存储帮我们解决了很多“说不清是模型问题还是数据问题”的争论——拉取快照对比不同批次的执行情况,比起在现场反复触发调试要科学得多。

5.4 什么情况下不建议用混合架构

混合架构也不是银弹。三个条件不满足时,建议还是保持简单方案:

第一,团队没有足够的架构掌控力。混合架构说白了要对编排和执行两个层面都有深入理解,如果团队里没有人能把框架源码的关键路径读明白,那混合架构的“混”只会带来双重复杂度。

第二,任务依赖关系简单明确。如果链路基本固定为单个 Agent 按顺序串行执行,自研一个二十行的 for 循环调用就够了,引入混合架构属于典型的杀鸡用牛刀。

第三,框架本身已经支持复杂的编排能力且团队愿意踩坑学习。有些通用框架的编排能力这两年进步明显,静态图、动态图都支持,也能和外部状态库对接。如果团队有足够的精力去驯服框架,那完全用框架可以让系统更内聚,混合架构带来的分层反而会显得多余。

这里提醒一句,选型高票回答基本都是“看情况”,这不是正确的废话。因为多智能体系统本身的形态差异极大——两三个 Agent 的组合聊天和十个 Agent 的异步协作平台,技术栈完全不是一回事。看情况的背后,是要看你系统的瓶颈到底在编排能力的灵活性上,还是在单点智能的实现效率上。哪边是主要矛盾,哪边就值得优先投入。

我个人在实际操作中的体会是:混合架构的“那个度”非常难把握,但一旦把握住了,收益极其明显。我们目前的生产系统用的是完全分层的方案,编排层稳定运行了大半年,换过一次执行层框架,编排层代码一行没改。排查线上问题时的体验也比纯自研时期舒服太多——链路追踪串起了两层日志,状态快照随查随有,Agent 内部的工作细节可以放心交给框架托管,我们只需要关注协作层的健康状况。

最后再分享一个小技巧:混合架构落地时,不要把“自研层”和“框架层”的划分固化在文档里就完了,建议在代码仓库里用一个显眼的 MARKER 文件标注清楚边界规则,比如“编排层禁止直接调用框架 API”“执行层禁止直接访问编排层数据库”。新成员加入团队时先看这份边界文件,比看十页架构设计文档有效得多。架构的长期健康,靠的不是一次完美的设计,而是边界在执行层面的持续坚守。

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

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

立即咨询