接手几个企业AI落地项目后,我被问得最多的一个问题就是:ReAct Agent都已经能自动推理调工具了,为什么还要搞Workflow编排?这俩到底什么关系,Spring AI Alibaba里的Graph组件又是在解决什么问题?这篇文章就围绕这个核心矛盾展开,结合我在某模拟项目X里的完整落地过程,讲讲从ReAct Agent到Graph Workflow的思路转变、关键代码和那些文档里不会写的坑。
整个思路其实一句话可以概括:大模型的自由度要用对地方,企业流程需要的不是“万能Agent”,而是“该智能的地方智能、该确定的地方确定”的编排系统。这里的Spring AI Alibaba Graph,本质上是把LangGraph那套有向图编排思想搬进了Java生态,让咱们这些以Spring Boot为主的企业级开发团队,不用跨语言也能实现复杂的Agent工作流。如果你正在纠结到底是无脑堆Agent还是老老实实画流程图,这篇文章应该能给你一个明确的答案。
1. 从ReAct Agent到Workflow:先搞清楚你编排的是什么
1.1 为什么纯Agent模式在企业落地会卡壳
ReAct这个名字来自Reasoning + Acting,核心逻辑是让大模型在思考(Reasoning)和行动(Acting)之间循环:模型先分析当前任务需要什么信息,然后调用工具去获取,拿到结果后再思考下一步,直到任务完成。这个模式在单轮问答、知识检索、简单工具调用场景里表现非常出色,但它有个天然的问题——循环次数和中间决策完全由模型自己掌握。
我一开始接手某跨平台系统改造时,也跟大多数团队一样,直接把核心Service层替换成一个大Agent,把所有工具方法全部注册进去,让模型自己去编排。结果踩了一连串坑:模型经常在A工具和B工具之间来回试探,翻来覆去调用同一类接口;某些流程走到一半,模型突然“忘了”前面已经拿到过的关键参数,重新触发一轮检索;最头疼的是,整个调用链路过长,中间任何一次模型输出格式异常都会导致全线崩溃,排查问题像大海捞针。
这不是模型能力不够,而是企业流程本身的确定性需求与Agent的高自由度之间存在结构性冲突。业务流程要讲可预测、可审计、可回滚,Agent却天然带有随机性和不可解释性。你可以接受大模型帮你写一段摘要,但很难接受大模型替你决定“这笔退款到底走哪个审批分支”——至少在当前技术成熟度下不行。
1.2 ReAct与Workflow的本质差异
要理清这俩关系,得先明确一个容易被忽略的事实:ReAct Agent本身也可以看作一种特殊的Workflow,只不过它的“路由逻辑”由大模型脑内完成,每一步都可能是动态变化的。而Graph Workflow则把路由、分支、循环这些控制流显式地画成图结构,大模型只在需要它的节点上发挥作用。
打个比方。ReAct模式像一个新来的实习生,你告诉他“把这个客户投诉处理完”,他会自己决定先查订单、再查物流、再判断责任方、自己写处理方案——灵活,但你完全不知道他下一秒会干什么。Workflow模式则像一套标准作业程序:第一步查订单(这个步骤确定执行),第二步查物流(也确定),第三步让大模型分析责任方(决策点),第四步根据分析结果走不同分支(分支逻辑由代码控制)。整个流程的骨架是人定的,大模型的发挥空间被严格约束在“需要理解力”的那个节点里。
这带来两个关键能力:一是可干预性,你可以在任意两个节点之间插入人工审核、告警、数据校正逻辑;二是可观测性,每一步执行了什么、消耗了多少令牌、走到了哪个分支,全部有迹可循。
1.3 企业流程编排的拆分思路
所以正确的做法不是“二选一”,而是把复杂流程拆成两半:确定性流程用Workflow骨架固定,不确定性语义理解用Agent节点填充。这也是Spring AI Alibaba Graph设计思路的核心:提供一个通用的有向图执行引擎,把多个Agent、普通Java方法、条件判断、循环逻辑全部作为节点挂载进去。
我建议每个团队在动手写代码前,先干一件事:把要实现的业务画成一张最朴素的流程图,用方框表示步骤、菱形表示判断、箭头表示流转方向。然后逐个方框问自己:这一步需要大模型的理解能力吗?如果需要,就设计成Agent节点或者模型调用节点;如果不需要,直接用普通Java方法实现。最终你会得到一个非常清晰的目标结构:少量智能节点镶嵌在大量确定性节点的骨架里。
我在某图像处理Demo和某跨平台系统的重构中都是按这个套路来的,效果比一开始的“全Agent”方案稳定得多。接下来就进入Spring AI Alibaba Graph本身。
2. 深入理解Spring AI Alibaba Graph的执行模型
2.1 核心抽象:节点、边与状态
Spring AI Alibaba Graph的编程模型受LangGraph启发,核心元素就三个:Node(节点)、Edge(边)、State(状态)。Node是执行单元,Edge是流转规则,State是贯穿全程的共享数据容器。整个执行过程就是:从起始节点进入,按边定义的规则在节点间游走,每经过一个节点就更新State,直到到达终止节点。
每个节点本质上都是一个接收State并返回新State的处理器。你不需要继承什么复杂的基类,只需要实现节点处理逻辑。我在某跨平台系统里的第一步,就是把业务拆成五个节点:工单信息解析节点、历史工单检索节点、责任方判定节点、处理方案生成节点、结果归档节点。前两个和后两个都是确定性Java代码,只有中间两个需要模型能力,整个流程的控制流一目了然。
这里有个特别重要的设计:图的执行引擎只认State的流转,不认节点的执行顺序是否“聪明”。也就是说,你画出来的结构直接决定流程下限,模型再聪明也不可能突破你设定的骨架。这恰恰是企业场景最需要的特性——把关键流程的决策权收回到工程团队手里。
2.2 State机制:数据如何在节点间传递
State是Graph运行时在节点间传递数据的核心容器。在我的实践里,它就是一个可以承载任意业务字段的Map结构,Spring AI Alibaba Graph提供了几种字段更新策略(KeyStrategy),常见的有覆盖写入(REPLACE)、追加写入(APPEND)等。
这个机制的威力在于它天然支持跨节点的上下文共享:工单解析节点把原始文本写入state.raw_text,历史检索节点读取后写入retrieved_docs,责任方判定节点再读取这两个字段,推理出responsibility字段……整个链路的数据流不需要任何方法参数传递,全部通过State隐式流转。
但这也带来一个容易被新手忽略的坑:State字段名冲突与覆盖策略选择。如果两个节点往同一个字段写入不同含义的数据,并且你用的是覆盖策略,后执行的节点就会静默冲掉前一个节点的结果。我在后面的排查章节会专门讲这个坑。
2.3 条件路由与并行执行的底层逻辑
Graph的第二个关键能力是条件分支。你可以给节点之间的边绑定一个判断函数,执行引擎拿到State后先跑判断逻辑,根据返回结果决定下一步走向哪个节点。这就是把菱形判断从模型手里收回到代码手里的核心手段。
并行执行是另一个实用能力。Graph引擎支持将某个节点的多个下游节点并行启动,等所有分支都完成后,再把它们的输出合并回主流程。这个特性非常适合“同时查多个子系统信息再汇总”的场景。我第一次在某运营后台用并行节点同时查询订单、物流、商品三块数据,耗时从串行的三倍接口延迟压缩到单次最大延迟,效果非常直观。
值得注意的是,并行分支之间不要有共享字段的写依赖,否则会出现执行业务上的隐式竞态。遇到这种场景,我一般会把并行节点各自负责的字段划分清楚,最后再在汇总节点统一处理。
3. 实战:构建一个可落地的工单分类编排系统
3.1 业务场景设定:售后工单智能处理
我先交代一下我们要构建的目标系统。某电商平台售后工单的原始流程是:收到一条用户投诉文本 -> 人工判断问题类型 -> 查相关订单 -> 确定责任方 -> 生成回复方案 -> 归档。传统实现需要大量人工参与,前两步尤其费人力。我们要做的,是用Spring AI Alibaba Graph把这套流程编排成一条可观测、可干预的智能处理链路。
这个系统设计了六个节点:入口校验节点(EntryNode)、工单解析节点(AnalyzeNode)、订单查询节点(OrderQueryNode)、方案生成节点(SolutionNode)、质检节点(QualityCheckNode)和归档节点(ArchiveNode)。其中AnalyzNode和SolutionNode是大模型节点,其余都是确定性Java节点。节点之间的流转规则:入口校验失败直接进入ArchiveNode标记废弃;解析节点判断是否需要补单(比如文本缺少订单号),需要就走一个用户补录分支;订单查询完成进入方案生成;方案生成结果经过质检,质检通过才归档,不通过则打回方案生成节点重新生成,最多重试两次。
为什么这样设计?因为这套流程的核心不确定性集中在两处:一是从自由文本里抽取结构化信息,二是基于检索结果生成人性化回复。其余环节——查订单、判断字段完整性、控制重试次数——都是完全确定的,没道理交给模型赌博。这就是我前面反复强调的“少量智能节点镶嵌在确定性骨架里”的最佳体现。
3.2 关键代码实现解析
直接看关键实现。先定义一个状态类,Graph会把节点返回的状态合并到同一个Map容器里运行,为了类型安全我习惯用final字段包装。这一步的要点是:所有节点共享一个State流转,但每类数据要规划清晰的前缀命名空间。
public class WorkflowState { private String rawText; // 原始投诉文本 private String orderId; // 解析出的订单号 private List<OrderInfo> orderInfos; // 查询到的订单信息 private String responsibility; // 责任方判定 private String draftSolution; // 生成的方案草稿 private String finalSolution; // 质检后的终稿 private int retryCount; // 重试计数 }节点的实现本身不复杂,以入口校验节点为例:
public class EntryNode implements Node<WorkflowState> { @Override public WorkflowState apply(WorkflowState state) { if (state.getRawText() == null || state.getRawText().trim().isEmpty()) { state.setResponsibility("INVALID_INPUT"); } return state; } }真正体现Graph能力的是构建图结构和绑定条件路由的代码。这里用到Spring AI Alibaba Graph构建器,通过注册节点、编排边关系、绑定自定义Condition来完成整个流程编排:
Graph<WorkflowState> graph = GraphBuilder.<WorkflowState>builder() .addNode("entry", new EntryNode()) .addNode("analyze", new AnalyzeNode(chatClient)) .addNode("orderQuery", new OrderQueryNode(orderService)) .addNode("solution", new SolutionNode(chatClient)) .addNode("qualityCheck", new QualityCheckNode()) .addNode("archive", new ArchiveNode(archiveService)) // 起始流转 .addEdge("entry", "analyze") // 条件路由:文本缺少订单号则跳转补录分支,否则进入订单查询 .addConditionalEdge("analyze", state -> state.getOrderId() == null ? "missing_order" : "ready", Map.of("missing_order", "archive", "ready", "orderQuery")) .addEdge("orderQuery", "solution") // 质检结果条件路由:通过归档,不通过且未超重试上限则回炉 .addConditionalEdge("qualityCheck", state -> "pass".equals(state.getQualityResult()) ? "pass" : "retry", Map.of("pass", "archive", "retry", "solution")) .build();这段代码把整个控制流完整画出来了。addConditionalEdge的三个参数很有讲究:第一个是上游节点,第二个是一个State -> String的判定函数,第三个是分支结果到下游节点的映射表。我在映射表上踩过一次坑:分支结果字符串跟映射key必须严格一致,多一个空格都会导致运行时找不到下游节点。
3.3 大模型节点的实现细节与配置要点
责任方判定节点的实现是调用ChatClient,通过prompt约束模型输出JSON,然后解析出结构化字段。这里的关键不是怎么写prompt,而是如何保证模型输出能被代码稳定解析。
我一般会在prompt里做层层设防:先交代任务背景与输入字段,再严格指定输出格式模板,最后追加一句“只输出JSON,不要任何解释或Markdown代码块标记”。然后在代码里用容错解析工具解析,能拿完整JSON尽量拿,拿不到就通过正则抽取花括号内的子串。经过多次实操,我发现在最终方案里加入few-shot示例,比任何“要严格输出”的强调都管用。
public class AnalyzeNode implements Node<WorkflowState> { private final ChatClient chatClient; ... public WorkflowState apply(WorkflowState state) { String prompt = """ 你是工单分析助手。根据用户投诉文本,抽取订单号和问题类型。 输入:%s 只输出JSON:{"orderId": "字符串或null", "category": "string"} """.formatted(state.getRawText()); String response = chatClient.call(prompt); JsonNode json = parseJsonSafely(response); state.setOrderId(json.path("orderId").asText(null)); state.setCategory(json.path("category").asText("UNKNOWN")); return state; } }配置层面有几个要点:模型方面我选择支持函数调用和稳定JSON输出的型号,Context Window至少16K以上,因为工单文本偶尔会带大量上下文;温度参数默认是0到0.3之间,问题归类场景我认为直接用偏低温度即可;超时设置我习惯使用30秒,拆分大文本时用60秒兜底。
3.4 运行链路实测结果分析
把流程跑起来后,观察到的链路是这样的:入口节点校验数据完整性,工单解析节点抽取出订单号和问题分类,订单查询节点去订单服务拉取订单状态与商品信息,方案生成节点结合订单信息生成回复草稿,质检节点检查草稿是否包含“道歉、解释、处理方案”三要素,通过后归档。
整个过程中最费令牌的环节集中在方案生成节点,因为它需要把检索到的订单详情、历史沟通记录、商品信息全部拼进上下文。为了控制成本,我特意在订单查询节点里做了字段裁剪:只保留与售后处理相关的核心字段,比如订单状态、签收时间、商品名称,而不是把整个订单实体倒进上下文。
实测下来,一次完整工单处理耗时在8到15秒之间,链路里模型调用占了大头,但因为有Graph的分支控制,重试次数被限制在最多2次,单工单的令牌消耗基本是可控的。这套结构上线后,我最大的感受就是:问题定位变得极其舒服。流程跑到哪一步、哪个节点耗时最长、哪个分支被触发,都是一眼可见的。
4. ReAct vs Workflow:几个容易被忽略的选型参考点
4.1 成本与耗时的现实对比
我看到很多团队在选型时,只盯着“哪个效果更好”,却忽略了成本和耗时的结构性差异。ReAct模式下,模型需要自主思考多轮,每一轮都是完整请求,有的场景需要调用5到8次模型接口才能收敛。Workflow模式下,模型调用次数是设计期就决定的,比如上面这套工单系统,理想情况下就是两次模型请求。
这让两者的成本曲线完全不同。ReAct是动态成本,任务越复杂、模型发挥越不稳定,成本越容易失控;Workflow是固定成本预算,最坏情况就是重试次数乘以单次调用,上限是可计算的。我经历过一个严重案例:某ReAct Agent在处理一个多条件筛选任务时陷入循环,连续调用了20多次工具接口,账单数字让人肉疼。换成Graph编排后,同样的任务被限制在3次工具调用内,成本降低了一个数量级。
4.2 可观测性与调试体验对比
这一点是我最想让团队开发者重视的。ReAct Agent的每一次推理和行动虽然都能记录日志,但问题的核心在于:你很难回答“为什么它选择了这条路径”。因为路径选择本身是模型随机性的体现,你再怎么复盘,也不一定能复现。而Graph的每个节点、每条边和每个分支都是显式定义的,任何一步的流转逻辑都有确定的代码依据。
调试体验的差距也很大。Graph模式下,某个节点出问题,我直接看这个节点的输入输出State就能定位;ReAct模式下,我得把模型的多轮思考链全拉出来,还得猜测是模型“理解错了”还是工具本身报错了。我自己的项目里,引入Graph后故障平均定位时间从小时级缩减到分钟级,这一点都不夸张。
4.3 与现有业务代码的集成方式
企业里从来不是从零开始,Graph的另一个优势是能很好地嵌入已有技术栈。每个节点可以是纯Java方法,天然就是一笔微服务调用或数据库操作,不需要额外封装。如果你的企业已有业务流程引擎或状态机,也可以把Graph嵌入进去作为某个复杂子流程的编排器。
我把这套东西接入到某跨平台系统的过程就很有代表性:订单查询、库存扣减这些操作直接复用已有的Service方法,Agent只负责调用这些方法并组装返回结果。没有重写核心业务代码,只是在外部包了一层工作流编排。这一点对企业项目落地其实非常关键,意味着引入 Graph Workflow 的风险可控,不需要推翻重来。
5. 实战过程中的常见问题与排查经验
5.1 State字段覆盖与数据丢失问题
我在开发第一版Graph流程时,多次出现下游节点读不到上游数据的情况,最终定位到是State字段更新策略使用不当。由于每个节点都返回整个新的WorkflowState,如果只设置部分字段,其他字段就会被null覆盖,等于把前序节点的成果丢了。
解决方式是使用追加合并策略或字段级更新策略,让新返回的State只更新声明过的字段,而保留State容器中其他已有数据。这里要理解Spring AI Alibaba Graph的State更新机制:它不是简单用新对象替换旧对象,而是按照配置的KeyStrategy逐字段合并。建议所有跨节点共享的业务字段都显式声明更新策略,不要依赖默认行为。
另外还有一个隐蔽场景:并行分支各自往State里写同一字段时会产生互相覆盖。我后来把并行分支的产出字段做了严格隔离,用不同前缀命名,宁可汇总节点里多写几行合并逻辑,也不让两个分支写同一个键。
5.2 大模型节点输出不稳定引发死循环
有过一次印象很深的故障:质检节点返回不通过,流程按照条件路由重新进入方案生成节点,但方案生成节点连续多轮生成的结果都过不了质检,直接触发重试上限进入归档失败分支。当时第一反应是模型能力不行,实际排查后发现是prompt上下文里缺少上一轮生成结果的反馈信息。
也就是说,方案生成节点第二次生成时,并不清楚自己上一轮为什么被质检打回,它只是在“盲猜”。解决办法是在State里增加一个feedback字段,把质检不通过的原因写进去,方案生成节点的prompt里明确带上历史反馈。加上这一轮之后,重试的有效率大幅提升。这算是我在Graph编排里学到的最重要一课:工作流里的重试不能是无脑重试,必须携带失败原因,否则只是重复犯错。
5.3 超时、重试与熔断的工程化配置
Graph流程里嵌入了模型调用,就必须把工程化的防护机制做齐全。超时方面,我会在网络调用层区分连接超时和读取超时,模型接口一般需要更长的读取超时。重试方面,要区分两类重试:一类是网络抖动导致的失败重试,可以退避重试两到三次;另一类是业务校验不通过引发的流程重试,也就是上面说的重入节点,必须有次数上限。
我这里有一张统一整理的排查速查表,分享出来供你参考:
| 故障现象 | 可能原因 | 排查手段 | 解决方案 |
|---|---|---|---|
| 下游节点读到null | State字段被覆盖 | 打印全链路State快照 | 调整字段更新策略 |
| 条件路由不生效 | 分支Key不匹配 | 比对映射表与返回值 | 加断言校验返回字符串 |
| 流程进入死循环 | 重试节点上下文缺反馈 | 检查prompt输入拼接 | 增加反馈字段 |
| 并行节点数据错乱 | 共享字段写冲突 | 梳理字段归属 | 并行分支字段隔离 |
| 模型节点频繁超时 | 上下文过长 | 监控请求体大小 | 裁剪输入、压缩上下文 |
5.4 从单图到子图:企业级复杂流程的进阶编排
当你把一套Graph流程跑顺之后,自然会遇到更复杂的需求:某个主流程里嵌套了一段独立的子流程,甚至需要多个Agent协作。这时候直接往同一个图里堆节点会让图变得难以维护。Spring AI Alibaba Graph支持把一张子图作为一个节点嵌入另一张图,这使跨团队的流程封装成为可能。
我现在的做法是:每个业务域维护一张独立的子图,主图通过子图节点把它们组装起来。比如工单处理图的“订单查询”节点,在另一条产线里可以替换成一张“订单全链路追踪子图”,它内部自己管理多数据源查询、聚合和超时处理。主图不需要关心子图内部怎么实现,只需要定义好输入输出State。这种模块化能力对团队协作的意义非常大:每个团队只维护自己的图,节点契约(State字段)就是接口,互不干扰。
另外值得提的是人工审批节点的嵌入。纯自动化的AI流程在企业里很难直接上场,但Graph的条件路由天然支持接入人工审批:方案生成节点后加一个“等待人工审批”节点,审批结果通过才归档,不通过就打回重写。企业级AI流程编排的关键,往往不是模型多聪明,而是能不能把人工兜底、自动决策、业务规则无缝地编排在一起。
6. 踩坑之后的几点心得体会
从我个人的落地经验来看,Spring AI Alibaba Graph真正解决的问题,是把大模型的“聪明”关进了流程的“笼子”里。Graph Workflow 并不是要替代 ReAct Agent,而是在更高维度上管理 Agent 的执行边界。
在做这套工单编排系统时,我踩过不少坑,但回头看最值得庆幸的是坚持了“确定性优先”的原则:所有能用规则解决的步骤绝不让模型参与,模型只处理真正需要语义理解的部分。这套原则同样值得推荐给正在做企业AI流程编排的你。
如果说下一步还有什么可扩展的方向,我目前在做的是把可观测性数据接入到时延与令牌消耗监控中,这样每次工单流转结束,就能自动统计每个节点的成本和耗时。如果团队成员或需要协作,基于子图的方式非常合适,每个团队能独立维护自己的图节点。希望大家在动手做自己的Spring AI Alibaba Graph流程时,能避开我踩过的这些坑,把企业AI流程编排做得更扎实。