文章目录
- [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. 构造函数
初始化执行步骤:
- 设置最大迭代次数 :从编译配置中获取递归限制,赋值给当前对象的最大迭代次数属性
- 保存原始状态图 :将传入的状态图对象存储为当前类的成员变量,保留原始数据
- 初始化键策略映射 :从状态图中获取键策略工厂,执行创建逻辑并转换为
Map集合,完成策略映射初始化 - 处理节点和边 :处理状态图和编译配置,生成节点、边及配置的封装数据
- 合并子图的键策略 :遍历处理后的子图策略映射,将不存在于主策略中的键值对进行合并补充
- 验证中断节点存在性 :分别校验前置/后置中断节点
ID,若节点不存在则抛出节点不存在异常 - 重建编译配置 :基于原有配置,整合中断节点信息,构建新的编译配置对象
- 存储节点工厂(线程安全) :遍历所有节点,获取节点动作工厂并校验非空,存入节点工厂集合保证线程安全使用
- 评估边并创建并行节点 :遍历处理后的所有边,执行复杂的边逻辑处理与并行节点创建
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)