AsyncCommandAction 详细分析
源码路径:
spring-ai-alibaba-graph-core/src/main/java/com/alibaba/cloud/ai/graph/action/AsyncCommandAction.java
1. 类的定位
AsyncCommandAction 是条件路由的核心函数接口,定义了一条边在运行时"如何决定下一个节点"的契约。
java
public interface AsyncCommandAction
extends BiFunction<OverAllState, RunnableConfig, CompletableFuture<Command>> {}
它是整个 action 接口族中能力最完整的路由接口:既能读取完整运行时配置(RunnableConfig),又能在返回路由结果的同时携带状态更新(Command.update)。
2. 函数签名拆解
swift
BiFunction< OverAllState, RunnableConfig, CompletableFuture<Command> >
↑ ↑ ↑
输入:当前状态 输入:运行时配置 输出:异步路由命令
| 参数/返回 | 类型 | 说明 |
|---|---|---|
state |
OverAllState |
当前图的全量状态,路由逻辑从中读取数据 |
config |
RunnableConfig |
运行时配置,含线程 ID、检查点、模型名等元数据 |
| 返回值 | CompletableFuture<Command> |
异步路由结果,Command 携带下一节点 key 和可选状态更新 |
Command record:
java
record Command(String gotoNode, Map<String, Object> update) {}
// gotoNode → mappings 中的逻辑 key(非真实节点 ID)
// update → 可选的状态更新,会合并进 OverAllState
3. 与其他 action 接口的对比
action 包中存在一组由简到繁的路由接口族,AsyncCommandAction 是其中能力最强的一档:
| 接口 | 函数签名 | 能读 config? | 能更新 state? | 异步? |
|---|---|---|---|---|
EdgeAction |
(OverAllState) → String |
否 | 否 | 否 |
AsyncEdgeAction |
(OverAllState) → CF<String> |
否 | 否 | 是 |
EdgeActionWithConfig |
(OverAllState, RunnableConfig) → String |
是 | 否 | 否 |
AsyncEdgeActionWithConfig |
(OverAllState, RunnableConfig) → CF<String> |
是 | 否 | 是 |
CommandAction |
(OverAllState, RunnableConfig) → Command |
是 | 是 | 否 |
AsyncCommandAction |
(OverAllState, RunnableConfig) → CF<Command> |
是 | 是 | 是 |
4. 三个静态工厂方法
node_async(CommandAction syncAction) --- 同步转异步
java
static AsyncCommandAction node_async(CommandAction syncAction) {
return (state, config) -> {
Context context = Context.current(); // 保留 OpenTelemetry 上下文
var result = new CompletableFuture<Command>();
try {
result.complete(syncAction.apply(state, config));
} catch (Exception e) {
result.completeExceptionally(e);
}
return result;
};
}
将同步的 CommandAction(可抛出受检异常)包装为异步接口。异常不会抛出,而是通过 completeExceptionally 传入 future,由调用方的 .get() / .join() 触发。
Context.current() 的捕获是为了在异步线程中保留 OpenTelemetry 的 trace 上下文,防止链路追踪断裂。
of(AsyncEdgeAction action) --- 简单路由适配
java
static AsyncCommandAction of(AsyncEdgeAction action) {
return (state, config) -> action.apply(state).thenApply(Command::new);
}
将只返回 String(节点 key)的轻量级 AsyncEdgeAction 提升为 AsyncCommandAction。Command::new 使用单参构造器,update 默认为空 Map,即不携带状态更新。
java
// 等价写法
AsyncCommandAction.of(state -> completedFuture("approved"))
// 等同于
(state, config) -> completedFuture(new Command("approved"))
of(AsyncEdgeActionWithConfig action) --- 带配置的路由适配
java
static AsyncCommandAction of(AsyncEdgeActionWithConfig action) {
return (state, config) -> action.apply(state, config).thenApply(Command::new);
}
与上一个类似,但可以访问 RunnableConfig(如读取线程 ID、模型名等元数据来影响路由决策),同样不携带状态更新。
5. 四种创建方式对比
java
// 方式 1:直接 lambda(最灵活,可携带状态更新)
AsyncCommandAction action = (state, config) ->
completedFuture(new Command("approved", Map.of("audit_result", "passed")));
// 方式 2:node_async 包装同步逻辑(适合有 try/catch 的复杂路由)
AsyncCommandAction action = AsyncCommandAction.node_async((state, config) -> {
String score = (String) state.value("score").orElse("0");
return new Command(Integer.parseInt(score) > 80 ? "pass" : "fail");
});
// 方式 3:of(AsyncEdgeAction) 适配简单路由(无 config,无状态更新)
AsyncCommandAction action = AsyncCommandAction.of(
state -> completedFuture(state.value("flag").orElse("").equals("Y") ? "yes" : "no")
);
// 方式 4:of(AsyncEdgeActionWithConfig) 适配带配置的路由
AsyncCommandAction action = AsyncCommandAction.of(
(state, config) -> completedFuture(config.getMetadata("mode").equals("fast") ? "quick" : "slow")
);
6. 在 StateGraph 中的使用入口
AsyncCommandAction 通过以下两个入口进入图定义:
java
// 入口 1:作为条件边(最常用)
graph.addConditionalEdges("nodeA", action, Map.of("yes", "nodeB", "no", "nodeC"));
// 入口 2:作为兼具路由能力的节点(语法糖)
graph.addNode("nodeA", action, Map.of("yes", "nodeB", "no", "nodeC"));
// 内部等价于:
graph.addNode("nodeA", (state, config) -> completedFuture(Map.of()))
.addConditionalEdges("nodeA", action, mappings);
AsyncEdgeAction / AsyncEdgeActionWithConfig 最终也都通过 AsyncCommandAction.of() 转换后走同一路径:
java
// StateGraph.addConditionalEdges 的两个适配重载
addConditionalEdges(sourceId, AsyncEdgeAction, mappings)
└─→ addConditionalEdges(sourceId, AsyncCommandAction.of(action), mappings)
addConditionalEdges(sourceId, AsyncEdgeActionWithConfig, mappings)
└─→ addConditionalEdges(sourceId, AsyncCommandAction.of(action), mappings)
7. 运行时执行流程
java
// CompiledGraph.nextNodeId 中(简化)
var singleAction = edgeCondition.singleAction(); // 取出 AsyncCommandAction
// 1. 执行 action,传入当前状态和运行时配置
var command = singleAction.apply(derefState, config).get();
// 2. 从 Command 中取出逻辑 key
var newRoute = command.gotoNode(); // e.g. "approved"
// 3. 通过 mappings 查找真实节点 ID
String result = edgeCondition.mappings().get(newRoute); // e.g. "sendEmail"
// 4. 合并 Command 携带的状态更新(若有)
var currentState = OverAllState.updateState(state, command.update(), keyStrategyMap);
return new Command(result, currentState);
Command.update 的合并时机是路由决策之后、跳转之前,路由函数可以在决定下一节点的同时向状态中写入路由相关的元数据(如审核结果、决策原因等)。
8. 完整接口族关系图
arduino
路由接口族(由简到繁):
EdgeAction (state) → String 同步,无 config,无更新
└─ AsyncEdgeAction.edge_async() 包装
AsyncEdgeAction (state) → CF<String> 异步,无 config,无更新
└─ AsyncCommandAction.of() 适配 ──────────────────────────────────────┐
│
EdgeActionWithConfig (state, config) → String 同步,有 config,无更新 │
AsyncEdgeActionWithConfig (state, config) → CF<String> 异步,有 config,无更新 │
└─ AsyncCommandAction.of() 适配 ──────────────────────────────────────┤
▼
CommandAction (state, config) → Command 同步,有 config,有更新
└─ AsyncCommandAction.node_async() 包装
AsyncCommandAction (state, config) → CF<Command> ← 本类 异步,有 config,有更新
9. 实际测试用例(来自 StateGraphTest)
java
// 直接 lambda:同时路由到 C2,并向状态写入 "next_node"="C2"
AsyncCommandAction commandAction = (state, config) ->
completedFuture(new Command("C2", Map.of("messages", "B", "next_node", "C2")));
var graph = new StateGraph()
.addNode("A", makeNode("A"))
.addNode("B", commandAction, Map.of(END, END, "C1", "C1", "C2", "C2"))
.addNode("C1", makeNode("C1"))
.addNode("C2", makeNode("C2"))
.addEdge(START, "A")
.addEdge("A", "B")
.addEdge("C1", END)
.addEdge("C2", END)
.compile();
// 执行后:B 节点路由到 C2,状态中同时写入 next_node="C2"
10. 总结
| 维度 | 说明 |
|---|---|
| 类型 | @FunctionalInterface,可用 lambda 直接实现 |
| 能力 | 路由接口族中最完整:异步 + 可读 config + 可更新 state |
| 核心参数 | OverAllState(读状态)+ RunnableConfig(读运行配置) |
| 返回值 | CompletableFuture<Command>,Command 携带路由 key 和可选状态更新 |
| 创建方式 | 直接 lambda、node_async(同步包装)、of(从简单接口适配) |
| 使用入口 | StateGraph.addConditionalEdges / addNode(id, action, mappings) |
| 运行时 | 由 CompiledGraph.nextNodeId 调用,结果通过 mappings 映射为真实节点 ID |
相关文件
EdgeCondition.md--- AsyncCommandAction 的存储容器EdgeValue.md--- 条件边目标的联合类型Edge.md--- 边的完整结构分析StateGraph.md--- 图定义层整体分析AsyncCommandAction.java--- 本文分析的源文件CommandAction.java--- 同步版,被node_async包装AsyncEdgeAction.java--- 轻量路由接口,被of()适配AsyncEdgeActionWithConfig.java--- 带配置的轻量路由接口,被of()适配Command.java--- 路由结果,携带 gotoNode + 状态更新CompiledGraph.java--- 运行时nextNodeId的调用方