SpringAi-alibaba Graph 工作流编排1

1.SAA Graph基础介绍

1.1基础介绍

Spring AI Alibaba Graph理解为一个专为Java开发者设计的智能体工作流编排框架。它的设计理念很大程度上借鉴了著名的LangGraph,可以看作是LangGraph在Java生态中的实现 。它允许你将一个复杂的AI任务拆解成一系列由节点(Node)和边(Edge)组成的图(Graph),并通过一个共享的状态(State)来驱动整个流程的执行。

1.2 核心概念

状态(State):状态是整个图执行过程中的"共享记忆" 。它本质上是一个 Map<String, Object>,负责在节点之间传递数据 。例如,用户输入、模型生成的中间结果、最终答案等,都存储在State中。状态的管理策略是理解它的关键。State中的每个字段都可以定义自己的更新策略,这决定了当多个节点都想修改同一个字段时,最终结果是什么 。最常用的3种策略是:

ReplaceStrategy(覆盖策略):这是默认策略。新值会直接覆盖旧值 。适用于只需保留最终结果的场景,比如"最终答案"。

AppendStrategy(追加策略):新值会被追加到旧值的列表中 。这对于需要记录完整过程的场景至关重要,比如保存对话历史(messages),或者记录一个任务的多个中间思考步骤。

MergeStrategy (聚合策略):对数据进行聚合,如Map类型的数据聚合

节点(Node ):节点是图里的基本执行单元,负责完成一项具体的工作 。一个节点可以是任何逻辑,比如调用一次大模型、执行一个本地函数、或者调用一个外部工具 。每个节点接收当前的State作为输入,执行自己的任务,然后返回对State的更新(一个Map增量)。Spring AI Alibaba提供了很多开箱即用的节点,比如:

LlmNode:用于调用大语言模型。

QuestionClassifierNode:用于对输入进行分类。

ToolNode:用于执行工具调用。

你也可以通过实现 NodeAction 接口来创建完全自定义的节点 。

边(Edge):边定义了节点之间的控制流,即一个节点执行完后,接下来该去哪个节点 。边有两种主要类型:

直接边(Direct Edge):从一个节点无条件地连接到下一个节点,形成顺序执行流 。

条件边(Conditional Edge):根据当前State中的信息,动态地决定下一个执行的节点,从而实现分支逻辑 。例如,一个"分类器"节点执行后,可以通过条件边判断:如果结果是"positive",则走"结束"节点;如果结果是"negative",则走"售后处理"节点 。

1.3 核心构建块

**StateGraph:**你可以把它看作一个"图构建器"。它提供了一系列流畅的API(如.addNode(), .addEdge(), .addConditionalEdges())来帮助你组装节点和边,从而定义整个工作流的蓝图 。

CompiledGraph:当你用StateGraph定义好蓝图后,需要调用.compile()方法来编译它 。编译过程会进行一些基本的检查(比如确保没有孤立节点),并生成一个可执行的、不可变的图 。之后,你就可以通过调用CompiledGraph的invoke()或stream()方法来启动并执行这个工作流了 。

2.项目实操

2.1.创建Springboot项目,pom添加核心依赖

