Spring AI Alibaba 1.x 系列【49】状态图运行时引擎:CompiledGraph 源码解析

文章目录

  • [1. 概述](#1. 概述)
  • [2. 核心字段](#2. 核心字段)
    • [2.1 StateGraph](#2.1 StateGraph)
    • [2.2 CompileConfig](#2.2 CompileConfig)
    • [2.3 nodeFactories](#2.3 nodeFactories)
    • [2.4 edges](#2.4 edges)
    • [2.5 keyStrategyMap](#2.5 keyStrategyMap)
    • [2.6 ProcessedNodesEdgesAndConfig](#2.6 ProcessedNodesEdgesAndConfig)
    • [2.7 maxIterations](#2.7 maxIterations)
  • [3. 构造函数](#3. 构造函数)
    • [3.1 处理节点和边](#3.1 处理节点和边)
    • [3.2 边处理](#3.2 边处理)
  • [4. 核心方法](#4. 核心方法)
    • [4.1 状态管理](#4.1 状态管理)
    • [4.2 图执行流](#4.2 图执行流)
    • [4.3 状态构造 & 克隆](#4.3 状态构造 & 克隆)
    • [4.4 执行入口(同步/响应式调用)](#4.4 执行入口(同步/响应式调用))
    • [4.5 调度执行(定时/异步调度)](#4.5 调度执行(定时/异步调度))
    • [4.6 内部资源访问](#4.6 内部资源访问)
    • [4.7 图可视化](#4.7 图可视化)

1. 概述

在之前我们介绍了 StateGraph 是定义层,CompileConfig 是编译配置,有了这两个以后,就可以将状态图编译为可执行形态,CompiledGraph 就是工作流的核心运行时引擎。

核心职责

职责 说明
图结构存储 持有编译后的节点工厂和边映射
执行入口 提供 invoke()stream() 执行方法
状态管理 状态的获取、更新、历史查询
中断控制 管理 interruptsBeforeinterruptsAfter
持久化协调 CheckpointSaver 协作实现状态持久化
并行执行 支持 ParallelNodeConditionalParallelNode

2. 核心字段

java 复制代码
public class CompiledGraph {

    private static final Logger log = LoggerFactory.getLogger(CompiledGraph.class);
    
    private static String INTERRUPT_AFTER = "__INTERRUPTED__";

    // ========== 核心数据结构 ==========
    
    /** 原始状态图定义 */
    public final StateGraph stateGraph;
    
    /** 编译配置 */
    public final CompileConfig compileConfig;
    
    /** 节点工厂映射 - 线程安全设计 */
    final Map<String, Node.ActionFactory> nodeFactories = new LinkedHashMap<>();
    
    /** 边映射 - 源节点ID → 目标边值 */
    final Map<String, EdgeValue> edges = new LinkedHashMap<>();
    
    /** 键策略映射 - 状态键更新策略 */
    private final Map<String, KeyStrategy> keyStrategyMap;
    
    /** 预处理后的节点和边数据 */
    private final ProcessedNodesEdgesAndConfig processedData;
    
    /** 最大迭代次数 - 防止无限循环 */
    private int maxIterations = 25;
}

2.1 StateGraph

StateGraph 保存原始状态图定义,作为编译的源数据。

java 复制代码
public final StateGraph stateGraph;

2.2 CompileConfig

CompileConfig 编译配置类存储图编译和运行时的所有配置选项。

java 复制代码
public final CompileConfig compileConfig;

2.3 nodeFactories

Map<String, Node.ActionFactory>(节点 ID → 动作工厂)存储所有节点的动作工厂,实现线程安全的节点执行。

java 复制代码
final Map<String, Node.ActionFactory> nodeFactories = new LinkedHashMap<>();

Node.ActionFactory 接口定义:

java 复制代码
public interface ActionFactory {
    AsyncNodeActionWithConfig apply(CompileConfig config) throws GraphStateException;
}

ActionFactory 每次执行都会调用 factory.apply (config) 创建新实例,每个执行上下文获得独立的动作实例,完全避免并发问题,支持每次执行使用不同配置。

如果直接存储 AsyncNodeActionWithConfig 实例,多线程并发执行时,同一实例会被多次调用,实例内部状态可能被并发修改,状态隔离无法保证。


2.4 edges

Map<String, EdgeValue>(源节点ID → 边值)存储编译后的边映射,用于确定节点间的路由关系。

java 复制代码
final Map<String, EdgeValue> edges = new LinkedHashMap<>();

EdgeValue 结构:

java 复制代码
public record EdgeValue(String id, EdgeCondition value) {
    // id: 直接目标节点ID(普通边)
    // value: 条件边定义(条件边)
    
    // 普通边:id != null, value == null
    // 条件边:id == null, value != null
}

2.5 keyStrategyMap

Map<String, KeyStrategy>(状态键 → 更新策略)定义每个状态键的更新策略,控制状态值如何合并或替换。

java 复制代码
private final Map<String, KeyStrategy> keyStrategyMap;

2.6 ProcessedNodesEdgesAndConfig

ProcessedNodesEdgesAndConfig(预处理记录类型)存储图预处理后的结果,包括展开的子图、合并的中断点等。

java 复制代码
private final ProcessedNodesEdgesAndConfig processedData;

ProcessedNodesEdgesAndConfig 数据结构:

java 复制代码
public record ProcessedNodesEdgesAndConfig(
    StateGraph.Nodes nodes,              // 处理后的节点集合
    StateGraph.Edges edges,              // 处理后的边集合
    Set<String> interruptsBefore,        // 处理后的前中断点
    Set<String> interruptsAfter,         // 处理后的后中断点
    Map<String, KeyStrategy> keyStrategyMap  // 合并的键策略
) {
    // 预处理逻辑在静态方法 process() 中
    static ProcessedNodesEdgesAndConfig process(StateGraph stateGraph, CompileConfig config);
}

Record 定义

java 复制代码
public record ProcessedNodesEdgesAndConfig(
    StateGraph.Nodes nodes,              // 处理后的节点集合
    StateGraph.Edges edges,              // 处理后的边集合
    Set<String> interruptsBefore,        // 处理后的前中断点
    Set<String> interruptsAfter,         // 处理后的后中断点
    Map<String, KeyStrategy> keyStrategyMap  // 合并的键策略
) {
    // 预处理逻辑在静态方法 process() 中
    static ProcessedNodesEdgesAndConfig process(StateGraph stateGraph, CompileConfig config);
}

预处理流程

复制代码
┌─────────────────────────────────────────────────────────────────┐
│               ProcessedNodesEdgesAndConfig.process()            │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│   输入:StateGraph + CompileConfig                              │
│                                                                 │
│   处理步骤:                                                    │
│                                                                 │
│   1. 检查子图节点                                               │
│      var subgraphNodes = stateGraph.nodes.onlySubStateGraphNodes();│
│      if (subgraphNodes.isEmpty()) → 直接返回                    │
│                                                                 │
│   2. 处理每个子图                                               │
│      for (var subgraphNode : subgraphNodes) {                   │
│                                                                 │
│          2.1 合并键策略                                         │
│              subgraphNode.keyStrategies().forEach(...);         │
│              processedSubGraph.keyStrategyMap().forEach(...);   │
│                                                                 │
│          2.2 处理子图 START 节点                                │
│              - 将指向子图的边重定向到子图入口                    │
│              - 处理 interruptsBefore 映射                       │
│                                                                 │
│          2.3 处理子图 END 节点                                  │
│              - 将子图的 END 边连接到父图的后续节点               │
│              - 处理 interruptsAfter 映射                        │
│                                                                 │
│          2.4 处理子图内部边                                     │
│              - 添加子图内部边到父图                             │
│              - ID 添加前缀避免冲突                              │
│                                                                 │
│          2.5 处理子图内部节点                                   │
│              - 添加子图节点到父图                               │
│              - ID 添加前缀避免冲突                              │
│      }                                                          │
│                                                                 │
│   输出:ProcessedNodesEdgesAndConfig                            │
│      - nodes: 包含子图展开后的所有节点                          │
│      - edges: 包含子图展开后的所有边                            │
│      - interruptsBefore/After: 映射后的中断点                   │
│      - keyStrategyMap: 合并后的策略映射                         │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

子图 ID 格式化

java 复制代码
// SubStateGraphNode.formatId()
// 将子图节点 ID 格式化为:父图节点ID:子图节点ID
// 例:subgraphA:node1

2.7 maxIterations

最大迭代次数,防止图执行时出现无限循环,限制节点执行的最大次数。

java 复制代码
private int maxIterations = 25;

3. 构造函数

初始化执行步骤

  1. 设置最大迭代次数 :从编译配置中获取递归限制,赋值给当前对象的最大迭代次数属性
  2. 保存原始状态图 :将传入的状态图对象存储为当前类的成员变量,保留原始数据
  3. 初始化键策略映射 :从状态图中获取键策略工厂,执行创建逻辑并转换为 Map 集合,完成策略映射初始化
  4. 处理节点和边 :处理状态图和编译配置,生成节点、边及配置的封装数据
  5. 合并子图的键策略 :遍历处理后的子图策略映射,将不存在于主策略中的键值对进行合并补充
  6. 验证中断节点存在性 :分别校验前置/后置中断节点 ID,若节点不存在则抛出节点不存在异常
  7. 重建编译配置 :基于原有配置,整合中断节点信息,构建新的编译配置对象
  8. 存储节点工厂(线程安全) :遍历所有节点,获取节点动作工厂并校验非空,存入节点工厂集合保证线程安全使用
  9. 评估边并创建并行节点 :遍历处理后的所有边,执行复杂的边逻辑处理与并行节点创建
java 复制代码
protected CompiledGraph(StateGraph stateGraph, CompileConfig compileConfig) throws GraphStateException {
    // 步骤 1: 设置最大迭代次数
    this.maxIterations = compileConfig.recursionLimit();
    
    // 步骤 2: 保存原始状态图
    this.stateGraph = stateGraph;
    
    // 步骤 3: 初始化键策略映射
    this.keyStrategyMap = stateGraph.getKeyStrategyFactory()
            .apply()
            .entrySet()
            .stream()
            .map(e -> Map.entry(e.getKey(), e.getValue()))
            .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
    
    // 步骤 4: 处理节点和边
    this.processedData = ProcessedNodesEdgesAndConfig.process(stateGraph, compileConfig);
    
    // 步骤 5: 合并子图的键策略
    for (var entry : processedData.keyStrategyMap().entrySet()) {
        if (!this.keyStrategyMap.containsKey(entry.getKey())) {
            this.keyStrategyMap.put(entry.getKey(), entry.getValue());
        }
    }
    
    // 步骤 6: 验证中断节点存在性
    for (String interruption : processedData.interruptsBefore()) {
        if (!processedData.nodes().anyMatchById(interruption)) {
            throw Errors.interruptionNodeNotExist.exception(interruption);
        }
    }
    for (String interruption : processedData.interruptsAfter()) {
        if (!processedData.nodes().anyMatchById(interruption)) {
            throw Errors.interruptionNodeNotExist.exception(interruption);
        }
    }
    
    // 步骤 7: 重建编译配置
    this.compileConfig = CompileConfig.builder(compileConfig)
            .interruptsBefore(processedData.interruptsBefore())
            .interruptsAfter(processedData.interruptsAfter())
            .build();
    
    // 步骤 8: 存储节点工厂(线程安全)
    for (var n : processedData.nodes().elements) {
        var factory = n.actionFactory();
        Objects.requireNonNull(factory, format("action factory for node id '%s' is null!", n.id()));
        nodeFactories.put(n.id(), factory);
    }
    
    // 步骤 9: 评估边并创建并行节点
    for (var e : processedData.edges().elements) {
        // ... 复杂的边处理逻辑
    }
}

3.1 处理节点和边

ProcessedNodesEdgesAndConfig.process() 执行流程:

1、检查子图节点

  • 提取当前图中所有子图节点:stateGraph.nodes.onlySubStateGraphNodes()
  • 若无任何子图节点,直接返回原始图与配置,无需后续展开处理

2、 初始化处理容器

变量 说明
interruptsBefore 前中断点集合(会被动态更新)
interruptsAfter 后中断点集合(会被动态更新)
nodes 新节点集合,初始不含子图节点
edges 新边集合,复制原始边列表
keyStrategyMap 用于合并的键策略映射

3、循环遍历所有子图节点 subgraphNode依次执行

3.1 合并键策略

  • 合并当前子图节点自身的键策略
  • 递归处理嵌套子图,合并深层子图的键策略映射,保证不重复覆盖

3.2 处理子图 START 起始节点

  • 校验子图起始不支持并行分支,非法则抛异常
  • 格式化子图内部起始节点 ID ,避免全局 ID 冲突
  • 父图中指向子图节点的边,重定向到子图真实入口节点
  • 同步映射更新前置中断节点 interruptsBefore

3.3 处理子图 END 结束节点

  • 校验子图输出不支持并行分支,非法则抛异常
  • 子图暂不支持后置中断 interruptsAfter,命中直接抛出异常
  • 将子图所有 END 终止边,重定向连接到父图中子图的后继节点
  • 移除父图中原子图节点的出边

3.4 处理子图内部普通边

  • 过滤掉子图内 STARTEND 特殊边
  • 对子图普通边做 ID 前缀格式化
  • 合并加入顶层父图边集合

3.5 处理子图内部普通节点

  • 对子图所有节点做 ID 前缀格式化,隔离命名空间
  • 合并加入顶层父图节点集合

最终输出 ProcessedNodesEdgesAndConfig

  • nodes:展开所有嵌套子图后的完整节点集合
  • edges:重定向、合并后的完整边集合
  • interruptsBefore / interruptsAfter:子图映射转换后的中断节点配置
  • keyStrategyMap:递归合并去重后的全局键策略映射

完整源码:

java 复制代码
/**
 * 处理状态图,解析并展开子图,生成最终的节点、边、中断策略和键策略集合
 * 核心作用:递归解析子图节点,将子图展开为顶层图的一部分,统一管理节点ID、边关系、中断配置
 * 
 * @param stateGraph 原始状态图(包含子图节点)
 * @param config 编译配置(包含中断节点等信息)
 * @return 处理完成后的节点、边、中断配置、键策略封装对象
 * @throws GraphStateException 图状态异常(子图不支持的语法、节点缺失、配置错误等)
 */
static ProcessedNodesEdgesAndConfig process(StateGraph stateGraph, CompileConfig config)
        throws GraphStateException {

    // 获取当前状态图中所有【子图类型】的节点
    var subgraphNodes = stateGraph.nodes.onlySubStateGraphNodes();

    // 如果没有子图节点,直接返回原始数据(无需处理)
    if (subgraphNodes.isEmpty()) {
        return new ProcessedNodesEdgesAndConfig(stateGraph, config);
    }

    // 从配置中获取【前置中断节点】和【后置中断节点】集合
    var interruptsBefore = config.interruptsBefore();
    var interruptsAfter = config.interruptsAfter();
    
    // 构建【非子图】的顶层节点集合(排除子图节点)
    var nodes = new StateGraph.Nodes(stateGraph.nodes.exceptSubStateGraphNodes());
    // 初始化顶层边集合(使用原始边)
    var edges = new StateGraph.Edges(stateGraph.edges.elements);

    // 存储子图递归合并后的【键策略】(保证插入顺序:LinkedHashMap)
    Map<String, KeyStrategy> keyStrategyMap = new LinkedHashMap<>();

    // ===================== 遍历所有子图节点,递归展开子图 =====================
    for (var subgraphNode : subgraphNodes) {

        // 获取当前子图节点内部的工作流(子图本身)
        var sgWorkflow = subgraphNode.subGraph();

        // 合并当前子图节点自身的键策略(不存在则放入)
        subgraphNode.keyStrategies().forEach(keyStrategyMap::putIfAbsent);
        // 递归处理子图内部的子图,合并深层子图的键策略
        ProcessedNodesEdgesAndConfig processedSubGraph = process(sgWorkflow, config);
        processedSubGraph.keyStrategyMap().forEach(keyStrategyMap::putIfAbsent);

        // 获取递归处理后的子图节点与边
        StateGraph.Nodes processedSubGraphNodes = processedSubGraph.nodes;
        StateGraph.Edges processedSubGraphEdges = processedSubGraph.edges;

        // ===================== 处理子图 START 节点 =====================
        // 获取子图中以 START 为源的边
        var sgEdgeStart = processedSubGraphEdges.edgeBySourceId(START).orElseThrow();

        // 子图暂不支持以并行分支开头
        if (sgEdgeStart.isParallel()) {
            throw new GraphStateException("subgraph not support start with parallel branches yet!");
        }

        // 获取 START 边指向的目标节点
        var sgEdgeStartTarget = sgEdgeStart.target();

        // 目标节点ID不能为空
        if (sgEdgeStartTarget.id() == null) {
            throw new GraphStateException(format("the target for node '%s' is null!", subgraphNode.id()));
        }

        // 格式化子图节点ID(添加父节点前缀,避免ID冲突)
        var sgEdgeStartRealTargetId = subgraphNode.formatId(sgEdgeStartTarget.id());

        // ===================== 处理【前置中断】配置 =====================
        // 如果中断节点指向当前子图节点,则替换为子图实际起始节点ID
        interruptsBefore = interruptsBefore.stream()
                .map(interrupt -> Objects.equals(subgraphNode.id(), interrupt) ? sgEdgeStartRealTargetId
                        : interrupt)
                .collect(Collectors.toUnmodifiableSet());

        // 查找所有【指向当前子图节点】的顶层边(需要替换这些边的目标)
        var edgesWithSubgraphTargetId = edges.edgesByTargetId(subgraphNode.id());

        // 必须存在指向子图节点的边,否则图结构异常
        if (edgesWithSubgraphTargetId.isEmpty()) {
            throw new GraphStateException(
                    format("the node '%s' is not present as target in graph!", subgraphNode.id()));
        }

        // 替换边的目标:将指向【子图节点】的边 → 指向【子图内部真实起始节点】
        for (var edgeWithSubgraphTargetId : edgesWithSubgraphTargetId) {

            var newEdge = edgeWithSubgraphTargetId.withSourceAndTargetIdsUpdated(subgraphNode, Function.identity(),
                    id -> new EdgeValue((Objects.equals(id, subgraphNode.id())
                            ? subgraphNode.formatId(sgEdgeStartTarget.id())
                            : id)));
            edges.elements.remove(edgeWithSubgraphTargetId);
            edges.elements.add(newEdge);
        }

        // ===================== 处理子图 END 节点 =====================
        // 获取子图中所有指向 END 的边
        var sgEdgesEnd = processedSubGraphEdges.edgesByTargetId(END);

        // 获取顶层图中【从当前子图节点出发】的边
        var edgeWithSubgraphSourceId = edges.edgeBySourceId(subgraphNode.id()).orElseThrow();

        // 子图不支持输出到并行分支
        if (edgeWithSubgraphSourceId.isParallel()) {
            throw new GraphStateException("subgraph not support routes to parallel branches yet!");
        }

        // ===================== 处理【后置中断】配置 =====================
        // 子图节点不支持后置中断,直接抛出异常
        if (interruptsAfter.contains(subgraphNode.id())) {

            var exceptionMessage = (edgeWithSubgraphSourceId.target()
                    .id() == null) ? "'interruption after' on subgraph is not supported yet!"
                            : format(
                                    "'interruption after' on subgraph is not supported yet! consider to use 'interruption before' node: '%s'",
                                    edgeWithSubgraphSourceId.target().id());
            throw new GraphStateException(exceptionMessage);
        }

        // 将子图的 END 边 → 替换为指向顶层图中子图节点的下一个节点
        sgEdgesEnd.stream()
                .map(e -> e.withSourceAndTargetIdsUpdated(subgraphNode, subgraphNode::formatId,
                        id -> (Objects.equals(id, END) ? edgeWithSubgraphSourceId.target()
                                : new EdgeValue(subgraphNode.formatId(id)))))
                .forEach(edges.elements::add);
        // 移除原始子图节点的出边
        edges.elements.remove(edgeWithSubgraphSourceId);

        // ===================== 处理子图普通边(非START/非END) =====================
        // 格式化子图内部边的ID,添加到顶层边集合
        processedSubGraphEdges.elements.stream()
                .filter(e -> !Objects.equals(e.sourceId(), START))
                .filter(e -> !e.anyMatchByTargetId(END))
                .map(e -> e.withSourceAndTargetIdsUpdated(subgraphNode, subgraphNode::formatId,
                        id -> new EdgeValue(subgraphNode.formatId(id))))
                .forEach(edges.elements::add);

        // ===================== 处理子图普通节点 =====================
        // 格式化子图节点ID,添加到顶层节点集合
        processedSubGraphNodes.elements.stream().map(n -> {
            return n.withIdUpdated(subgraphNode::formatId);
        }).forEach(nodes.elements::add);
    }

    // 返回最终处理完成的所有数据(展开后的节点、边、中断、键策略)
    return new ProcessedNodesEdgesAndConfig(nodes, edges, interruptsBefore, interruptsAfter, keyStrategyMap);
}

3.2 边处理

边处理是构造函数中最复杂的部分,主要处理:

  • 普通边 / 条件边 → 存入 edges
  • 多目标 / 多命令条件 → 生成并行节点(ParallelNode / ConditionalParallelNode

完整源码:

java 复制代码
		// ===================== 边处理核心逻辑:遍历所有边,构建执行流 =====================
		for (var e : processedData.edges().elements) {
			// 获取当前边的所有目标节点
			var targets = e.targets();
			
			// ===================== 情况1:边只有 1 个目标节点 =====================
			if (targets.size() == 1) {
				var target = targets.get(0);
				
				// 判断是否为【条件边】:target.value() 不为空
				if (target.value() != null) {
					var edgeCondition = target.value();
					
					// 判断条件是否为【多命令并行条件】
					if (edgeCondition.isMultiCommand()) {
						// ============== 多命令条件 → 创建条件并行节点 ConditionalParallelNode ==============
						var conditionalParallelNode = new ConditionalParallelNode(
								e.sourceId(),
								edgeCondition,
								nodeFactories,
								keyStrategyMap,
								compileConfig);

						// 将条件并行节点注册到工厂
						nodeFactories.put(conditionalParallelNode.id(), conditionalParallelNode.actionFactory());
						// 原边指向条件并行节点
						edges.put(e.sourceId(), new EdgeValue(conditionalParallelNode.id()));

						// 获取条件映射的所有目标节点ID
						var mappedNodeIds = edgeCondition.mappings().values().stream()
								.collect(Collectors.toSet());

						// 校验所有映射节点必须存在
						var missingNodeIds = mappedNodeIds.stream()
								.filter(nodeId -> !nodeFactories.containsKey(nodeId))
								.collect(Collectors.toSet());
						if (!missingNodeIds.isEmpty()) {
							throw new GraphStateException("Conditional multi-command mapping from node '"
									+ e.sourceId() + "' references unknown target nodes: " + missingNodeIds);
						}

						// 查找并行节点的统一出口节点
						var parallelNodeTargets = findParallelNodeTargets(mappedNodeIds);
						if (!parallelNodeTargets.isEmpty()) {
							// 条件并行节点 → 指向统一出口
							edges.put(conditionalParallelNode.id(), new EdgeValue(parallelNodeTargets.iterator().next()));
						} else {
							// 无出口则抛出异常
							throw Errors.illegalMultipleTargetsOnParallelNode.exception(e.sourceId(), 0);
						}
					} else {
						// ============== 单命令条件边 → 直接存储 ==============
						edges.put(e.sourceId(), target);
					}
				} else {
					// ============== 普通无条件边 → 直接存储 ==============
					edges.put(e.sourceId(), target);
				}
			} 
			// ===================== 情况2:边有多个目标节点 → 并行执行 =====================
			else {
				// 构建流:过滤出存在于节点工厂的目标
				Supplier<Stream<EdgeValue>> parallelNodeStream = () -> targets.stream()
						.filter(target -> nodeFactories.containsKey(target.id()));

				// 查找并行节点对应的边
				var parallelNodeEdges = parallelNodeStream.get()
						.map(target -> new Edge(target.id()))
						.filter(ee -> processedData.edges().elements.contains(ee))
						.map(ee -> processedData.edges().elements.indexOf(ee))
						.map(index -> processedData.edges().elements.get(index))
						.toList();

				// 获取并行边指向的目标节点ID集合
				var parallelNodeTargets = parallelNodeEdges.stream()
						.map(ee -> ee.target().id())
						.collect(Collectors.toSet());

				// 校验:并行节点只能有一个共同出口,不支持多出口
				if (parallelNodeTargets.size() > 1) {
					// 检查是否包含条件边,并行节点不支持条件边
					var conditionalEdges = parallelNodeEdges.stream()
							.filter(ee -> ee.target().value() != null)
							.toList();
					if (!conditionalEdges.isEmpty()) {
						throw Errors.unsupportedConditionalEdgeOnParallelNode.exception(e.sourceId(),
								conditionalEdges.stream().map(Edge::sourceId).toList());
					}
					throw Errors.illegalMultipleTargetsOnParallelNode.exception(e.sourceId(), parallelNodeTargets);
				}

				// 获取合法的并行目标列表
				var targetList = parallelNodeStream.get().toList();

				// 为每个并行目标创建执行动作
				var actions = targetList.stream()
						.map(target -> {
							try {
								return nodeFactories.get(target.id()).apply(compileConfig);
							} catch (GraphStateException ex) {
								throw new RuntimeException("Failed to create parallel node action for target: "
										+ target.id() + ". Cause: " + ex.getMessage(), ex);
							}
						})
						.toList();

				// 收集并行节点ID
				var actionNodeIds = targetList.stream().map(EdgeValue::id).toList();
				// 获取并行节点统一出口
				var targetNodeId = parallelNodeTargets.iterator().next();

				// ===================== 创建并行执行节点 =====================
				var parallelNode = new ParallelNode(e.sourceId(), targetNodeId, actions, actionNodeIds, keyStrategyMap,
						compileConfig);

				// 注册并行节点到工厂
				nodeFactories.put(parallelNode.id(), parallelNode.actionFactory());
				// 原边 → 并行节点
				edges.put(e.sourceId(), new EdgeValue(parallelNode.id()));
				// 并行节点 → 统一出口节点
				edges.put(parallelNode.id(), new EdgeValue(targetNodeId));
			}
		}

ConditionalParallelNode 创建示例

java 复制代码
if (edgeCondition.isMultiCommand()) {
    // 创建条件并行节点
    var conditionalParallelNode = new ConditionalParallelNode(
            e.sourceId(),
            edgeCondition,
            nodeFactories,
            keyStrategyMap,
            compileConfig);
    
    // 注册节点工厂
    nodeFactories.put(conditionalParallelNode.id(), conditionalParallelNode.actionFactory());
    
    // 设置边映射
    edges.put(e.sourceId(), new EdgeValue(conditionalParallelNode.id()));
    
    // 验证映射的目标节点存在
    var mappedNodeIds = edgeCondition.mappings().values().stream()
            .collect(Collectors.toSet());
    var missingNodeIds = mappedNodeIds.stream()
            .filter(nodeId -> !nodeFactories.containsKey(nodeId))
            .collect(Collectors.toSet());
    if (!missingNodeIds.isEmpty()) {
        throw new GraphStateException("...");
    }
}

4. 核心方法

4.1 状态管理

java 复制代码
// 获取状态历史记录
public Collection<StateSnapshot> getStateHistory(RunnableConfig config)

// 获取当前状态快照(强制)
public StateSnapshot getState(RunnableConfig config)

// 获取当前状态快照(Optional)
public Optional<StateSnapshot> stateOf(RunnableConfig config)

// 获取最后一次状态快照
public Optional<StateSnapshot> lastStateOf(RunnableConfig config)

// 更新图状态(带指定节点)
public RunnableConfig updateState(RunnableConfig config, Map<String, Object> values, String asNode) throws Exception

// 更新图状态(自动寻找下一个节点)
public RunnableConfig updateState(RunnableConfig config, Map<String, Object> values) throws Exception

4.2 图执行流

java 复制代码
// 查找并行节点的目标节点
private Set<String> findParallelNodeTargets(Set<String> sourceNodeIds)

// 根据边路由获取下一个节点命令
private Command nextNodeId(EdgeValue route, Map<String, Object> state, String nodeId, RunnableConfig config) throws Exception

// 根据节点ID获取下一个节点命令
private Command nextNodeId(String nodeId, Map<String, Object> state, RunnableConfig config) throws Exception

// 获取图执行入口节点
private Command getEntryPoint(Map<String, Object> state, RunnableConfig config) throws Exception

// 判断是否需要在节点执行前中断
private boolean shouldInterruptBefore(String nodeId, String previousNodeId)

// 判断是否需要在节点执行后中断
private boolean shouldInterruptAfter(String nodeId, String previousNodeId)

// 添加执行检查点
private Optional<Checkpoint> addCheckpoint(RunnableConfig config, String nodeId, Map<String, Object> state, String nextNodeId, OverAllState overAllState) throws Exception

4.3 状态构造 & 克隆

java 复制代码
// 获取初始状态
public Map<String, Object> getInitialState(Map<String, Object> inputs, RunnableConfig config)

// 克隆状态(内部)
OverAllState cloneState(Map<String, Object> data, OverAllState overAllState) throws IOException, ClassNotFoundException

// 克隆状态(公开)
public OverAllState cloneState(Map<String, Object> data) throws IOException, ClassNotFoundException

// 创建全局状态对象
private OverAllState stateCreate(Map<String, Object> inputs)

4.4 执行入口(同步/响应式调用)

java 复制代码
// 调用并返回完整响应
public GraphResponse<NodeOutput> invokeAndGetResponse(Map<String, Object> inputs, RunnableConfig config)
public GraphResponse<NodeOutput> invokeAndGetResponse(OverAllState state, RunnableConfig config)

// 获取响应流
public Flux<GraphResponse<NodeOutput>> graphResponseStream(Map<String, Object> inputs, RunnableConfig config)
public Flux<GraphResponse<NodeOutput>> graphResponseStream(OverAllState state, RunnableConfig config)

// 流式执行(输出 NodeOutput)
public Flux<NodeOutput> stream(Map<String, Object> inputs, RunnableConfig config)
public Flux<NodeOutput> streamFromInitialNode(OverAllState overAllState, RunnableConfig config)
public Flux<NodeOutput> stream(Map<String, Object> inputs)
public Flux<NodeOutput> stream()

// 流式执行(快照模式)
public Flux<NodeOutput> streamSnapshots(Map<String, Object> inputs, RunnableConfig config)

// 同步执行(返回最终状态)
public Optional<OverAllState> invoke(Map<String, Object> inputs, RunnableConfig config)
public Optional<OverAllState> invoke(OverAllState overAllState, RunnableConfig config)
public Optional<OverAllState> invoke(Map<String, Object> inputs)

// 同步执行(返回最终输出)
public Optional<NodeOutput> invokeAndGetOutput(OverAllState overAllState, RunnableConfig config)
public Optional<NodeOutput> invokeAndGetOutput(Map<String, Object> inputs, RunnableConfig config)
public Optional<NodeOutput> invokeAndGetOutput(Map<String, Object> inputs)

4.5 调度执行(定时/异步调度)

java 复制代码
// 调度图执行
public ScheduledAgentTask schedule(ScheduleConfig scheduleConfig)

4.6 内部资源访问

java 复制代码
// 获取节点动作
public AsyncNodeActionWithConfig getNodeAction(String nodeId)

// 获取边路由
public EdgeValue getEdge(String nodeId)

// 获取键策略映射
public Map<String, KeyStrategy> getKeyStrategyMap()

// 获取最大迭代次数
public int getMaxIterations()

// 设置最大迭代次数(已废弃)
@Deprecated
public void setMaxIterations(int maxIterations)

4.7 图可视化

java 复制代码
// 获取图表示(带条件边开关)
public GraphRepresentation getGraph(GraphRepresentation.Type type, String title, boolean printConditionalEdges)

// 获取图表示(默认打印条件边)
public GraphRepresentation getGraph(GraphRepresentation.Type type, String title)

// 获取图表示(默认标题)
public GraphRepresentation getGraph(GraphRepresentation.Type type)

相关推荐
i嘻嘻8321 小时前
餐厅送餐机器人行业研究报告
人工智能
城事漫游Molly1 小时前
研究设计核心 Toolkit:从“知道方法”到“真正会设计”
大数据·人工智能·算法·ai写作·论文笔记
love在水一方1 小时前
【导读】基于层次化多模态场景图的快慢推理视觉语言导航
人工智能
一只幸运猫.1 小时前
核心概念层——深入理解 Agent 是什么
大数据·数据库·人工智能
Tutankaaa1 小时前
从10队到50队:知识竞赛软件的高并发场景如何设计?
java·经验分享·后端·spring
大囚长2 小时前
意识与物质的一体两面
人工智能
下次再写2 小时前
微服务架构实战:Spring Boot + Spring Cloud 从入门到精通
java·spring boot·spring cloud·微服务架构·服务注册与发现·分布式系统·api网关
●VON2 小时前
小米突然发短信:送你100万亿Token!有人已收到,有人还没?手把手教你白嫖
数据库·人工智能·skills
星辰徐哥2 小时前
AI时代最容易上手的5个副业,月入5000+
人工智能·ai·chatgpt·工具·副业·ai副业