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男朋友说:"因为我的爱是至死不渝的。""
}
相关推荐
Sendingab2 小时前
LuoGen-罗根AI 数字人IP口播视频自动化生成工具
人工智能·ai·数字人·自媒体·ai智能体·口播·罗根
sg_knight3 小时前
OpenClaw 能做什么?几个真实使用场景说明
算法·ai·大模型·llm·agent·openclaw·小龙虾
spencer_tseng3 小时前
Nested Learning The Illusion of Deep Learning
ai
沃和莱特3 小时前
Copy as fetch + Skill:自动化问题记录分析的实践与思考
运维·ai·自动化·编程·skills
码农杂谈00073 小时前
全栈可视化开发新选择 网易 CodeWave 开发效率拉满
人工智能·ai
yingxiao8883 小时前
土耳其拟加强数字平台监管;腾讯或参投派拉蒙收购华纳兄弟交易
游戏·ai·腾讯·手游出海·任天堂·clash royale
marsh02064 小时前
6 OpenClaw架构深度剖析:理解其设计哲学与核心组件
ai·架构·编程·技术
墨10244 小时前
与 AI 并肩成长:从个人知识库到每日新闻系统的实践记录
人工智能·ai·ai编程·openclaw
无心水5 小时前
【OpenClaw:实战部署】10、OpenClaw自动化调度——打造7x24小时无人值守AI工作流
人工智能·ai·ai工作流·openclaw·openclaw·三月创作之星·养龙虾