Spring AI Alibaba 学习(三):Graph Workflow 深度解析(上篇)

📌 系列文章


目录

[📌 系列文章](#📌 系列文章)

[📖 前言](#📖 前言)

[一、Graph Workflow 是什么?](#一、Graph Workflow 是什么?)

[1.1 核心概念](#1.1 核心概念)

​编辑

[1.2 Graph vs 可视化拖拽平台](#1.2 Graph vs 可视化拖拽平台)

[二、Graph Workflow 三大核心组件](#二、Graph Workflow 三大核心组件)

[2.1 State(状态)](#2.1 State(状态))

[2.2 Node(节点)](#2.2 Node(节点))

[2.3 Edge(边)](#2.3 Edge(边))

[三、构建你的第一个 Graph Workflow](#三、构建你的第一个 Graph Workflow)

[3.1 完整流程](#3.1 完整流程)

[3.2 小Demo案例🌰:智能客服路由](#3.2 小Demo案例🌰:智能客服路由)

四、状态管理:KeyStrategy

[4.1 什么是 KeyStrategy?](#4.1 什么是 KeyStrategy?)

[4.2 什么是 KeyStrategyFactory?](#4.2 什么是 KeyStrategyFactory?)

[4.3 为什么需要 KeyStrategy?](#4.3 为什么需要 KeyStrategy?)

[4.4 工作原理](#4.4 工作原理)

[4.5 内置策略详解](#4.5 内置策略详解)

[1️⃣ ReplaceStrategy(替换策略)](#1️⃣ ReplaceStrategy(替换策略))

[2️⃣ AppendStrategy(追加策略)](#2️⃣ AppendStrategy(追加策略))

[3️⃣ 自定义策略](#3️⃣ 自定义策略)

[4.6 完整的实现 KeyStrategyFactory 示例](#4.6 完整的实现 KeyStrategyFactory 示例)

[4.7 KeyStrategy 最佳实践](#4.7 KeyStrategy 最佳实践)

[✅ 好的实践](#✅ 好的实践)

[❌ 不好的实践](#❌ 不好的实践)

[4.8 默认行为](#4.8 默认行为)

[🔄 五、边的类型详解](#🔄 五、边的类型详解)

[5.1 普通边(固定路由)](#5.1 普通边(固定路由))

[5.2 条件边(相当于动态路由)](#5.2 条件边(相当于动态路由))

[5.3 并行边](#5.3 并行边)

[📊 六、可视化导出](#📊 六、可视化导出)

[🎯 七、Graph vs ReactAgent](#🎯 七、Graph vs ReactAgent)

[7.1 ReactAgent 的本质](#7.1 ReactAgent 的本质)

[7.2 何时使用 Graph?何时使用 ReactAgent?](#7.2 何时使用 Graph?何时使用 ReactAgent?)

[📚 八、总结](#📚 八、总结)

[8.1 核心要点回顾](#8.1 核心要点回顾)

[8.2 Graph Workflow vs ReactAgent](#8.2 Graph Workflow vs ReactAgent)

[📚 参考资源](#📚 参考资源)


📖 前言

前两篇我们一起啃完了 RAG 和 Agent 架构,相信大家已经有点感觉啦~当 LLM 配上 Agent 框架 + 检索增强,直接从只会对话的 "单纯大模型",进化成有脑子、有小手、还带记忆的智能体!

不过你肯定也发现了 ------单个 Agent 再强,也扛不住太复杂的任务 。复杂任务往往需要多步骤编排、条件分支、并行执行、状态持久化,这些靠单点智能体根本玩不转。

所以今天博主直接爆肝 (真的肝痛预警🥲),带大家深挖 Spring AI Alibaba 的底层核心:Graph Workflow 图工作流------ 用可视化、可编排的图结构,让多步骤、多模块的 AI 任务真正跑起来!

++你可能会问:我们不是已经学过 ReactAgent 了吗?为什么还要学 Graph Workflow?++

答案是:ReactAgent 本质上就是基于 Graph Workflow 实现的! 理解 Graph Workflow,你就能:

  • ✅ 构建比 ReactAgent 更复杂的 AI 应用
  • ✅ 实现自定义的 Agent 编排逻辑
  • ✅ 掌握 Spring AI Alibaba 的底层原理

本文将涵盖

  • ✅ Graph Workflow 核心概念
  • ✅ 与可视化拖拽平台的对比
  • ✅ StateGraph 的构建方法
  • ✅ Node、Edge、State 详解
  • ✅ 完整实战案例

一、Graph Workflow 是什么?

1.1 核心概念

Graph Workflow(图工作流) 是一种基于有向图(DAG)**(图论的警觉!)**的流程编排机制。

1.2 Graph vs 可视化拖拽平台

说到工作流,很多人第一反应会是:都叫 "图" 了,那不就是可视化拖拽拼装吗?这里先明确一点:Spring AI Alibaba 的 Graph Workflow 并不是可视化拖拽平台。它和 n8n、Zapier、飞书多维表格自动化这类低代码工具,定位完全不同。

特性 可视化拖拽平台 Spring AI Graph Workflow
定义方式 🖱️ UI 拖拽 💻 Java 代码
适用人群 业务人员、零代码用户 开发者
灵活性 受平台能力限制 完全可编程,自由度拉满
调试 平台日志查看 代码级调试
版本控制 平台内管理 Git 友好
可视化 ✅ 原生支持 ✅ 可导出 Mermaid/PlantUML 图

二者在底层逻辑上其实是相通的,本质都是有向图(DAG),都由节点(Node)和边(Edge)构成,支持条件分支、循环逻辑,也都会管理任务的状态流转。

但它们的设计思路截然不同:Spring AI Graph 没有提供拖拽式 UI,也不主打零代码,而是以代码为核心,更适合复杂 AI 应用的工程化落地。同时它又保留了可视化能力,能通过代码直接生成流程图,在可编程性与可读性之间做到了很好的平衡。


二、Graph Workflow 三大核心组件

2.1 State(状态)

State 是什么?

  • 在整个 Graph 执行过程中流转的数据
  • 本质是 Map<String, Object>
  • 每个节点都可以读取和修改 State
java 复制代码
// State 示例
Map<String, Object> state = Map.of(
    "user_query", "北京今天天气怎么样?",
    "intent", "weather_query",
    "result", "北京今天晴,15°C",
    "messages", List.of("msg1", "msg2")
);

2.2 Node(节点)

Node 是什么?

  • 执行具体业务逻辑的单元
  • 接收 State,处理后返回新的 State
  • 可以是同步或异步
java 复制代码
// Node 的本质
// State → Node → State

// 实现一个 Node
public class WeatherNode implements NodeAction {
    @Override
    public Map<String, Object> apply(OverAllState state) throws Exception {
        // 1. 读取 State
        String city = (String) state.value("city").orElse("北京");
        
        // 2. 执行业务逻辑
        String weather = getWeather(city);
        
        // 3. 返回新的 State
        return Map.of("weather", weather);
    }
}

2.3 Edge(边)

Edge 是什么?

  • 连接节点的路径
  • 定义执行顺序
  • 支持条件路由
java 复制代码
// 普通边:固定路由
workflow.addEdge("nodeA", "nodeB");

// 条件边:动态路由
workflow.addConditionalEdges("classifier",
    edge_async(state -> {
        String intent = (String) state.value("intent").orElse("default");
        return intent;  // 返回下一个节点名称
    }),
    Map.of(
        "weather", "weather_node",
        "news", "news_node",
        "default", "default_node"
    )
);

三、构建你的第一个 Graph Workflow

3.1 完整流程

java 复制代码
// 1. 定义 KeyStrategyFactory(状态更新策略)
KeyStrategyFactory keyStrategyFactory = () -> {
    Map<String, KeyStrategy> strategies = new HashMap<>();
    strategies.put("messages", new AppendStrategy());   // 追加
    strategies.put("result", new ReplaceStrategy());    // 替换
    return strategies;
};

// 2. 创建 StateGraph
StateGraph workflow = new StateGraph(keyStrategyFactory);

// 3. 添加节点
workflow.addNode("nodeA", node_async(new NodeA()));
workflow.addNode("nodeB", node_async(new NodeB()));

// 4. 添加边
workflow.addEdge(START, "nodeA");
workflow.addEdge("nodeA", "nodeB");
workflow.addEdge("nodeB", END);

// 5. 编译
CompiledGraph compiled = workflow.compile();

// 6. 执行
Map<String, Object> initialState = Map.of("input", "Hello");
OverAllState result = compiled.invoke(initialState, config).orElseThrow();

3.2 小Demo案例🌰:智能客服路由

java 复制代码
public class CustomerServiceGraph {
    
    // 节点 1: 意图分类
    public static class ClassifyNode implements NodeAction {
        private final ChatClient chatClient;
        
        @Override
        public Map<String, Object> apply(OverAllState state) {
            String query = (String) state.value("user_query").orElse("");
            
            String prompt = String.format("""
                分类用户意图(只返回类别):
                - technical: 技术问题
                - billing: 账单问题
                - general: 一般咨询
                
                用户问题:%s
                """, query);
            
            String intent = chatClient.prompt()
                .user(prompt)
                .call()
                .content()
                .trim()
                .toLowerCase();
            
            return Map.of("intent", intent);
        }
    }
    
    // 节点 2: 技术支持
    public static class TechnicalSupportNode implements NodeAction {
        @Override
        public Map<String, Object> apply(OverAllState state) {
            String query = (String) state.value("user_query").orElse("");
            // 搜索技术文档
            String answer = searchTechDocs(query);
            return Map.of("answer", answer);
        }
    }
    
    // 节点 3: 账单处理
    public static class BillingNode implements NodeAction {
        @Override
        public Map<String, Object> apply(OverAllState state) {
            String query = (String) state.value("user_query").orElse("");
            // 查询账单系统
            String answer = queryBillingSystem(query);
            return Map.of("answer", answer);
        }
    }
    
    // 构建 Graph
    public static CompiledGraph createGraph(ChatModel chatModel) {
        ChatClient.Builder builder = ChatClient.builder(chatModel);
        
        KeyStrategyFactory factory = () -> {
            Map<String, KeyStrategy> strategies = new HashMap<>();
            strategies.put("user_query", new ReplaceStrategy());
            strategies.put("intent", new ReplaceStrategy());
            strategies.put("answer", new ReplaceStrategy());
            return strategies;
        };
        
        StateGraph workflow = new StateGraph(factory)
            .addNode("classify", node_async(new ClassifyNode(builder.build())))
            .addNode("technical", node_async(new TechnicalSupportNode()))
            .addNode("billing", node_async(new BillingNode()))
            .addNode("general", node_async(new GeneralNode()));
        
        // 添加边
        workflow.addEdge(START, "classify");
        
        // 条件路由
        workflow.addConditionalEdges("classify",
            edge_async(state -> (String) state.value("intent").orElse("general")),
            Map.of(
                "technical", "technical",
                "billing", "billing",
                "general", "general"
            )
        );
        
        workflow.addEdge("technical", END);
        workflow.addEdge("billing", END);
        workflow.addEdge("general", END);
        
        return workflow.compile();
    }
    
    // 使用
    public static void main(String[] args) {
        CompiledGraph graph = createGraph(chatModel);
        
        Map<String, Object> initialState = Map.of(
            "user_query", "我的软件无法启动"
        );
        
        var config = RunnableConfig.builder()
            .threadId("customer_001")
            .build();
        
        // 流式执行
        graph.stream(initialState, config)
            .doOnNext(output -> {
                System.out.println("节点: " + output.node());
                System.out.println("状态: " + output.state().data());
            })
            .blockLast();
    }
}

四、状态管理:KeyStrategy

4.1 什么是 KeyStrategy?

KeyStrategy(键策略) 是 Graph Workflow 中用于控制状态更新行为的核心机制。

java 复制代码
// KeyStrategy 的本质是一个函数
public interface KeyStrategy {
    Object apply(Object oldValue, Object newValue);
}

作用:当节点返回新的状态数据时,KeyStrategy 决定如何将新值与旧值合并。

4.2 什么是 KeyStrategyFactory?

KeyStrategyFactory 是一个工厂接口,用于为 State 中的每个键指定更新策略。

java 复制代码
public interface KeyStrategyFactory {
    Map<String, KeyStrategy> getStrategies();
}

// 使用示例
KeyStrategyFactory factory = () -> {
    Map<String, KeyStrategy> strategies = new HashMap<>();
    strategies.put("messages", new AppendStrategy());   // messages 使用追加策略
    strategies.put("result", new ReplaceStrategy());    // result 使用替换策略
    strategies.put("counter", new CustomStrategy());    // counter 使用自定义策略
    return strategies;
};

4.3 为什么需要 KeyStrategy?

问题场景:当多个节点修改同一个 State 键时,如何合并?

KeyStrategy 就是用来解决这个问题的!

4.4 工作原理

4.5 内置策略详解

1️⃣ ReplaceStrategy(替换策略)

用途:直接用新值替换旧值(最常用)

java 复制代码
strategies.put("result", new ReplaceStrategy());

// 行为示例:
// oldValue: "old result"
// newValue: "new result"
// 返回: "new result"  ← 直接替换

// 适用场景:
// - 单一结果值(result、answer、status)
// - 配置项(config、settings)
// - 标识符(id、type、intent)
2️⃣ AppendStrategy(追加策略)

用途:将新值追加到列表中

java 复制代码
strategies.put("messages", new AppendStrategy());

// 行为示例:
// oldValue: ["msg1", "msg2"]
// newValue: "msg3"
// 返回: ["msg1", "msg2", "msg3"]  ← 追加到列表

// 如果 oldValue 不是列表:
// oldValue: "msg1"
// newValue: "msg2"
// 返回: ["msg1", "msg2"]  ← 自动转换为列表

// 适用场景:
// - 消息历史(messages、history)
// - 日志记录(logs、events)
// - 收集结果(results、findings)
3️⃣ 自定义策略

用途:实现自己的合并逻辑

java 复制代码
// 示例 1: 累加策略(数值累加)
strategies.put("counter", (oldValue, newValue) -> {
    int old = oldValue != null ? (int) oldValue : 0;
    int add = (int) newValue;
    return old + add;
});

// 使用:
// oldValue: 5
// newValue: 3
// 返回: 8

// 示例 2: Map 合并策略
strategies.put("metadata", (oldValue, newValue) -> {
    Map<String, Object> old = (Map<String, Object>) oldValue;
    Map<String, Object> newMap = (Map<String, Object>) newValue;
    Map<String, Object> merged = new HashMap<>(old);
    merged.putAll(newMap);  // 新值覆盖旧值
    return merged;
});

// 使用:
// oldValue: {"key1": "value1", "key2": "value2"}
// newValue: {"key2": "new_value2", "key3": "value3"}
// 返回: {"key1": "value1", "key2": "new_value2", "key3": "value3"}

// 示例 3: 去重追加策略
strategies.put("tags", (oldValue, newValue) -> {
    Set<String> tags = new HashSet<>((List<String>) oldValue);
    tags.add((String) newValue);
    return new ArrayList<>(tags);
});

4.6 完整的实现 KeyStrategyFactory 示例

java 复制代码
public class MyKeyStrategyFactory implements KeyStrategyFactory {
    
    @Override
    public Map<String, KeyStrategy> getStrategies() {
        Map<String, KeyStrategy> strategies = new HashMap<>();
        
        // 1. 单一值使用替换策略
        strategies.put("user_query", new ReplaceStrategy());
        strategies.put("intent", new ReplaceStrategy());
        strategies.put("result", new ReplaceStrategy());
        strategies.put("status", new ReplaceStrategy());
        
        // 2. 列表使用追加策略
        strategies.put("messages", new AppendStrategy());
        strategies.put("history", new AppendStrategy());
        strategies.put("logs", new AppendStrategy());
        
        // 3. 数值使用累加策略
        strategies.put("token_count", (oldValue, newValue) -> {
            int old = oldValue != null ? (int) oldValue : 0;
            return old + (int) newValue;
        });
        
        // 4. Map 使用合并策略
        strategies.put("metadata", (oldValue, newValue) -> {
            if (oldValue == null) return newValue;
            Map<String, Object> merged = new HashMap<>((Map) oldValue);
            merged.putAll((Map) newValue);
            return merged;
        });
        
        return strategies;
    }
}

// 使用
StateGraph workflow = new StateGraph(new MyKeyStrategyFactory());

4.7 KeyStrategy 最佳实践

✅ 好的实践
java 复制代码
KeyStrategyFactory factory = () -> {
    Map<String, KeyStrategy> strategies = new HashMap<>();
    
    // 1. 明确每个键的用途
    strategies.put("user_input", new ReplaceStrategy());      // 用户输入
    strategies.put("conversation", new AppendStrategy());     // 对话历史
    strategies.put("final_answer", new ReplaceStrategy());    // 最终答案
    
    // 2. 为所有可能的键都定义策略
    strategies.put("error", new ReplaceStrategy());
    strategies.put("metadata", new ReplaceStrategy());
    
    return strategies;
};
❌ 不好的实践
java 复制代码
KeyStrategyFactory factory = () -> {
    Map<String, KeyStrategy> strategies = new HashMap<>();
    
    // ❌ 只定义部分键(其他键会使用默认策略,可能导致意外行为)
    strategies.put("messages", new AppendStrategy());
    
    // ❌ 策略选择不当
    strategies.put("result", new AppendStrategy());  // result 应该用 ReplaceStrategy
    
    return strategies;
};

4.8 默认行为

如果某个键没有定义 KeyStrategy,会怎样?

复制代码
// 默认行为:使用 ReplaceStrategy
// 即:新值直接替换旧值

建议:为所有可能出现的键都显式定义策略,避免依赖默认行为。


🔄 五、边的类型详解

5.1 普通边(固定路由)

java 复制代码
// 单个边
workflow.addEdge("nodeA", "nodeB");

// 从 START 到节点
workflow.addEdge(START, "nodeA");

// 从节点到 END
workflow.addEdge("nodeZ", END);

5.2 条件边(相当于动态路由)

java 复制代码
workflow.addConditionalEdges(
    "classifier",                    // 源节点
    edge_async(state -> {            // 条件函数
        String type = (String) state.value("type").orElse("default");
        
        // 根据 State 决定下一个节点
        if ("urgent".equals(type)) {
            return "urgent_handler";
        } else if ("normal".equals(type)) {
            return "normal_handler";
        } else {
            return "default_handler";
        }
    }),
    Map.of(                          // 路由映射
        "urgent_handler", "urgent_handler",
        "normal_handler", "normal_handler",
        "default_handler", "default_handler"
    )
);

5.3 并行边

java 复制代码
// A 完成后,B1/B2/B3 并行执行
workflow.addEdge("A", List.of("B1", "B2", "B3"));

// B1/B2/B3 都完成后,执行 C
workflow.addEdge(List.of("B1", "B2", "B3"), "C");

📊 六、可视化导出

虽然 Graph Workflow 是代码定义的,但可以导出为可视化图表!

java 复制代码
// 导出为 Mermaid 图
GraphRepresentation mermaid = workflow.getGraph(
    GraphRepresentation.Type.MERMAID,
    "Customer Service Workflow"
);
System.out.println(mermaid.getContent());

// 输出:
// graph TD
//     __START__ --> classify
//     classify --> technical
//     classify --> billing
//     classify --> general
//     technical --> __END__
//     billing --> __END__
//     general --> __END__

将输出的 Mermaid 代码粘贴到支持 Mermaid 的平台(如 GitHub、Notion),即可看到可视化图表!


🎯 七、Graph vs ReactAgent

7.1 ReactAgent 的本质

ReactAgent 本质上就是一个预定义的 StateGraph!

java 复制代码
// ReactAgent 内部实际上是这样的 Graph:
StateGraph reactGraph = new StateGraph()
    .addNode("llm_node", llmNode)        // 调用 LLM
    .addNode("tool_node", toolNode)      // 执行工具
    .addEdge(START, "llm_node")
    .addConditionalEdges("llm_node",
        edge_async(state -> {
            // 如果需要调用工具,路由到 tool_node
            // 否则结束
            return needsTools(state) ? "tool_node" : END;
        }),
        Map.of("tool_node", "tool_node", END, END)
    )
    .addEdge("tool_node", "llm_node");   // 工具执行后回到 LLM

7.2 何时使用 Graph?何时使用 ReactAgent?

场景 使用 原因
标准的 ReAct 循环 ReactAgent 开箱即用
自定义复杂流程 StateGraph 完全控制
多个 Agent 协作 StateGraph 灵活编排
需要人工介入 StateGraph 支持中断点
并行执行任务 StateGraph 支持并行边

📚 八、总结

8.1 核心要点回顾

  1. Graph Workflow 是底层机制

    • ReactAgent 基于 Graph 实现
    • 提供完全的控制能力
  2. 三大核心组件

    • State:数据流转
    • Node:业务逻辑
    • Edge:执行顺序
  3. 与可视化平台的对比

    • 本质相同(都是 DAG)
    • 形式不同(代码 vs UI)
    • 各有优势
  4. 灵活的编排能力

    • 条件路由
    • 并行执行
    • 状态管理

8.2 Graph Workflow vs ReactAgent

特性 Graph Workflow ReactAgent
核心能力 自定义流程编排 标准 ReAct 循环
适用场景 复杂流程、多 Agent 协作 标准工具调用
可组合性 ✅ ReactAgent 基于它实现 ✅ 可以作为 Graph 的节点

📚 参考资源

  • 官方文档https://java2ai.com
  • 官方GitHubhttps://github.com/alibaba/spring-ai-alibaba
  • 系列文章
    • Spring AI Alibaba 学习(一):RAG 架构详解
    • Spring AI Alibaba 学习(二):Agent 智能体架构深度解析
    • Spring AI Alibaba 学习(三):Graph Workflow 深度解析(上篇)(本文)

下一篇预告《Spring AI Alibaba 学习(三):Graph Workflow 与 DeepResearch 实战(下篇)》,我们将用 Graph Workflow 实现 DeepResearch,什么是DeepResearch呢?

相关推荐
星始流年1 小时前
从 Tool 到 Skill——基于 LangChain 的服务端Skill实现
前端·langchain·agent
菜鸟谢1 小时前
Rust 智能指针完整详解
后端
凌奕1 小时前
让你的 AI 编程助手「偷懒」:50k Star 的 Ponytail,让 Agent 少写一半代码
chatgpt·agent·claude
大模型真好玩1 小时前
什么是Loop Engineering?最通俗易懂的Loop Engineering核心概念
人工智能·agent·deepseek
菜鸟谢1 小时前
Rust 函数完整知识点详解
后端
叁两1 小时前
前端转型AI Agent该如何学习?(前置篇)
前端·人工智能·node.js
爱勇宝1 小时前
淡泊名利之前,先承认我们都很焦虑
前端·后端·程序员
菜鸟谢1 小时前
Rust 闭包(Closure)完整详解
后端
ServBay1 小时前
如何利用本地技术栈构建 0 成本 AI SaaS 雏形
后端·aigc·ai编程
菜鸟谢1 小时前
Rust 集合 + 迭代器完整详解
后端