Spring AI Alibaba Graph 工作流:节点和边到底是怎么回事?

摘要:搞懂 Spring AI Alibaba Graph 里的节点、边、State 这三个核心概念,才能玩转工作流编排。本文从实际项目踩坑经验出发,带你彻底搞清楚 DAG 是怎么跑起来的。


最近在啃 Spring AI Alibaba Graph 的源码,刚开始真的有点懵。

节点是啥?边又是啥?State 怎么传的?条件分支怎么搞?

查了一圈文档,发现讲概念的不少,但真正告诉你「为什么这么设计」「企业里怎么用」的并不多。

所以这篇文章,门主把自己踩过的坑、翻过的源码,整理出来分享给你。


一、Graph 到底是个啥?

Spring AI Alibaba Graph 本质上就是一个 DAG(有向无环图) 引擎。

一句话:把一个复杂的 Agent 任务,拆成多个小步骤,每个步骤是一个「节点」,节点之间用「边」连起来规定顺序,整个流程就跑起来了。

先看个图感受一下:

mermaid-diagram

核心三件套:

| 概念

|

作用

|

通俗理解

State(状态)

节点之间传递的数据结构,Map<String, Object>

|

共享的数据包,每个节点都能读写

| | Node(节点) |

执行逻辑的单元,接收 State → 处理 → 返回新 State

|

流水线上的一个工人

| | Edge(边) |

定义节点之间的控制流,决定下一步去哪

|

流水线上的传送带

|

记住这句话:节点负责干活,边负责指挥往哪走,State 负责传数据。


二、两种开发模式,别搞混了

Spring AI Alibaba Graph 提供了两套 API:

| 模式

|

适合场景

|

灵活度

Agentic API

快速开发,声明式定义 Agent

|

低,框架帮你搞定细节

| | Graph API |

需要精细控制每个步骤

|

高,节点、边全部自己定义

|

简单说:

  • 如果你的 Agent 就是「接收输入 → 调用 LLM → 返回结果」,用 Agentic API,几行代码搞定

  • 如果你的流程有分支、有并行、需要人工干预,老老实实用 Graph API

企业里面,复杂场景居多,所以 Graph API 是重点。


三、节点(Node)是怎么干活的?

3.1 节点的本质

节点就是一个函数,输入 State,输出 State。

mermaid-diagram

3.2 自定义节点

你需要实现 NodeAction 或 NodeActionWithConfig

<span style="color:#6A737D;"><span leaf="">// 简单版:只管处理状态</span></span><span leaf=""><br></span><span style="color:#D73A49;"><span leaf="">public</span></span><span style="color:#D73A49;"><span leaf="">&nbsp;interface</span></span><span style="color:#6F42C1;"><span leaf="">&nbsp;NodeAction</span></span><span style="color:#24292E;"><span leaf="">&nbsp;{</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">&nbsp; &nbsp; Map&lt;</span></span><span style="color:#D73A49;"><span leaf="">String</span></span><span style="color:#24292E;"><span leaf="">,&nbsp;</span></span><span style="color:#D73A49;"><span leaf="">Object</span></span><span style="color:#24292E;"><span leaf="">&gt;&nbsp;</span></span><span style="color:#6F42C1;"><span leaf="">apply</span></span><span style="color:#24292E;"><span leaf="">(OverAllState&nbsp;</span></span><span style="color:#E36209;"><span leaf="">state</span></span><span style="color:#24292E;"><span leaf="">)&nbsp;</span></span><span style="color:#D73A49;"><span leaf="">throws</span></span><span style="color:#24292E;"><span leaf="">&nbsp;Exception;</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">}</span></span><span leaf=""><br></span><span leaf=""><br></span><span style="color:#6A737D;"><span leaf="">// 高级版:多拿一个配置参数</span></span><span leaf=""><br></span><span style="color:#D73A49;"><span leaf="">public</span></span><span style="color:#D73A49;"><span leaf="">&nbsp;interface</span></span><span style="color:#6F42C1;"><span leaf="">&nbsp;NodeActionWithConfig</span></span><span style="color:#24292E;"><span leaf="">&nbsp;{</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">&nbsp; &nbsp; Map&lt;</span></span><span style="color:#D73A49;"><span leaf="">String</span></span><span style="color:#24292E;"><span leaf="">,&nbsp;</span></span><span style="color:#D73A49;"><span leaf="">Object</span></span><span style="color:#24292E;"><span leaf="">&gt;&nbsp;</span></span><span style="color:#6F42C1;"><span leaf="">apply</span></span><span style="color:#24292E;"><span leaf="">(OverAllState&nbsp;</span></span><span style="color:#E36209;"><span leaf="">state</span></span><span style="color:#24292E;"><span leaf="">, RunnableConfig&nbsp;</span></span><span style="color:#E36209;"><span leaf="">config</span></span><span style="color:#24292E;"><span leaf="">)&nbsp;</span></span><span style="color:#D73A49;"><span leaf="">throws</span></span><span style="color:#24292E;"><span leaf="">&nbsp;Exception;</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">}</span></span><span leaf=""><br></span>

