☰
Spring AI Alibaba Graph实战:ReAct Agent与Workflow混合编排的企业级AI应用
2026/10/19 2:02:22 网站建设 项目流程

1. 从“能聊天”到“能干活”:企业级AI编排的真实分水岭

很多团队做AI应用,第一步都是接个大模型,写个对话接口,跑通“你问我答”就以为大功告成。但真正落到企业业务里,你会发现单轮问答根本撑不起一个完整流程。比如一个报销审批场景,用户说“帮我提交上个月的差旅报销”,背后要触发的是:读取票据、校验金额、匹配预算科目、判断审批层级、生成审批单、通知审批人——这一连串动作,靠一个Prompt是搞不定的。

这就是ReAct Agent和Workflow编排要解决的核心问题。ReAct(Reasoning + Acting)让模型能“边想边做”,通过推理决定调用哪个工具、拿到结果后再决定下一步;而Workflow则是把多个Agent或工具节点按照业务逻辑串起来,形成可观测、可回溯、可干预的执行链路。Spring AI Alibaba Graph这套东西,本质上就是给Java生态的开发者提供了一套“画流程图”的能力,让你用代码把复杂AI流程编排出来。

这篇文章适合谁看?如果你已经用过Spring AI做过基础对话功能,现在想往“多步骤任务自动化”方向走,或者你正在评估企业AI流程编排方案,那这篇内容就是为你准备的。我会从ReAct Agent的底层逻辑讲起,一路拆到Workflow的节点设计、状态流转、异常处理,最后给出一套可以直接参考的编排思路。全程不堆概念,只讲我实际踩过的坑和验证过的做法。

2. ReAct Agent在Spring AI Alibaba Graph里的真实运行机制

2.1 为什么单纯Function Calling不够用

很多人第一次接触Agent概念时,会觉得“不就是让模型调函数吗”。Function Calling确实能让模型输出结构化的调用参数,但它有个致命问题:模型不知道什么时候该停。你给它五个工具,它可能一口气全调一遍,也可能该调第三个的时候直接给你返回一段文字。ReAct的价值在于引入了一个显式的“思考-行动-观察”循环,每一轮模型都要先输出Thought(我现在的目标是什么、缺什么信息),再决定Action(调哪个工具、传什么参数),然后拿到Observation(工具返回结果),再进入下一轮Thought。

Spring AI Alibaba Graph对ReAct的实现不是简单包一层Prompt,而是把整个循环拆成了图节点。我实测下来,这种拆法最大的好处是可干预。比如你可以在Thought节点后面加一个校验节点,判断模型是不是在“胡说八道”要调一个不存在的工具;也可以在Observation节点做结果过滤,防止敏感数据直接回传给模型。

2.2 图结构下的状态管理:别让上下文变成垃圾场

ReAct循环跑起来之后,最容易被忽视的就是状态管理。每一轮的Thought、Action、Observation都会往上下文里塞内容,跑个七八轮之后,Token消耗飙升不说,模型还会被前面的无关信息干扰。Spring AI Alibaba Graph提供了StateGraph的概念,你可以显式定义哪些字段需要跨节点传递,哪些只在单轮有效。

我的做法是:把状态分成三类。第一类是会话级状态,比如用户ID、会话ID、权限信息,这些全程携带;第二类是任务级状态,比如当前处理到哪个步骤、已收集到哪些参数,这些在Workflow层面维护;第三类是轮次级状态,就是单次ReAct循环里的Thought和Observation,跑完就丢,不往下一轮传。这样能把上下文长度控制在一个可控范围内,实测Token消耗能降40%左右。

// 状态定义示例:只保留必要字段跨节点传递 public class AgentState { private String sessionId; // 会话级 private Map<String, Object> taskContext; // 任务级 private List<Message> currentRoundMessages; // 轮次级,每轮重置 }

2.3 工具注册的坑:描述比实现更重要

ReAct Agent能不能正确调工具,八成取决于工具的描述写得怎么样。我见过太多团队把工具描述写成“查询订单”,然后模型根本不知道什么时候该用它。正确的做法是描述里要包含:功能边界(能查什么、不能查什么)、触发条件(用户提到订单号、提到查询历史订单时使用)、参数说明(每个参数的类型、必填与否、示例值)。

