☰
从ReAct Agent到Graph Workflow:企业级AI流程编排实战与踩坑记录
2026/10/12 5:49:52 网站建设 项目流程

弄企业AI的时候,我最初跟大多数人一样,脑子里只有“ReAct Agent”这个概念——让大模型自己想、自己调工具、自己给结论。但真正放到生产环境,规范化流程跑起来之后,问题就来了:大模型一个请求里既要做意图判断又要做数据查询还要生成回复,链路一长,某一环出错整个对话就废了。最典型的是我们内部那个工单自动处理项目,用户问一句“我的订单为什么还没审核通过”,Agent得查订单、查库存、查物流、查审核状态,任何一个查询失败或者返回格式不对,整条链路就白跑。就在那个节点,我开始研究Spring AI Alibaba Graph这套编排框架,用图结构把流程拆成显式节点,让每一步都看得见、控得住。这篇文章不写官方文档里已有的东西,就把我从ReAct Agent迁到Graph Workflow的真实经历、设计思路、踩坑过程完整讲一遍。

这内容适合正在做企业级AI应用、被大模型不可控性折磨、想把多个Agent编排成稳定业务流的开发者。尤其适合那种“AI只是流程里的一环,而不是整个流程本身”的场景——比如风控审核、工单处理、客服质检、供应链异常处理。下面从设计思路开始,一步步拆解。

1. 先想清楚:Agent和Workflow的边界到底划在哪

1.1 ReAct Agent再快,也顶不住流程里的“顺序约束”

很多人一开始迷信ReAct,是因为它确实灵活。你给大模型一堆工具,它自己决定先调哪个、后调哪个、什么时候结束。这种模式对付单轮问答、开放域闲聊绰绰有余,但企业流程恰恰是“反灵活”的:必须先做A才能做B,B的结果不合格就必须走C分支,D步骤要等待人工确认,超时就自动升级到主管。这种强烈的顺序约束和状态依赖,ReAct在结构上就不支持。

我举个最直观的例子:某公司有个内部报销审核流程,正常顺序是“票据识别→真伪校验→预算检查→审批”,每一步的结果都会被下一步当作输入。如果只是让ReAct Agent自己一路调工具,那它大概率会在“预算检查”时突然想起还应该确认一下报销人有没有违规记录,于是多调一个接口——这本身没错,但一旦这个额外查询拖慢了主流程,审批时效就崩了。更麻烦的是,一旦某一步超时,Agent没有显式的重试策略,它会自己编造一个“看起来合理但其实不对”的中间结果。

所以ReAct的本质是“推理驱动的工具调用循环”,适合探索性和弹性场景;而企业的核心链路优先要的是“确定性的步骤”。这两种诉求的根本矛盾,决定了我们得在Agent外面包一层流程控制壳。

1.2 Graph工作流存在的理由:把路由、分支、回退都变成显式节点

Spring AI Alibaba Graph给我的第一感觉就是“用有向无环图约束大模型的自由”。每个节点负责一件事,节点之间通过边连接,条件边决定流程往哪走,状态对象在节点之间传递数据。这样一来,一个完整的业务流不再是大模型自己“临场发挥”的舞步,而是完全受控的轨道。

我们还是拿报销审核来说。Graph化之后,流程是这样的:入口节点“票据识别”调用OCR模型,输出票据文本;然后边走到“真伪校验”节点,调验真接口;校验结果作为条件边的判断依据——通过则进入“预算检查”,不通过则直接进入“人工复核”。整个过程中,大模型唯一的职责是做好“票据识别”这一步,或者“预算检查”时读一下历史数据给个建议——它不再需要负责整条链路,整个流程的推进完全由图结构控制。

这种做法的好处有三层:第一,每一步都可观测,日志里能看到当前执行到哪个节点,卡在哪;第二,每一步都可重试,单节点失败只影响该节点,不再把整个对话搞崩;第三,条件路由是写死的,不会出现“模型自作主张跳过关键校验”这种低级事故。说白了,Graph是把“AI的能力框进业务的形状里”,而不是让AI自己长成业务的形状。

1.3 选Agent还是Workflow?我的判断标准

总结下来,我现在的判断标准就三条:

  • 有明确步骤顺序和状态流转的,无脑用Workflow。比如审核、风控、工单处理,步骤是业务定义的,不是模型定义的。
  • 步骤本身不确定,需要“探索”的,用Agent。比如知识库问答,用户问什么不确定,模型需要自己判断查哪些文档、怎么组合答案。
  • 混合场景,用Graph把Agent嵌在节点里。比如先分类,再按类别进入不同的Agent子流程——这是我们后面要重点讲的做法。

