ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OpenRig架构复盘:多Agent持久化编排与并发恢复实战

OpenRig架构复盘:多Agent持久化编排与并发恢复实战 做过 Agent 项目的人应该都有这种体验单个 AI Agent 在限定任务里表现惊艳一旦把它丢进一个需要多步骤、多角色、跨时段的真实业务流里立刻就拉胯。OpenRig 就是为这个问题做的——一个把离散 AI Agent 编织成持久化协作系统的多智能体编排框架。我从最早用 LangGraph 硬凑多 Agent 流程到后来自己从零设计这套编排内核中间踩了不少坑这篇文章就当一次完整复盘。我会把任务拆解、路由机制、协作通信、持久化恢复、并发伸缩这些部分怎么设计、为什么这么选、实际跑起来会遇到什么问题全部摊开讲。适合正在做 AI Agent 编排、尤其是被多智能体协同和状态恢复折磨的工程师读。1. 为什么非要从“一个Agent搞定一切”转向“一支Agent队伍协作”先讲个真实场景。前阵子我接到一个需求做一个竞品分析要求半小时内出结果流程包含抓取公开信息、提炼要点、对比自家产品、生成结构化报告最后还要按模板推送内部系统。如果按传统思路一个 Agent 扛到底表面上很优雅实际上会遇到几个绕不开的坎。1.1 单Agent的四个天花板第一是上下文窗口。竞品分析里要看的资料、要读的文章、要对比的字段加起来远超任何模型能塞进一次调用的上限。第二是工具链破碎抓取要用 Playwright提炼要用结构化抽取报告要用模板渲染每个环节的依赖和参数都不一样硬塞给一个 Agent提示词会膨胀到难以维护。第三是失败恢复代价高中间任何一步超时或者解析失败只能整条流程从头跑前面花掉的 token 和时间全部打水漂。第四是并行能力多个竞品可以同时抓取、同时分析但一个 Agent 迭代执行只能一个一个来。这四个问题叠加起来结果就是Demo 阶段怎么调怎么顺一进真实环境就频繁断链。1.2 协作系统要回答的四个问题我决定换一个思路不再做一个“超级全能 Agent”而是把整个任务拆成多个负责单一工序的小 Agent再由一个编排运行时把它们串成流水线。这样想清楚之后系统设计就变成回答四个问题。谁来做任务怎么拆分每个子任务交给哪个 Agent依据是什么。怎么做子任务之间的依赖关系用什么机制表达是顺序、并行还是根据中间结果动态切换。做到哪了每一步的状态必须可观测、可恢复不能像单 Agent 一样一断电就失忆。失败怎么办某个 Agent 挂掉之后是重试、换人还是降级策略。这四个问题正好是 OpenRig 核心设计的主线。如果你也在做多 Agent 系统可以先用这四个问题给自己现有方案打个分大部分出问题的项目都是第三个和第四个没做扎实。1.3 Agent是“工序”编排运行时才是“项目管理者”把一句话打在这里Agent 负责干活编排运行时负责管理。很多团队做多 Agent重心全放在“怎么把提示词写好让 Agent 更聪明”却忽略了生产过程本身需要一套管理系统结果再聪明的 Agent 也只是散兵游勇。OpenRig 的定位很明确Agent 是执行单元可以随时替换、升级、扩缩容编排运行时才是整个体系的地基。地基要做持久化、要扛并发、要能恢复Agent 只关注自己的单一职责。这个边界一旦划清楚后面所有模块的取舍就变得非常清晰。2. OpenRig的三大选型运行时分层、自研DAG内核、数据底座分工架构选型这事儿没有绝对标准但每个选择都得有理由。OpenRig 的三个关键选型我分别说下当时比较过的方案和最终取舍。2.1 运行时Rust管调度Python管技能OpenRig 的编排核心用 Rust 写Agent 侧提供 Python SDK。这个组合一开始很多人质疑觉得引入两种语言徒增成本。我的理由很简单编排调度是并发密集和状态一致性敏感的部分Rust 在内存安全和并发控制上有天然优势tokio 的异步运行时可以支撑大量 Agent 节点同时挂载而 Agent 本身要频繁调用各种模型、连各种工具Python 生态确实最全LangChain、LlamaIndex 这些现成库可以直接接。Rust 这一层负责四件事Agent 注册与发现、任务 DAG 的推进、事件日志的落盘、并发调度和重试策略。Python 这一层只做一件事承接 Agent 的技能逻辑。你写一个抓取 Agent里面完全可以继续用你熟悉的库只是通过 SDK 暴露一个统一入口给编排核心调用。2.2 编排内核自研DAG加事件驱动而不是全盘依赖LangGraph我用过一段时间 LangGraph它把图的定义和状态的读写绑在一起做小规模原型很爽但到生产环境我遇到两个痛点一是状态管理偏内存化跨进程恢复能力弱二是图的结构一旦定义下来对动态插入节点、按运行结果分支这件事支持得不够灵活。OpenRig 没有完全弃用这套思路而是把编排内核收窄成两件事DAG 负责表达静态依赖事件总线负责表达动态流转。DAG 用来描述“哪些子任务必须先于哪些子任务完成”事件总线用来表达“某个 Agent 完成之后广播一个结果其他 Agent 订阅这个结果再决定下一步”。静态依赖用 DAG 保证不会出现环动态流转用事件驱动保证灵活性。两者结合既能提前画出并行路径又能在运行中根据中间结果临时插入新子任务。2.3 数据底座三套存储各管一段多 Agent 协作系统本质上是一个分布式状态机存储设计决定了它的边界。PostgreSQL存放任务事件、Agent 注册信息、执行快照和最终产物索引。所有需要跨重启保留的元数据都在这。Redis存放运行中的临时状态、分布式锁、Agent 心跳。这些数据允许丢失丢了可以从事件日志重建。对象存储存放 Agent 产出的结构化或非结构化数据比如抓取页面、生成的报表、中间计算文件。这个分工的核心逻辑是Redis 只做热数据的短暂中转PG 是唯一的真相来源对象存储只存大块文件不进数据库。最开始的版本我试图把运行态也塞进 PG结果就是高频写入把数据库拖垮。后来把运行态切到 Redis问题迎刃而解。3. 编织的核心机制任务拆分、路由协议与协作通信名字里带个“编织”是指把多个离散的 Agent 像线一样交错织起来。这里最关键的三块Agent 怎么描述自己、任务怎么找到合适的 Agent、Agent 之间怎么对话。3.1 每个Agent先把自己“说明白”多 Agent 系统里最怕的就是 Agent 没有一个统一的能力声明编排器只能靠猜。OpenRig 给每个 Agent 定义一个 manifest里面包含标识、能力标签、输入协议、输出协议和资源预估以 JSON Schema 形式注册到编排核心。{ agent_id: collector-http, capabilities: [web-crawler, article-extract], input_schema: { type: object, properties: { url: {type: string, format: uri}, depth: {type: integer, default: 1} } }, output_schema: { type: object, properties: { content: {type: string}, links: {type: array, items: {type: string}} } }, cost_profile: {avg_latency_ms: 5000, token_budget: 4000}, version: 1.2.0 }注册之后编排器就能把输入校验提前拿到不需要等调用了才报错。cost_profile 字段尤其有用调度时可以根据 Agent 的预估延迟和 token 消耗做负载均衡后面讲扛并发时会细说。3.2 任务拆分从意图到DAG任务拆分是我最早开始做的模块因为拆分粒度直接决定整个编排效率。OpenRig 的做法不是纯靠大模型自由发挥而是“模板模型”组合式拆分每个任务类型预置一个拆分策略模板比如“竞品分析”模板会固定产出“采集—提炼—对比—报告”四条支线模型只需要在模板基础上补充具体参数。这样既保留了灵活性又不会让 DAG 结构失控。拆完之后每条支线之间如果有数据依赖就用有向边描述如果没有依赖就标记为可并行节点。编排器拿到 DAG 之后生成一个全局的 task_id每个节点有自己的 run_id父子关系写进事件日志。这套数据结构为后面的持久化恢复打下了基础。3.3 路由规则兜底历史成功率加权任务节点生成之后怎么决定派发给哪个 AgentOpenRig 采用两级路由。先走规则匹配根据能力标签和输入 schema 过滤出候选集合如果有多个候选再走加权策略权重由历史执行成功率、平均延迟、当前队列深度三个因子决定。成功率高的 Agent 不一定永远优先如果它当前队列已经排了十个任务调度器会把新任务优先派给队列更空的候选。这套思路和负载均衡的 Least Connections 策略很像只是把权重换成 Agent 的历史指标。路由结果会记录到事件日志后续可以根据真实执行情况自动调整权重。3.4 Agent之间的协作事件总线不是Agent直连One of the biggest traps in a multi-agent system is that agents call each other directly. 一旦 Agent 之间互相嵌套调用整个 DAG 就成了一张蜘蛛网想控制并发、想排查故障都变得无比困难。OpenRig 明确禁止 Agent 直连所有协作必须通过内部事件总线。Agent 完成一个子任务后把结果发布成一个带有 role、content、status 的事件编排器校验过后写入事件日志同时广播给订阅了该事件的下游节点。这个设计的直接好处是任何一个 Agent 的变更不影响上下游替换实现版本时不会产生接口风暴排查问题时翻事件日志就能看清整个协作链条谁在等谁、谁把数据传给了谁一目了然。4. 持久化层的设计功夫状态快照、事件溯源与断点续跑“持久化协作系统”里持久化不是简单地把数据存起来而是要让整个系统能在进程崩溃、机器宕机、网络分区之后恢复到一个正确的状态继续跑。这一章是 OpenRig 最核心的部分。4.1 三层持久化运行态、任务态、产物态我把持久化拆成三层每层的恢复目标不同。运行态Redis保存当前正在执行的任务节点、临时变量、锁。恢复目标是“快速清理垃圾”进程重启后直接丢弃因为任务态里有事件日志可以重建。任务态PostgreSQL保存任务 DAG、每个节点的执行状态、事件流。恢复目标是“回到断点”崩溃前已经完成的任务节点不需要重跑。产物态对象存储保存 Agent 输出的文件和大块数据。恢复目标是“不丢成果”已完成的结果通过索引引用直接复用。三层之间通过 run_id 和 task_id 关联。查询任何一个编号就能把该任务从诞生到现在的完整轨迹拉出来。4.2 事件溯源所有状态变化都能回放传统做法是把“当前状态”存成一张表每次变更直接更新。问题在于你丢失了“为什么变成这样”的历史信息。OpenRig 采用事件溯源任何一次状态变化都追加为一条不可变事件当前状态可以理解为这些事件的累计投影。这张 event_log 表是整张系统的命脉。字段说明event_id全局唯一ID用雪花算法生成task_id归属的任务IDrun_id归属的执行节点IDagent_id产生事件的AgentIDevent_type枚举值如 TASK_STARTED、TASK_SUCCEEDED、TASK_FAILEDpayloadJSONB记录输入输出摘要、异常堆栈等version乐观锁版本号created_at发生时间查询当前状态时只需要把某个 run_id 的事件按 version 排序重放一遍即可。好处有两个可以精确重建崩溃前的位置也可以回溯问题现场做审计。代价是事件表会涨得很快这个后面讲踩坑时会专门说怎么治理。4.3 断点续跑检查点加幂等键只有事件日志还不够恢复执行时必须避免重复执行已完成的副作用。这里需要两个机制配合检查点和幂等键。检查点的作用是标记“这一步已经确认完成”。Agent 执行完后编排器写一条 TASK_SUCCEEDED 事件同时把产物索引写入产物态。恢复进程扫描 task_id 所有节点的状态凡是已经存在 TASK_SUCCEEDED 的节点直接跳过。幂等键则是为了防止恢复过程中出现重复的副作用操作。比如一个发送报告的 Agent如果第一次执行后网络抖动、确认事件没写成功重试时可能又发一次。解决方式是每个任务节点进入执行前先生成一个唯一的 execution_id带在请求头里下游服务用这个ID做去重。def resume_task(task_id: str) - None: events load_events(task_id) completed_nodes { e.run_id for e in events if e.event_type TASK_SUCCEEDED } dag build_dag(events) for node in topological_iter(dag): if node.run_id in completed_nodes: continue execute_with_idempotency_key( node, keyf{task_id}:{node.run_id}:{node.retry_count} )这个逻辑看起来简单实际跑通之后恢复能力从“理论上有”变成了“真要能靠它救命”。有一次线上容器被系统杀掉几十个运行中的任务在十分钟内自动恢复没有一条链路因为崩溃而彻底报废。5. 从单机到集群OpenRig抗多并发的一条可复制路线多 Agent 系统跑起来之后下一个绕不开的问题就是并发。标题里也反复提到的“AI Agent 怎么扛并发”我在这块踩到的坑最多。5.1 并发瓶颈到底在哪先定位瓶颈。OpenRig 的调用链路有三段编排器调度、Agent 执行、Agent 内部调用模型。实际压测下来最耗时的不是编排器的调度计算而是 Agent 内调用模型和外部服务的 IO 等待。一个 Agent 平均花 3-8 秒等模型返回期间如果不做别的整个系统就废了。所以 Concurrency 的核心思路只有一个让等待时间变成调度时间。所以必须在编排器层面做完全的异步化Agent 调用模型时编排器继续推进其他节点的任务而不是排队傻等。5.2 异步任务队列加同节点并发上限OpenRig 引入消息队列做缓冲Agent 的执行请求先进入队列再由工作池消费。每个 Agent 类型有独立的队列和并发上限比如抓取类 Agent 并发 8分析类 Agent 并发 4这样不会出现某类任务洪峰把大批一次性塞给下游。队列的好处还能削峰。如果某个时刻系统收到 50 个同类任务队列先把请求存下来工作池按最大并发慢慢消化不会直接把数据库打爆。消费端使用可重入的 worker 进程任务取出后如果执行超时自动重新放回重试队列。5.3 水平扩展按任务分片保持一致性单机跑通之后很自然想扩展到多节点。此时最头疼的是状态一致性问题。OpenRig 的做法是按 task_id 哈希分片把每个任务固定到一个编排节点处理。这样同一任务的并发操作只会发生在一个节点内避免跨节点的分布式事务。跨任务之间可以分布式并行因为它们的 DAG 和事件都是独立的。执行 Agent 的工作池可以单独扩容和编排节点解耦。用 Redis 做分布式锁保证同一个 run_id 不会被两个 worker 同时执行。这套方案虽然牺牲了一点均衡性但换取了极大的一致性保障在没有强一致性诉求的任务场景里完全够用。5.4 Token预算与成本治理跑得动还要付得起账并发一旦上去token 消耗指数级增长。我之前好几个 Agent 任务并发一高账单就失控。后来在 OpenRig 里加了 Token 预算机制每个任务节点在路由时就要申请预算超过预算的请求直接降级为简化调用。具体措施有三条一是同一 session 内重复调用相同输入给模型时结果优先走缓存二是每类 Agent 设置 token 上限管理员可以按任务优先级调整三是支持对同一模型调用批量复用比如竞品分析里多个 Agent 需要索取同一份公开文章摘要只请求一次、共享结果。这点对控制成本非常有效建议每个多 Agent 项目一开始就把预算模块做进去不要等账单爆了再救火。6. 跑起来之后的坑幂等失效、事件膨胀、死锁和状态漂移纸上谈兵的时候觉得设计很完美上线之后一个接一个的坑教做人。挑四个最有代表性的记录一下。6.1 幂等键失效重复发邮件的深夜告警第一次事故是深夜两点系统重复发送了八封报告邮件。排查后发现我一开始把幂等键设计成 task_id 加 run_id但同样的 run_id 在重试时有可能返回之前已成功的结果导致下游拿到成功响应后误以为可以发邮件。真正的修复是把幂等键粒度从 run_id 细化到 run_id 加 retry_count并且下游消费端自己也要做一次结果去重源头就算漏了下游也能挡住。光是这一点就让我对所有分布式系统的幂等设计多了一分敬畏。6.2 事件表膨胀一个月涨到30GB事件溯源带来的副作用是存储膨胀。我最初没规划好归档策略一个月 event_log 表就干到 30GB查询开始明显变慢。治理方式是分层归档已完成任务在 48 小时后把 payload 中的大数据挪到对象存储只保留索引和事件摘要超过 30 天的任务事件自动转存到历史库业务库只保留活跃任务的完整轨迹。同时给 event_log 加按时间分表写入和查询都轻松很多。6.3 编排死锁Agent A等BB等A多 Agent 依赖一旦出现环就可能导致互相等待。我遇到过一次两个 Agent 因为一个共享变量互相等待任务卡死。排查链路是从事件日志看到 A 发了等待消息B 也在等 A 的结果两个节点互相锁死。修复的核心不是砍掉一个 Agent而是给所有跨 Agent 协作加上租约超时机制每个 Agent 在接到下游请求时建立一个租约下游必须在租约有效期内返回到期自动释放锁并标记失败进行重试。这个机制上线后再没出现互相等待的僵局。6.4 状态漂移事件回放和实际状态对不上事件溯源理论上能重建状态但如果某些操作绕过事件日志直接改了外部系统回放出来就会“对不上账”。我遇到过清理任务把对象存储里的文件删了但事件里只记录“清理完成”恢复时拿事件重建认为产物还在一引用就 404。解决办法是给产物读加一层引用检查每次重建状态时凡是涉及产物引用的地方都做一次存在性校验校验失败就走重新生成流程。别让事件日志假设外部世界不会变实际生产环境里有些变化根本进不了日志。7. 收个尾这套编排思路的边界在哪里整个 OpenRig 从设计到落地最深的体会是编排系统的难度不在“能不能让 Agent 跑起来”而在“怎么让几十上百个 Agent 在真实环境里稳定地协同”。我见过很多人上来就设计二十个 Agent最后互相干扰、状态混乱推倒重来。我的建议是永远从一个小闭环开始三个 Agent、一个协调器、完整的事件日志先跑通持久化恢复再加并发再扩节点每一步都验证边界。另外一个很务实的观点是不是所有状态都值得持久化。Agent 内部的思考过程、临时草稿这些丢失了反而更干净真正值得持久化的是决策边界——任务是否完成、产物在哪里、谁依赖谁。把这个精简到极致系统会轻很多。最后说一个我现在正在做的扩展方向可观测性。给每个 Agent 节点接入 OpenTelemetry 的 trace把一次协作的完整调用链可视化出来。排查故障从翻事件日志变成看链路图效率高了一个量级。如果你的多 Agent 系统也进入了稳定运行阶段建议往这个方向投入比继续堆 Agent 数量有意义得多。
返回列表