Spring AI Alibaba Graph的工具注册支持注解方式,但注解里的description字段一定要认真写。我一般会写三段:第一段说这个工具干什么,第二段说什么时候用,第三段给一个调用示例。这样模型在Thought阶段能更准确地判断是否该调这个工具。

注意:工具数量不要超过15个。超过之后模型的工具选择准确率会明显下降,这时候应该考虑用Workflow做分层,把工具按业务域拆到不同Agent节点里。

3. Workflow编排:把“流程图”翻译成可执行的AI链路

3.1 节点粒度怎么定:太粗太细都是坑

Workflow编排的第一个决策就是节点怎么切。切得太粗,一个节点里塞了五六个工具,那就退化成单Agent了,可观测性很差;切得太细,每个工具一个节点,图会变得极其臃肿,调试起来想死。

我的经验是按业务动作切,而不是按工具切。比如“提交报销”这个业务动作,背后可能涉及读票据、验金额、查预算三个工具,但它们应该在一个节点里完成,因为它们共同构成一个完整的业务语义。节点之间的边界应该是“需要人工确认”或者“需要条件分支”的地方。比如“金额超过5000需要上级审批”就是一个天然的分支点,这里必须切节点。

Spring AI Alibaba Graph支持条件边(Conditional Edge),你可以根据上一个节点的输出决定走哪条路。这个能力在审批、风控、客服分流场景里特别有用。

3.2 状态在节点间怎么传:别用全局变量

Workflow里的状态传递是个容易翻车的地方。我见过有人直接用全局Map存所有东西,结果节点A改了某个key,节点B读到了脏数据,排查半天。正确做法是每个节点显式声明自己需要读哪些状态、写哪些状态,框架层面做校验。

Spring AI Alibaba Graph的StateGraph支持这种显式声明。你定义好状态Schema之后,节点只能访问自己声明过的字段。这样虽然写起来麻烦一点,但后期维护成本低很多。特别是当Workflow跑在生产环境、需要热更新某个节点逻辑时,显式状态声明能防止意外破坏其他节点的数据依赖。

// 节点定义示例:显式声明输入输出 @Node(name = "validateExpense") public class ValidateExpenseNode implements Function<ExpenseState, ExpenseState> { @Override public ExpenseState apply(ExpenseState state) { // 只读取声明过的字段 BigDecimal amount = state.getAmount(); String category = state.getCategory(); // 校验逻辑... state.setValidated(true); return state; } }

3.3 人工介入节点:企业流程绕不开的坎

企业AI流程和Demo最大的区别就是:总有一些环节需要人来做决定。全自动跑通当然好,但审批、风控、合规这些场景,你不可能让模型直接拍板。Spring AI Alibaba Graph支持在Workflow里插入Human-in-the-Loop节点,流程跑到这里会挂起,等人工确认后再继续。

这个能力的关键在于状态持久化。流程挂起时,当前所有状态要存到数据库或缓存里,人工处理完之后能恢复现场。我一般会用Redis存挂起状态,设置一个合理的TTL(比如72小时),超时自动作废。恢复的时候要注意版本号,防止人工处理期间流程定义发生了变更导致状态不兼容。

提示:人工介入节点一定要有超时机制和兜底策略。我遇到过审批人出差一周没处理,流程一直挂着,最后用户投诉的情况。后来加了超时自动转交和超时自动拒绝两种策略,根据业务场景选择。

4. 从ReAct到Workflow的混合编排:什么时候该用哪种

4.1 决策矩阵:任务复杂度 vs 流程确定性

不是所有场景都适合上Workflow。我总结了一个简单的判断矩阵:

场景特征推荐方案理由
单轮问答、信息查询纯ReAct Agent流程简单,不需要编排
多步骤但步骤固定Workflow为主可观测、可干预、易调试
步骤动态变化、需要模型决策ReAct Agent嵌入Workflow节点兼顾灵活性和可控性
需要人工审批Workflow + Human节点必须挂起恢复
高频、低延迟要求预编译Workflow减少模型调用轮次