这三条不是拍脑袋想出来的,是跑了几个项目之后总结出来的。早期我只要看到“复杂”两个字就上Agent,结果被超时、幻觉、不可复现虐得体无完肤;后来凡是跟业务强关联的流程一律Graph编排,Agent只做理解、生成、总结这些“纯AI”的事,线上稳定性明显提升了一个量级。

2. Spring AI Alibaba Graph的核心设计拆解:节点、状态、条件边

2.1 节点就是“函数”,状态就是“全局变量”,边就是“路由规则”

Spring AI Alibaba Graph的实际写法和LangGraph非常接近,这让我一个从Python转过来的人特别亲切。核心就三个概念:

节点(Node):一个处理单元。Spring AI Alibaba里,节点就是一个Java方法,输入是当前的状态对象,输出也是状态对象。每个节点只干一件事,比如“调用大模型生成一句问候语”是一个节点,“调用SQL查询用户订单”是另一个节点,绝不混在一起。这样设计最大的好处是单元测试特别好写——一个节点就是一个纯函数,给它一组输入,断言它的输出,完事。

状态(State):一个贯穿全流程的数据对象。你可以把它理解成一个“全局变量”,所有节点都能读它,也都能改它。Spring AI Alibaba Graph里通常用一个Map或者自定义的POJO来承载。状态里该放什么、不该放什么,直接决定了你能不能排查问题。我的习惯是:原始输入、每个节点的中间产物、最终输出,全部放进State,方便事后审计;但“临时变量”这类不入状态的东西,坚决不塞进去,否则状态会膨胀得没法看。

边(Edge):节点之间的连接关系。边分两种:普通边表示“无条件执行下一节点”,条件边表示“根据当前状态判断下一个节点是谁”。条件边是Workflow的灵魂——它把“业务规则”和“模型任意性”隔离开来。比如校验失败走A节点、校验成功走B节点,这个判断可以是一段Java代码,而不是让大模型来拍板。

2.2 状态机思维:从入口到出口,为什么路径设计比模型能力更关键

如果你以前写过状态机,上手Spring AI Alibaba Graph会非常顺畅。本质上它就是一张“流程图”:入口节点是start,出口节点是end,中间任何节点都可以作为状态判断的分水岭。但这里有经验的差别:路径到底该设计成“多步串行”还是“主路+分支回退”,这决定了流程的健壮度。

我的经验是:宁可多画一步,也不要让某个节点里塞了太重的逻辑判断。比如有个场景,“判断用户是否在黑名单里”这一步,我一开始是放在“用户信息查询”节点内部一起做的,结果黑名单接口超时,连带用户信息也查不了,整个节点就只能报错。后来拆成两个节点:“用户信息查询”和“黑名单校验”,前者失败了还能走缓存兜底,后者失败了只影响风控分支,路径清晰,容错也好做。

另一个点是:入口要有校验,出口要有兜底。入口校验是说,在进入正式业务分支之前,先做一个“输入合法性判断”节点,数据不对直接走error结束,别让脏数据进主流。出口兜底是说,所有末端节点都尽量接一个“兜底回复”节点,以防某个分支忘了处理某种边界情况,导致用户收到的是一段大模型硬编的报错话术。

2.3 条件路由怎么配:三种写法和我的选型习惯

Spring AI Alibaba Graph的条件路由有三种常见实现方式,我都试过:

第一种是在节点内部直接改State,然后在边配置里用SpEL表达式判断路由。这种方式好在“配置驱动”,路由规则和数据是解耦的,适合规则经常变的场景;缺点是SpEL写多了之后不够直观,调试要额外花一点时间。

第二种是在节点里通过返回值指定下一节点ID,然后Graph执行引擎根据返回值路由。这个方式体验最好,就像写一个方法时return"nextNodeA"一样,逻辑一目了然;缺点是把路由规则散落在各个节点的代码里,流程图不能一眼看到全貌。

第三种是结合Spring AI Alibaba自身的条件边API来写,你可以在构建Graph时对每条条件边定义自己的判断函数。这个方式改动大但最灵活,适合那种判断逻辑复杂的场景。

我个人现在的习惯是:简单分支用第一种,复杂分支用第二种,极少用第三种。因为第一种的配置集中在Graph定义处,流程图看着清晰,排查问题的时候看一眼配置就明白整个流程的走向,体验最接近“用纸笔画流程图”的心智模型。

3. 实战:构建一个企业风控多Agent协作工作流

