跳到主要内容Spring AI Alibaba Graph 初探与实践 | 极客日志JavaAIjava
Spring AI Alibaba Graph 初探与实践
综述由AI生成Spring AI Alibaba Graph 框架提供了构建复杂 Agent 工作流的强大能力。通过状态图管理上下文,结合节点与边实现逻辑编排。演示了从基础依赖配置到条件边、循环边及状态存储的完整开发流程,包含英语学习助手与笑话生成两个实战案例,帮助开发者快速掌握 Graph 在智能体应用中的落地方法。
技术博主23 浏览 Spring AI Alibaba Graph 初探与实践
一、概述
为什么需要 Graph
在构建复杂的 Agent 应用时,简单的线性调用往往难以满足需求。Graph(图)结构允许我们定义状态流转、条件分支和循环逻辑,从而更灵活地编排大模型的工作流。

核心概念
理解 StateGraph 的核心组件是上手的关键:

二、快速入门
我们先实现一个简单的工作流:开始节点 → node1 → node2 → 结束节点,并在过程中用 node2 的值替换 node1 的值。
依赖版本
- spring-boot: 3.4.0
- spring-ai-alibaba: 1.0.0.4
pom.xml 添加核心依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-zhipuai</artifactId>
</dependency>
<dependency>
com.alibaba.cloud.ai
spring-ai-alibaba-graph-core
<groupId>
</groupId>
<artifactId>
</artifactId>
</dependency>
</dependencies>
修改配置文件 application.yaml
我们需要配置智谱大模型的 API Key 以及模型参数。
server:
port: 8889
spring:
application:
name: agent-graph
ai:
zhipuai:
api-key: ${ZHIPU_KEY}
chat:
options:
model: glm-4-flash
创建状态图的配置类
这里我们定义一个 GraphConfig,通过 StateGraph 构建流程并编译为 CompiledGraph。
@Configuration
@Slf4j
public class GraphConfig {
@Bean("quickStartGraph")
public CompiledGraph quickStartGraph() throws GraphStateException {
KeyStrategyFactory keyStrategyFactory = () -> Map.of(
"input1", new ReplaceStrategy(),
"input2", new ReplaceStrategy()
);
StateGraph stateGraph = new StateGraph("quickStartGraph", keyStrategyFactory);
stateGraph.addNode("node1", AsyncNodeAction.node_async(state -> {
log.info("node1 state: {}", state);
return Map.of("input1", 1, "input2", 1);
}));
stateGraph.addNode("node2", AsyncNodeAction.node_async(state -> {
log.info("node2 state: {}", state);
return Map.of("input1", 2, "input2", 2);
}));
stateGraph.addEdge(StateGraph.START, "node1");
stateGraph.addEdge("node1", "node2");
stateGraph.addEdge("node2", StateGraph.END);
return stateGraph.compile();
}
}
创建一个 Controller
@RestController
@RequestMapping("/graph")
@Slf4j
public class GraphController {
private final CompiledGraph compiledGraph;
public GraphController(CompiledGraph compiledGraph) {
this.compiledGraph = compiledGraph;
}
@GetMapping("/quickStartGraph")
public String quickStartGraph() {
Optional<OverAllState> overAllStateOptional = compiledGraph.call(Map.of());
log.info("overAllStateOptional: {}", overAllStateOptional);
return "OK";
}
}
启动程序,查看效果
访问 http://localhost:8889/graph/quickStartGraph。
可以看到 input1 和 input2 的值已被成功替换为 2,说明状态流转正常。
三、API 详解
KeyStrategyFactory(键策略工厂)
用于定义状态图中各个字段的更新策略,例如是覆盖还是追加。
NodeAction & AsyncNodeAction
StateGraph(状态图)
这是状态图的抽象定义层,需要配置状态策略、节点和边。配置完成后,必须调用 compile() 方法将其转换为可执行的 CompiledGraph。
CompiledGraph(编译图)
CompiledGraph 是 StateGraph 编译后的产物,只有它才能被实际调用执行。通常做法是将编译好的图放入 Spring 容器,通过依赖注入获取。
四、案例:开发一个英语学习小助手
需求
输入一个单词,系统基于该单词生成英文句子,然后翻译成中文,最后返回造句和翻译结果。
思路分析
- SentenceConstructionNode:利用 LLM 根据单词造句。
- TranslationNode:将生成的英文句子翻译成中文。
流程图
开始节点(输入单词)→ 造句节点 → 翻译节点 → 结束节点(输出结果)
代码编写
定义 SentenceConstructionNode 造句节点
public class SentenceConstructionNode implements NodeAction {
private final ChatClient chatClient;
public SentenceConstructionNode(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@Override
public Map<String, Object> apply(OverAllState state) throws Exception {
String word = state.value("word", "");
PromptTemplate promptTemplate = new PromptTemplate(
"你是一个英语造句专家,能够基于给定的单词进行造句。"
+ "要求只返回最终造好的句子,不要返回其他信息。"
+ "给定的单词:{word}"
);
promptTemplate.add("word", word);
String prompt = promptTemplate.render();
String content = chatClient.prompt().user(prompt).call().content();
return Map.of("sentence", content);
}
}
定义 TranslationNode 翻译节点
public class TranslationNode implements NodeAction {
private final ChatClient chatClient;
public TranslationNode(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@Override
public Map<String, Object> apply(OverAllState state) throws Exception {
String sentence = state.value("sentence", "");
PromptTemplate promptTemplate = new PromptTemplate(
"你是一个英语翻译专家,能够把英文翻译成中文。"
+ "要求只返回翻译的中文结果,不要返回英文原句。"
+ "要翻译的英文句子:{sentence}"
);
promptTemplate.add("sentence", sentence);
String prompt = promptTemplate.render();
String content = chatClient.prompt().user(prompt).call().content();
return Map.of("translation", content);
}
}
定义状态图
在 GraphConfig 中增加 simpleGraph 的定义。
@Bean("simpleGraph")
public CompiledGraph simpleGraph(ChatClient.Builder clientBuilder) throws GraphStateException {
KeyStrategyFactory keyStrategyFactory = () -> {
HashMap<String, KeyStrategy> keyStrategyHashMap = new HashMap<>();
keyStrategyHashMap.put("word", new ReplaceStrategy());
keyStrategyHashMap.put("sentence", new ReplaceStrategy());
keyStrategyHashMap.put("translation", new ReplaceStrategy());
return keyStrategyHashMap;
};
StateGraph stateGraph = new StateGraph("simpleGraph", keyStrategyFactory);
stateGraph.addNode("SentenceConstructionNode", AsyncNodeAction.node_async(new SentenceConstructionNode(clientBuilder)));
stateGraph.addNode("TranslationNode", AsyncNodeAction.node_async(new TranslationNode(clientBuilder)));
stateGraph.addEdge(StateGraph.START, "SentenceConstructionNode");
stateGraph.addEdge("SentenceConstructionNode", "TranslationNode");
stateGraph.addEdge("TranslationNode", StateGraph.END);
return stateGraph.compile();
}
新增 API 接口
@RestController
@RequestMapping("/graph")
@Slf4j
public class GraphController {
private final CompiledGraph compiledGraph;
private final CompiledGraph simpleGraph;
public GraphController(@Qualifier("quickStartGraph") CompiledGraph compiledGraph,
@Qualifier("simpleGraph") CompiledGraph simpleGraph) {
this.compiledGraph = compiledGraph;
this.simpleGraph = simpleGraph;
}
@GetMapping("/quickStartGraph")
public String quickStartGraph() {
Optional<OverAllState> overAllStateOptional = compiledGraph.call(Map.of());
log.info("overAllStateOptional: {}", overAllStateOptional);
return "OK";
}
@GetMapping("/simpleGraph")
public Map<String, Object> simpleGraph(@RequestParam("word") String word) {
Optional<OverAllState> overAllStateOptional = simpleGraph.call(Map.of("word", word));
Map<String, Object> data = overAllStateOptional.map(OverAllState::data).orElse(Map.of());
return data;
}
}
启动服务,访问接口
GET 请求:http://localhost:8889/graph/simpleGraph?word=sky
五、条件边
条件边允许根据当前状态的值决定下一步流向哪个节点。
代码结构
定义 GenerateJokeNode 生成笑话节点
public class GenerateJokeNode implements NodeAction {
private final ChatClient chatClient;
public GenerateJokeNode(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@Override
public Map<String, Object> apply(OverAllState state) throws Exception {
String topic = state.value("topic", "");
PromptTemplate promptTemplate = new PromptTemplate(
"你需要写一个关于指定主题的短笑话。"
+ "要求返回的结果中只能包含笑话的内容"
+ "主题:{topic}"
);
promptTemplate.add("topic", topic);
String prompt = promptTemplate.render();
String content = chatClient.prompt().user(prompt).call().content();
return Map.of("joke", content);
}
}
定义 EvaluateJokesNode 评估笑话节点
public class EvaluateJokesNode implements NodeAction {
private final ChatClient chatClient;
public EvaluateJokesNode(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@Override
public Map<String, Object> apply(OverAllState state) throws Exception {
String joke = state.value("joke", "");
PromptTemplate promptTemplate = new PromptTemplate(
"你是一个笑话评分专家,能够对笑话进行评分,基于效果的搞笑程度给出 0 到 10 分的打分。"
+ "0 到 3 分是不够优秀,4 到 10 分是优秀。"
+ "要求结果只返回优秀或者不够优秀,不能输出其他内容。"
+ "要评分的笑话:{joke}"
);
promptTemplate.add("joke", joke);
String prompt = promptTemplate.render();
String content = chatClient.prompt().user(prompt).call().content();
return Map.of("result", content.trim());
}
}
定义 EnhanceJokeQualityNode 优化笑话节点
public class EnhanceJokeQualityNode implements NodeAction {
private final ChatClient chatClient;
public EnhanceJokeQualityNode(ChatClient.Builder builder) {
this.chatClient = builder.build();
}
@Override
public Map<String, Object> apply(OverAllState state) throws Exception {
String joke = state.value("joke", "");
PromptTemplate promptTemplate = new PromptTemplate(
"你是一个笑话优化专家,你能够优化笑话,让它更加搞笑"
+ "要优化的话:{joke}"
);
promptTemplate.add("joke", joke);
String prompt = promptTemplate.render();
String content = chatClient.prompt().user(prompt).call().content();
return Map.of("newJoke", content);
}
}
在 GraphConfig 下面定义图
@Bean("conditionalGraph")
public CompiledGraph conditionalGraph(ChatClient.Builder clientBuilder) throws GraphStateException {
KeyStrategyFactory keyStrategyFactory = () -> Map.of("topic", new ReplaceStrategy());
StateGraph stateGraph = new StateGraph("conditionalGraph", keyStrategyFactory);
stateGraph.addNode("生成笑话", AsyncNodeAction.node_async(new GenerateJokeNode(clientBuilder)));
stateGraph.addNode("评估笑话", AsyncNodeAction.node_async(new EvaluateJokesNode(clientBuilder)));
stateGraph.addNode("优化笑话", AsyncNodeAction.node_async(new EnhanceJokeQualityNode(clientBuilder)));
stateGraph.addEdge(StateGraph.START, "生成笑话");
stateGraph.addEdge("生成笑话", "评估笑话");
stateGraph.addConditionalEdges("评估笑话", AsyncEdgeAction.edge_async(
state -> state.value("result", "优秀"),
Map.of("优秀", StateGraph.END, "不够优秀", "优化笑话")
));
stateGraph.addEdge("优化笑话", StateGraph.END);
return stateGraph.compile();
}
在 GraphController 下创建接口
private final CompiledGraph conditionalGraph;
public GraphController(@Qualifier("quickStartGraph") CompiledGraph compiledGraph,
@Qualifier("simpleGraph") CompiledGraph simpleGraph,
@Qualifier("conditionalGraph") CompiledGraph conditionalGraph) {
this.compiledGraph = compiledGraph;
this.simpleGraph = simpleGraph;
this.conditionalGraph = conditionalGraph;
}
@GetMapping("/conditionalGraph")
public Map<String, Object> conditionalGraph(@RequestParam("topic") String topic) {
Optional<OverAllState> overAllStateOptional = conditionalGraph.call(Map.of("topic", topic));
Map<String, Object> data = overAllStateOptional.map(OverAllState::data).orElse(Map.of());
return data;
}
验证效果
访问 http://localhost:8889/graph/conditionalGraph?topic=爱情。
如果评估结果是'优秀',直接输出;如果模拟断点修改结果为'不够优秀',则会进入优化节点重新生成。
六、循环边
循环边允许流程回到之前的节点,直到满足特定条件为止。
新增 LoopEvaluateJokesNode 循环评分节点
@Slf4j
public class LoopEvaluateJokesNode implements NodeAction {
private final ChatClient chatClient;
private final Integer targetScore;
private final Integer maxLoopCount;
public LoopEvaluateJokesNode(ChatClient.Builder builder, Integer targetScore, Integer maxLoopCount) {
this.chatClient = builder.build();
this.targetScore = targetScore;
this.maxLoopCount = maxLoopCount;
}
@Override
public Map<String, Object> apply(OverAllState state) throws Exception {
String joke = state.value("joke", "");
Integer loopCount = state.value("loopCount", 0);
PromptTemplate promptTemplate = new PromptTemplate(
"你是一个笑话评分专家,能够对笑话进行评分,基于效果的搞笑程度给出 0 到 10 分的打分。"
+ "要求结果只返回最后的打分,打分必须是整数,不能输出其他内容。"
+ "要评分的笑话:{joke}"
);
promptTemplate.add("joke", joke);
String prompt = promptTemplate.render();
String content = chatClient.prompt().user(prompt).call().content();
Integer score = Integer.parseInt(content.trim());
log.info("joke: {},score: {},循环次数:{}", joke, score, loopCount);
String result = "loop";
if (score >= targetScore || loopCount >= maxLoopCount) {
result = "break";
}
loopCount++;
return Map.of("result", result, "loopCount", loopCount);
}
}
在 GraphConfig 下面定义图
@Bean("loopGraph")
public CompiledGraph loopGraph(ChatClient.Builder clientBuilder) throws GraphStateException {
KeyStrategyFactory keyStrategyFactory = () -> Map.of("topic", new ReplaceStrategy());
StateGraph stateGraph = new StateGraph("loopGraph", keyStrategyFactory);
stateGraph.addNode("生成笑话", AsyncNodeAction.node_async(new GenerateJokeNode(clientBuilder)));
stateGraph.addNode("评估笑话", AsyncNodeAction.node_async(new LoopEvaluateJokesNode(clientBuilder, 8, 5)));
stateGraph.addEdge(StateGraph.START, "生成笑话");
stateGraph.addEdge("生成笑话", "评估笑话");
stateGraph.addConditionalEdges("评估笑话", AsyncEdgeAction.edge_async(
state -> state.value("result", "loop"),
Map.of("loop", "生成笑话", "break", StateGraph.END)
));
return stateGraph.compile();
}
测试
访问 http://localhost:8889/graph/loopGraph?topic=爱情。当分数达到目标值或达到最大循环次数时退出。
七、状态存储
默认情况下 Graph 将状态存储在内存中。如果需要跨会话保持上下文,可以配置状态存储。
在 ConfigGraph 中创建状态图
@Bean("saveGraph")
public CompiledGraph saveGraph(ChatClient.Builder clientBuilder) throws GraphStateException {
KeyStrategyFactory keyStrategyFactory = () -> Map.of();
StateGraph stateGraph = new StateGraph("saveGraph", keyStrategyFactory);
stateGraph.addNode("对话存储", AsyncNodeAction.node_async(new NodeAction() {
@Override
public Map<String, Object> apply(OverAllState state) throws Exception {
String msg = state.value("msg", "");
ArrayList<Object> historyMsg = state.value("historyMsg", new ArrayList<>());
historyMsg.add(msg);
return Map.of("historyMsg", historyMsg);
}
}));
stateGraph.addEdge(StateGraph.START, "对话存储");
stateGraph.addEdge("对话存储", StateGraph.END);
return stateGraph.compile();
}
在 GraphController 中创建接口
通过 conversationId 隔离不同请求者的数据。
@GetMapping("/saveGraph")
public Map<String, Object> saveGraph(@RequestParam("msg") String msg, @RequestParam("conversationId") String conversationId) {
RunnableConfig runnableConfig = RunnableConfig.builder().threadId(conversationId).build();
Optional<OverAllState> overAllStateOptional = saveGraph.call(Map.of("msg", msg), runnableConfig);
Map<String, Object> data = overAllStateOptional.map(OverAllState::data).orElse(Map.of());
return data;
}
测试
第一次调用后,再次使用相同的 conversationId 调用,历史消息会被保留。
八、打印图
为了直观查看定义的图结构,可以使用 PlantUML 导出。
GraphRepresentation representation = stateGraph.getGraph(GraphRepresentation.Type.PLANTUML, "stateGraph");
log.info("\n===打印 UML Flow===");
log.info(representation.content());
log.info("====================\n");
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- RSA密钥对生成器
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
- Mermaid 预览与可视化编辑
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online