xml 复制代码
  <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring-boot.version>3.4.0</spring-boot.version>
        <spring-ai.version>1.0.0</spring-ai.version>
        <spring-ai-alibaba.version>1.0.0.4</spring-ai-alibaba.version>
    </properties>


    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- Spring AI -->
            <dependency>
                <groupId>org.springframework.ai</groupId>
                <artifactId>spring-ai-bom</artifactId>
                <version>${spring-ai.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <!-- Spring AI -->
            <dependency>
                <groupId>com.alibaba.cloud.ai</groupId>
                <artifactId>spring-ai-alibaba-bom</artifactId>
                <version>${spring-ai-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    
    <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>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-graph-core</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
    </dependencies>

2.2 添加核心配置 application.yml

yaml 复制代码
server:
  port: 8085
  servlet:
    encoding:
      charset: UTF-8
      force: true
      enabled: true
spring:
  application:
      name: saa-graph-demo-project
  ai:
    zhipuai:
      api-key: your-token-key
      base-url: "https://open.bigmodel.cn/api/paas"
      chat:
        options:
          model: GLM-4.7

2.3 核心java代码

2.3.1 GraphCofig 配置类
java 复制代码
package com.java.ai.graph.config;

import com.alibaba.cloud.ai.graph.*;
import com.alibaba.cloud.ai.graph.exception.GraphStateException;
import com.alibaba.cloud.ai.graph.state.strategy.ReplaceStrategy;
import com.java.ai.graph.edge.ReJokeConditionEdgeAction;
import com.java.ai.graph.node.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Map;

import static com.alibaba.cloud.ai.graph.action.AsyncNodeAction.node_async;

@Slf4j
@Configuration
public class GraphCofig {
  
    /**
     * keyStrategyFactory :3个策略 替换,合并 map类型,追加 List类型
     * state
     *
     * @return
     * @throws GraphStateException
     */
    @Bean
    public CompiledGraph jokeGraph(ChatClient.Builder chatClientBuilder) throws GraphStateException {

        KeyStrategyFactory keyStrategyFactory = new KeyStrategyFactory() {
            @Override
            public Map<String, KeyStrategy> apply() {
                return Map.of("topic", new ReplaceStrategy());
            }
        };
        //定义状态图state
        StateGraph startGraph = new StateGraph("jokeGraph", keyStrategyFactory);
        //定义节点node  nodeAction 定义节点异步执行逻辑
        startGraph.addNode("GenerateJokeNode", node_async(new GenerateJokeNode(chatClientBuilder)));
        startGraph.addNode("LoopEvaluateJokeNode", node_async(new LoopEvaluateJokeNode(chatClientBuilder,10,5)));

        //定义边edge
        //1.生成笑话->2.评估笑话->3.重新生成笑话
        startGraph.addEdge(StateGraph.START, "GenerateJokeNode");
        startGraph.addEdge("GenerateJokeNode", "LoopEvaluateJokeNode");
        //loop条件边判断
        ReJokeConditionEdgeAction conditionEdgeAction = new ReJokeConditionEdgeAction();
        startGraph.addConditionalEdges("LoopEvaluateJokeNode",
                conditionEdgeAction,
                Map.of("break", StateGraph.END, "loop", "GenerateJokeNode"));
        //graph 可视化图
        GraphRepresentation graphRepresentation = startGraph.getGraph(GraphRepresentation.Type.PLANTUML, "JokeGraph");
        log.info("graphRepresentation:{}", graphRepresentation.content());
        //编译状态图
        return startGraph.compile();
    }
}
3.2 自定义node节点

生成笑话节点node

java 复制代码
package com.java.ai.graph.node;

import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.action.NodeAction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;

import java.util.Map;

/**
 * 生成笑话节点node
 */
@Slf4j
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 {
        //从state中获取要翻译的句子
        String topic = state.value("topic", "");
        //模型调用
        PromptTemplate promptTemplate = new PromptTemplate("你是一个讲笑话大师,能够基于给定的主题{topic}进行讲笑话。要求只返回最终讲好的笑话,不要返回其他信息。");
        Prompt prompt = promptTemplate.create(Map.of("topic", topic));
        String content = chatClient
                .prompt(prompt)
                .call().content();
        log.info("joke:{}", content);
        //结果保存在state中
        return Map.of("topic", topic, "joke", content);
    }
}

循环评估笑话的节点

java 复制代码
package com.java.ai.graph.node;

import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.action.NodeAction;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.chat.prompt.PromptTemplate;

import java.util.Map;

/**
 * 评估笑话节点node
 */
@Slf4j
public class LoopEvaluateJokeNode implements NodeAction {

    private final ChatClient chatClient;
    private final Integer limitLoopCount;
    private final Integer targetScore;


    public LoopEvaluateJokeNode(ChatClient.Builder builder, Integer limitLoopCount, Integer targetScore) {
        this.chatClient = builder.build();
        this.limitLoopCount = limitLoopCount;
        this.targetScore = targetScore;
    }

    @Override
    public Map<String, Object> apply(OverAllState state) throws Exception {
        //从state中获取joke
        String joke = state.value("joke", "");
        Integer loopCount = state.value("loopCount", 1);
        //模型调用
        PromptTemplate promptTemplate = new PromptTemplate("你是一个讲笑话评估师,能够基于给定的笑话搞笑层度对{joke}进行评分。要求只返回最终评估结果为 0-10的整数,分数越高越优秀,不要返回其他信息。");
        Prompt prompt = promptTemplate.create(Map.of("joke", joke));
        String scoreStrResult= chatClient
                .prompt(prompt)
                .call().content();
        Integer score = Integer.parseInt(scoreStrResult.trim());
        log.info("score:{}",score);
        String action="loop";
        //分数大于7或者循环次数大于10则结束循环
        if (score > targetScore || loopCount > limitLoopCount){
            action="break";
        }
        //结果保存在state中
        return Map.of("joke", joke, "result", action, "loopCount", loopCount+1);
    }
}

自定义一个条件edge

java 复制代码
package com.java.ai.graph.edge;

import com.alibaba.cloud.ai.graph.OverAllState;
import com.alibaba.cloud.ai.graph.action.AsyncEdgeAction;

import java.util.concurrent.CompletableFuture;

/***
 * 自定义条件边
 */
public class ReJokeConditionEdgeAction implements AsyncEdgeAction {
    @Override
    public CompletableFuture<String> apply(OverAllState state) {
        CompletableFuture<String> result = new CompletableFuture<>();
        try {
            // Get the condition result from the state
            String conditionResult = state.value("result", "break");
            result.complete(conditionResult);
        } catch (Exception e) {
            result.completeExceptionally(e);
        }
        return result;
    }
}

测试调用

java 复制代码
package com.java.ai.graph.controller;

import com.alibaba.cloud.ai.graph.CompiledGraph;
import com.alibaba.cloud.ai.graph.RunnableConfig;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/graph")
public class SpringAIGraphController {

    private final CompiledGraph jokeGraph;

    public SpringAIGraphController(@Qualifier("jokeGraph") CompiledGraph jokeGraph) {
        this.jokeGraph = jokeGraph;
    }


    @GetMapping("/joke")
    public Map joke(@RequestParam("topic") String topic) {
        try {
            return jokeGraph.call(Map.of("topic", topic)).get().data();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new HashMap<>();
    }
}

接口响应结果

bash 复制代码
{
  "result": "break",
  "topic": "爱情",
  "loopCount": 2,
  "joke": "女朋友问男朋友:"亲爱的,你爱我吗?"\n男朋友深情地说:"爱。"\n女朋友又问:"那你愿意为了我去死吗?"\n男朋友坚定地说:"不愿意。"\n女朋友生气了:"为什么?"\n男朋友说:"因为我的爱是至死不渝的。""
}
相关推荐
GISer_Jing5 小时前
浏览器 Agent 插件开发规格书 (SPEC)
前端·ai·前端框架·edge浏览器
特立独行的猫a6 小时前
鸿蒙 PC 命令行工具迁移实战直播课 · pngquant命令行移植实战
华为·ai·harmonyos·vcpkg·鸿蒙pc·lycim
hhb_6186 小时前
【无标题】
ai
俊哥V7 小时前
每日 AI 研究简报 · 2026-05-15
人工智能·ai
沉浸式学习ing8 小时前
B站视频怎么快速总结?AI自动生成要点+思维导图+逐字稿
人工智能·ai·自然语言处理·音视频·语音识别·notion
MatrixOrigin8 小时前
什么是AI Native的组织,它该具备什么样的特点
人工智能·ai·opc
踏着七彩祥云的小丑9 小时前
AI——Dify常见报错与排查
人工智能·ai
wujian83119 小时前
豆包导出pdf方法
人工智能·ai·pdf·豆包·deepseek·ai导出鸭
项目治理之道10 小时前
用 Trace Skills 生成产品原型:从概念到可交互 Demo 的实战经验
ai·交互·skills
笨蛋©10 小时前
[实战] 2026年数字化质量管理:工程图纸识别与检验计划自动化指南
ai·cad·质量管理·制造业·图纸识别