EdgeCondition 详细分析
源码路径:
spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/internal/edge/EdgeCondition.java
1. 类的定位
EdgeCondition 是条件边路由逻辑的完整封装,包含两个要素:
action:运行时执行的路由函数,根据当前状态决定走哪个分支mappings:action返回的逻辑 key → 实际节点 ID 的映射表
它只存在于 EdgeValue.value != null 的条件边场景,普通固定边不涉及此类。
StateGraph.addConditionalEdges() → EdgeCondition.single(action, mappings)
StateGraph.addParallelConditionalEdges() → EdgeCondition.multi(action, mappings)
2. 字段结构与类型守卫
public record EdgeCondition(Object action, Map<String, String> mappings) {
public EdgeCondition {
if (action != null
&& !(action instanceof AsyncCommandAction)
&& !(action instanceof AsyncMultiCommandAction)) {
throw new IllegalArgumentException("...");
}
}
}
action 声明为 Object 而非接口,是因为两种 action 类型没有共同父接口,用 Object + 运行时 instanceof 区分是 Java 中实现联合类型的标准做法。紧凑构造器充当类型守卫,在对象创建时拦截非法类型,而不是等到运行时路由才报错。
mappings 是 Map<String, String>,key 是 action 返回的逻辑名,value 是图中真实的节点 ID:
mappings = {
"approved" -> "sendEmail",
"rejected" -> "archiveRecord",
"pending" -> END
}
3. 两种路由模式对比
| 维度 | 单路由(single) | 多路由并行(multi) |
|---|---|---|
| action 类型 | AsyncCommandAction | AsyncMultiCommandAction |
| 返回值类型 | CompletableFuture<Command> | CompletableFuture<MultiCommand> |
| 每次路由目标数 | 1 个节点 | ≥1 个节点(并行) |
| 运行时处理 | 直接跳转目标节点 | 插入 ConditionalParallelNode 并行分发 |
isMultiCommand() | false | true |
4. action 接口族与返回值
AsyncCommandAction → 返回 Command
// 函数签名
BiFunction<OverAllState, RunnableConfig, CompletableFuture<Command>>
// Command record
record Command(String gotoNode, Map<String, Object> update) {}
// gotoNode → 逻辑 key,通过 mappings 映射为真实节点 ID
// update → 可同时携带状态更新(会合并进 OverAllState)
创建方式:
// 方式1:直接 lambda(最常用)
AsyncCommandAction action = (state, config) ->
completedFuture(new Command(state.value("score") > 80 ? "pass" : "fail"));
// 方式2:从同步 CommandAction 包装
AsyncCommandAction action = AsyncCommandAction.node_async((state, config) ->
new Command(decide(state)));
// 方式3:从 AsyncEdgeAction 适配(只返回 key,不带状态更新)
AsyncCommandAction action = AsyncCommandAction.of(state ->
completedFuture(state.value("score") > 80 ? "pass" : "fail"));
AsyncMultiCommandAction → 返回 MultiCommand
// 函数签名
BiFunction<OverAllState, RunnableConfig, CompletableFuture<MultiCommand>>
// MultiCommand record
record MultiCommand(List<String> gotoNodes, Map<String, Object> update) {}
// gotoNodes → 多个逻辑 key,每个通过 mappings 映射为真实节点 ID
// gotoNodes 不能为空(构造器有守卫)
创建方式:
// 方式1:直接 lambda
AsyncMultiCommandAction action = (state, config) ->
completedFuture(new MultiCommand(List.of("nodeA", "nodeB")));
// 方式2:从返回 List<String> 的 Function 适配
AsyncMultiCommandAction action = AsyncMultiCommandAction.of(state ->
completedFuture(selectBranches(state)));
5. 四个方法的职责
// 工厂方法:语义明确,防止直接用 new EdgeCondition() 写错类型
EdgeCondition.single(action, mappings) // 单路由
EdgeCondition.multi(action, mappings) // 多路由并行
// 类型判断:运行时分支派发的入口
boolean isMultiCommand() // action instanceof AsyncMultiCommandAction
// 类型安全的 action 获取(非对应模式时返回 null)
AsyncCommandAction singleAction()
AsyncMultiCommandAction multiAction()
singleAction() / multiAction() 返回 null 而非抛异常,调用方须配合 isMultiCommand() 先判断再取值:
// 标准调用模式(来自 CompiledGraph / GraphRunnerContext)
if (edgeCondition.isMultiCommand()) {
// 多路由分支
} else {
var singleAction = edgeCondition.singleAction(); // 此处保证非 null
var command = singleAction.apply(state, config).get();
String result = edgeCondition.mappings().get(command.gotoNode());
}
6. 编译期处理(CompiledGraph 构造器)
编译时,EdgeCondition 决定了最终的运行时节点图结构:
isMultiCommand() == false(单路由条件边)
→ 直接存入路由表:edges.put(sourceId, target)
→ 运行时由 nextNodeId 执行 singleAction
isMultiCommand() == true(多路由并行条件边)
→ 创建 ConditionalParallelNode,注入 edgeCondition
→ edges.put(sourceId, new EdgeValue(conditionalParallelNode.id()))
→ edges.put(conditionalParallelNode.id(), new EdgeValue(convergenceNodeId))
→ 运行时由 ConditionalParallelNode 内部执行 multiAction
多路由的编译期流程:
// CompiledGraph 构造器中(简化)
if (edgeCondition.isMultiCommand()) {
// 1. 创建专用并行节点,将 edgeCondition 注入其中
var conditionalParallelNode = new ConditionalParallelNode(
e.sourceId(), edgeCondition, nodeFactories, keyStrategyMap, compileConfig);
// 2. 注册并行节点
nodeFactories.put(conditionalParallelNode.id(), conditionalParallelNode.actionFactory());
// 3. 改写路由:原 sourceId → ConditionalParallelNode
edges.put(e.sourceId(), new EdgeValue(conditionalParallelNode.id()));
// 4. ConditionalParallelNode → 汇聚节点
edges.put(conditionalParallelNode.id(), new EdgeValue(convergenceNodeId));
}
图结构变化示意:
编译前(定义层):
nodeA ──[EdgeCondition.multi]──▶ {nodeB, nodeC}
编译后(运行层):
nodeA ──▶ ConditionalParallelNode(nodeA)
├──▶ nodeB
├──▶ nodeC
└──▶ convergenceNode
7. 运行时执行(nextNodeId 中的单路由路径)
// 1. 取出 EdgeCondition
var edgeCondition = route.value();
// 2. 执行 action,得到 Command(含逻辑 key 和可选状态更新)
var singleAction = edgeCondition.singleAction();
var command = singleAction.apply(derefState, config).get();
// 3. 用 gotoNode(逻辑 key)查 mappings,得到真实节点 ID
String result = edgeCondition.mappings().get(command.gotoNode());
if (result == null) {
throw RunnableErrors.missingNodeInEdgeMapping.exception(nodeId, command.gotoNode());
}
// 4. 合并 Command 携带的状态更新
var currentState = OverAllState.updateState(state, command.update(), keyStrategyMap);
return new Command(result, currentState);
mappings 在此充当解耦层:action 只需关心业务逻辑(返回 "approved" 还是 "rejected"),不需要知道真实节点叫什么名字,节点 ID 的绑定在图定义时由 mappings 完成。
8. 完整调用链总结
图定义期:
StateGraph.addConditionalEdges(sourceId, action, mappings)
└─→ EdgeCondition.single(action, mappings)
└─→ EdgeValue(null, EdgeCondition)
└─→ Edge(sourceId, [EdgeValue])
└─→ 存入 StateGraph.edges
编译期(CompiledGraph 构造器):
遍历 processedData.edges()
└─→ target.value() != null → 是条件边
├─→ isMultiCommand() == true
│ └─→ 创建 ConditionalParallelNode(edgeCondition)
│ └─→ 改写 edges 路由表
└─→ isMultiCommand() == false
└─→ 直接写入 edges 路由表
运行时(nextNodeId):
edges.get(nodeId) → EdgeValue
└─→ value != null → 是条件边
├─→ isMultiCommand() == true
│ └─→ 跳转 ConditionalParallelNode(内部执行 multiAction)
└─→ isMultiCommand() == false
└─→ singleAction.apply(state, config)
└─→ command.gotoNode() → mappings.get(key) → 真实节点 ID
9. 总结
| 维度 | 说明 |
|---|---|
| 类型 | Java Record(不可变) |
| 核心字段 | action(路由函数)+ mappings(key → 节点 ID 的解耦映射) |
| action 类型 | Object,用 instanceof 在运行时区分单路由/多路由 |
| 类型守卫 | 紧凑构造器在对象创建时拦截非法 action 类型 |
| 单路由 | 运行时直接执行 singleAction,查 mappings 得到节点 ID |
| 多路由 | 编译期插入 ConditionalParallelNode,运行时并行分发 |
| mappings 的作用 | 解耦 action 逻辑(返回逻辑 key)与图拓扑(节点 ID),两者独立变化 |
相关文件
StateGraph.md— 图定义层整体分析Edge.md— 边的完整结构分析EdgeValue.md— 边目标联合类型分析EdgeCondition.java— 本文分析的源文件AsyncCommandAction.java— 单路由 action 接口AsyncMultiCommandAction.java— 多路由 action 接口Command.java— 单路由返回值,携带 gotoNode + 状态更新MultiCommand.java— 多路由返回值,携带 gotoNodes 列表CompiledGraph.java— 编译期 ConditionalParallelNode 创建逻辑GraphRunnerContext.java— 运行时 nextNodeId 派发实现