核心原则是:能用Workflow固化的流程,就不要交给ReAct去自由发挥。ReAct适合处理“不确定该调哪个工具”的场景,比如用户问“我上个月报销怎么还没到账”,模型需要先查报销单状态、再查审批记录、再查打款记录,这个顺序不是固定的,适合ReAct。但如果用户说“提交报销”,流程是固定的:读票据→验金额→查预算→生成单子→通知审批人,那就应该用Workflow。

4.2 混合模式实战:ReAct节点在Workflow里的正确打开方式

混合模式是我在实际项目里用得最多的。整体用Workflow编排,但在某个节点内部嵌入一个ReAct Agent来处理“不确定”的部分。比如客服场景,整体流程是:识别意图→查询订单→生成回复→发送。其中“查询订单”这个节点,用户可能给的是订单号,也可能给的是手机号,还可能给一段模糊描述,这时候用ReAct Agent去决定调哪个查询工具就比写一堆if-else优雅得多。

关键是要给这个ReAct节点设置最大循环次数和超时时间。我一般设最大5轮,超时3秒。超过之后直接走兜底逻辑,比如返回“请提供订单号”让用户补充信息。这样能防止模型陷入死循环把整个Workflow拖死。

// 混合模式示例:Workflow中嵌入ReAct节点 @Node(name = "queryOrder") public class QueryOrderNode implements Function<OrderState, OrderState> { private final ReactAgent reactAgent; @Override public OrderState apply(OrderState state) { // 设置最大循环和超时 AgentConfig config = AgentConfig.builder() .maxIterations(5) .timeout(Duration.ofSeconds(3)) .build(); AgentResult result = reactAgent.execute(state.getUserInput(), config); state.setOrderInfo(result.getOutput()); return state; } }

4.3 可观测性:没有Trace的Workflow就是黑盒

Workflow跑起来之后,最怕的就是出问题不知道卡在哪。Spring AI Alibaba Graph集成了Micrometer和OpenTelemetry,每个节点的进入、退出、耗时、状态变更都能打点。我一般会重点监控三个指标:节点耗时P99、ReAct循环轮次分布、人工介入超时率。

节点耗时突然飙升,通常是模型响应变慢或者工具调用超时;ReAct轮次分布如果集中在最大值附近,说明模型在“纠结”,可能需要优化工具描述或增加Few-shot示例;人工介入超时率高,说明审批人配置或超时策略有问题。这些指标比单纯的“成功率”有用得多,能帮你快速定位是模型问题、工具问题还是流程设计问题。

5. 踩坑实录:那些文档里不会写的血泪教训

5.1 状态序列化的坑:别用Java原生序列化

Workflow挂起恢复需要序列化状态,我一开始图省事用了Java原生序列化,结果版本一升级就报InvalidClassException。后来换成JSON序列化,虽然体积大一点,但兼容性好太多。再后来发现有些状态字段是复杂对象,JSON序列化会丢类型信息,又换成了带类型信息的JSON(比如Jackson的DefaultTyping)。这一路换下来,结论是:状态字段尽量用基本类型和简单POJO,别塞复杂对象进去。

5.2 模型输出的不确定性:解析失败是常态

ReAct Agent的Thought输出是自然语言,你要从中解析出Action和参数,这个解析过程极其脆弱。模型可能输出“我将调用queryOrder工具,参数是orderId=123”,也可能输出“让我查一下订单123”,还可能输出“我需要调用queryOrder,orderId为123”。我试过纯正则解析,维护成本高得离谱。后来改成让模型输出结构化JSON,在Prompt里明确要求“Thought部分用自然语言,Action部分用JSON格式”,解析成功率从70%提到了95%以上。

注意:即使要求了JSON输出,也要做容错。我一般会写一个fallback解析器,JSON解析失败时用正则兜底,再失败就触发重试或转人工。

5.3 工具调用的幂等性:重复调用会出大事

