openrig:用声明式装配台搞定多Agent工作流编排
openrig是我最近一直在折腾的一个开源小框架。这个名字拆开来看就是“打开你的装配台”它解决的痛点其实挺具体的当你手上有多个大模型API、多个数据处理工具还要把它们串成一条可维护的工作流时怎么不靠硬编码、不靠复制粘贴而是用一套声明式配置把整个流程“组装”出来。我做这个项目的初衷就是想给AI应用开发这件事一个相对轻量的脚手架。它能做什么呢简单说就是你在配置里画好节点、接好线调用大模型、检索知识库、调用外部工具、处理中间结果openrig帮你把这一整套跑起来。适合谁用呢一是被各种Agent编排折腾过的全栈工程师二是刚接触AI工作流、想快速出原型的产品研发三是想把自己的工程经验沉淀成标准化模板的团队。市面上类似的东西其实不少但openrig的取舍和它们不太一样。它不追求把所有能力塞进一个引擎里而是把重点放在“如何让装配过程本身变得可控、可调试、可复用”。下面这几节我尽量把设计思路、核心实现、实操过程和一些常见的坑都摊开来讲。1. 项目整体设计思路为什么需要一台“装配台”1.1 多Agent协作的真正难点不在模型而在编排我最早尝试做多Agent应用时最初的写法是每个工具写一个类再在主流程里用if-else判断下一步调用谁。流程图很简单的时候这套写法完全没问题。但一旦加入检索、生成、校验、改写这些环节代码会迅速退化成一大团互相调用的函数。真正麻烦的不是“调一个大模型出结果”这件事而是结果如何流转、出错之后怎么回退、上下文怎么裁剪、每一步超时了怎么处理。openrig换了个思路把每一步都抽象成一个节点节点之间用边连接整条链路用配置描述。就像一条实体生产线模型、工具、判分支是不同工序配置是装配图纸。这样多个Agent的协作对手是从代码层面抽离出去变成了数据流层面的问题后续调优和排查就容易很多。1.2 openrig的定位不上不下刚刚好对比一下市面上偏重的编排框架会带很多内置能力学习成本和自托管的复杂度都不低纯代码的轻量方案又很难应付频繁变化的流程。openrig的目标落在这两者之间用一个简短的DSL描述流程运行时负责调度扩展能力靠插件协议补。这个定位让我在项目早期就明确了边界不做大而全的框架做一个足够好用的工作台底座。下面是当初做技术选型时列的一份对比表供方向参考方案灵活度上手成本维护成本适用场景纯代码硬编码编排高低高经常改流程固定、小规模重型编排框架高高中复杂系统、团队统一施策openrig中高低低中等复杂度流程、快速迭代选择openrig路线最核心的原因只有一个大多数研发团队的AI业务复杂度还没到需要重型框架的程度但已经超出了硬编码能健康维护的水平。这时候用声明式配置最划算。1.3 配置驱动的好处配置驱动还有一个额外红利产品同学和研发同学可以同一份流程文档交流。大家在需求评审里说“先检索再判断是否需要生成摘要摘要出来了再做要点提取”我直接把对应的YAML字段贴在后面即可沟通成本降得很明显。对团队而言多Agent项目的可维护性往往不是看代码写得好不好而是看需求变化后改动是否足够小。配置驱动之后多数改动都不碰核心代码。2. 核心架构与关键技术选型拆解2.1 三个基础模块描述层、运行层、扩展层openrig从工程结构上分成三块描述层负责解析YAML/JSON定义的工作流运行层负责按拓扑顺序执行节点并传递上下文扩展层负责把大模型、工具、数据库等能力注册成节点。这个分层设计参照了我做后端服务时惯用的“配置-调度-实现”三分法好处是每一层可以独立测试不会因为改了一个工具实现而影响整个引擎。描述层我选择了YAML作为第一语言。YAML写嵌套结构比JSON顺手字段之间的关系一眼能看明白。为了防止太长的配置文件变得难维护我支持$include语法可以按业务域把一条大型流程拆成多个文件。对于喜欢更严格类型约束的使用者后续也补了JSON Schema校验方案。运行层是最有趣的部分。它需要处理几个麻烦问题多分支如何决定后续路径、多个叶子节点是否并发执行、循环结构如何防止死循环。最终我借鉴了有向无环图DAG的调度方式早期版本干脆禁止了循环依赖让排查简单很多。节点之间的数据传递通过一个context对象完成类似HTTP请求里的request上下文后续节点只读自己关心的字段。扩展层的协议其实很简单每个插件只需要声明自己是什么类型、接受什么参数、返回什么结构即可。我用TypeScript写了一套接口定义JavaScript实现的第三方插件也能用duck typing方式接入注意关键字段别漏就行。2.2 技术栈清单整个项目没有刻意追逐新东西尽量选了我自己熟悉且社区稳健的组件模块选型原因运行时Node.js TypeScript生态丰富适合IO密集和异步编排前端工作台React 轻量画布组件便于可视化调试流程配置描述YAML JSON Schema对人友好可校验存储SQLite默认单机可跑后续可切换PostgreSQL部署Docker Compose一键启动适合内网使用选Node的一个重要考量是工具生态里有大量现成的npm包比如各种文件解析、网页抓取、消息推送SDK插件化包装成本很低。如果换成Python很多前端工具链接入反而要绕路。Python方向我留了HTTP调用接口跨语言调用也能做但默认推荐还是Node。2.3 为什么不做成无代码平台有一段时间我认真想过要不要加拖拽编排界面做成完整无代码平台。后来还是忍住了。原因是无代码平台真正吃功夫的地方在于“可视化配置的复杂交互”这会把大量精力吸走偏离项目验证“装配思路”的核心目标。现在做的可视化只定位成调试辅助跑完流程后能回看节点状态、耗时和上下文快照。用户完成装配的主要方式仍然是写YAML配置这也是为什么它不是面向纯业务人员的图形工具。3. 核心实现细节装配台上最关键的那些节点3.1 内置节点类型不只是调用大模型openrig目前内置了六类节点llm、tool、retriever、router、aggregator、http。名字直白但实操里需要注意的细节很多。llm节点是最常用的。除了model、prompt、temperature这些常规参数我加了一个叫responseSchema的字段。在项目里接LLM输出结构化数据时如果只靠提示词要求“返回JSON”偶尔会因为格式不合法导致后续节点崩溃。用了responseSchema之后运行时会在拿到原始输出后先做一个本地校验不符合就直接重试最多重试三次。这让整体稳定性提升非常明显尤其在批量跑历史数据的时候。tool节点的设计参考了MCP的“工具即函数”思想。一个工具就是一个带输入输出的函数声明openrig不强求工具自己处理异常而是统一捕获异常后按预设策略执行重试、降级或直接失败。比如调外部天气API超时可以配置为“返回空的天气对象继续往下走”而不是让整条流程断裂。router节点承担分支决策。它读取context里某个字段做字符串匹配或数值比较输出路由结果。比如我在客服场景里用它判断用户意图是“退款”还是“咨询”然后把不同分支指向不同节点。早期版本路由逻辑比较死后来我允许配置一个prompt字段让大模型返回一个标签再映射到分支灵活性高了很多。// 路由节点核心逻辑简化版 type RouterConfig { inputField: string; prompts?: string; routes: Recordstring, string; default?: string; }; async function routeNode(ctx: Context, config: RouterConfig) { const raw ctx.get(config.inputField); if (config.prompts) { const label await callLLM(config.prompts, raw); ctx.set(${config.inputField}_label, label); return config.routes[label] ?? config.default ?? end; } return config.routes[raw] ?? config.default ?? end; }3.2 上下文传递规则与数据裁剪上下文是整个装配台的地基。我采用局部优先的取值规则ctx里有一个核心对象和临时缓冲区临时缓冲区里的字段优先级更高处理完一条分支后自动清掉这样同一链路里不同分支之间不容易串数据。粗看像是多此一举实际跑复杂流程时它可以省掉非常多“这个字段到底哪来的”之类的问题。LLM输入长度始终是个坎。长文档摘要、多轮对话这类场景很容易把Token打满。我在llm节点里加了一个contextStrategy字段可选truncate、mapReduce、refine。truncate是暴力截断适合不敏感的描述性文本mapReduce是分段总结再合并适合长文档refine是逐段迭代完善输出适合要求连贯性的生成任务。这个特性不是openrig独有的但把它做进配置项里使用者不需要在自己的代码里重复实现省了很多事。3.3 重试与超时策略多Agent应用最影响体验的其实是“偶发不可控”。外部模型API偶尔超时、偶尔返回空内容这些都不该直接杀掉整个流程。我在配置里预设了统一的策略块execution: timeout: 60s retries: 2 retryDelay: 1.5s onError: continue # 可选 fail / continue / fallback实测下来单节点重试两次、超时60秒在普通文本生成场景下足够覆盖绝大多数异常。重要的是不要让用户等太久后直接看到失败页面。onError: continue会让流程带一个警告标记跑完后续人工介入兜底对内部工具型应用比较友好。4. 实操过程从一个“需求拆分Agent”看完整落地4.1 场景设定与安装启动这里拿一个我给自己团队做的“需求拆分Agent”当案例。需求是这样的产品经理丢一段原始需求描述系统先判断它是否包含验收标准如果没有就提取关键点生成问题清单然后综合原始需求和问题清单输出用户故事列表最后把输出整理成Markdown格式。整个过程涉及检索判断、两次LLM调用、一次结构化输出非常适合演示openrig的使用方式。第一步安装openrig。因为是个人项目分支我直接用Docker Compose起的服务关键就两步git clone 你的openrig仓库地址 openrig cd openrig docker compose up -d启动后访问本地工作台地址默认端口3030能看到节点状态列表和一个简单的调试面板。首次使用需要填一个模型API的base地址和keyopenrig兼容OpenAI接口格式所以主流的模型服务商都能接。工作台面板本身不是必需组件如果你只把它当库嵌入也可以直接import运行时包代码里初始化Engine对象加载YAML文件运行。Docker方案是为团队协作设计的统一环境省去各人本地依赖不同的烦恼。4.2 编写流程配置核心流程配置比我想象的短。下面是简化后的实际配置id: requirement-splitter name: 需求拆分Agent version: 1.0.0 nodes: - id: has_acceptance type: llm prompt: | 判断以下需求是否已经包含明确的验收标准。 需求内容{{input.requirement}} 只返回 yes 或 no。 responseSchema: type: string enum: [yes, no] output: has_acceptance - id: ask_questions type: llm prompt: | 根据以下需求生成不超过5个需要澄清的问题。 需求内容{{input.requirement}} 请以 numbered list 输出。 output: questions - id: split_stories type: llm prompt: | 结合需求与澄清问题输出用户故事列表。 需求{{input.requirement}} 问题{{nodes.ask_questions.output}} responseSchema: type: array items: type: object properties: title: { type: string } acceptance_criteria: { type: array, items: { type: string } } output: stories edges: - from: start to: has_acceptance - from: has_acceptance to: ask_questions condition: output no - from: has_acceptance to: split_stories condition: output yes - from: ask_questions to: split_stories - from: split_stories to: format_markdown type: pass配置里比较关键的一点是output字段。它相当于给每个节点的结果起了个名字后续节点通过nodes.节点id.output引用。这样整条流程像水管一样每段管道都有清晰的接口。刚开始用的时候很容易忘了给节点加output结果后续取不到数据换成现在的命名方案后基本没再犯过。format_markdown节点我没写详细配置实际是一个tool类型的文本处理节点接收JSON数组渲染成Markdown列表。这种“模型输出结构化数据工具节点做最后格式化”的组合在实际项目里非常常见因为LLM处理长文本格式容易丢样式传统代码处理又需要精确的数据结构。4.3 运行结果与调试观察启动流程后输出面板会实时显示节点状态。我观察到几个比较有意思的行为。第一has_acceptance节点返回no的时候整条链路会先走ask_questions再走split_stories而返回yes时直接跳过提问节点这里条件路由生效。第二因为split_stories用了responseSchema运行时自动做了JSON解析和校验偶尔模型返回的内容带了一些多余文字openrig也会在本地重试一次基本上不会把脏数据传给后面的格式化工具。一次典型运行的时间分布大概是两个LLM节点各占10到15秒格式化节点不到1秒。如果哪天某个节点耗时异常飙升工作台里能看到具体超时时间和上下文快照排查起来比看日志直观很多。4.4 把流程发布成一个小应用openrig还提供了一个轻量发布接口运行好的流程可以注册成一个HTTP端点。比如上面的需求拆分流程只加一段配置api: path: /api/requirement-split method: POST input: requirement: string output: markdown跑起来之后对外就多了POST /api/requirement-split这个接口入参是requirement字符串出参是Markdown。这个能力让我在内部系统里直接复用同一套流程逻辑不需要重新实现前端页面或机器人服务。发布接口这一层做得很轻本质上就是在运行时外围包了一层HTTP路由但因为编排逻辑都在YAML里新接一个流程的成本非常低。5. 常见问题与排查技巧实录使用过程中遇到的问题不少我把最有代表性的几条整理成速查表方便大家直接对着解决。问题原因解决思路节点A的返回在节点B里取不到没设置output字段或字段名拼写不一致统一用nodes.id.output访问并检查YAML缩进LLM输出JSON解析失败模型返回了额外说明文字或格式不规范启用responseSchema强制重试给prompt加“只输出JSON”约束流程一直循环或卡住配置里出现了环状依赖或某节点超时未配置早期强制使用DAG避免循环统一配置超时时间某个工具节点异常影响整条链路外部API偶发故障未兜底使用onError: continue加警告标记保留上下文快照prompt里模板变量没被替换模板变量引用的字段不存在启动前做静态解析校验跑之前检查上下文里的字段列表节点状态正常但http接口返回空发布接口的output声明和实际节点输出不一致严格按照最终节点output字段填写api.output第一条问题看起来很小但我真被坑过好几次。YAML是一种对缩进非常敏感的描述格式一个字段配错层级解析出来的对象结构就不是你想要的而且经常不报错。后来我在设计里加入了“启动前静态检查”加载YAML之后先遍历所有output引用如果发现有节点引用了不存在的输出字段直接报错并提示可能的原因。这个检查给后续使用省了非常多时间。除此之外调试多节点流程还有一个我特别推荐的做法每跑完一个节点就把该节点的输入和输出快照固化下来存成一个可回放的日志。这个日志的作用有点像一个自动化测试用例以后修改prompt或者调整工具参数时可以拿之前的case回归测试验证结果是否有退化。在个人项目里这件事靠手动做就行但只要涉及到团队协作一定要让它成为流程的一部分。最后提醒一点千万别把所有业务逻辑都塞进一个大LLM节点。虽然大模型能处理很多事情但这样做会让流程的可控性变差而且单点超时会拖垮整个应用。openrig的价值恰恰在于把一个大任务拆成多个小步骤哪一步不对就单独修哪一步。6. 后续还能怎么扩展说实话openrig现在还远谈不上完美。后续我比较想补的方向有三个。第一是让router节点更聪明支持基于语义相似度而不是简单的字符串匹配这样在意图识别场景下能少依赖一次LLM调用。第二是增加简单的锁机制当多个流程实例读到共享资源的时候避免并发冲突。第三是插件市场让社区贡献的节点能力可以一键安装。这些功能我不会全堆进核心引擎里更多的会做成官方插件。从设计思路上说这个项目最值得关注的不是某一个具体节点的效果而是它提供了一个“把复杂流程当作可配置资产”的方法。很多团队做多Agent应用最大的瓶颈不是模型能力不够而是流程改了之后没法快速验证和落地。openrig的装配台形式至少让“改流程”这件事在一个可控的范围里发生。如果你也在做类似的事不妨试着先把你的业务流程画成节点和边的结构再想哪些环节需要模型、哪些需要代码处理这个思考过程本身就可能帮你减少一半以上的无效代码改动。