区别很简单:

| 对比项

|

NodeAction

|

NodeActionWithConfig

参数

|

只要 state

|

state + config

| |

能拿到什么

|

只有业务数据

|

业务数据 + 运行时元数据(线程ID等)

| |

适用场景

|

纯业务逻辑

|

需要读取运行配置、做日志追踪

|

门主建议:90% 的情况用 NodeAction 就够了,除非你需要从 config 里拿东西。

3.3 节点类型

工作流里常见的节点就这几种:

| 类型

|

说明

|

示例

处理节点

纯业务逻辑,数据处理

|

文本清洗、数据格式化

| | AI 节点 |

调用 LLM 做推理

|

查询扩写、内容生成

| | 条件节点 |

根据 State 值做路由判断

|

判断文本长度决定下一步

| | 聚合节点 |

收集多个并行节点的结果

|

合并多个搜索结果

| | Agent 节点 |

把整个 Agent 当作一个节点

| agent.asNode() |

3.4 节点开发最佳实践

  1. 单一职责:一个节点只做一件事,别搞成大杂烩

  2. 不要直接改 State:返回新的 Map,而不是修改入参(避免副作用)

  3. 异常处理:内部 catch 住,返回错误信息,别让异常中断整个流程

  4. 加日志:工作流排错全靠日志,不加日志就是给自己挖坑


四、边(Edge)是怎么指挥交通的?

边定义了控制流,告诉工作流「下一步该去哪」。

4.1 两种边

| 边类型

|

方法

|

说明

固定边 addEdge(from, to)

无条件,A 执行完一定去 B

| | 条件边 | addConditionalEdges(节点, 路由函数, 路由映射) |

根据返回值动态决定下一步

|

4.2 固定边

最简单的连线:

<span style="color:#24292E;"><span leaf="">graph.</span></span><span style="color:#6F42C1;"><span leaf="">addEdge</span></span><span style="color:#24292E;"><span leaf="">(</span></span><span style="color:#032F62;"><span leaf="">"节点A"</span></span><span style="color:#24292E;"><span leaf="">,&nbsp;</span></span><span style="color:#032F62;"><span leaf="">"节点B"</span></span><span style="color:#24292E;"><span leaf="">); &nbsp;</span></span><span style="color:#6A737D;"><span leaf="">// A 执行完,无条件去 B</span></span><span leaf=""><br></span>

4.3 条件边

这个才是工作流的灵魂。

