大概半年前,我们团队接到一个活儿:把公司内部的客服工单处理流程,从“人肉流转”升级成一套多智能体协作系统。第一版技术评审的时候,会场上吵得最凶的两个字就是“编排”——工单进来之后,谁来决定先调哪个Agent、每个Agent干到什么程度算完、中途失败了怎么补救,这些问题绕不开编排。有人坚持自研编排,理由是业务规则太特殊,现成框架肯定兜不住;有人力推通用框架,理由是社区生态成熟,没必要重复造轮子。两边的Demo都跑过,各自都有亮点,也都有让人手心冒汗的瞬间。最后我们定下来的方案,既不是纯自研,也不是纯依赖某个通用编排框架,而是各让一步,搭了一套混合架构。这篇文章就把我们当时的决策过程、落地细节和翻车现场整理出来,希望能给同样在纠结的人一些参考。
1. 为什么会产生这次架构抉择:我们到底被什么问题卡住了
1.1 业务场景概述
我们处理的不是那种“问一句答一句”的聊天机器人,而是一个完整的工单闭环。工单从业务系统进来,先要判断类型和紧急程度,再分给不同的Agent去做信息抽取、方案生成、审批材料整理,最后由一个审查Agent把关,把结果送回业务系统。业务方提的要求很直接:流程要可追溯、操作要可回滚、权限要有边界。这意味着系统的难点不在单个Agent的聪明程度,而在多个Agent怎么被组织起来、怎么协作、怎么失败恢复。
我们调研了一圈市面上的方案,发现很多产品把多智能体系统做成了“乐高积木”,给你一堆Agent,让你自己拼。但我们这里的问题恰恰是:拼好之后,谁来监视它们?谁来决定下一步拼哪块?拼错了怎么拆?这些职责,就是我们说的“编排层”。它看起来只是系统里的一个管理层,但实际上一旦出问题,所有锅都会先砸到编排层头上。
1.2 三条硬约束
在这个前提下,我们给架构方案列了三条约约束:
- 长尾能力扩展。业务方每隔一两个月就会提出新的工单类型,意味着要新增Agent,或者给已有Agent增加工具。也就是说,我们的架构必须允许“低成本加新玩具”。
- 业务规则可演化。审批条件、超时时间、路由策略,这些在业务方看来都是“配置”,不能每次变都改代码发布。
- 可观测性和可运维性。出了问题,我们要能在半小时内定位是哪个Agent、哪一步、哪个上下文出了问题,而不是翻半天日志都找不到任务轨迹。
这三条约束,在评审会上听起来都“合理但模糊”,真正落地的时候才发现,每一条都是在给架构出难题。长尾扩展要求低耦合,可演化要求动态配置,可观测性要求全链路链路追踪——每一条单独拎出来都能挡住一个方案。
1.3 两个阵营的初赛表现
我们当时做了两个对照Demo:一个用自研的极简编排器,跑通了一个三Agent协作任务;另一个用社区开源的通用编排框架,也跑通了同样的任务。Demo阶段两个方案都没有明显劣势,而且大家都在暗暗较劲。自研版的代码量多了一倍,但每一步都在掌控之中;框架版只需要一两百行配置,但在边界case里明显有点“使唤不动”。
这就进入了一个经典困境:看起来都能用,但都不知道往下走半年会变成什么样。选自研的人怕的是“掉进工程泥潭”,选框架的人怕的是“被框架的天花板卡住”,两边谁也说服不了谁。所以与其说是选型之争,不如说是在争夺一个确定性:我们到底能不能掌控这个系统的命运。
2. 自研编排路线的技术盘点:自由度、可塑性,以及被低估的工作量
2.1 自研的优势:主动权在自己手里
支持自研的同事有一个核心论点:业务规则是我们自己的,Agent之间的协作模式是我们自己的,凭什么让一个通用框架来定义?
这个论点在初期确实很有说服力。我们需要把某些工单按来源路由给特定Agent,需要按业务方的时间窗口做批量调度,甚至把审批人角色动态塞进会话上下文——这些在自研里都是“改代码”的事。而且我们可以完全控制任务的执行顺序,想同步就同步,想异步就异步,想并行就并行。
这种自由度在架构讨论里很诱人。很多团队选择自研,不是因为框架不够好,而是因为想要那种“想怎么调度就怎么调度”的掌控感。掌控感本身不是坏事,但问题是,自由度的背面往往写着“所有问题都是你的”。
2.2 一个可用编排层要写多少东西
很多人一听到“编排”两个字,脑子里想的是“调几个API,串一下流程”。但实际上,一个能在生产环境扛住压力的编排层,远不止串流程那么简单。我把我们在自研过程中需要解决的组件列了个清单:
| 组件 | 要解决的核心问题 | 踩过才知道的坑 |
|---|---|---|
| 任务队列 | 谁先执行、谁后执行、并发上限 | 队列积压、优先级反转、消费幂等 |
| 状态存储 | Agent执行到哪一步了,结果放哪 | 状态与外部副作用不一致 |
| 上下文管理 | 一个任务从开始到结束的上下文传递 | 上下文膨胀、token开销爆炸 |
| 工具调用协议 | Agent怎么安全地调用外部系统 | 接口版本、权限校验、超时处理 |
| 失败重试 | 出错之后怎么恢复、重跑哪一段 | 重试与副作用叠加,造成重复执行 |
| 审计日志 | 每个环节谁在什么时候做了什么 | 日志链路断裂、格式不统一 |
| 并发控制 | 多个任务同时跑不会互相干扰 | 锁粒度控制、死锁、事务边界 |
单独看每一项,工作量都不算大。但把它们组合在一起,就是一套需要长期维护的分布式系统。最坑的是,这些组件每一个都有成熟中间件可以抄,但抄过来之后还要和我们的Agent逻辑适配,这个适配成本远超预期。
2.3 我们写的mini编排器长什么样
为了验证自研路线的可行性,我们当时的架构师花了不到两周,写了一个迷你编排器。核心逻辑其实不复杂:一个任务状态机加一个路由策略模块加一个事件总线。任务进来先转成统一结构,状态机根据当前节点决定下一步动作,路由策略决定把任务交给哪个Agent,事件总线负责在各模块之间传消息。
第一版跑通的时候,大家都很兴奋,觉得“自研也不过如此”。但随着我们往里加需求,情况开始失控。原来只要维护状态机的几个节点,后来要在每个节点上记录输入输出、处理超时、支持人工干预,还要把状态持久化到数据库,这样进程挂了才能恢复。
代码量几倍几倍地涨,最后已经没办法一个人维护了。最要命的是,每加一个新的Agent类型,就要同时改状态机的节点定义、数据库迁移脚本、前端调试面板的展示逻辑。业务方等得直挠头,问我们“加一个Agent怎么要两周”。
2.4 踩过的坑
这个过程中,有几次翻车让我印象特别深。
第一次是状态不一致。我们把每一步的状态都存在数据库里,然后从库里恢复。结果有一个Agent的工具调用是有副作用的——它会给外部系统发消息。如果这个Agent执行到一半进程挂了,我们重跑时,外部系统就会收到两条相同消息。后来我们不得不给所有工具接口都设计幂等键,但这让Agent的调用逻辑变得极其臃肿。
第二次是日志链路断。自研状态下,每次重启、恢复、重试都会产生新的执行片段,原本设计好的调用链很容易断开。排一次问题,要从数据库状态反推执行过程,再翻服务器日志找对应时间点。查一次问题半天起步,业务方已经黑脸了。
第三次是迭代速度。每加一个长尾场景,就要动整个编排器的前前后后。我们清楚的认识到一件事:编排层真正的复杂度,根本不在“流程怎么串”,而在于“工程可靠性”。这部分是一个持续投入的黑洞,如果全部自研,我们团队至少需要再增加两到三名专职后端工程师才能扛住。
3. 通用编排框架的实测体验:开箱即用的甜点与隐形天花板
3.1 为什么我们曾高度倾向通用框架
支持通用框架的同事,理由也很真实:社区生态成熟,内置状态图、检查点、人工介入、可视化调试这些能力,如果完全自研,保守估计要半年。
我当时去翻了一下某个开源编排框架的文档,确实被它的能力清单打动。它把一个Agent内部的执行流程建模成状态图,节点之间通过事件跳转,还提供了持久化检查点,进程挂了可以从某个节点恢复。最吸引我的是它自带可视化界面,每一步的输入输出、工具调用结果都展示得很清楚。这些恰恰是自研方案里最难搞的那部分。
3.2 实测的优点
我们拿同样的工单场景做了验证,效果非常直观:
- 跑通demo的时间从两周缩到了两天,因为状态图、节点、工具调用协议都是现成的。
- 调整业务流程时,只需要改图定义,重新加载配置就能生效,不需要发布代码。
- 内置的工具调用模块可以直接对接一批常用的外部API,省去了很多胶水代码。
在这个阶段,框架带来的幸福感是真实的。团队里甚至有人说“早知道何必纠结,直接用框架不就行了”。但人就是这样,demo里看不见的问题,只有真正往生产环境推的时候才会浮出水面。
3.3 深入之后的天花板
我们拿着框架去做更复杂的场景,大概三周后,问题开始出现。
第一个是对“图”的表达假设。框架的核心抽象是图,默认节点和边是静态的。但我们的工单流程是动态的,有时候要根据模型的输出决定下一个动作,有时候要在运行时才确定需要哪些工具,有时候要临时并发多个Agent再聚合结果。这些场景在框架里要么写一堆自定义节点,要么绕过框架的调度逻辑,套一层自己的壳。一旦开始套壳,当初“省事”的优势就消失了一半。
第二个是跨节点的共享状态管理。框架的检查点机制是针对整个会话的,它的设计思路是“整个会话作为一个执行单元”。但我们希望每个Agent有独立的持久化上下文,而且部分上下文要跨Agent共享。这个需求和框架的会话模型不吻合,导致我们只能把上下文对象在每个节点的input/output里传来传去,非常笨拙,而且很快遇到了token开销问题。
第三个是多实例并发的控制。框架默认一个会话对应一个执行链,同一时间只有一个节点在跑。但我们的业务里,同一个工单可能同时需要分诊Agent和检索Agent并行工作,最后再聚合成结果。这种并行聚合的场景,框架不直接支持,需要靠外部机制来补,优势又被削弱了。
第四个是版本演进带来的升级负担。框架迭代太快,接口经常变,我们试用期内就碰到了两次不兼容更新。第一次影响不大,第二次直接把我们的一个关键节点实现报废了。这种层面的不可控,对于一个要长期维护的业务系统来说,是很危险的。
3.4 关键结论
测试了一圈之后,我们的结论是:通用框架适合流程相对固定、长期变化少、团队不想深耕Agent工程化的项目。它可以快速搭建一个可用的多智能体应用,但对于我们这种“流程复杂、长期演进、强治理需求”的业务系统,单押框架的风险太高。
风险具体来自两个地方:一是框架的抽象模型和我们的业务模型存在错位,这个错位靠配置补不回来,只能靠改造;二是框架的升级节奏不在我们手里,一旦上游做出不兼容变更,整个系统的技术债就要被迫兑现。对一个要服务业务方好几年的系统来说,这两点都是硬伤。
4. 混合架构的切分逻辑:编排层与执行层各自该干什么
4.1 总体设计思路
折腾了两轮之后,我们决定不再做二选一,而是把系统拆成两层,各用各的。
- 全局编排层(自研):这是一个轻量的调度服务,负责任务拆分、跨Agent路由、全局会话状态、业务参数管理、对外API、审计与权限校验。它只关心“下一步该干什么”,不关心“某个Agent内部怎么干”。
- Agent执行层(基于通用框架):框架负责单个Agent内部的工具调用组装、上下文切片消费、状态图执行、局部检查点保存。它只关心“这个Agent收到输入之后怎么产生输出”,不关心全局还有哪些Agent在跑。
这个划分看着简单,实际上是把两边的复杂度都压到了各自最擅长的地方。自研部分不再需要处理Agent内部的复杂状态流转,框架部分也不需要理解全局路由策略,两个系统各干各的,之间的接口清晰到可以用一张表写明白。
4.2 切分边界表
| 关注点 | 全局编排层(自研) | Agent执行层(通用框架) |
|---|---|---|
| 任务分配 | 决定下一个Agent是谁 | 不感知全局,只做输入到输出 |
| 状态管理 | 全局会话状态 + 业务参数 | Agent局部上下文 + 工具侧中间结果 |
| 失败重试 | 编排层发起重试、补偿、人工干预 | 报告失败原因,支持定点续跑 |
| 上下文传递 | 分配切片上下文给执行层 | 消费编排层下发的上下文切片 |
| 可观测性 | 分配traceID、记录节点审计日志 | 局部埋点、工具调用日志透传trace |
| 权限与合规 | 前置校验、结果放行 | 工具调用权限校验、输出脱敏 |
这张表贴在我们内部Wiki上,之后所有新增Agent的开发都按照这个边界去对齐,很少再出现跨层代劳的情况。
4.3 为什么这个切分刚好
很多人会问:为什么不直接让框架管全局,或者干脆全自研?我们的答案是:把复杂度放在它最合适的位置上。
自研需要维护的规模被砍掉了很大一块——不用再写Agent内部的状态机、工具调用协议、检查点恢复机制,这部分直接用框架的。框架的天花板也被绕开了——它不需要懂全局路由和跨Agent状态,只需要把“单个Agent的事”做好。
这个切分还有另一个好处:责任边界清晰了。出了问题,先看是编排层的路由问题,还是执行层的Agent内部问题,排查范围直接小了一半。业务方提新需求时,如果只是新加一个Agent类型,只需要在编排层注册一条路由规则,然后在执行层加一张图定义,两边互不干扰。
4.4 一次真实任务流转
为了说清楚这个架构跑起来是什么样子,我描述一次真实的工单处理流程:
- 编排层收到业务系统的工单,先做意图归一化和分诊,产出任务计划。
- 编排层生成一个全局任务ID,把需要传给首个Agent的上下文切片好,调用执行层接口。
- 执行层启动对应Agent,加载框架里的图定义,开始执行工具调用。每完成一个关键步骤,回调编排层一次,上报执行状态和中间结果。
- 编排层根据回调结果,判断下一步是让这个Agent继续,还是切换到另一个Agent,或者是多个Agent并行执行。
- 最终执行完成,编排层做汇总,把结果写回业务系统,同时归档审计日志,整条链路有一个贯穿始终的traceID。
整个流程看下来,执行层像一个“被点名的执行者”,只负责把自己那一段做好;编排层像一个“项目经理”,负责盯进度、管状态、做决策。这种角色划分也让人力安排更清晰——框架部分可以交给Agent开发同学,编排层由后端工程师维护,两边技能栈不重叠,反而减少了相互等待的时间。
5. 落地过程中真正难搞的工程问题:跨层通信、上下文切片与可观测性
5.1 跨层通信协议
方案定了之后,第一个实际要解决的工程问题是两层之间的通信协议。我们最终用了JSON格式,配合事件回调接口,每个接口都有三个核心字段:task_id、agent_id、trace_id。其中task_id对应全局任务,agent_id对应执行层里的某个具体Agent,trace_id贯穿全链路。
协议设计里最容易忽略的是版本兼容。执行层的框架升级后,接口可能悄悄变化,所以我们在协议里加了version字段,编排层统一负责做版本适配。这个适配层大概只有几百行代码,但它在后期框架升级时帮我们挡了几次灾。
5.2 上下文切片与传递
上下文管理是混合架构里最需要小心的地方。我们把它分成了三层:
- 全局上下文:来自业务系统,比如工单原文、客户ID、历史处理记录。它只在编排层维护,不会被推到Agent内部。
- 运行期上下文:编排层在任务推进过程中生成的临时信息,比如分诊结果、路由决策、中间评审意见。这部分编排层自己存。
- Agent内上下文:执行层里框架管理的那部分,包括Agent当前状态、工具调用参数、局部中间结果。
传递时有一个“序列化边界”很重要。最初我们的做法是把整个全局上下文直接塞给Agent,结果token开销爆炸,一个月光模型调用费就让人头疼。后来我们做了上下文压缩策略:摘要加截断加分段检索。只把当前Agent需要的那一小段原文传给它,其他信息按需从外部知识库检索。这个优化带来的收益,比我们花两周调调度策略大得多。
5.3 可观测性
可观测性这块,我们的经验是:traceID必须由编排层在任务入口统一生成,然后透传到所有层。执行层里所有日志、框架检查点日志、工具调用日志,都必须带上这个traceID。只有这样,出问题时才能从编排层的一个任务入口,通过一个ID把所有相关日志拉出来。
我们埋点的标准是:每个Agent启动、每个工具调用、每个人工干预动作、每个重试行为,都要记录事件类型、耗时、结果、错误码。一开始有人觉得埋点太繁琐,后来一次线上事故,我们用traceID定位到某个Agent在某个工具节点反复重试,花了不到十分钟就找到了根因。那次之后,再没人抱怨埋点麻烦。
5.4 失败重试与幂等
混合架构下的失败重试,比单层系统要复杂得多。因为重试可能发生在编排层,也可能发生在执行层。最开始我们没有约定好,结果某个Agent执行失败后,框架内置重试触发了三次,编排层又把它重新调度了两次,等于一个副作用操作被执行了五次。
我们的解决方案是:约定重试只发生在执行层,编排层只负责“重调度”,不负责“重执行”。也就是说,执行层内部失败,框架自己重跑那个节点;如果整个Agent的任务彻底失败,它向编排层回报一个失败事件,由编排层决定是否换个Agent重做,还是转入人工处理。同时,所有工具调用接口都要求设计幂等键,同一个request_id不能产生两次外部副作用。
5.5 压测情况与参数选择
最后说一组我们在项目里的实测数据,供参考。并发40个工单,平均每个工单会触发6到8个Agent节点,单Agent平均响应时间约2秒。在混合架构下,编排层的CPU占用大约15%,执行层是主要开销。也就是说,瓶颈既不在自研编排层,也不在框架本身,而在底层模型调用的耗时上。
这个结果让我们松了一口气。之前担心的“自研层会不会成为性能瓶颈”并没有出现,因为编排层每个决策任务的计算量很小,真正耗时的是Agent内部与模型、工具打交道的部分。这也验证了我们的切分是合理的——把计算密集的部分交给执行层,把决策密集的部分留在编排层,两边各走各的路,互不拖累。
6. 复盘:这套混合架构的适用边界,以及我们主动留下的后门
6.1 混合架构真正解决的问题和没解决的问题
跑了大半年之后,我们回头做了个复盘,把混合架构的好处和它没能解决的问题都列了一遍:
| 真正解决的问题 | 还没有解决的问题 |
|---|---|
| 编排自由度:全局路由、跨Agent协作完全可控 | 模型输出的随机性仍然会导致流程漂移,需要护栏和大量样本测试 |
| 长尾扩展:新增Agent只需注册路由 + 加图定义 | 框架升级依然要跟版本,只是影响被限制在执行层 |
| 可观测性:traceID贯穿两层,排查效率明显提升 | 上下文压缩策略的效果依赖知识库质量,检索不准时会掉链子 |
| 团队分工清晰:Agent开发与后端维护互不干扰 | 编排层代码逐渐膨胀,需要定期重构,否则也会变成无人区 |
这个表格在我们内部讨论时冲击力很大。特别是最后一行“编排层代码逐渐膨胀”,因为它意味着我们不能因为用了混合架构,就觉得自己彻底安全了。自研编排层依然需要维护,只不过它的复杂度被控制在了可接受的范围内。
6.2 什么样的场景适合这种架构
根据我们的实际体验,混合架构比较适合下面几种情况:
- 业务有长尾,Agent类型会持续增加,而且每次增加都希望不影响主流程。
- 流程本身复杂,存在并行、聚合、人工介入、条件分支等动态行为。
- 系统要长期演进,不只是做个demo,而是要服务业务方很多年。
- 团队有足够的工程能力,至少能养一个两到三人的后端小组来维护编排层。
如果这几条都满足,混合架构会是一个很舒服的选择,因为它的核心价值就在于“把不确定性留给了自己,把确定性交给了框架”。
6.3 什么样的场景不适合
反过来,有些场景其实不适合混合架构:
- 只是想快速验证一个多智能体想法,流程固定,变化少,那直接用通用框架就行,没必要多维护一个自研服务。
- 团队本身不具备很强的后端能力,那自研编排层会成为包袱,倒不如找个现成平台先用起来。
- 流程极其简单,比如固定三步走,那混合架构反而是过度设计,多出来的两层通信成本完全是浪费。
我们当时如果只做一个简单的工单问答demo,绝不会选择混合架构。正是因为我们明确知道自己要长期演进、要处理复杂动态流程,这个决策才成立。所以架构选择这件事,永远先看场景,再看技术。
6.4 给想抄作业的人的建议
如果你看完文章,觉得混合架构这条路可以试试,这里有几条我们踩出来的经验可以直接拿走:
- 预留好抽象接口。我们特意在编排层和执行层之间定义了一层薄薄的“运行时适配”,未来即使换掉框架,也只影响执行层,不会把编排层一起拖下水。
- 锁定框架版本。不要一有新版就急着升级,先看清楚不兼容变更,再评估影响面。我们后来直接在CI里卡了版本号,只有通过回归测试才允许升级。
- 先跑通最小闭环再铺开。不要上来就铺十个Agent,先把一个工单类型从入口到出口完整走一遍,验证通信、上下文、重试、可观测性都到位,再往外扩展。
- 给业务方留配置入口。路由策略、超时时间、审批人角色这些,尽量做成配置项,不要写死在代码里。否则每个需求都要排期改代码,业务方等不起。
- 每周留出固定时间review编排层代码。这个层容易在不知不觉中膨胀,需要定期把“怎么减少样板代码、怎么收敛路由逻辑”当成正常迭代的一部分来做。
6.5 一点个人体会
现在回头看这次架构抉择,我最大的体会是:选择混合架构不是因为它技术更先进,而是因为它和我们的问题更匹配。自研编排给我们掌控感,框架执行层给我们效率,两者之间的那条边界,恰好把我们能控制的和不用控制的分开了。
如果你们也正面临同样的选择,我建议先别急着二选一,花一周时间把业务里的“长尾”列出来,看看未来的流程会有多动态,再决定把边界画在哪。架构本身就是一种取舍,想清楚自己愿意承受哪边的复杂,答案自然就出来了。