写在前面:关于Agent系统,你需要知道的第一个事实
“Agent”这个词在技术圈已经热了很久,市面上谈多Agent协作的文章也一抓一大把,但真正能把一套多Agent系统落到企业生产环境的团队,坦白说不多。我最近刚完成一个企业级多Agent协作系统的设计与落地,系统里跑着三种角色类型的Agent:规划型(Planner)、执行型(Executor)和审查型(Reviewer),它们不靠堆Prompt硬聊,而是通过一套明确的协议、状态中心和编排引擎协同工作。这篇文章就把这套系统从设计思路到落地细节完整拆出来,包括为什么企业级场景下不能只押注单个Agent、三种角色怎么划分职责边界、消息协议和状态中心怎么设计、以及你在真实生产环境会踩到的那些可靠性、安全性和可观测性上的坑。想看Demo级玩具代码的可以绕道,这篇讲的是能扛住业务压力的工程方案。
1. 先回答一个问题:企业级场景下为什么“单Agent打天下”走不通
1.1 三个把单Agent方案压垮的真实场景
在讨论多Agent协作架构之前,我先讲讲自己过去踩过的坑。去年我在一个数据项目里坚持单Agent方案,把所有的指令、工具描述、业务规则全塞进一个大Prompt里,效果前期看着还行,但随着业务接入的深度增加,问题一个接一个冒出来。
第一个是上下文窗口的物理上限。企业里的真实任务往往是跨域的,比如“分析本月销售数据,生成一份带趋势解读和预警建议的经营月报”。这个单次请求背后至少需要数据库查询、数据清洗、指标计算、趋势分析、图文排版五个环节的能力,任何一个模型只靠一个超长Prompt都很难同时掌握全链路上下文。更麻烦的是,单Agent在执行过程中上下文会不断膨胀,做到第三步的时候,前两步的细节已经开始被遗忘,输出质量肉眼可见地下降。
第二个是工具调用链路的失控问题。单Agent接入十几个工具之后,幻觉式调用出现的概率高到离谱。我见过系统明明想调“查询订单量”接口,结果传过去的参数居然是“查询商品名称”的字段格式。工具一多,模型在function calling时选错函数、填错参数、漏掉必填项就成了常态。做过一次粗测,工具链超过五个环节之后,整条链路跑通的概率断崖式下跌,这在生产环境根本没法接受。
第三个是缺少质量闭环。单Agent写完报告、生成完SQL之后,不会有人用第二视角替它做校验,错误会顺着流程直接流到业务侧。我在内部review时看到过Agent生成的SQL差点把生产库的全表扫崩,这个问题根源当然有权限管控的责任,但如果有一个审查Agent在发出指令之前做拦截,这类事故根本不会走到那一步。
1.2 角色分工本质上是组织管理逻辑的软件化
后来我认了一件事:与其让一个Agent无所不能,不如让多个Agent各司其职。这个想法不是新鲜发明,你去看看任何一个运转正常的公司,写方案、干活、验收这三个职能一定是分开的,因为一个人既做执行又做质检,天然存在“自己检查自己”的盲区。多Agent协作系统里的角色划分,本质上就是把这套人类组织里成熟的分工逻辑搬进软件系统。
所以我定下了这套系统的核心设计:三种角色类型的Agent,各自守住一段流程,互相配合但不越权。规划型Agent负责理解任务、拆解步骤、编排执行单元;执行型Agent负责调工具、操作API、读写数据,一件事做到底;审查型Agent负责核对执行结果、校验输出质量、把不合格的返工指令打回去。三者串起来正好是“目标拆解——执行落地——质量把关”的闭环,这也是这套企业级多Agent协作系统最根本的设计起点。
当然,光有角色分工还不够。企业级系统跟技术Demo之间最大的差别在于,Demo可以接受Agent之间互相写个临时JSON就完成通信,生产环境必须考虑状态中心、消息协议、失败处理、链路追踪一整套工程问题。这些下文一个个展开。
2. 三种角色的职责边界与协作协议设计
2.1 Planner Agent:任务拆解的颗粒度决定了系统上限
Planner Agent(规划型Agent)是所有业务请求进入系统后的第一个接收者。它的核心职责可以拆成三层。
第一层是意图理解与目标澄清。进来的自然语言请求很少一上来就是机器可执行的,比如业务方丢来一句“查一下最近的销售异常”,Planner必须把这个模糊需求补全成边界明确的任务:时间范围是近30天还是本季度、异常判定用同比跌20%还是环比跌15%、数据源是那张事实表、结果要明细还是汇总。我在实现里给Planner配了一套“需求澄清思路”,遇到边界条件缺失时主动列出待确认项,必要时回传业务侧确认,而不是带着一堆问号硬拆。
第二层是任务颗粒度拆解。这里我总结了最实用的一条线:一个最小执行单元应该做到“只调用一个工具、操作一个数据源、完成一个完整动作”。比如“拉取订单数据”是一单元,“计算同比变化”是另一个单元,不能把“拉数据、算指标、生成图表”全塞进一个单元让Executor一口气完成。颗粒度拆得过粗,Executor的上下文就会重新膨胀,颗粒度拆得过细,Agent之间通信调度开销又太大,实践中以上面这条“三个一”为基准去把握,基本不会跑偏。
第三层是依赖关系编排。拆出来的多个执行单元之间往往存在先决关系:先取数,再计算,最后才能画图。Planner的输出物我设计成一张任务图谱,每个节点是一个执行单元,边上面挂着依赖条件,允许存在可并行执行的分支。这张图谱会交给编排引擎去落地调度,Planner本身不直接调用任何Executor,它只负责决策,不负责执行调度动作,这个边界我在后面还会强调。
2.2 Executor Agent:只做一件事,并且做出一件事的确定性
Executor Agent(执行型Agent)是这三种角色里唯一真正接触工具和数据的一环。我给它立的规矩是“单一职责加明确输入输出”:执行单元描述里面写着什么,它就执行什么,不自由发挥、不额外加戏。
企业级场景里最常见的翻车点,是Executor跟业务系统直接耦合。很多团队让Agent直接拼SQL、直接调内部接口,代码写在Prompt里,出了事连日志都定位不到。我这边全套设计是引入一层统一的工具接口层(内部叫Tool Center),所有外部系统的操作能力全部抽象成注册式工具,每个工具在接入时必须声明完整的Schema,包括入参、出参、权限等级、调用速率限制。Executor要做什么操作,先去Tool Center查工具、拿Schema、按Schema生成参数,用完返回结构化结果。
但是注意,LLM生成的参数天生有格式抖动问题。明明是JSON输出,它可以给你多一个逗号、少一个引号,甚至把字符串值跟数字值搞混。Executor这个环节必须加一道确定性处理:先用结构化抽取把模型输出里的参数部分单独剥出来,接着做Schema校验,校验不过就做默认值补全或重新生成。这一套组合拳打下来,把大模型输出的不确定性和外部系统需要的确定性之间做了一层缓冲,实际运行里工具调用成功率从裸调的六七成直接拉到95%以上。
2.3 Reviewer Agent:质检不能只是“让LLM再读一遍”
Reviewer Agent(审查型Agent)很容易被设计成一个摆设。我看过不少团队做的所谓质检,就是把执行结果丢给另一个Agent“再读一遍”,然后得到一个“看起来没问题”的结论。这不叫质量把关,这叫复读两遍,两个模型的盲区往往高度重合,第一遍没看出来的问题第二遍照样漏掉。
我设计的Reviewer有三层具体动作。第一层是结果校验,用确定性手段做硬校验:输出JSON是否符合Schema、生成的文件是否存在且有正常大小、数据库更新的影响行数是否符合预期、金额字段是否通过汇总勾稽关系。这些全部是规则和脚本,不依赖模型判断。第二层是语义一致性检查,这一步才让大模型登场,核心是核对执行结果与Planner原始目标之间的匹配度,比如报告结论里的数据够不够支撑业务判断、是否存在张冠李戴。第三层是返工机制,Reviewer发现问题后不能只丢一句“不合格”,它要把问题转成结构化返工指令,说明哪个执行单元、什么问题、建议怎么改,再回传给Planner重新走流程。
这三层设计带来的一个直接好处是,系统对模型能力的依赖被降了下来。Reviewer环节有70%的检查靠规则兜底,模型只做那30%需要语义理解的部分,整个系统的质量下限是可控的。我在后面讲监控的时候会复盘,这种“规则为主、模型兜底”的思想也是企业级Agent系统跟个人玩具项目之间最大的一道分水岭。
2.4 角色之间的消息协议:没有协议就没有协作
三种角色要协作,第一步是定义消息协议。我参考企业内部API设计规范,设计了一套统一消息结构,所有Agent之间传递的消息长这样:
task_id:全局唯一的任务ID,贯穿任务全生命周期trace_id:链路追踪ID,用于全链路日志串联role:发送方角色标识(planner/executor/reviewer)message_type:消息类型,请求、响应、通知、任务取消payload:消息体,实际传递的任务描述、执行结果、返工指令等priority:优先级,高优先级任务可抢占队列资源timestamp:消息产生时间,用于时延统计和审计回溯
这套协议解决的最关键问题是“每个Agent不需要知道全局状态”。Executor收到一条执行指令时,只需要根据单元ID去状态中心拿自己的上下文,不需要关心这个任务前面经历了什么、后面还有什么工序。各环节消息全部落盘到消息日志里,一旦出问题,可以通过trace_id把一条任务从进入系统到交付结果的全过程完整回放出来。这就是审计价值,在企业级环境里属于硬性要求,不是加分项。
3. 编排、状态与通信:企业级系统的三个底盘设计
3.1 协作拓扑选型:为什么我选了“主从编排加图执行”
多Agent协作系统的拓扑结构直接决定系统的复杂度和故障半径。我梳理过常见的几种选型。
流水线模式最简单,Agent按固定顺序依次执行,适合高度标准化、流程不怎么变的业务,缺点是一旦某个环节出错就要整体重跑。网状协商模式最灵活,Agent之间互相协商谁来做下一步,适合高度动态的场景,但系统行为不可预测,故障定位极其困难,我在企业级环境里基本不碰它。图编排模式用DAG表达任务依赖关系,适合复杂分支场景,工程实现上需要额外的执行引擎支撑。
我最终用的是“主从编排加图执行引擎”的组合。Planner作为唯一编排决策者,它拆解出任务图谱后提交给一个纯确定性的轻量执行引擎,由引擎按照节点依赖关系下发执行指令给Executor。这里有一个重要的设计取舍:调度不交给LLM,交给代码。LLM只负责拆解阶段的决策,执行阶段按图走路的是确定性代码,这样才能保证任务顺序可控、可预测、可重试。企业在没有充分把握之前,不要去追求Agent全自主调度,先跑通确定性底座,是更稳健的企业级落地方案。
3.2 状态管理:集中式工作流状态存储解决“记忆碎片化”
多Agent协作最容易踩的架构陷阱是各Agent自己保留上下文,导致整个任务状态碎成一地。每个Agent只知道自己做过的那一段,一旦任务中途失败或者需要回溯,没有任何一个地方能复原全貌。
我的方案是引入集中式状态中心。数据库里放一张任务状态表和一张执行单元状态表,任务表存宏观进度,单元表存每个执行单元的详情,包括输入输出、执行状态、返工次数。Redis侧放实时任务队列和临时数据缓存,负责运行时的高吞吐状态流转。一条任务从进入系统开始,状态大致走这样一条路径:
CREATED:任务已被接收,等待规划PLANNING:Planner正在拆解任务图谱EXECUTING:执行引擎按图谱下发单元任务REVIEWING:Reviewer对结果做各层校验COMPLETED/REJECTED:通过或需要返工ARCHIVED:归档入库
如果任务被打回返工,状态在REJECTED之后由Planner重新拆解并再次进入EXECUTING,整个循环由状态中心统一管控,任何一步都有据可查。状态集中之后还有一个额外的好处,就是可以轻松实现人工介入——业务方在某个环节打了一个暂停标记,编排引擎读到状态变化就会停止下发后续单元,这在灵活性和容错上给了系统很大的回旋余地。
3.3 通信层的选型:解耦Agent之间的直接调用
Agent之间不能直接通过HTTP互相调用。原因很简单:一旦业务高峰有几千个任务并行,Agent之间的点对点调用会瞬间打满网络连接,而且任何一个Agent实例重启,正在等待响应的调用方就会全部超时。我的选型是引入消息中间件做异步解耦。
内部系统我用的是Redis Streams,而不是Kafka或者RabbitMQ,核心理由是轻量、运维成本低、消费者组天然支持任务分发。Redis在企业内部已经很普遍,为了一个Agent协作系统专门引一套Kafka集群,前期性价比不高。当然,如果你要支撑跨地域多数据中心或者极高吞吐的场景,Kafka是更稳妥的选择,这个看团队的运维能力和业务量级来决定。
企业级环境里用好Redis Streams有几个硬性细节要卡死:消息必须有持久化配置,不能因为重启丢消息;消费者处理任务必须做幂等设计,重复消费同一消息不能产生重复执行副作用;消费失败的消息要进死信队列,不能无限重试卡住整个消费者组;最后要留消息重放接口,排查问题时可以直接拉到对应trace_id的消息记录。
4. 可靠性、安全性与可观测性:企业级系统和Demo的分水岭
4.1 失败处理:超时、重试与防死循环
LLM服务超时、工具调用报错、输出格式不合法,这些在企业级Agent系统里不是异常,是日常。一套完整可用的失败处理机制大概长这样。
超时控制方面,每个Agent的模型调用和工具调用分别设置两档超时:连接超时短一些,比如10秒,快速暴露网络问题;总超时按任务的复杂度放宽,比如60秒到180秒,给模型充分生成时间。超过总超时后任务标记为失败,进入重试队列。重试策略用指数退避加最大重试次数,重试间隔从2秒起步递增,最多三次,三次都失败就落死信队列等待人工介入。
防死循环这块需要单独强调。Agent系统如果不做上限控制,Reviewer和Planner之间可能会递归循环到天荒地老。我给每个执行单元设置了最大迭代次数,默认5次,超过上限自动转人工处理,同时给整个任务也设置总时间预算,超时就自动熔断降级。这些限制意味着系统出问题时最坏结果是“任务终止并通知人”,而不是“不受控地消耗模型token和机器资源”,两个结局的差别有多重,有过生产事故经验的人都懂。
4.2 安全边界:权限隔离、工具白名单与Prompt注入防护
企业级Agent系统的安全问题比普通Web系统更棘手,因为Agent的行为边界是大模型动态决策的,静态的安全策略不一定约束得住它。
权限上面我的原则是“一Agent一身份,最小权限”。Planner、Executor、Reviewer不要共用一个服务账号,每个Agent进程内部持有独立的身份凭证,数据库侧只授当前角色真正需要的权限。这样即使是Executor被诱导执行了危险操作,受到的伤害也被限制在一张表、一个数据源,不会波及其他系统。
工具侧做白名单管理。Tool Center在Agent运行时校验被调工具是否在白名单内,不在名单内的请求直接拒绝并发告警。内部工具还要区分等级,只读类工具(查数据、拉报表)和写操作类工具(更新、删除、执行脚本)严格分开,写操作类工具强制要求Reviewer先行审批,这是硬性安全流程。
还有Prompt注入的问题。Agent在执行业务时会接触上游传入的不可信内容,比如用户提供的文件内容、网页抓取结果,这些内容里可能埋着“忽略之前的指令”之类的注入话术。我的防护思路是:模型输入里的不可信动态内容一律放隔离区,用结构化标记包裹,并在系统提示词中明确标注这部分内容只能作为待处理数据,不能作为对Agent的指令,同时接入一个规则过滤器,扫描输入内容中的注入特征和敏感信息,命中即拦截并告警。
4.3 可观测性:一条任务的全链路追踪怎么落地
多Agent系统本质上是分布式系统,且比传统微服务更难排查,因为消息是异步流转的,出问题时连“现在任务跑在哪个Agent上”都是动态变化的。我这边从项目第一天就要求所有Agent输出结构化日志,每一行日志必须带trace_id和task_id,统一上报到集中的链路追踪平台,同时将每次LLM调用记录成独立Span,包含模型版本、输入摘要、输出摘要、耗时和token消耗。
这样一套追踪体系铺下去之后,效果非常直观:一条任务从入口进入系统,到最终交付结果,中间的整个流转链条,包括每一步经过哪个Agent、调用了哪个工具、参数是什么、耗时多长、Reviewer打回了几次,全部可以回溯。有一个客户反馈“某天上午10点那批日报结果异常”,我可以直接按时间维度拉出那批任务的完整链路,定位到是Executor调用的数据源连接超时导致重试,还是Reviewer的语义检查误判导致返工。
token消耗按task_id维度汇总还有一个额外收益,就是成本归因变得清晰。管理层问“这个月Agent系统花了多少token、都花在哪类业务上”,一张报表就能回答,这在企业里好用得超出预期。
5. 一个可参考的最小实践骨架
5.1 系统组件与部署拓扑
说完理论,给一个可以在你本地跑通的最小骨架参考。先看组件构成,整体分为五块:
- 接入层:接收业务侧请求,做基础校验后落库建任务
- 编排引擎:读任务状态,调用Planner生成图谱,按图执行
- Agent容器:三个角色三个独立进程,各自水平扩展
- Tool Center:工具注册、Schema校验、权限控制、调用审计
- 中间件层:Redis Streams做消息队列,PostgreSQL存状态,Elasticsearch等存链路日志
部署上,Agent容器全部做成无状态,实例可以随时扩缩容,任务状态和消息都在中间件里,任何Agent实例挂了之后由新的实例从队列里接过任务继续跑,这是企业级最基本的高可用保障。
5.2 核心消息结构与任务状态定义
消息结构在2.4已经给了,这里补充一个执行单元状态定义的JSON示例,方便你照着落库。
{ "task_id": "task_20250601001", "trace_id": "trace_8f3a2c1d", "unit_id": "unit_001", "type": "fetch_data", "status": "pending", "input": { "datasource": "sales_order", "date_range": "2025-05-01~2025-05-31" }, "output": null, "attempt_count": 0, "max_attempts": 5, "created_at": "2025-06-01T10:00:00Z", "updated_at": "2025-06-01T10:00:00Z" }执行引擎每下发一个单元时,把status从pending改成running,Executor完事之后回填output并置为done,Reviewer校验不通过时置为rejected,并把返工原因写进一个review_comment字段。这套结构简单但足够承载真实业务流转。
5.3 一个简化版的调用链代码示例
用Python给你示意一下最核心的编排调用逻辑,不依赖任何重量级框架,方便你理解全流程。
# engine.py: 简化版编排引擎 class OrchestrationEngine: def __init__(self, planner, executor, reviewer): self.planner = planner self.executor = executor self.reviewer = reviewer def run(self, task_description: str): # 1. Planner输出任务图谱 task_graph = self.planner.plan(task_description) # 2. 按拓扑序执行每个单元 for unit in self.topological_sort(task_graph): # 2.1 Executor执行单个单元 raw_result = self.executor.execute(unit) # 2.2 Reviewer做质量校验 review = self.reviewer.check(unit, raw_result) # 2.3 校验不通过则请求重试 if review["passed"] is False: self.handle_retry(unit, review["comment"]) return task_graph实际生产代码里编排引擎还要处理超时、并行分支、断点续跑,这里省略。重点看三者的调用关系:Planner不动手,Executor出活,Reviewer把关,编排引擎作为确定性调度者把它们串起来。
5.4 从Demo走到生产,要跨过的关键门槛
跑通最小骨架很容易,落到生产要过三个门槛。第一是提示词工程化,Planner、Executor、Reviewer的System Prompt和工具描述不能写死在代码里,要抽成中心化配置并且支持按版本灰度,这样调整一个Agent的行为不需要重新发版。第二是Agent进程无状态化,内存里不要保存任何业务数据,所有上下文都从状态中心读、所有中间结果写回状态中心,没有这个基础,扩缩容和故障恢复都是空谈。第三是灰度上线,先挑一个低风险的只读业务线跑通全流程,跑满两周以上数据、确认Reviewer拦截率和漏放率都达标之后,再逐步放开到写操作类业务。我一贯的立场是,企业级系统不追求一次上线一个大版本,追求的是每一个增量都稳定可控。
最后分享一个我实测下来的体会。这套三种角色的多Agent协作系统跑上线之后,单看每个环节,Planner的拆解效果、Executor的调用成功率、Reviewer的拦截率都没有什么惊艳的数字,但系统整体呈现出来的稳定性是完全不同的层级。三个角色之间的错位纠察机制带来的不是某一个环节变强了,而是整个系统有了“自纠偏”的能力——Executor的粗心会被Reviewer兜住,Reviewer的误杀会被Planner的判断修正,Planner的拆分盲区会被Executor的执行反馈暴露出来。多Agent系统多出来的复杂度是真实的,但企业级场景值得付出这份复杂度。如果你正准备从单AgentDemo往生产级多Agent系统过渡,记住一件事,先把质量闭环跑通,再谈智能升维。