3.1 需求拆解:为什么风控必须用Graph而不是裸ReAct

风控是所有企业流程里最适合用Graph演示的,因为它有一条天然的“次序链路”:先识别主体,再查基础信息,再做规则判断,最后人工复核。每一步之间数据依赖非常强,而且必须有明确的分支处理。如果用裸ReAct,大模型在“识别主体”之后可能直接跳到“规则判断”,跳过了“信息完整性校验”,产生误判尚可忍受,产生漏判就完全不可接受了。

所以下面这个实战例子,我用一个简化的“风控审核工作流”来演示Graph的核心用法:入口先做用户输入解析,把“被审核对象”提取出来;接着并行拉取“用户基本信息”和“历史违规记录”;再进“规则引擎”判断风险等级;最后根据风险等级走不同分支——高风险进人工复核,中低风险自动通过。

整个流程里,大模型只出现在两个位置:输入解析节点和人工复核辅助建议节点。其他全是确定性逻辑,这就是Graph在风控里最好的形态。

3.2 第一个里程碑:三个基础节点串联成功

先用最简单的三节点穿起来跑通骨架:解析输入 → 查询用户信息 → 输出结果。这个阶段不涉及任何条件路由,纯粹验证Graph基本API能不能转起来。

Graph graph = new Graph(); graph.addNode("parse", ParseNode::execute); graph.addNode("queryUser", QueryUserNode::execute); graph.addNode("output", OutputNode::execute); graph.setEntryPoint("parse"); graph.addEdge("parse", "queryUser"); graph.addEdge("queryUser", "output"); graph.setEndPoint("output");

这三个节点的代码都不复杂,核心是State的读取和写入。ParseNode从输入字符串里抽取出userId放进State;QueryUserNode根据userId调RPC查数据,把查到的userInfo放回State;OutputNode从State里组一段话术作为最终结果。这里有个细节必须提一下:每个节点写完State之后最好打印一行日志,否则后面节点多了,你根本不知道数据是哪个节点塞进去的,这个问题越到后期越致命。

跑通这个骨架之后,我再把代码改成Spring AI Alibaba Graph的Spring Boot风格配置,通过ApplicationRunner在启动时构建流程定义,每个Node注册成Bean,State用ConcurrentHashMap做实现。一定有读者问为什么不直接用并发安全的Map——我的回答是:企业流程里大概率会有多实例并发,State本身必须线程隔离,后面我专门说这个坑。

3.3 条件分支:让流程自己决定走哪条路

骨架通了之后,加分支。现在的需求是:查询完用户信息之后,判断该用户是不是VIP,VIP走“VIP专属处理”,非VIP走“普通处理”。这个判断我们用条件边实现,路由信息放在State的一个字段里。

graph.addConditionalEdge("queryUser", state -> { UserInfo user = (UserInfo) state.get("userInfo"); return user.isVip() ? "vipProcess" : "normalProcess"; }, List.of("vipProcess", "normalProcess"));

这段代码的运行效果是:queryUser节点结束之后,Graph会调用这个Lambda去判断该去哪个节点——没错,这个Lambda就是条件路由的“业务规则”,完全由我们写死,不经过大模型。这看起来是个小步骤,但价值非常大:它把流程中“决策”的逻辑从模型手里夺了回来。

这里我踩过一个坑:条件路由里返回的节点名必须跟addNode时的名字完全一致,大小写都不能错。一旦不一致,执行引擎会报“找不到目标节点”的错,而且报错信息里只会给出一个节点ID,不提示候选列表,定位起来非常浪费时间。所以我的习惯是:所有节点名定义一个常量类统一管理,绝不裸写字符串。

3.4 子图与并行:避免把图做成“意大利面条”

当流程节点超过10个之后,Graph定义会变得越来越长,把全部节点铺在一个平面上,维护起来非常痛苦。以风控举例,光是“规则引擎”内部就包含了命中条件组装、规则执行、结果解析三个子节点,如果平铺画出来,主线图和分支图交织在一起,阅读性极差。

Spring AI Alibaba Graph支持子图嵌套,这个设计非常像编程里的函数封装。我把“规则引擎”那三个节点包成一个子图,对外只暴露一个“ruleEngine”节点。这样在大图层面看起来,从“queryUser”到“ruleEngine”再到“judgeResult”只有三个节点,清晰明了;但子图内部又是完整的Graph执行逻辑。