<span style="color:#24292E;"><span leaf="">graph.</span></span><span style="color:#6F42C1;"><span leaf="">addConditionalEdges</span></span><span style="color:#24292E;"><span leaf="">(</span></span><span leaf=""><br></span><span style="color:#032F62;"><span leaf="">&nbsp; &nbsp; "condition_node"</span></span><span style="color:#24292E;"><span leaf="">, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</span></span><span style="color:#6A737D;"><span leaf="">// 条件节点</span></span><span leaf=""><br></span><span style="color:#6F42C1;"><span leaf="">&nbsp; &nbsp; edge_async</span></span><span style="color:#24292E;"><span leaf="">(state&nbsp;</span></span><span style="color:#D73A49;"><span leaf="">-&gt;</span></span><span style="color:#24292E;"><span leaf="">&nbsp;state.</span></span><span style="color:#6F42C1;"><span leaf="">value</span></span><span style="color:#24292E;"><span leaf="">(</span></span><span style="color:#032F62;"><span leaf="">"_condition_result"</span></span><span style="color:#24292E;"><span leaf="">,&nbsp;</span></span><span style="color:#032F62;"><span leaf="">"default"</span></span><span style="color:#24292E;"><span leaf="">).</span></span><span style="color:#6F42C1;"><span leaf="">toString</span></span><span style="color:#24292E;"><span leaf="">()),&nbsp;</span></span><span style="color:#6A737D;"><span leaf="">// 路由函数</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">&nbsp; &nbsp; Map.</span></span><span style="color:#6F42C1;"><span leaf="">of</span></span><span style="color:#24292E;"><span leaf="">(</span></span><span leaf=""><br></span><span style="color:#032F62;"><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; "error_handling"</span></span><span style="color:#24292E;"><span leaf="">,&nbsp;</span></span><span style="color:#032F62;"><span leaf="">"error_node"</span></span><span style="color:#24292E;"><span leaf="">, &nbsp; &nbsp; &nbsp; &nbsp;</span></span><span style="color:#6A737D;"><span leaf="">// 值是 error_handling → 去 error_node</span></span><span leaf=""><br></span><span style="color:#032F62;"><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; "data_processing"</span></span><span style="color:#24292E;"><span leaf="">,&nbsp;</span></span><span style="color:#032F62;"><span leaf="">"process_node"</span></span><span style="color:#24292E;"><span leaf="">, &nbsp; &nbsp;&nbsp;</span></span><span style="color:#6A737D;"><span leaf="">// 值是 data_processing → 去 process_node</span></span><span leaf=""><br></span><span style="color:#032F62;"><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; "default"</span></span><span style="color:#24292E;"><span leaf="">, StateGraph.END &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;</span></span><span style="color:#6A737D;"><span leaf="">// 默认 → 结束</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">&nbsp; &nbsp; )</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">);</span></span><span leaf=""><br></span>

流程是这样的:

mermaid-diagram


五、StateGraph:整个工作流的大管家

StateGraph 是构建工作流的核心类,所有节点、边都归它管。

5.1 使用流程

mermaid-diagram

代码示例:

<span style="color:#6A737D;"><span leaf="">// 1. 创建 StateGraph(带状态策略)</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">StateGraph graph&nbsp;</span></span><span style="color:#D73A49;"><span leaf="">=</span></span><span style="color:#D73A49;"><span leaf="">&nbsp;new</span></span><span style="color:#6F42C1;"><span leaf="">&nbsp;StateGraph</span></span><span style="color:#24292E;"><span leaf="">(keyStrategyFactory);</span></span><span leaf=""><br></span><span leaf=""><br></span><span style="color:#6A737D;"><span leaf="">// 2. 添加节点</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">graph.</span></span><span style="color:#6F42C1;"><span leaf="">addNode</span></span><span style="color:#24292E;"><span leaf="">(</span></span><span style="color:#032F62;"><span leaf="">"processor"</span></span><span style="color:#24292E;"><span leaf="">,&nbsp;</span></span><span style="color:#6F42C1;"><span leaf="">node_async</span></span><span style="color:#24292E;"><span leaf="">(</span></span><span style="color:#D73A49;"><span leaf="">new</span></span><span style="color:#6F42C1;"><span leaf="">&nbsp;TextProcessorNode</span></span><span style="color:#24292E;"><span leaf="">()));</span></span><span leaf=""><br></span><span leaf=""><br></span><span style="color:#6A737D;"><span leaf="">// 3. 添加边</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">graph.</span></span><span style="color:#6F42C1;"><span leaf="">addEdge</span></span><span style="color:#24292E;"><span leaf="">(StateGraph.START,&nbsp;</span></span><span style="color:#032F62;"><span leaf="">"processor"</span></span><span style="color:#24292E;"><span leaf="">);</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">graph.</span></span><span style="color:#6F42C1;"><span leaf="">addEdge</span></span><span style="color:#24292E;"><span leaf="">(</span></span><span style="color:#032F62;"><span leaf="">"processor"</span></span><span style="color:#24292E;"><span leaf="">, StateGraph.END);</span></span><span leaf=""><br></span><span leaf=""><br></span><span style="color:#6A737D;"><span leaf="">// 4. 编译</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">CompiledGraph compiledGraph&nbsp;</span></span><span style="color:#D73A49;"><span leaf="">=</span></span><span style="color:#24292E;"><span leaf="">&nbsp;graph.</span></span><span style="color:#6F42C1;"><span leaf="">compile</span></span><span style="color:#24292E;"><span leaf="">();</span></span><span leaf=""><br></span>

5.2 node_async 是啥?

node_async() 是个工具方法,把 NodeAction 包装成异步执行的节点

为什么要异步?因为工作流可能涉及多个并行节点,异步执行才能发挥性能优势。


六、START 和 END:起点和终点

StateGraph 预定义了两个特殊节点:

| 常量

|

作用

|

说明

StateGraph.START

工作流的起点

|

第一个执行的节点,不需要实现

| | StateGraph.END |

工作流的终点

|

执行到这里整个流程结束

|

这两个是虚拟节点,不需要写实现类,直接用就行。

<span style="color:#24292E;"><span leaf="">graph.</span></span><span style="color:#6F42C1;"><span leaf="">addEdge</span></span><span style="color:#24292E;"><span leaf="">(StateGraph.START,&nbsp;</span></span><span style="color:#032F62;"><span leaf="">"first_node"</span></span><span style="color:#24292E;"><span leaf="">); &nbsp;&nbsp;</span></span><span style="color:#6A737D;"><span leaf="">// 从起点开始</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">graph.</span></span><span style="color:#6F42C1;"><span leaf="">addEdge</span></span><span style="color:#24292E;"><span leaf="">(</span></span><span style="color:#032F62;"><span leaf="">"last_node"</span></span><span style="color:#24292E;"><span leaf="">, StateGraph.END); &nbsp; &nbsp; &nbsp;</span></span><span style="color:#6A737D;"><span leaf="">// 到终点结束</span></span><span leaf=""><br></span>

七、门主踩坑:闭环!闭环!闭环!

说三遍是因为太重要了。

7.1 什么是闭环?

从 StateGraph.START 出发,沿着边能走到 StateGraph.END,中间不能有断开的路径。

7.2 正确示例 ✅

<span style="color:#24292E;"><span leaf="">graph.</span></span><span style="color:#6F42C1;"><span leaf="">addEdge</span></span><span style="color:#24292E;"><span leaf="">(StateGraph.START,&nbsp;</span></span><span style="color:#032F62;"><span leaf="">"A"</span></span><span style="color:#24292E;"><span leaf="">);</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">graph.</span></span><span style="color:#6F42C1;"><span leaf="">addEdge</span></span><span style="color:#24292E;"><span leaf="">(</span></span><span style="color:#032F62;"><span leaf="">"A"</span></span><span style="color:#24292E;"><span leaf="">,&nbsp;</span></span><span style="color:#032F62;"><span leaf="">"B"</span></span><span style="color:#24292E;"><span leaf="">);</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">graph.</span></span><span style="color:#6F42C1;"><span leaf="">addEdge</span></span><span style="color:#24292E;"><span leaf="">(</span></span><span style="color:#032F62;"><span leaf="">"B"</span></span><span style="color:#24292E;"><span leaf="">,&nbsp;</span></span><span style="color:#032F62;"><span leaf="">"C"</span></span><span style="color:#24292E;"><span leaf="">);</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">graph.</span></span><span style="color:#6F42C1;"><span leaf="">addEdge</span></span><span style="color:#24292E;"><span leaf="">(</span></span><span style="color:#032F62;"><span leaf="">"C"</span></span><span style="color:#24292E;"><span leaf="">, StateGraph.END);</span></span><span leaf=""><br></span>

流程:START → A → B → C → END ✅ 完整闭环

7.3 错误示例 ❌

<span style="color:#24292E;"><span leaf="">graph.</span></span><span style="color:#6F42C1;"><span leaf="">addEdge</span></span><span style="color:#24292E;"><span leaf="">(StateGraph.START,&nbsp;</span></span><span style="color:#032F62;"><span leaf="">"A"</span></span><span style="color:#24292E;"><span leaf="">);</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">graph.</span></span><span style="color:#6F42C1;"><span leaf="">addEdge</span></span><span style="color:#24292E;"><span leaf="">(</span></span><span style="color:#032F62;"><span leaf="">"A"</span></span><span style="color:#24292E;"><span leaf="">,&nbsp;</span></span><span style="color:#032F62;"><span leaf="">"B"</span></span><span style="color:#24292E;"><span leaf="">);</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">graph.</span></span><span style="color:#6F42C1;"><span leaf="">addEdge</span></span><span style="color:#24292E;"><span leaf="">(</span></span><span style="color:#032F62;"><span leaf="">"B"</span></span><span style="color:#24292E;"><span leaf="">,&nbsp;</span></span><span style="color:#032F62;"><span leaf="">"C"</span></span><span style="color:#24292E;"><span leaf="">);</span></span><span leaf=""><br></span><span style="color:#6A737D;"><span leaf="">// 没有连到 END!C 执行完就断了 → 运行时报错</span></span><span leaf=""><br></span>

条件边更容易踩坑:

<span style="color:#24292E;"><span leaf="">graph.</span></span><span style="color:#6F42C1;"><span leaf="">addConditionalEdges</span></span><span style="color:#24292E;"><span leaf="">(</span></span><span style="color:#032F62;"><span leaf="">"A"</span></span><span style="color:#24292E;"><span leaf="">,</span></span><span leaf=""><br></span><span style="color:#6F42C1;"><span leaf="">&nbsp; &nbsp; edge_async</span></span><span style="color:#24292E;"><span leaf="">(state&nbsp;</span></span><span style="color:#D73A49;"><span leaf="">-&gt;</span></span><span style="color:#24292E;"><span leaf="">&nbsp;state.</span></span><span style="color:#6F42C1;"><span leaf="">value</span></span><span style="color:#24292E;"><span leaf="">(</span></span><span style="color:#032F62;"><span leaf="">"type"</span></span><span style="color:#24292E;"><span leaf="">).</span></span><span style="color:#6F42C1;"><span leaf="">toString</span></span><span style="color:#24292E;"><span leaf="">()),</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">&nbsp; &nbsp; Map.</span></span><span style="color:#6F42C1;"><span leaf="">of</span></span><span style="color:#24292E;"><span leaf="">(</span></span><span leaf=""><br></span><span style="color:#032F62;"><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; "a"</span></span><span style="color:#24292E;"><span leaf="">,&nbsp;</span></span><span style="color:#032F62;"><span leaf="">"B"</span></span><span style="color:#24292E;"><span leaf="">,</span></span><span leaf=""><br></span><span style="color:#032F62;"><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; "b"</span></span><span style="color:#24292E;"><span leaf="">,&nbsp;</span></span><span style="color:#032F62;"><span leaf="">"C"</span></span><span leaf=""><br></span><span style="color:#6A737D;"><span leaf="">&nbsp; &nbsp; &nbsp; &nbsp; // 如果 type 是 "c" 呢?没有匹配的路径 → 断流!</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">&nbsp; &nbsp; )</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">);</span></span><span leaf=""><br></span>

门主提醒:条件边的映射一定要覆盖所有可能的情况,实在拿不准就用 default 兜底。


八、节点和边是怎么协作的?

最后串起来看整个流程:

mermaid-diagram

核心关系:

| 关系

|

说明

节点 → State

节点读取 State 的输入,处理完后返回更新的 State

| | 边 → 节点 |

边决定哪个节点先执行、哪个后执行

| | 条件边 → State |

条件边读取 State 的值来决定路由方向

| | 节点 ↔ 节点 |

不直接通信,通过 State 间接传递数据

|

记住:节点之间不直接说话,都靠 State 传话。


👇 三连支持,动力源泉

如果这篇文章帮你省下了踩坑的时间,欢迎:

🔹 点赞 —— 让更多人看到这篇干货
🔹 在看 —— 你的认可是我持续输出的动力
🔹 转发 —— 分享给身边正在做AI Agent的朋友

你的每一个小动作,对我都很重要 ❤️


🙏 关于作者

你好,我是 空门技术栈,一个常年和Bug战斗、持续填坑的Java开发者。

专注分享:

  • ✅ Java / Spring Boot / Spring AI Alibaba 企业级实战

  • ✅ RAG知识库、AI Agent、多智能体协作落地经验

  • ✅ Docker部署、微服务架构、线上问题排查

  • ✅ 偶尔聊聊「如何保住头发」这类程序员终极话题 😂

不搞水文,不贩卖焦虑,只写能跑通、能落地、能帮你少加班的实战内容。

关注我,咱们一起少踩坑,多写优雅代码。


📖 更多干货推荐