别只会画流程图!彻底搞懂 Spring AI Alibaba Graph 的节点、边与 State
Spring AI Alibaba Graph 工作流:节点和边到底是怎么回事?
摘要:搞懂 Spring AI Alibaba Graph 里的节点、边、State 这三个核心概念,才能玩转工作流编排。本文从实际项目踩坑经验出发,带你彻底搞清楚 DAG 是怎么跑起来的。
最近在啃 Spring AI Alibaba Graph 的源码,刚开始真的有点懵。
节点是啥?边又是啥?State 怎么传的?条件分支怎么搞?
查了一圈文档,发现讲概念的不少,但真正告诉你「为什么这么设计」「企业里怎么用」的并不多。
所以这篇文章,门主把自己踩过的坑、翻过的源码,整理出来分享给你。
一、Graph 到底是个啥?
Spring AI Alibaba Graph 本质上就是一个 DAG(有向无环图) 引擎。
一句话:把一个复杂的 Agent 任务,拆成多个小步骤,每个步骤是一个「节点」,节点之间用「边」连起来规定顺序,整个流程就跑起来了。
先看个图感受一下:
核心三件套:
| 概念
|
作用
|
通俗理解
| 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。
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=""> interface</span></span><span style="color:#6F42C1;"><span leaf=""> NodeAction</span></span><span style="color:#24292E;"><span leaf=""> {</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf=""> Map<</span></span><span style="color:#D73A49;"><span leaf="">String</span></span><span style="color:#24292E;"><span leaf="">, </span></span><span style="color:#D73A49;"><span leaf="">Object</span></span><span style="color:#24292E;"><span leaf="">> </span></span><span style="color:#6F42C1;"><span leaf="">apply</span></span><span style="color:#24292E;"><span leaf="">(OverAllState </span></span><span style="color:#E36209;"><span leaf="">state</span></span><span style="color:#24292E;"><span leaf="">) </span></span><span style="color:#D73A49;"><span leaf="">throws</span></span><span style="color:#24292E;"><span leaf=""> 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=""> interface</span></span><span style="color:#6F42C1;"><span leaf=""> NodeActionWithConfig</span></span><span style="color:#24292E;"><span leaf=""> {</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf=""> Map<</span></span><span style="color:#D73A49;"><span leaf="">String</span></span><span style="color:#24292E;"><span leaf="">, </span></span><span style="color:#D73A49;"><span leaf="">Object</span></span><span style="color:#24292E;"><span leaf="">> </span></span><span style="color:#6F42C1;"><span leaf="">apply</span></span><span style="color:#24292E;"><span leaf="">(OverAllState </span></span><span style="color:#E36209;"><span leaf="">state</span></span><span style="color:#24292E;"><span leaf="">, RunnableConfig </span></span><span style="color:#E36209;"><span leaf="">config</span></span><span style="color:#24292E;"><span leaf="">) </span></span><span style="color:#D73A49;"><span leaf="">throws</span></span><span style="color:#24292E;"><span leaf=""> 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 节点开发最佳实践
-
单一职责:一个节点只做一件事,别搞成大杂烩
-
不要直接改 State:返回新的 Map,而不是修改入参(避免副作用)
-
异常处理:内部 catch 住,返回错误信息,别让异常中断整个流程
-
加日志:工作流排错全靠日志,不加日志就是给自己挖坑
四、边(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="">, </span></span><span style="color:#032F62;"><span leaf="">"节点B"</span></span><span style="color:#24292E;"><span leaf="">); </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=""> "condition_node"</span></span><span style="color:#24292E;"><span leaf="">, </span></span><span style="color:#6A737D;"><span leaf="">// 条件节点</span></span><span leaf=""><br></span><span style="color:#6F42C1;"><span leaf=""> edge_async</span></span><span style="color:#24292E;"><span leaf="">(state </span></span><span style="color:#D73A49;"><span leaf="">-></span></span><span style="color:#24292E;"><span leaf=""> 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="">, </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="">()), </span></span><span style="color:#6A737D;"><span leaf="">// 路由函数</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf=""> 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=""> "error_handling"</span></span><span style="color:#24292E;"><span leaf="">, </span></span><span style="color:#032F62;"><span leaf="">"error_node"</span></span><span style="color:#24292E;"><span leaf="">, </span></span><span style="color:#6A737D;"><span leaf="">// 值是 error_handling → 去 error_node</span></span><span leaf=""><br></span><span style="color:#032F62;"><span leaf=""> "data_processing"</span></span><span style="color:#24292E;"><span leaf="">, </span></span><span style="color:#032F62;"><span leaf="">"process_node"</span></span><span style="color:#24292E;"><span leaf="">, </span></span><span style="color:#6A737D;"><span leaf="">// 值是 data_processing → 去 process_node</span></span><span leaf=""><br></span><span style="color:#032F62;"><span leaf=""> "default"</span></span><span style="color:#24292E;"><span leaf="">, StateGraph.END </span></span><span style="color:#6A737D;"><span leaf="">// 默认 → 结束</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf=""> )</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">);</span></span><span leaf=""><br></span>
流程是这样的:
五、StateGraph:整个工作流的大管家
StateGraph 是构建工作流的核心类,所有节点、边都归它管。
5.1 使用流程
代码示例:
<span style="color:#6A737D;"><span leaf="">// 1. 创建 StateGraph(带状态策略)</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">StateGraph graph </span></span><span style="color:#D73A49;"><span leaf="">=</span></span><span style="color:#D73A49;"><span leaf=""> new</span></span><span style="color:#6F42C1;"><span leaf=""> 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="">, </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=""> 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, </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 </span></span><span style="color:#D73A49;"><span leaf="">=</span></span><span style="color:#24292E;"><span leaf=""> 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, </span></span><span style="color:#032F62;"><span leaf="">"first_node"</span></span><span style="color:#24292E;"><span leaf="">); </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); </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, </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="">, </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="">, </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, </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="">, </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="">, </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=""> edge_async</span></span><span style="color:#24292E;"><span leaf="">(state </span></span><span style="color:#D73A49;"><span leaf="">-></span></span><span style="color:#24292E;"><span leaf=""> 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=""> 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=""> "a"</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="">,</span></span><span leaf=""><br></span><span style="color:#032F62;"><span leaf=""> "b"</span></span><span style="color:#24292E;"><span leaf="">, </span></span><span style="color:#032F62;"><span leaf="">"C"</span></span><span leaf=""><br></span><span style="color:#6A737D;"><span leaf=""> // 如果 type 是 "c" 呢?没有匹配的路径 → 断流!</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf=""> )</span></span><span leaf=""><br></span><span style="color:#24292E;"><span leaf="">);</span></span><span leaf=""><br></span>
门主提醒:条件边的映射一定要覆盖所有可能的情况,实在拿不准就用 default 兜底。
八、节点和边是怎么协作的?
最后串起来看整个流程:
核心关系:
| 关系
|
说明
| 节点 → State |
节点读取 State 的输入,处理完后返回更新的 State
| | 边 → 节点 |
边决定哪个节点先执行、哪个后执行
| | 条件边 → State |
条件边读取 State 的值来决定路由方向
| | 节点 ↔ 节点 |
不直接通信,通过 State 间接传递数据
|
记住:节点之间不直接说话,都靠 State 传话。
👇 三连支持,动力源泉
如果这篇文章帮你省下了踩坑的时间,欢迎:
🔹 点赞 —— 让更多人看到这篇干货
🔹 在看 —— 你的认可是我持续输出的动力
🔹 转发 —— 分享给身边正在做AI Agent的朋友
你的每一个小动作,对我都很重要 ❤️
🙏 关于作者
你好,我是 空门技术栈,一个常年和Bug战斗、持续填坑的Java开发者。
专注分享:
-
✅ Java / Spring Boot / Spring AI Alibaba 企业级实战
-
✅ RAG知识库、AI Agent、多智能体协作落地经验
-
✅ Docker部署、微服务架构、线上问题排查
-
✅ 偶尔聊聊「如何保住头发」这类程序员终极话题 😂
不搞水文,不贩卖焦虑,只写能跑通、能落地、能帮你少加班的实战内容。
关注我,咱们一起少踩坑,多写优雅代码。
📖 更多干货推荐
-
[一文搞懂 Spring AI Alibaba Workflow:10 个实战案例带你彻底掌握 AI 工作流编排(https://mp.weixin.qq.com/s/_d1UzD0G3g1G9h3fDSCPWQ)
-
LangChain Agent终于讲透了:短期记忆、Redis持久化、Middleware企业级实战,一篇带你从入门到生产
-
LangChain 结构化输出终于讲透了:ProviderStrategy、ToolStrategy、动态 Schema 一篇全会
-
[Spring WebFlux 真的比 MVC 快?我用 5 万长连接测出了真相]
(https://mp.weixin.qq.com/s/CNv2U98Mg5cqftidjatP5w) -
[别只会配 MCP!从 JSON-RPC 到 Streamable HTTP,带你用 Spring AI 2.0 搭建 MCP Server]
(https://mp.weixin.qq.com/s/-TnW4mTcxrwtDFrQlf6KkQ) -
[Spring AI Alibaba Graph学习代码]
(https://gitcode.com/kongmen/SpringAIAlibabaGraphLearn)
- 原文作者:知识铺
- 原文链接:https://index.zshipu.com/ai001/post/20260822/%E5%88%AB%E5%8F%AA%E4%BC%9A%E7%94%BB%E6%B5%81%E7%A8%8B%E5%9B%BE%E5%BD%BB%E5%BA%95%E6%90%9E%E6%87%82-Spring-AI-Alibaba-Graph-%E7%9A%84%E8%8A%82%E7%82%B9%E8%BE%B9%E4%B8%8E-State/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
- 免责声明:本页面内容均来源于站内编辑发布,部分信息来源互联网,并不意味着本站赞同其观点或者证实其内容的真实性,如涉及版权等问题,请立即联系客服进行更改或删除,保证您的合法权益。转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。也可以邮件至 sblig@126.com