使用Spring AI Alibaba构建基于图的工作流
[LOADING...]
1. 概述
在构建 AI 智能体时,通常只向 LLM 发出一个提示是不够的。解决复杂任务需要多个步骤,其中一步的输出决定下一步会发生什么。然而,手动实现并维护这种编排逻辑很困难。
在本教程中,我们将使用 Spring AI Alibaba 来解决这个问题。我们将用它把一个智能体工作流建模为步骤图,并在运行时自行决定路径。
2. 基于图的工作流入门
基于图的工作流将复杂任务拆分为更小的工作单元,并把它们连接成图。无须硬编码执行顺序,我们只需描述可能的路径。工作流会根据自身持有的数据沿着合适的路径执行。
这类工作流包含三个主要概念:
- 状态:在工作流中流动的共享数据。
- 节点:图中的单个工作单元。它们通常会读取状态、执行业务逻辑,并在最后更新状态。
- 边:节点之间的连接。它们通过决定下一个要移动到哪个节点来控制执行顺序。
为了实际了解这些概念,我们将构建一个借口升级工作流。首先,一个员工节点会为工作中给定的情况编造借口。然后,一个经理节点会评价该借口的可信程度。
接着,一条边会检查经理的评分。如果经理相信这个借口,工作流就结束。否则,流程会循环回员工节点,由员工对原借口进行完善。
3. 配置 LLM
现在,让我们设置项目。首先向 pom.xml 文件中添加必要的依赖:
这里,我们首先添加 Spring AI 的 OpenAI starter 依赖,用于与 LLM 交互。接着添加 Spring AI Alibaba graph core 依赖。它提供了定义基于图的工作流所需的类。
为了避免这些依赖之间的版本冲突和兼容性问题,我们还要引入 Spring AI 物料清单(BOM):
接下来,在 application.yaml 文件中配置 OpenAI API 密钥 和 聊天模型:
这里,我们使用 gpt-5.6-luna 模型 ID 指定 OpenAI 的 GPT-5.6 Luna 模型。或者,我们也可以使用其他聊天模型,因为具体使用哪个 AI 模型或提供商对本演示并不重要。
设置这两个属性后,Spring AI 会自动创建一个 ChatClient.Builder bean。我们将在节点中使用它与指定的模型交互。
4. 定义状态键
工作流的状态是一组键值对。在节点读取或写入它们之前,我们需要声明这些键以及相应策略。这些策略控制图如何更新某个值。
让我们在 KeyStrategyFactory bean 中定义这些键:
这里,situation 键保存工作流的初始输入。其余键表示节点将产生的值。
对其中大多数键,我们使用 ReplaceStrategy,它会用新值覆盖现有值。不过,对于 managerReplies 键,我们使用 AppendStrategy,它会将每个新值添加到一个列表中。
5. 实现节点
定义好状态键后,让我们构建图的节点。要创建一个节点,我们实现 NodeAction 接口及其 apply() 方法。该方法会以 OverAllState 实例的形式接收当前状态。然后,在执行完业务逻辑后,我们返回一个 Map,其中包含我们想对状态进行的更新。
5.1. 编造借口
首先,创建扮演员工角色的节点:
在这个新类中,我们在提示模板里定义 LLM 指令。此外,我们注入 Spring AI 自动配置的 ChatClient.Builder bean,并用它构建 ChatClient 实例。接下来,实现 apply() 方法:
这里,我们使用 value() 方法读取当前状态。我们为这些键定义了默认值,situation 键除外。相反,我们把它视为启动工作流的必需输入。
然后,我们将这些值填入提示模板,调用 LLM,并通过 content() 方法获取 excuse。最后,用新借口以及递增后的尝试次数更新状态。
5.2. 以经理身份回应
接下来,我们需要一个经理节点来评价员工的借口。让我们创建新的 ManagerReactsNode 类并实现 apply() 方法:
与之前的节点类似,我们读取当前状态,填充提示模板,并调用 LLM。不过,这里不调用 content() 方法,而是调用 entity() 方法来获得 结构化输出。最后,我们将 reply 和 believability 分数作为状态更新返回。
6. 创建条件边
现在,我们需要一种方式来连接已实现的节点。此外,还需要编写逻辑来决定工作流应当结束,还是让员工再试一次。为此,我们将通过实现 EdgeAction 接口来创建一条条件边:
这里,我们从状态中读取 believability 和 attempts 键。如果经理的评分达到我们配置的阈值,就返回 stop 标签。否则,返回 escalate 标签,给员工另一次机会。
在第一个条件中,我们还会检查员工是否已用完最大尝试次数。这对于避免工作流出现无限循环很重要。
7. 组装图
所有构建模块都已就绪,现在把它们连接起来,并暴露一个 CompiledGraph bean:
首先,我们创建一个 StateGraph 实例,并将 KeyStrategyFactory bean 传给它。这有助于图处理每个状态键的更新。接着,我们使用 addNode() 方法注册两个节点,并为它们分别指定唯一名称。
然后,我们使用 addEdge() 方法定义流程。我们将 START 常量连接到 invent_excuse 节点,把它标记为入口点。从那里,再将 invent_excuse 连接到 manager_reacts 节点。
之后,我们使用 addConditionalEdges() 方法将 excuseDispatcher 附加到 manager_reacts 节点。这里传入的 map 将 dispatcher 返回的标签与其目标节点关联起来。escalate 标签会循环回 invent_excuse 节点。与此同时,stop 标签会转到 END 常量,从而结束工作流。
8. 测试工作流
现在我们已经组装好图,来看看工作流在不同情况下的表现。
8.1. 暴露 REST API
首先,暴露一个 API 端点,让我们可以启动工作流:
这里,我们在注入的 CompiledGraph bean 上调用 invoke() 方法。我们将请求中的 situation 作为初始状态传入。此外,还传入一个带有随机 threadId 的 RunnableConfig 实例。这有助于将并发请求的执行彼此隔离。
invoke() 方法从开始到结束运行整个图,并返回最终状态。然后,我们只需从这个状态中读取值,并用它们准备 API 响应。
8.2. 调用 API 端点
现在,让我们使用 HTTPie 命令行工具调用 API 端点:
这里,我们输入一个平淡无奇的情况。看看会得到什么响应:
如我们所见,经理相信了第一个借口,因此工作流在一次尝试后就结束了。因此,managerReplies 列表只包含一条回复,因为循环没有再运行。
接下来,让我们尝试一个更难解释的情况,并看看 API 响应:
这里,经理不相信任何一个借口。因此,工作流会一直循环,直到达到最大尝试次数。## 9. 结论
在本文中,我们使用 Spring AI Alibaba 实现了一个基于图的智能体工作流。
我们首先定义了状态键及其策略。然后,我们实现了两个由 LLM 驱动的节点,以及一个条件边,用于决定工作流是继续循环还是停止。最后,我们将所有内容组装成一个图,并观察了工作流在运行时如何调整其路径。
一如既往,本文中使用的所有代码示例都可以在 GitHub 上获取。