资讯动态

从ReAct到Workflow:Spring AI Alibaba企业级AI编排实战

发布时间:2026/10/13 5:37:57 来源:尧图企业网站定制
最近大半年我一直在折腾企业级AI应用感触最深的一件事就是单体Agent看着很美一旦进入真实业务流程立刻变成一团乱麻。尤其是用到Spring AI Alibaba这套技术栈时从ReAct Agent到Workflow不只是一条技术演进路线更是被业务逼着走出来的必经之路。今天我就把这几个月踩过的坑、重构过的架构、以及Spring AI Alibaba Graph的核心用法一次性说清楚。这篇内容适合两类人一类是正在用LLM做企业应用但发现单靠Agent提示词撑不住复杂业务的Java开发者另一类是团队里负责AI系统架构需要给老板解釋为什么流程必须编排的技术负责人。我会从ReAct的局限讲起再到Graph的建模思路最后给一个可以照抄的实战改造案例保证你看完能直接上手评估自己手里的项目。1. 为什么单体Agent撑不起企业流程三个让我下定决心改造的信号1.1 第一堵墙推理能力越强流程越不可控最初我们的设想很简单把业务逻辑全部塞进System Prompt让模型自己推理行动。以客服工单处理为例Prompt里写好你有工具A查订单、工具B发退款、工具C通知用户然后期望模型自动完成整个闭环。ReAct模式确实灵活模型会先思考用户的问题是订单未到然后决定调用工具A查物流再根据结果决定是否退款。这套机制在Demo阶段跑得飞快但走到生产环境就露馅了。问题出在决策自由度上。你给模型的自由度越高它就越可能走出你意料之外的路径。比如模型在一次退款失败后居然自动尝试了三次不同的工具参数每次都是再试一次。三次调用就是三倍的token成本而且每次都写进了审计日志。更麻烦的是某次线上事故中模型在查询订单状态时误读了返回字段直接判断为已退款后续所有流程全部基于错误前提执行。这不是模型笨而是ReAct的设计哲学本身就是开放的、探索式的。它适合开放域问答但企业核心流程要求的是确定性这一步骤执行什么、什么条件下走哪条分支、失败后做什么补偿全部要有明确规则。把这种确定性建立在模型每次都能想对上面实在太脆弱了。1.2 第二堵墙工具调用链断了恢复只能靠重跑一遍单体Agent的另一个致命问题是中间环节一旦失败整个链路都得从头再来。有一次我们的工单流程是先调用库存系统锁定商品再调用CRM创建客户记录最后调用通知服务发送确认邮件。结果第三步通知服务超时了整个Agent任务报错。按当时的处理方式我们只能手动把整个工单重新提交一遍。问题来了重新提交后库存系统再次执行锁仓导致同一位客户被锁定两次CRM也创建了重复客户记录。因为Agent作为一个整体执行它没有一个断点续跑的机制失败了就整体回滚或整体重试这在企业场景下是不可接受的。我后来跟某团队聊过他们甚至在最外层套了大任务重试机制——只要Agent报错就整单重来。听起来很省事但遇到那种第三步写库成功、第四步通知失败的情况重来就会产生脏数据。真正能救命的做法是把流程拆成独立节点每个节点有单独的重试和补偿逻辑。在单体架构里这个目标基本没法实现。1.3 第三堵墙黑盒流程让测试与协作寸步难行第三个信号其实来自QA同事的吐槽。单体Agent拿去做测试测试人员根本不知道该怎么设计用例。你说测一下退款失败的场景测试用例写了一大堆Prompt变体结果模型有时候触发成功路径、有时候触发失败路径同样的输入跑三次三次结果都不一样。这种不确定性直接拖垮了开发和测试的协作效率。开发人员没法对某个独立功能做单元测试因为所有逻辑都揉在同一个Agent循环里。跨团队协作也是重灾区运营说退款通知文案我要改一下开发得从一大段Prompt里捞出来改改完还要担心会不会影响其他行为。一把梭的Agent适合做一个人能包圆的小事但企业的复杂流程天生是多角色、多系统、多阶段的协作问题。流程必须先被拆解、再被编排才能被测试、被维护。这就是我转向Graph编排的根本原因。2. 先搞懂ReAct再谈替换它的内功与适用边界2.1 ReAct的循环机制复盘ReAct的核心可以浓缩成一个循环Reasoning思考→ Acting行动→ Observing观察→ 再思考。模型先根据当前状态判断我该调哪个工具执行工具后把返回结果接回来再判断下一步。这个循环的真正优势是自适应你不需要提前罗列所有可能的路径模型会自己绕路、自己换工具、自己尝试新策略。比如用户问为什么我的积分没到账模型可以先查订单、再查积分日志、再查活动规则全程不需要开发者预定义路径。但也有代价而且代价相当隐蔽Token开销不可控每多一轮思考-行动-观察就多一组完整PromptCompletion。复杂任务跑上七八轮成本是预设流程的好几倍。日志与可审计性差模型为什么选择这条路径调用了哪个工具拿到什么结果虽然有trace但很难转换成标准的流程审计格式。故障排查困难一个环节返回异常数据模型可能会带着错误信息继续循环好几轮问题被滚雪球式放大。我在内部培训时常打一个比方ReAct像一个想法很多但做事随缘的新人Graph则是一套每个工位都画清楚操作手册的流水线。新人适合处理未知问题流水线适合稳定交付已知问题。企业的大部分核心流程其实是已知问题没必要让新人临场发挥。2.2 什么时候保留ReAct什么时候上Graph经过几次重构我总结了下面这张决策表可以直接拿去对照判断维度保留ReAct改上Graph流程步骤步骤高度不确定需探索式解决步骤基本固定只有分支变化工具依赖只用1~2个工具单轮可解决需要4个以上工具协作失败成本失败重试无副作用失败可能产生脏数据、重复调用规范性要求无需审计可接受随机策略每一步需要记录、可复盘延迟容忍可接受长耗时思考需要稳定延迟关键步骤有SLA典型场景开放问答、方案生成、创意辅助工单处理、交易流程、风控审核、审批流转判断标准很简单如果一项任务你闭上眼睛能画出流程图那它就不该用ReAct硬扛。反之如果你自己也说不清楚下一步可能是什么那Graph帮不了你还是ReAct或纯LLM问答更合适。当然这两者不是互斥的。我目前用的最多的是Graph骨架 ReAct局部的组合整体流程用Graph控制走向遇到需要开放思考的环节比如给用户生成个性化退款方案Graph里嵌套一个Agent节点让模型在局部自由发挥。这样既保留灵活性又锁住了主流程的确定性。3. Spring AI Alibaba Graph核心建模把流程图写进代码3.1 核心概念炼一遍State、Node、Edge、ConditionSpring AI Alibaba Graph是对整个编排思想的Java化落地。它借鉴了图计算的核心概念但用Spring的风格重写了一遍Java后端接上手感很顺。先把这个框架最核心的四个概念讲透State状态贯穿整个流程的数据载体。相当于一条流水线上所有工位共用的文件夹每个节点都能读写。在实际代码里它通常是一个结构化的对象你定义好字段流程执行中不断被更新。Node节点流程中的一个独立工作单元。可以是一个同步方法调用也可以是一个异步任务。节点拿到State处理完把结果写回State。Edge边描述节点之间的连接关系A执行完走到B就是一条边。Condition条件边上可以挂条件比如判断结果是否合法合法走A不合法走B。这是Graph能实现业务流程分支的关键机制。这四个概念组合下来一次流程执行就是一次带状态、带方向的图遍历。你写Graph的时候本质上是在用一种可执行的方式画业务流程图。3.2 从流程图到代码的映射关系很多同学第一次接触Graph最大的困惑是我脑子里有流程图但不知道代码怎么组织。我分享一个百试不爽的建模方式先画图再提炼节点最后写代码。比如一个简单的订单审核流程接收订单 → 风控检查 → 人工审核 → 返回结果。翻译成Graph就是四个节点和三条边。代码结构大致是这样以当前版本的常规API示意具体类名以官方文档为准GraphState state new GraphState(); state.setOrderId(ORD-20240518-001); Graph graph new GraphBuilder() .state(state) .node(receiveOrder, new ActionNode(ctx - receiveOrder(ctx))) .node(riskCheck, new ActionNode(ctx - riskCheck(ctx))) .node(manualReview, new ActionNode(ctx - manualReview(ctx))) .node(finish, new ActionNode(ctx - finish(ctx))) .edge(receiveOrder, riskCheck) .edge(riskCheck, manualReview) .edge(manualReview, finish) .build(); GraphResult result graph.execute();你看这段代码跟流程图几乎一一对应。每个node后面的Lambda就是一个可独立测试的节点函数任意节点出问题你都能单独拎出来排查。这就是Graph第一眼的价值可读性。流程长什么样代码就长什么样。3.3 状态设计是Graph成败的一半节点和边是Graph的骨架State才是Graph的灵魂。我在多个项目里发现流程跑出问题十有八九是State设计不合理。先说原则State应该是只增不改的演进式设计。不要把State设计成每个节点随手往里面塞字段的万能Map那样很快你会分不清某个字段是哪个节点、在哪个阶段写入的。我在工程里习惯把所有状态字段包在一个上下文对象里每个节点命名规范统一比如inputOrderId、riskResult、manualReviewOpinion一眼就能看出这个字段由谁产出。同时State里不要存放大数据体比如长文本内容、文件流尽量只放引用比如文件路径或存储ID避免流程流转时把JVM内存撑爆。另外要注意State的线程安全设计。Graph本身支持并行节点这个后面细说如果多个分支节点同时写State里的同一个字段就会出现竞争问题。我的方案是并行分支之间尽量保证各写各的字段需要合并时才在汇聚节点统一处理。这一点在建模初期就要想清楚。4. 实战拆解把客服工单处理从ReAct重构成Graph4.1 原始ReAct实现长什么样痛点在哪我们团队当时最典型的案例是客服工单自动处理。用户提交工单系统判断问题类型、查订单、决定是否退款/补发/转人工最后通知用户。最初用ReAct实现核心逻辑就是给模型配置了一堆工具和方法。第一版跑起来效果还行简单工单能自动处理掉但生产环境一压就出问题。最典型的几次事故模型在判断是否退款时没有参考公司最新的售后政策自作主张给了一个不符合规定的退款金额。用户在工单里说我不要退款我要补发模型仍然执行了退款因为它只看到了历史订单中的退款记录没有真正理解用户意图。整个流程对耗时没有预期有的工单跑了两分钟还没结束用户已经等得不耐烦了。分析下来核心问题就是第1节讲的ReAct的自由度不适合用在带规则的业务决策上。退款金额的确定、补发还是退款这些本来就是确定的业务规则应该由代码判断而不是让模型临场发挥。4.2 图编排的目标流程六个节点的状态机我们重构后的Graph流程定了六个节点走一遍工单接收节点拉取工单基础信息存入State生成处理流水号。问题分类节点用LLM做一次文本分类工单内容 → 问题类型这一步保持模型推理但只输出结构化结果。规则判定节点纯代码逻辑根据问题类型和用户信息走分支退款类 → 走退款处理补发类 → 走补发处理其他 → 转人工退款处理节点 / 补发处理节点调用对应业务系统做幂等校验、写操作日志。通知节点调用通知服务给用户发送处理结果。结束节点整理审计日志关闭工单。关键的分支逻辑放在第3步用代码判断而不是让模型选择。LLM在整个流程中的角色只剩下文本分类和话术生成这两个真正需要语言能力的环节。4.3 完整改造步骤与关键代码改造步骤我按下面这个顺序推进每一步都验证过再走下一步第一步定义State结构。public class WorkOrderState extends GraphState { private String workOrderId; private String userMessage; private String problemType; // 问题分类结果 private String actionType; // refund / resend / manual private BigDecimal refundAmount; private String noticeMessage; // getter / setter 省略 }第二步定义节点函数。分类节点保留LLM但输出约束成枚举node(classify, ctx - { WorkOrderState state ctx.getState(); String type llmService.classify(state.getUserMessage()); state.setProblemType(type); return type; })第三步用条件边定义分支。.edge(classify, ruleJudge) .condition(ruleJudge, refundHandler, state - refund.equals(state.getActionType())) .condition(ruleJudge, resendHandler, state - resend.equals(state.getActionType())) .condition(ruleJudge, manualHandler, state - manual.equals(state.getActionType()))这段代码的意图非常直白分类完成后进入规则判定根据actionType的值走三个不同的下游节点。测试的时候你只需要单测ruleJudge这个函数返回不同枚举时边的走向对不对不用关心模型怎么想。第四步执行流程。GraphResult result graph.execute(); WorkOrderState finalState result.getState(); // finalState里可以拿到最终的noticeMessage、refundAmount等4.4 对比重构前后的硬数据改造完上线跑了一个月我把两组数据拉出来对比指标ReAct版Graph版平均处理耗时65秒18秒Token消耗/工单约9.8k约2.3k自动处理成功率61%89%规则类错误违规退款等每月3~5次0次故障恢复方式整单重跑节点级重试我最满意的是第二行Token成本直接降了四分之三。原因是Graph把大量不需要语言能力的步骤规则判断、状态流转、幂等校验从模型手中拿走了模型只剩分类和话术生成两个轻量调用。省下来的不只是钱还有处理延迟和出错率。注意重构不是彻底抛弃LLM而是把LLM放到它擅长的位置。分类、总结、话术生成这些语言理解任务交给模型参数校验、规则判定、事务处理这些确定性任务交回代码。Graph的核心价值之一正是帮你完成这种人机分工。5. 复杂流程里躲不开的分支、并行与循环真实场景怎么设计5.1 确定性分支用规则替LLM做判断题第4节的工单案例里已经用了条件边做分支这里再深入说一下设计理念。企业流程里存在大量判断题比如用户欠费吗余额充足吗商品在售后期内吗之前ReAct版把这些判断题交给了模型让它读完数据自行判断。现在我用Graph的条件边把判断题全部改成代码表达式——因为这根本不是语言问题是逻辑问题。条件边的判定函数就是普通的Java Lambda写起来非常顺手.condition(checkBalance, paymentHandler, state - state.getBalance().compareTo(state.getAmount()) 0) .condition(checkBalance, insufficientHandler, state - state.getBalance().compareTo(state.getAmount()) 0)这里有个经验条件边覆盖的分支一定要配一个兜底分支。比如判断余额充足走支付、不充足走提示但万一金额字段为null呢加了兜底分支后任何异常输入都会走到一个明确节点而不是让流程戛然而止。没有兜底的Graph等于没有异常处理的代码。5.2 并行节点同时跑多个独立子任务后如何汇总真实业务里最常遇到的另一个需求是并行。比如风控审核时需要同时查黑名单、查历史违约记录、查关联交易三个查询互相独立串行跑要30秒并行跑只要10秒。Spring AI Alibaba Graph支持并行节点多个节点可以挂在同一个上游节点下面同时执行。我的用法是.edge(riskInit, blacklistCheck) .edge(riskInit, historyCheck) .edge(riskInit, relationCheck)这三个check节点并发执行全部跑完后汇聚到下一个节点。汇聚节点的职责是读取三个分支各自写入State的字段合并出最终风控结论。并行设计里最容易踩的坑我在3.3节已经提过——State的字段冲突。三个分支同时写riskResult字段后写的会覆盖先写的结果就是数据丢失。我在项目里的方案是给每个分支定义独立的字段blacklistResult、historyResult、relationResult然后再有专门的汇总节点把它们聚合成一个finalRiskResult。这个设计虽然看起来多此一举但它保证了并行安全和可追踪性。5.3 循环与重试的边界别让流程陷入死循环Graph天然支持循环比如未审核通过则返回重新处理但循环也是最容易失控的地方。我见过一个悲剧某个审批流程做了未通过自动退回的逻辑结果人工审核没配置退出条件工单在审核→退回→重新提交→审核之间无限循环把审批系统接口打到熔断。给循环加边界我有三条铁律每次循环必须更新关键计数State里放一个loopCount字段每经过一遍循环节点就1。循环出口永远有上限比如最多重试3次超过直接转人工或结束。重试逻辑要考虑幂等重试前检查这一步是否已经执行过、执行结果是否已落库避免重复扣款、重复发单。.condition(reviewNode, retryNode, state - !state.isApproved() state.getLoopCount() 3) .condition(reviewNode, manualIntervention, state - !state.isApproved() state.getLoopCount() 3)循环不是免费午餐它本质上是把系统不确定性转嫁给业务流程。能用一次判断解决的就别用循环必须循环时把循环体和退出条件设计得越窄越好。6. 企业落地时绕不开的工程化问题可观测性、版本与成本6.1 每一步都有迹可循节点级链路追踪从单体Agent换成Graph之后最大的工程红利其实是可观测性。以前排查Agent问题时只能看一大坨Prompt日志和工具调用记录现在每个节点天然是一个可追踪的步骤定位问题简单太多。我在工程里给每个节点加了一层统一的日志包装节点进入时记录一条日志带上工单号、节点名、时间戳节点结束时记录结果摘要和耗时。这样从日志平台随便搜一个工单号就能把整个流程的每一步时间线拉出来。调试Graph脚本时这一步的价值极大。某次线上工单在退款处理节点失败日志显示是上游传给它的refundAmount格式异常。我顺着状态快照一看发现是分类节点把金额字段误写成了字符串类型。以前这种问题在单体Agent里要翻半天对话记录现在一眼定位到节点边界。6.2 超时与熔断不只是加一个try-catch企业AI流程跟外部系统打交道超时是常态。Graph里的每个Node都应该有独立的超时控制和失败策略不能寄希望于最外层的一个大try-catch。我在项目里给每个节点配置了两层兜底超时控制节点执行超过指定阈值就中断走失败分支。降级策略失败后不是直接结束而是降级到简化版本。比如LLM分类节点超时降级成规则关键字匹配通知服务不可用降级成短信模板发送而不是卡住整个流程。这里有个容易被忽略的点LLM调用的失败模式跟普通RPC不一样。普通接口超时是秒级的LLM的响应时间可能从3秒到60秒剧烈波动。如果给LLM节点设置的超时太短会频繁误杀正常请求太长又容易拖垮整体SLA。我建议先跑一段压测统计P50和P99延迟再分别设置正常超时和熔断阈值中间留出缓冲。6.3 图的版本演进如何让新旧流程平滑过渡企业流程不是上线就完了政策一变流程就要改。Graph的优势在版本管理上体现得很明显——整个流程是一个明确定义的产物你可以给每条边、每个节点打版本号。我的做法是同一条业务线维护多个Graph版本线上流量先切小部分到新版本观测指标无异常再全量切换。比如退款流程从v2调整成v3先让5%的工单走v3对比自动处理成功率、客服介入率、客诉率达标后再逐步放量。Spring AI Alibaba Graph的State设计也天然适合版本演进新版本可以往State里加字段但如果要删字段或改字段语义就要特别注意兼容性——旧版本流程可能还在跑它依赖的字段不能被新版本随意改动。我们团队的习惯是State字段一旦上线就不改语义确实要改就新增字段并存续一整个发布周期。6.4 成本账本多少个LLM调用每一美元花在哪最后聊一个老板最爱问的问题成本。单体Agent最难控成本因为它每个决策步骤都要调用模型Graph则可以精确控制哪里该用模型、哪里不该用。以一个完整工单处理流程为例我们可以这样分配流程步骤是否调用LLM成本策略工单接收否纯代码零成本问题分类是用小模型/低档位模型规则判定否纯代码零成本客服话术生成是按用户情绪选择档位审计日志否纯代码零成本流程建模完成后我建议顺手做一张这样的成本清单它能帮你发现两个问题哪些步骤的LLM调用其实是可以砍掉的哪些步骤的小模型换成大模型能带来明显的准确率提升。另外Shared的State数据流也能做缓存复用——比如同一用户短时间内提交多个相似工单分类结果可以直接命中缓存省掉重复调用。写在最后编排的本质是把不确定性装进笼子Graph这几个月用下来我最大的体会是编排不是把简单事情变复杂而是把复杂事情变可控。ReAct给了AI极大的自由度让它像人一样临场思考但企业的核心流程要的不是像人一样思考而是像机构一样规范——每个动作有依据、每个分支有规则、每个失败有补偿。Graph恰好提供了这样一套把流程固化成代码的容器。如果你手头的项目还在用单体Agent扛核心业务我建议你按第2.2节的表格对号入座一下。只要有一项命中上Graph那一列就不妨先拿一个最小流程做试点。我个人实际操作下来改造收益最快见效的往往不是延迟而是规则类错误清零和排查问题的时间从小时级降到分钟级这两件事。先跑通一个流程再把经验复制到其他场景这条路比一上来重构全部系统稳妥得多。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