并行这块我也一并说了。在风控场景里,“查基本信息”和“查历史记录”之间没有依赖关系,完全可以并行。Spring AI Alibaba Graph的并行实现跟常规JAVA并发没什么区别——状态对象各自构建、Future来聚合结果,最后把多个结果合并进同一个State。这里有一句忠告:并行节点的State写回必须非常小心。两个并行分支同时改State的同一个key,后写会覆盖先写,导致数据丢失。我的做法是:并行分支里绝不写同一个主键,每个分支写独立的key,汇合节点统一合并,这样才能彻底避开并发写冲突。

4. 从ReAct Agent到Graph Workflow的一次真实重构:工程师工单自动处理

4.1 业务背景:旧方案的三个核心痛点

这个案例是某公司内部的IT支持工单自动处理系统。最初的方案是纯ReAct Agent:大模型接到员工工单后,自行决定调“身份查询”“故障知识库”“工单系统”等工具,最后生成处理建议。模型很聪明,demo演示效果也好,但一上线问题就露出来了。

第一个痛点是查“身份”和查“工单”的接口可能各自返回超时,Agent会在没有完整上下文的情况下“聪明地”生成一个建议,后果是员工被建议去重启一台根本没有问题的服务器。第二个痛点是工单有严格的“服务等级协议”要求——高危故障必须在2小时内处理,而ReAct Agent不会主动给工单分级,它把所有问题一视同仁,结果高危工单经常被埋在普通工单后面。第三个痛点是排错过程完全不可控,产品同学要求我解释“这个建议是怎么来的”,我根本说不清大模型中间做了多少次工具调用。

这三个痛点本质上都源于“AI主导了整个流程”。而从ReAct迁到Graph之后,这三个问题迎刃而解。

4.2 Graph重构后的流程设计

重构后我画了一张图:入口节点接收工单文本 → “分类节点”用大模型给工单打标签(网络类/账号类/硬件类)→ “分级节点”根据标签和故障描述判断SLA等级(P0/P1/P2)→ “查询节点”并行拉取员工身份、设备资产、历史工单 → “知识库检索节点”按标签查相关解决方案 → “回复生成节点”汇总以上所有信息生成最终处理建议。

这个流程跟原来的ReAct Agent看起来很像,但本质区别在于:每一步都从“模型可能做”变成了“流程必须做”。分类错了,后面流程照走,但日志里能看到“分类节点输出错误标签”;查询失败,流程会走“重试→降级→转人工”的分支,而不是让模型自己编数据。

尤其是“分级”这一步,它完全是业务规则驱动的:工单标题里有“无法开机”“蓝屏”“数据丢失”这类词直接判为P0;普通网络问题判为P2。这一步根本不需要大模型,一个关键词匹配就能完成。但脱离Graph之后,它在ReAct模型里是一个“工具”,模型想用才用,不想用就跳过——这就是不可控的根源。

4.3 关键节点实现细节与难产点

整个重构里,最麻烦的节点是“分类节点”。之前我用ReAct的时候,分类和回复是同一个模型调用,天然带上下文。现在拆成独立节点后,这个节点就只负责输出一个JSON:{"category": "network", "sla": "P2"}。为了让结果稳定,我给它加了输出约束,直接把期望的JSON结构写进System Prompt里,并且用Spring AI Alibaba的JsonSchema方式定义输出结构。实测效果很好,输出格式非常稳定,但仍有极小概率输出非法JSON。我的兜底方案是:分类节点后面加一个“解析检查”子节点,解析失败就直接走默认分类,不让整个流程卡死。

“知识库检索节点”也算一个难产点。检索本身不难,难的是检索结果的截断——知识库经常返回几万字的长文本,如果全塞给最后一步生成节点,输入长度会被撑爆。我的处理是:在检索节点内部做了摘要,用另一条轻量模型调用把长文本压缩到500字以内,这样下游生成节点始终只面对规整、精简的上下文。

4.4 上线后遇到的问题:重试风暴和状态残留

重构后第一次上线就遇到“重试风暴”。当时“查询节点”调工单接口超时,Graph默认重试3次,3次失败后走“降级分支”查询本地缓存。但我在写重试逻辑时没有加退避间隔,相当于3次重试在几毫秒内全部打向一个已经过载的接口,直接把对方打得更慢了。后来在重试配置里加上了“指数退避+最大重试上限”,问题才缓解。

第二个问题是状态残留。当时为了图省事,State用的全局单例Map,结果A用户跑完流程之后留下的userInfo,被B用户的下一次流程读到了——这在开发环境几乎不可能发现,但在生产环境就是严重的数据泄露事故。解决方式是每次流程开始前new一个State对象,所有节点共享这一个实例,流程结束之后整个State随着请求一起销毁。这一点怎么强调都不为过:Spring AI Alibaba Graph的State生命周期必须跟着请求走,绝不能复用。

