ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

多智能体编排实战:OpenRig事件驱动持久化协作系统解析

多智能体编排实战:OpenRig事件驱动持久化协作系统解析 先说一个我在多智能体项目里反复体会到的结论单个 Agent 再聪明它也只是一个人的单人办公室真正让 AI 在业务里发挥价值靠的是把几十个能力各异的 Agent 组成一个能长期稳定运转的协作网络。OpenRig 就是我在这个方向上做的一套多智能体编排实践——它的目标很纯粹把那些彼此孤立的 AI Agent 当作独立节点用一套持久化协作系统把任务流转、对话记录、状态快照、失败重试全部接管下来让 Agent 之间像流水线上的工人一样有序配合。这篇文章不讲虚概念直接讲 OpenRig 是怎么设计的、怎么搭起来、怎么在真实场景里用以及我踩过的那些坑。适合读这篇内容的人一是已经跑通单 Agent 业务、正准备往多 Agent 方向扩展的工程师二是被“编排”和“协同”这类词绕晕、想搞明白底层原理的技术负责人。我尽量把每一步“为什么这么做”也讲透不只是给配置。1. 为什么单 Agent 够用之后还要做多智能体编排1.1 从“一个 Agent 干完”到“一群 Agent 分工”单 Agent 的调用模型通常很简单用户输入Agent 思考Agent 调用工具Agent 输出。这套链路处理单个域内的任务很顺手比如一个客服问答机器人你给它接上知识库和工单系统它就能独立回答大部分问题。可一旦业务横跨多个部门、多个数据源、多种专业技能单 Agent 就开始力不从心——不是模型能力不够而是它的上下文窗口有限、工具集庞大后决策变慢、职责边界模糊导致错误率上升。我自己的一个项目就是典型例子。最开始用一个 Agent 同时做“用户意图识别 商品推荐 订单查询 售后处理”看起来很美实际跑起来问题非常多Agent 经常在意图识别阶段就调用了订单接口或者在用户只是咨询的时候直接创建了售后工单。后来我把这个 Agent 拆成四个专职 Agent每个只负责一段职责再用编排系统把它们串起来准确率立刻提升了一个档次。这就是多智能体编排最基础的价值分而治之各司其职。有个点必须说清楚多智能体不是说“越多越好”。因为每一个 Agent 都会引入额外的延迟、通信成本、失败概率如果业务本身不复杂强行拆成多 Agent 只会让系统更脆。我的经验是当单个 Agent 需要管理超过 1520 个工具、或者上下文经常被无关信息撑爆的时候才值得考虑拆分。1.2 编排和调度的本质区别很多人把编排Orchestration和调度Scheduling混为一谈这俩在工程实现上完全是两码事。调度解决的是“任务来了派给谁执行”的问题本质是资源分配编排解决的是“任务之间的依赖、流转、状态怎么管理”的问题本质是流程控制。OpenRig 解决的是后者但同时也包含了前者的部分能力。举一个生活化的类比调度好比餐厅的排号机只负责告诉客人“轮到你了”编排则像后厨的整个流水线——有人切菜、有人炒菜、有人装盘每个环节都必须在下游环节之前完成而且任何一个环节出了问题后面的节奏都要跟着调整。OpenRig 做的事情就是把这个后厨的流程固化下来让环节之间的交接不需要人为干预同时记录每一步的状态确保任何时刻系统崩溃了都能从断点继续。用术语说OpenRig 是一个有状态的事件驱动编排框架。它跟无状态调度的最大差异在“持久化”三个字上——后面我会详细展开。2. OpenRig 的核心设计怎么把 Agent “编织”起来2.1 把 Agent 抽象成节点、边和记忆OpenRig 最核心的抽象是三样东西节点Node、边Edge、记忆Memory。节点就是一个个 Agent 实例边是连接节点的通路记忆是跟任务绑定、可以被任意节点读写的上下文数据。这看起来很像一个有向图但实际实现不是一张静态图而是一个会随任务动态生成实例的图结构。每个节点在 OpenRig 里被定义成一个带输入输出契约的独立服务。节点之间不能直接互相调用内存对象只能通过消息总线传递数据。这个约束一开始会让习惯直接函数调用的人很不适应但它是整套系统能持久化、能恢复、能伸缩的地基。节点之间的耦合被降到了最低你替换任何一个 Agent 的实现其他节点完全无感。节点还有一个属性叫“在岗时间”TTRTime to Respond用来控制 Agent 响应超时。因为 LLM 的响应时间天然不稳定同一个问题可能 2 秒也可能 20 秒如果没有超时控制一旦某个 Agent 卡住整个链路会被它拖死。我会在配置示例里展示这个参数怎么设。2.2 持久化协作系统到底在持久化什么这是 OpenRig 跟那些只做“消息转发”的多 Agent 框架最不一样的地方。一个持久化协作系统至少要对四类数据做落盘会话事件流、任务状态快照、节点上下文缓存、编排流程定义。这四类缺一个系统都没法做到真正的断点续跑。会话事件流记录了每个 Agent 收到什么、输出什么、中间调用了哪些工具按时间顺序追加存储。它既是审计日志也是回放素材。任务状态快照保存了整个任务图执行到哪一步、每个节点的状态是 pending、running、succeeded 还是 failed。节点上下文缓存解决的是 Agent 重启后的记忆丢失问题——LLM 本身没有记忆所谓记忆不过是一堆历史消息塞进 promptOpenRig 把这份“塞进 prompt 的内容”也集中管理起来。我见过很多团队在搭建多 Agent 系统的时候只把结果存数据库过程全部丢掉。结果就是任务到凌晨三点失败第二天只能从日志里猜原因。OpenRig 的设计思路是把过程当成第一公民来存储结果反而成了过程的一个副产品。调试的时候优势非常明显你可以把任何一周前的任务事件流完整回放出来。2.3 为什么选择事件驱动而不是直接调用直接调用比如 HTTP 同步请求、函数调用写起来简单一只 Agent 调另一只 Agent返回了再继续这是最直觉的做法。但放到生产环境里直接调用意味着调用方必须等被调用方执行完而 Agent 的执行时间动辄几十秒一个链路上串三四个 Agent请求总耗时轻松超过一分钟。用户体验差不说中间任何一个环节超时前面支付的算力成本全部浪费。事件驱动则完全反过来每个 Agent 只要把结果写进事件流、发布一个task.completed事件就可以立刻收工下一个节点监听到事件自行启动。整个系统像一个异步流水线节点之间不需要互相等待。代价是编写逻辑变得复杂因为你要处理“事件丢失”“重复消费”“乱序到达”这些分布式系统的经典难题。OpenRig 的做法是把这些底层问题尽量封装掉对上层用户暴露的编程模型仍然接近“写一个处理器函数”。这里有个关键的工程判断如果 Agent 之间的调用深度不超过两层而且单次调用时间低于 5 秒用直接调用能省很多事一旦链路变深或者单步执行时间变长事件驱动带来的吞吐量和稳定性优势就非常明显。OpenRig 的定位是后者更适合需要跨长时间运行的编排任务。3. 实操从零搭建一个 OpenRig 协作系统3.1 环境准备与基础配置OpenRig 的核心运行时代码是 Rust 写的所以在并发性能上天生有优势——这也能解释为什么它能扛比较高的请求密度。但使用端不需要你写 Rust它提供了 Python 和 Node.js 的 SDK我用下来 Python 版本的体验更成熟下面示例都用 Python。基础依赖只需要三样PostgreSQL 做事件与状态存储、Redis 做消息总线和分布式锁、OpenRig 运行时。PostgreSQL 存长期数据Redis 做临时缓冲两者角色不同不建议省掉任何一个。因为事件流数据量增长很快建议给 PostgreSQL 单独建表空间同时在上层建一个定期归档分区表的脚本。安装 OpenRig 的 Python SDK 很简单pip install openrig-sdk openrig-server --config ./rig.yaml一个最小化的rig.yaml配置如下server: host: 0.0.0.0 port: 7788 storage: postgres_dsn: postgresql://user:password127.0.0.1:5432/openrig redis_dsn: redis://127.0.0.1:6379/0 workers: default_concurrency: 8 max_waiting_tasks: 1000default_concurrency控制的是单个节点默认的并发执行数。这个值不是越大越好因为它受限于你调用的 LLM API 的 rate limit。我习惯设置为自己常用模型并发上限的六成留出余量给重试和突发流量。3.2 定义第一个 Agent 节点在 OpenRig 里定义节点不需要写一堆框架代码只需要一个类和一个配置注册一下。下面这个例子是一个意图识别节点from openrig import RigNode, RigContext, event class IntentNode(RigNode): name intent_detect max_retries 3 timeout_seconds 30 async def process(self, ctx: RigContext, payload: dict): user_text payload[text] # 这里调用任意 LLM也可以是本地小模型 intent await ctx.llm.complete( f判断用户意图: {user_text}只返回分类名称 ) await ctx.emit(task.intent_detected, { intent: intent, original: user_text }) return {status: ok}关键在于emit它把结果发布到事件流而不是直接 return 给调用方。后面其他节点通过订阅事件来联动。OpenRig 会把你 emit 的事件自动写进 PostgreSQL不用担心丢事件。注册这个节点到配置里nodes: - module: apps.nodes.intent.IntentNode注册完之后OpenRig 会自动加载这个节点等消息进入它订阅的队列时触发执行。我建议每个节点模块放在独立的 Python 包里这样后续做蓝绿发布、灰度升级都方便。3.3 编排一条跨 Agent 任务链路有了单个节点接下来要织出“协作系统”。OpenRig 的编排语言支持声明式的工作流定义你可以把它理解成一个轻量级的 DSL。下面是一段典型的流程定义描述“用户进来 → 意图识别 → 商品推荐 → 订单查询 → 结果汇总”workflow: customer_service_pipeline start: intent_detect steps: - id: intent_detect node: intent_detect next: match: intent ask_product: product_recommend intent check_order: order_query intent after_sale: after_sale_handle default: fallback_reply - id: product_recommend node: product_recommend_v2 next: result_aggregator - id: order_query node: order_query_node next: result_aggregator - id: result_aggregator node: aggregator_node end: true这套定义的本质是一个基于事件匹配的状态机。next.match会根据上游节点 emit 事件的字段决定下一步走向。我特别推荐这种“数据决定路由”的模式因为它把流程控制和业务逻辑完全分离产品人员都能看懂整个任务的走向。值得注意的一个细节这里每一步都是异步流转的而不是同步等待。也就是说intent_detect发出事件后立刻空闲出来可以处理其他任务product_recommend在事件总线上收到消息后才启动。这正是事件驱动跟传统工作流引擎比如 Camunda的区别点。3.4 并发、超时和重试的三重保护多 Agent 系统最容易用的毛病就是雪崩一个节点响应变慢事件在队列里堆积后续节点全部空转等待。OpenRig 给出三个基础防护参数——并发限制、超时控制、重试策略三者必须配合着一起设缺一个都有隐患。部分节点的配置文件可以长这样nodes: - name: product_recommend_v2 concurrency: 4 timeout_seconds: 45 retry: strategy: exponential_backoff max_attempts: 4 initial_backoff_ms: 1000关于超时我给一个经验值超时时间一般设为该节点预期耗时的 1.5 倍到 2 倍。比如一个调用 Agent 平均耗时 10 秒超时设 1520 秒最合理。设太短容易频繁误杀正常请求设太长则退化成了没有保护机制。重试策略建议首选指数退避不要用固定间隔重试原因很简单——Agent 卡住往往是因为后端模型服务过载固定间隔重试等于每两秒扇一次火。还有一个所有做多 Agent 的人都会掉进去的坑把并发数调大以为就能提高吞吐。实际上你上游 LLM API 的 rate limit 才是真正的瓶颈并发开再大超额请求只会被限流。所以在 OpenRig 里我建议把节点的并发数作为一个与外部依赖 rate limit 强相关的参数来管理而不是自由调整。4. 落地场景这些配置在真实业务里的表现4.1 客服团队里的 Agent 协作实例我把它部署在一个电商客服系统的副驾应用里用到了三个 Agent意图识别 Agent、订单查询 Agent、售后方案 Agent。原来的单 Agent 模式下用户一句“我的商品还没到怎么处理”经常被误判成催物流而直接调用快递接口导致答非所问。拆分后意图识别 Agent 只负责分类它判断出是“物流咨询”还是“申请售后”再由专门 Agent 分别处理准确率从 67% 提升到了 89%。这个场景里我觉得最有价值的不是准确率提升而是定位问题的速度。之前单 Agent 出错时你根本不知道是模型理解错误还是工具调用错误。现在通过 OpenRig 的事件流回放可以精确看到意图节点输出的结果是啥、订单查询节点拿到了怎样的原始数据每个环节清清楚楚。有个坑要提醒事件流的存储量非常大。我们这个客服场景日均调用量在 2 万次左右原始事件数据每天能产生几个 GB。如果不做冷热数据分离和定期归档PostgreSQL 撑不过三个月。我最终的方案是“热数据留 7 天冷数据按天分区压缩归档到对象存储”查询历史场景再通过专用任务做恢复。4.2 内容生产流水线里的多角色编排另一个场景是给一家内容团队做自动化生产流水线串了选题 Agent、大纲 Agent、初稿 Agent、改写 Agent、质检 Agent。这个流水线如果只做简单的 request–response根本跑不通因为环节之间的依赖和数据交换非常复杂一篇文章从选题到质检可能需要 58 分钟。用 OpenRig 做编排后整条流水线是一个持久化任务图。每个 Agent 做完自己的工作以后把半成品写入对象存储并把引用地址 emit 到事件流里下一个环节从事件流拿到地址、拉取内容、继续加工。这样单个 Agent 的内存负担很低因为不用把整篇文章一次性塞进上下文。这里我体会到的一个经验是Agent 之间传递的内容越“窄”越好。不要把一个 8000 字的大文档作为事件体直接发出去而是传一个文件路径或 URL。原因有两个一是事件总线传大对象会阻塞其他消息二是每经手一个 Agent上下文窗口都会被人为撑大费用也会非线性上升。我在后续项目里强制约束事件体大小不超过 64KB超过就存对象存储然后传引用。4.3 数据采集与清洗链路的容错设计做数据管道场景时我们面对的是几十个数据源每个数据源的接口稳定性差别非常大。有些接口偶尔返回 500有些接口字段结构三天两头变。这种情况下最需要 OpenRig 的重试和失败恢复能力。我做一个“采集 → 清洗 → 入库 → 质检”四段式流程。采集 Agent 负责从各个源拉数据清洗 Agent 做字段标准化入库 Agent 写数据库质检 Agent 最后校验。如果清洗 Agent 解析字段失败它会把清洗失败的数据作为事件task.cleaning_failed发出去同时原样保留原始数据块等待人工或者兜底节点修复规则后重新触发。流量高峰时这个管道会同时跑几百个采集任务。我在 Redis 里做了分布式锁配合 OpenRig 的幂等事件 ID保证每个任务不会被两个节点重复消费。真发生过节点崩溃重启后事件重复推送的情况正是因为每个事件都带着全局唯一 ID下游节点才能安全去重。这里还要强调一个原则Agent 的执行必须是幂等的。你在设计多 Agent 系统时从一开始就要假设同一个事件可能被处理两次。OpenRig 可以帮你把重复事件过滤掉大部分但 Agent 自身的写操作不会自动幂等。我的做法是在所有 Agent 的写逻辑里都先检查业务单据号如果已经处理过就直接返回旧结果不做第二次插入。5. 常见问题与排查实录5.1 高频问题速查表问题现象可能原因排查步骤解决办法节点一直不触发订阅的事件名与上游 emit 的事件名不一致查看事件流用工具订阅该事件主题统一事件命名规范尽量全局维护事件字典任务执行一半卡死某个 Agent 超时未设或模型 API 无响应查看节点 TTR 和日志配置超时时间与重试设置 dead letter 队列事件重复消费消费者重启未提交 offset查看消费进度和事件 ID开启幂等事件 ID消费逻辑做去重数据库存储增长过快事件流未做归档分区查看表大小建立冷热分区冷数据转对象存储并发一高就报限流错误上游 LLM API rate limit 不够查看提供商 rate limit 日志下调节点并发数或接入请求缓冲队列编排流程改不动流程定义被当成业务硬编码查看流程是否引用外部定义把流程定义放进配置中心支持热更新这张表里我见过最多的是第一行——事件命名不一致。团队一旦超过三个人一起开发多 Agent每个人都会按自己的习惯命名事件结果就是上游发user.recommend下游等recommend.user发现半天一直没反应。后来我们引入了一个事件命名规范业务域.动作.状态比如cs.intent.detected同时放一个中央 YAML 文件管理全部事件清单作为代码审查的一部分。5.2 三个最容易踩的坑第一个坑把事件流当数据库用。有时候我调试时习惯直接在事件流里查所有数据但事件流本质是个日志不是为查询设计的。如果某个 Agent 需要查“上周所有未完成的订单任务”应该专门建一个物化视图或者状态表通过查询服务去访问而不是 scan 整个事件流。我最早就是没注意这点结果事件表到了几千万行的时候一次状态查询要几十秒。第二个坑超时时间一刀切。所有节点都用同一个超时时间这是最偷懒也最危险的做法。不同模型、不同 prompt 长度、不同工具的响应时间差异非常大。建议按节点单独测算 P95 响应时间再乘以 1.5 作为超时初始值。不要用 P99否则线上偶然的长尾会导致大量误杀也不要用平均值那个值对超时没有参考意义。第三个坑忘了持久化 Agent 的“思考过程”。很多人只记录 Agent 的输入和输出但调试 Agent 问题时真正有用的是中间推理过程、工具调用轨迹、上下文被裁剪的记录。OpenRig 里我把每个节点的decisions字段都塞进了事件体内容不大但对问题回溯极其有价值。比如 Agent 明明答错了你回放它当时的中间步骤很可能是某一步工具返回了脏数据而不是模型不行。5.3 故障恢复的一次真实演练有一次我们准备上线新版本前做了一次故障演练把 OpenRig 运行时直接 kill 掉模拟最极端的宕机场景。重启后让我意外的是系统没有出现任何任务丢失所有进行中的任务自动从上次快照继续。这背后依赖的就是任务状态快照我们每完成一个步骤就落一次快照所以恢复粒度非常细。这次演练让我对持久化协作系统有了一个小反思快照不是越频繁越好。每步都落快照会拖慢整体吞吐每十步才落一次快照又会让恢复时重复执行太多。目前我常用的策略是“关键边界节点落快照、普通节点只落事件”智能体链路里的关键边界通常是涉及外部副作用下单、退款、发消息的节点在这些节点前强制做持久化。这个平衡点不同项目不一样需要压测再调整。6. 继续往下走OpenRig 还能怎么扩展我在实践里探索过的几个扩展方向如果你想继续深入多智能体编排可以考虑。第一个是混合编排把事件驱动的长链路和函数调用的短链路组合在一个工作流里简单场景走同步调用降低延迟复杂链路走事件驱动保证可靠。OpenRig 目前已经支持通过sync_call标记部分节点为同步执行但两种模式混用时的链路追踪还需要自己留心处理。第二个方向是自己写一个可视化编排面板。OpenRig 提供了一组管理 API可以拉取任务图状态和事件流Graffana 之类的工具能直接展示。我给内部团队搭了一个简单的看板展示“当前有多少任务在跑、每个节点什么状态、历史成功率趋势”。这套面板对管理层的意义大于对工程师的意义但在排障时也能让你一眼看到瓶颈节点是哪几个。第三个方向是接入人工审批节点。很多业务场景不允许 Agent 全自动执行敏感操作比如退款、删除数据、发对外消息。OpenRig 里我实现了一种human_approval节点Agent 执行到该节点时暂停等审批人在回调链接里确认后才继续往下走。这个能力让智能体系统从“全自动”变成了“半自动”业务方接受度高很多因为人还是在关键环节握有最终决定权。最后分享一个我自己体会最深的原则做多智能体编排不要一开始就追求“全自动”先把人工可干预的兜底留好让系统在 90% 的常规场景里自动跑10% 的特殊情况走人工处理。这套系统就能在业务里站得稳再随着数据和信心的积累逐步把那 10% 也自动化掉。OpenRig 目前已经是我团队里离不开的基础设施它最大的价值不是让 Agent 变聪明而是让整个 Agent 团队变得可管理、可观察、可恢复。这也是我认为多智能体落地最值得投入的方向。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进