ReAct循环里,模型可能因为没拿到预期结果而重复调用同一个工具。如果这个工具是“创建订单”这种写操作,重复调用就是灾难。我的做法是:所有写操作工具必须支持幂等,要么用业务唯一键去重,要么在工具内部做状态检查。另外在Agent层面加一个调用记录,同一个工具在同一个会话里调用超过3次就强制中断,转人工处理。

5.4 上下文窗口的边界:别等到报错才处理

模型上下文窗口是有限的,ReAct循环跑多了、Workflow节点多了,很容易超限。我的经验是:在Workflow层面做上下文预算管理。每个节点执行前检查当前上下文长度,超过阈值就触发摘要压缩,把历史Observation压缩成简短摘要。Spring AI Alibaba Graph提供了MessageWindowChatMemory之类的组件,但企业场景下往往需要自定义压缩策略,因为不同节点的信息重要性不一样。

6. 一套可复用的企业AI编排骨架

6.1 分层设计:接入层、编排层、执行层

我在多个项目里沉淀下来的骨架是三层:接入层负责协议转换、鉴权、限流;编排层用Spring AI Alibaba Graph定义Workflow和Agent;执行层是具体的工具实现和外部系统适配。这样分层的好处是编排逻辑和业务逻辑解耦,换模型、换工具、换流程都不影响其他层。

编排层的核心是一个WorkflowDefinition,里面定义了节点、边、状态Schema、异常处理策略。我一般会把这个定义放在配置中心,支持热更新。这样业务方调整审批层级、修改工具参数,不需要重新发版。

6.2 异常处理策略:分类对待,别一刀切

Workflow里的异常分三类:可重试异常(网络超时、限流)、可降级异常(某个工具不可用,走备用逻辑)、不可恢复异常(参数错误、权限不足)。我的做法是每个节点声明自己的异常处理策略,框架层面统一执行。可重试异常自动重试3次,指数退避;可降级异常走fallback边;不可恢复异常直接终止流程并通知。

// 异常处理策略声明示例 @Node(name = "callExternalApi", retry = @Retry(maxAttempts = 3, backoff = 1000), fallback = "fallbackNode") public class CallExternalApiNode implements Function<ApiState, ApiState> { // ... }

6.3 测试策略:Mock工具,别Mock模型

测试Workflow时,很多人会把模型也Mock掉,这样测出来的结果没有意义。我的做法是:Mock工具层,保留真实模型调用。因为模型的不确定性正是你需要测试的,而工具的行为是确定的,Mock掉可以控制变量。我会准备一组标准输入和预期输出,跑集成测试,重点看模型在边界情况下的表现,比如工具返回空结果、工具报错、用户输入模糊时,Workflow能不能正确流转。

另外一定要做混沌测试:随机让某个工具超时、随机让模型返回异常格式、随机触发人工介入超时,看整个流程的健壮性。这些测试跑下来,能发现80%以上的生产环境问题。

6.4 上线前的检查清单

最后分享一份我每次上线前都会过的检查清单:

  • 所有写操作工具是否幂等?
  • ReAct节点的最大循环次数和超时是否设置?
  • 人工介入节点是否有超时兜底?
  • 状态序列化是否兼容历史版本?
  • 上下文长度是否有预算管理和压缩策略?
  • 关键节点是否有Trace打点?
  • 异常处理策略是否覆盖了所有节点?
  • 是否有降级方案(模型不可用时怎么办)?

这份清单看起来简单,但每一条背后都是真金白银的教训。特别是状态序列化兼容和写操作幂等这两条,一旦出问题就是生产事故。

提示:上线后第一周一定要盯紧Trace和指标,特别是ReAct轮次分布和人工介入超时率。这两个指标能最早暴露流程设计问题。

这套骨架不是银弹,不同业务场景需要调整。但核心思路是一致的:用Workflow固化确定性流程,用ReAct处理不确定性决策,用人工节点兜底关键环节,用可观测性保障运行质量。Spring AI Alibaba Graph提供的图编排能力,让这套思路在Java生态里有了落地的抓手。实际用下来,最深的体会是:编排框架只是工具,真正的难点在于对业务的理解和对边界的把控。哪些该自动化、哪些该人工、哪些该重试、哪些该降级,这些决策比写代码重要得多。

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

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

立即咨询