5. 从工程视角看这套编排方案的成熟度

5.1 开发体验:上手成本不高,但设计成本不低

从开发者的体感来说,Spring AI Alibaba Graph的上手成本不算高。基本Node/Edge/State的概念,半天就能跑通第一个Demo。但真正难的是“设计”:怎么拆节点拆得合适,怎么设计State字段,条件路由的粒度多大合适,什么时候抽子图,什么时候并行。这些能力不是啃文档啃出来的,而是在真实业务里踩出来的。

我的经验是:拆节点要“按失败域拆”。一个节点包含两种可能失败的操作,就应该拆成两个节点。比如“查询订单”和“计算订单金额”是两个失败域,前者可能网络超时,后者可能数据缺失,拆开之后可以各自定义重试策略和降级方案——这是Graph编排较之于单体Agent最大的工程优势。

5.2 可观测性:Graph带来的运维红利

用Graph之后,监控那块儿轻松了很多。以前ReAct的日志是一长串模型调用记录,很难判断流程走到哪了;现在只要在每个节点入口打印一行日志,就能完整还原那次请求的路径。我还给每个节点加了耗时统计和成功/失败计数,用Micrometer暴露成指标,接线到监控大盘之后,哪个节点慢、哪个节点失败率高,一目了然。

这里有个小技巧:在State里新增一个list字段,每次节点执行完就把节点名追加进去,这样日志里配合traceId,随时能完整回放某次请求走过哪些节点、每个节点消耗多久。在做事故复盘的时候,这份数据价值极高。

5.3 编排引擎之外:组件生态还需要补课

坦率说,Spring AI Alibaba Graph目前还谈不上成熟。跟LangGraph相比,它的算子类型还不够丰富,比如“Map-Reduce”“Send”这类高级节点还没有现成的抽象,遇到复杂的扇出聚合逻辑只能自己手写并行和合并。另外它对“人工介入”的支持也比较原始,企业流程里最常见的“挂起等待人工审批”这个操作,官方没有开箱即用的实现,需要自己用异步回调或轮询去模拟。

但这恰恰意味着动手能力强的人可以发挥。我在实际使用里自己封装了“人工审批节点”的原型:节点执行时生成审批任务ID,把流程状态持久化到数据库,同时挂起线程;另一边审批接口回调后,用审批结果唤醒挂起流程并继续执行后续节点。这个实现虽然粗糙,但已经能支撑真实的联调测试,而不再是被动地在ReAct里“让模型假装等了一下”。

5.4 踩坑清单:状态污染、循环引用、超时累积

最后把这些坑梳理成一张速查表,给准备上手的人排雷:

典型问题触发场景推荐解法
状态污染State被复用或并行分支写同keyState按请求创建,并行分支分key写,汇合节点合并
循环引用条件边的目标节点名写错节点名统一常量管理,启动时做拓扑检测
超时累积上游节点没事,下游节点超时每个节点单独设置超时和重试,加指数退避
节点间数据格式不统一上游存的是对象,下游按字符串读用POJO定义State,而不是到处用Map裸转
并行分支信息合并丢失两分支同时写同一字段合并阶段显式声明字段优先级,杜绝覆盖
LLM输出不可解析分类节点输出非法JSON输出用JsonSchema约束,后端加解析兜底

如果你正在构建类似的智能体系统,尤其涉及输出不可控的内容生成时,可以多参考输出解析与元数据提取的经验,这类问题的通用解决思路是一致的:在模型之外构建确定性的校验与容错层,而不是把所有希望寄托在大模型的自我修正上。

从我的实践里提炼出的最终建议

如果一定让我给一条最实用建议,那就是:先画图,再写代码。打开白板,把业务流程的每一步、每个分支、每个异常处理路径画出来,确认无误了再对照图去写Node和Edge。大多数人写Graph代码失控,不是因为写代码的能力不行,而是因为图的拓扑就没设计清楚。

另外,不要迷信“全流程AI化”。很多地方用规则、用状态机、用传统代码就够了,把它们留在图里做确定性节点,把真正需要语义理解的地方交给大模型,这样才能既保住AI的灵活性,又不牺牲企业流程的稳定性。这是我用了半年多Spring AI Alibaba Graph之后最大的心得。

这个方向后续还有很大的扩展空间——比如多级子图联动、人机协同审批、基于图拓扑的自动回滚。技术演进不会停,但把复杂流程拆成可控节点的思路,在很长一段时间内都不会过时。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询