Spring AI Alibaba 1.x 系列【55】Interrupts 中断机制:静态中断源码分析

文章目录

  • [1. interruptBefore 模式](#1. interruptBefore 模式)
    • [1.1 中断判断逻辑](#1.1 中断判断逻辑)
    • [1.2 构建中断元数据](#1.2 构建中断元数据)
    • [1.3 返回中断响应](#1.3 返回中断响应)
    • [1.4 初始化【中断执行】上下文](#1.4 初始化【中断执行】上下文)
    • [1.5 合并状态(BUG)](#1.5 合并状态(BUG))
    • [1.6 执行结束](#1.6 执行结束)
  • [2. interruptsAfter 模式](#2. interruptsAfter 模式)
    • [2.1 设置 INTERRUPT_AFTER 标记](#2.1 设置 INTERRUPT_AFTER 标记)
    • [2.2 动态计算下一个节点](#2.2 动态计算下一个节点)
  • [3. 中断时机对比](#3. 中断时机对比)

1. interruptBefore 模式

核心特性

  • 执行前中断:节点业务逻辑执行前触发
  • 静态配置:节点 ID 配置在 interruptsBefore 列表
  • 无动态路由:恢复后直接执行原节点

1.1 中断判断逻辑

MainGraphExecutor#execute 关于中断处理的核心逻辑:

  1. 中断判断:调用上下文方法,校验是否满足流程中断条件
  2. 元数据构建:记录当前节点 ID + 克隆的状态数据(防止原状态被篡改)
  3. 响应返回:返回流程中断完成的响应,携带中断信息
java 复制代码
		// 3. 判断是否需要中断 → 触发中断,返回中断元数据
		if (context.shouldInterrupt()) {
			InterruptionMetadata metadata = InterruptionMetadata
				.builder(context.getCurrentNodeId(), context.cloneState(context.getCurrentStateData()))
				.build();
			return Flux.just(GraphResponse.done(metadata));
		}

GraphRunnerContext#shouldInterrupt() 是中断判断的入口,只要执行前中断执行后中断 满足一个,就会返回 true :

java 复制代码
/**
 * 判断当前流程是否需要中断
 * @return true-需要中断,false-不需要中断
 */
public boolean shouldInterrupt() {
    // 满足【节点执行前中断】 或 【节点执行后中断】 任一条件,即触发中断
    return shouldInterruptBefore(nextNodeId, currentNodeId) 
            || shouldInterruptAfter(currentNodeId, nextNodeId);
}

执行前中断 shouldInterruptBefore() 判断:

java 复制代码
/**
 * 判断是否需要在【目标节点执行前】进行中断
 * @param nodeId 即将执行的下一个节点ID
 * @param previousNodeId 当前执行完成的节点ID
 * @return true-执行前中断,false-不中断
 */
private boolean shouldInterruptBefore(String nodeId, String previousNodeId) {
    // 无上一个节点(流程初始状态),不执行前置中断
    if (previousNodeId == null)
        return false;
    // 判断节点ID是否配置在【执行前中断点】集合中
    return compiledGraph.compileConfig.interruptsBefore().contains(nodeId);
}

执行后中断 shouldInterruptAfter() 判断:

java 复制代码
/**
 * 判断是否需要在【当前节点执行后】进行中断
 * @param nodeId 即将执行的下一个节点ID
 * @param previousNodeId 当前执行完成的节点ID
 * @return true-执行后中断,false-不中断
 */
private boolean shouldInterruptAfter(String nodeId, String previousNodeId) {
    // 无下一个节点 或 下一个节点与当前节点是同一个,不执行后置中断
    if (nodeId == null || Objects.equals(nodeId, previousNodeId))
        return false;

    // 满足任一条件则后置中断:
    // 1. 开启了条件边执行前中断 + 节点标记为固定后置中断节点
    // 2. 节点ID配置在【执行后中断点】集合中
    return (compiledGraph.compileConfig.interruptBeforeEdge() && Objects.equals(nodeId, INTERRUPT_AFTER))
            || compiledGraph.compileConfig.interruptsAfter().contains(nodeId);
}

1.2 构建中断元数据

需要中断时,会构建 InterruptionMetadata

java 复制代码
                // 创建中断元数据,包含当前节点ID和状态
                InterruptionMetadata metadata = InterruptionMetadata
                    .builder(context.getCurrentNodeId(), context.cloneState(context.getCurrentStateData()))
                    .build();

构建参数:

  • currentNodeId:当前执行中断的节点名称
  • currentStateData:调用 overallState.data() 获取的状态 data 数据

build() 构建逻辑:

java 复制代码
// 1. 私有构造方法,只能通过内部 Builder 类创建对象
private InterruptionMetadata(Builder builder) {
    // 2. 调用父类构造方法,传入 builder 中的 nodeId 和 state
    super(builder.nodeId, builder.state);
    
    // 3. 给当前类的 metadata 字段赋值(直接引用 builder 中的值)
    this.metadata = builder.metadata();
    
    // 4. 给 toolFeedbacks 赋值:创建新 ArrayList,拷贝 builder 中的集合
    this.toolFeedbacks = new ArrayList<>(builder.toolFeedbacks);
    
    // 5. 安全赋值 toolsAutomaticallyApproved:空值防护
    if (builder.toolsAutomaticallyApproved != null) {
        this.toolsAutomaticallyApproved = builder.toolsAutomaticallyApproved;
    } else {
        // 6. 如果 builder 中为 null,赋值为空集合,避免后续 NPE
        this.toolsAutomaticallyApproved = new ArrayList<>();
    }
}

构建完成后的对象:

1.3 返回中断响应

MainGraphExecutor#execute 将中断数据包装为 GraphResponse 返回:

java 复制代码
return Flux.just(GraphResponse.done(metadata));

调用方需要判断输出类型为 InterruptionMetadata 时,说明流程中断了,需要向用户显示可处理的操作,用户执行操作后,进入到流程恢复阶段。

1.4 初始化【中断执行】上下文

用户操作后进入到流程恢复阶段,首先进入到 GraphRunner#run 方法初始化【执行】上下文,再调用执行器执行,和上篇动态中断一致。

1.5 合并状态(BUG)

和上篇动态中断一致。

1.6 执行结束

合并状态之后,说明这个暂停节点已经被正式恢复了,按照正常流程继续执行直到结束。

2. interruptsAfter 模式

核心特性

  • 执行后中断:节点业务逻辑执行完成后触发
  • 动态路由:开启 interruptBeforeEdge,恢复时重新计算下一个节点

interruptsAfterinterruptBefore 的处理流程一致,主要区别是 interruptsAfter 支持配置 interruptBeforeEdge 参数,支持在恢复时动态计算下一个节点,所以下面只介绍下不一样的地方。

2.1 设置 INTERRUPT_AFTER 标记

NodeExecutor 节点执行完成处理时,当前节点是否配置了【执行后中断】,并开启了 interruptBeforeEdge 配置, 会将下一个节点设置为固定的 INTERRUPT_AFTER

java 复制代码
// 判断是否开启【边中断】机制 + 当前节点是否配置了【执行后中断】
if (context.getCompiledGraph().compileConfig.interruptBeforeEdge()
    && context.getCompiledGraph().compileConfig.interruptsAfter()
        .contains(context.getCurrentNodeId())) {

    // ==============================================
    // 场景:节点执行后,走【中断】,不自动走向下一个节点
    // 下一个节点设置为固定的 INTERRUPT_AFTER
    // ==============================================
    context.setNextNodeId(INTERRUPT_AFTER);
}
else {
    // ==============================================
    // 正常场景:根据当前状态 + 节点路由规则,计算下一个真实节点
    // ==============================================
    Command nextCommand = context.nextNodeId(context.getCurrentNodeId(), context.getCurrentStateData());
    context.setNextNodeId(nextCommand.gotoNode());
}

2.2 动态计算下一个节点

创建好上下文后,调用 MainGraphExecutor 执行中,进入到处理中断恢复处理逻辑,如果配置了 interruptBeforeEdge = true 且下一个节点是 INTERRUPT_AFTER ,说明要重新计算下一个节点:

java 复制代码
        // 从哪里中断的,由 创建上下文时设置
		final var resumeFrom = context.getResumeFromAndReset();
		if (resumeFrom.isPresent()) {
			// 开启延迟路由 + 当前是中断标记 → 重新计算下一个节点
			if (context.getCompiledGraph().compileConfig.interruptBeforeEdge()
				&& java.util.Objects.equals(context.getNextNodeId(), INTERRUPT_AFTER)) {
				var nextNode = context.nextNodeId(resumeFrom.get(), context.getCurrentStateData());
				context.setNextNodeId(nextNode.gotoNode());
				context.setCurrentNodeId(null);
			}
		}

3. 中断时机对比

中断类型 触发位置 状态已更新 nextNodeId 已计算 checkpoint 已创建 适用场景
interruptsBefore MainGraphExecutor 节点执行前审批
interruptsAfter MainGraphExecutor 节点执行后审批
interruptBeforeEdge NodeExecutor ❌ (INTERRUPT_AFTER) 分支决策前审批
InterruptableAction.interrupt() NodeExecutor 自定义执行前中断
InterruptableAction.interruptAfter() NodeExecutor ✅ (先合并) 自定义执行后中断

相关推荐
传说故事3 小时前
【论文阅读】GEN-1: Scaling Embodied Foundation Models to Mastery
论文阅读·人工智能·机器人·具身智能
ting94520003 小时前
Codex 适配国产信创环境完整部署指南(深度技术篇)
人工智能·架构
JEECG低代码平台3 小时前
JimuReport 积木报表 v2.3.4 版本发布,免费的可视化 AI 报表
人工智能·低代码·数据可视化·报表工具
a752066283 小时前
飞书机器人+OpenClaw(小龙虾)本地AI:从创建应用到配置AppID/Secret全流程
人工智能·机器人·飞书·openclaw·小龙虾 ai·本地 ai 智能体
SuniaWang3 小时前
AgentX 专栏-00前言:一个Java开发者的Agent实践之路
java·人工智能·spring boot·langchain·系统架构
koharu1233 小时前
PointRCNN 精解:从原始点云到三维框的两阶段检测
人工智能·深度学习·目标检测·3d·三维点云
aneasystone本尊3 小时前
把小龙虾装进口袋:iOS / Android Node 配对
人工智能
梦想的初衷~3 小时前
claude code、codex双AI协同高水平论文撰写与质量校准:数据分析→论文初稿→交叉审稿全流程
人工智能·生物信息·实战教程·临床医学·claude code·codex cli·认知颠覆
@蔓蔓喜欢你3 小时前
GraphQL 入门:API 开发的新范式
人工智能·ai