spring-ai-alibaba-graph框架上手实战
Spring AI Alibaba Graph快速上手教程,实现自动化报告生成流程,输入主题即可生成报告
概述
工作流是以相对固化的模式来人为的拆解任务,将一个大的任务拆解为一个固化的有多个分支的流程。工作流的优势是确定性强,模型作为流程中的一个结点起到的更多是一个分类决策的职责,因此它更适合意图识别等类别属性强的应用场景。
工作流也有明显的劣势,它要求开发人员对业务流程有深刻的理解,整个流程是由人绘制的,模型在其中更多的只是内容生成、总结、分类识别的作用,并不能最大化利用模型的推理能力,因此很多人诟病这种模式是不够智能的。
用 Spring AI Alibaba Graph 可以轻松开发工作流,声明不同的结点,并将结点串联成一个流程图。下面以一个经典的报告生成流程为例:
pom依赖
下面添加了开发的基础依赖,如果不想使用阿里百炼平台,可以查看spring ai官网更换其它平台的依赖
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud.ai</groupId>
<artifactId>spring-ai-alibaba-graph-core</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
模型客户端配置
首先配置百炼平台的key,框架会自动创建DashScopeChatModel,这是最底层的模型类
server:
port: 9820
spring:
ai:
dashscope:
api-key: sk-xxxxxxxxxxxxxxxxx
chat:
options:
model: qwen-plus-latest
temperature: 0.7
再创建ChatClient ,我这里采用了自定义的方式创建,这种方式更加灵活,可以配置模型客户端的日志、提示词、记忆等功能
@Bean
public ChatClient qwenPlusNoMemoryClient(){
ChatClient.Builder builder = ChatClient.builder(dashScopeChatModel);
// 指定模型
builder.defaultOptions(DashScopeChatOptions.builder().withModel("qwen-plus-latest").build());
// 系统提示
builder.defaultSystem("你是一个乐于助人的智能助手");
// 日志打印
SimpleLoggerAdvisor loggerAdvisor = SimpleLoggerAdvisor.builder()
.requestToString(request -> {
List<Message> messageList = request.prompt().getInstructions();
List<Map<String,String>> textList = messageList.stream().map(message -> Map.of("type", message.getMessageType().name(), "content", message.getText())).toList();
return JSON.toJSONString(textList);
})
.responseToString(response -> JSON.toJSONString(response.getResult().getOutput())).build();
builder.defaultAdvisors(loggerAdvisor);
return builder.build();
}
graph 图编写
可以将graph图类比为流程图,它由节点和连接线(边)构成。流程图中包含大量变量,这些变量会被节点或带有判断条件的边所使用。Spring AI Alibaba Graph的工作原理与此相同。
定义变量
可以使用下面的方法,定义graph中的初始变量以及变量的更新策略,目前有追加和替换策略。这些键将贯穿整个工作流,用于在节点之间传递数据
OverAllStateFactory stateFactory = () -> {
OverAllState state = new OverAllState();
state.registerKeyAndStrategy("input", new ReplaceStrategy()); // 输入
state.registerKeyAndStrategy("outline", new ReplaceStrategy()); // 大纲
state.registerKeyAndStrategy("report", new ReplaceStrategy()); // 报告
return state;
};
定义节点
Spring AI Alibaba Graph 中提供大量预置节点,这些节点可以对标到市面上主流的如 Dify、百炼等低代码平台,方便用户快速串联工作流应用。
典型节点包括 LlmNode(大模型节点)、QuestionClassifierNode(问题分类节点)、ToolNode(工具节点)等,为用户免去重复开发、定义的负担,只需要专注流程串联。
在Graph 中自定义节点十分方便,实现NodeAction接口即可,这个接口只有一个方法apply。它接收当前的OverAllState作为输入,允许节点访问工作流的全部上下文。执行完毕后,它返回一个Map<String, Object>,这个Map中的键值对将被合并到OverAllState中,供后续节点使用
public interface NodeAction {
Map<String, Object> apply(OverAllState state);
}
下面使用了框架自带的llm节点,节点的作用是调用大模型,返回大模型处理结果
// 接受用户输入,生成写作大纲
LlmNode llmNode1 = LlmNode.builder()
.systemPromptTemplate("你是一位专业的写作大纲生成专家,根据用户的需求,生成合适的大纲。直接生成可以使用的大纲,不要添加无关信息")
.userPromptTemplateKey("input") // 节点的输入变量
.chatClient(qwenPlusNoMemoryClient)
.outputKey("outline") // 指定节点的输出数据存放在outline变量中
.build();
// 获取大纲,生成详细内容
LlmNode llmNode2 = LlmNode.builder()
.systemPromptTemplate("你是一位专业的写作专家,根据用户提供的的大纲,编写一个易于阅读的报告")
.userPromptTemplateKey("outline")
.chatClient(qwenPlusNoMemoryClient)
.messages(new ArrayList<>())
.outputKey("report").build();
定义流程图
下面创建了StateGraph 指定了节点和节点之间的边,需要注意的这里创建了一个variableParsing节点用于处理llm节点返回的AssistantMessage变量,提取其中的输出。
StateGraph graph = new StateGraph("写作工作流", stateFactory)
// 添加节点 -- 大纲生成节点
.addNode("llm1", node_async(llmNode1))
// 添加节点 -- 变量处理节点
.addNode("variableParsing",node_async(t ->{
AssistantMessage message = (AssistantMessage)t.value("outline").orElseThrow();
return Map.of("outline",message.getText());
}))
// 添加节点 -- 详细内容生成节点
.addNode("llm2", node_async(llmNode2))
// 定义流程图的节点与节点之间的边,START表示开始节点,END表示结束节点
.addEdge(START, "llm1") // 开始节点 --> 大纲生成节点
.addEdge("llm1", "variableParsing") // 生成大纲 --> 变量处理节点
.addEdge("variableParsing", "llm2") // 变量处理节点 --> 详细内容生成节点
.addEdge("llm2", END); // 详细内容生成节点 --> 结束节点
下面的方法可以打印graph图结构,使用PLANTUML插件可以进行可视化
GraphRepresentation graphRepresentation = stateGraph.getGraph(GraphRepresentation.Type.PLANTUML,
"workflow graph");
System.out.println("\n\n");
System.out.println(graph.content());
System.out.println("\n\n");
最后是编译图
return graph.compile();
完整代码
完整的代码如下
private ChatClient qwenPlusNoMemoryClient;
public CompiledGraph demo2() throws GraphStateException {
OverAllStateFactory stateFactory = () -> {
OverAllState state = new OverAllState();
state.registerKeyAndStrategy("input", new ReplaceStrategy()); // 输入
state.registerKeyAndStrategy("outline", new ReplaceStrategy()); // 大纲
state.registerKeyAndStrategy("report", new ReplaceStrategy()); // 报告
return state;
};
// 接受用户输入,生成写作大纲
LlmNode llmNode1 = LlmNode.builder()
.systemPromptTemplate("你是一位专业的写作大纲生成专家,根据用户的需求,生成合适的大纲。直接生成可以使用的大纲,不要添加无关信息")
.userPromptTemplateKey("input")
.chatClient(qwenPlusNoMemoryClient)
.outputKey("outline").build();
// 获取大纲,生成详细内容
LlmNode llmNode2 = LlmNode.builder()
.systemPromptTemplate("你是一位专业的写作专家,根据用户提供的的大纲,编写合适的报告")
.userPromptTemplateKey("outline")
.chatClient(qwenPlusNoMemoryClient)
.messages(new ArrayList<>())
.outputKey("report").build();
// 定义节点与节点之间的边
StateGraph graph = new StateGraph("写作工作流", stateFactory)
// 添加节点 -- 大纲生成节点
.addNode("llm1", node_async(llmNode1))
// 添加节点 -- 变量处理节点
.addNode("variableParsing",node_async(t ->{
AssistantMessage message = (AssistantMessage)t.value("outline").orElseThrow();
return Map.of("outline",message.getText());
}))
// 添加节点 -- 详细内容生成节点
.addNode("llm2", node_async(llmNode2))
// 定义流程图的节点与节点之间的边,START表示开始节点,END表示结束节点
.addEdge(START, "llm1") // 开始节点 --> 大纲生成节点
.addEdge("llm1", "variableParsing") // 生成大纲 --> 变量处理节点
.addEdge("variableParsing", "llm2") // 变量处理节点 --> 详细内容生成节点
.addEdge("llm2", END); // 详细内容生成节点 --> 结束节点
// 打印 PlantUML 流程图
printGraphImage(graph);
// 编译流程图
return graph.compile();
}
private static void printGraphImage(StateGraph stateGraph) {
GraphRepresentation graphRepresentation = stateGraph.getGraph(GraphRepresentation.Type.PLANTUML,
"workflow graph");
System.out.println("\n\n");
System.out.println(graphRepresentation.content());
System.out.println("\n\n");
}
启动
这里使用了graphService.demo2()获取了我们之前创建的编译后的graph图对象,然后使用invoke方法启动流程图,并且传递了流程变量值。
CompiledGraph提供了多种执行方式:
• invoke(): 以阻塞的方式执行整个工作流,并一次性返回最终的状态结果。适用于不需要实时反馈的场景。
• stream(): 以非阻塞的、流式的方式执行工作流。每当一个节点完成(甚至在节点执行过程中,如果节点本身支持流式输出),就会立刻返回一个NodeOutput事件。这对于需要向前端实时展示进度的Web应用来说是绝佳选择。
@GetMapping(value = "/graph/demo2",produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Object demo2(String question) throws Exception {
CompiledGraph compiledGraph = graphService.demo2();
Optional<OverAllState> invoke = compiledGraph.invoke(Map.of("input", question));
Map<String, Object> stringObjectMap = invoke.map(OverAllState::data).orElse(new HashMap<>());
// 这里可以获取流程中全部的变量
System.out.println(stringObjectMap.get("input"));
System.out.println(stringObjectMap.get("outline"));
System.out.println(stringObjectMap.get("report"));
return JSON.toJSONString(stringObjectMap.get("report"));
}
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐



所有评论(0)