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() 执行方法
状态管理 状态的获取、更新、历史查询
中断控制 管理 interruptsBefore 和 interruptsAfter
持久化协调 与 CheckpointSaver 协作实现状态持久化
并行执行 支持 ParallelNode 和 ConditionalParallelNode

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 处理子图内部普通边

  • 过滤掉子图内 START、END 特殊边
  • 对子图普通边做 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)

相关推荐
agicall.com2 小时前
信创电话助手录音盒多路设备配置教程
人工智能·语音识别·信创电话助手·座机语音转文字·固话座机录音转文字
MiYi124062 小时前
2026 企业 AI 办公工具选型指南:从需求分析到任务交付的完整评估框架
大数据·人工智能
海宇服务2 小时前
零信任架构实战:基于海宇车辆估值构建自动化车队残值重估网关
运维·人工智能·架构·自动化
johnsong2 小时前
AI前沿日报 2026-10-01:驯服、解剖与隐形 — AI治理溃败×编码Agent拆解×隐写推理突破
人工智能
林伽一2 小时前
常驻智能体产品化元年开启,安全治理成为产业新门槛|2026年10月1日
人工智能·安全·chatgpt
10年前端老司机2 小时前
MCP 技术分享:从协议握手到 LangGraph 多 Server 调用
人工智能·agent·mcp
工作10年+,存储芯片行业2 小时前
SSD 行业国际新闻汇总:市场动态与技术进展
人工智能·ssd·存储·pcie·主控·nand·闪存
Francek Chen2 小时前
【华为Mate90系列】麒麟9050Pro首秀!Mate90系列及多款新品正式发布
人工智能·华为·harmonyos·鸿蒙7·麒麟芯片
data analyse 4562 小时前
数据合规的同意记录怎么留存?
前端·人工智能·数据分析
zhangfeng11332 小时前
neo lab(也写作 Neolab / NeoLab)新一代AI前沿实验室
人工智能