跳到主要内容
Spring AI Alibaba Graph 初探与实践 | 极客日志
Java AI java
Spring AI Alibaba Graph 初探与实践 Spring AI Alibaba Graph 框架提供了构建复杂 Agent 工作流的强大能力。通过状态图管理上下文,结合节点与边实现逻辑编排。本文演示了从基础依赖配置到条件边、循环边及状态存储的完整开发流程,包含英语学习助手与笑话生成两个实战案例,帮助开发者快速掌握 Graph 在智能体应用中的落地方法。
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