Spring AI Alibaba 1.x 系列【52】Interrupts 中断机制:节点执行前后静态中断

文章目录

  • [1. 概述](#1. 概述)
    • [1.1 Human-in-the-Loop(人在回路)](#1.1 Human-in-the-Loop(人在回路))
    • [1.2 Interrupts(执行中断)](#1.2 Interrupts(执行中断))
    • [1.3 执行中断 VS 人在回路](#1.3 执行中断 VS 人在回路)
  • [2. interruptBefore 模式](#2. interruptBefore 模式)
    • [2.1 配置项说明](#2.1 配置项说明)
      • [2.1.1 CompileConfig](#2.1.1 CompileConfig)
      • [2.1.2 RunnableConfig](#2.1.2 RunnableConfig)
    • [2.2 配置中断节点](#2.2 配置中断节点)
    • [2.3 定义中断反馈结果](#2.3 定义中断反馈结果)
    • [2.4 流程中断](#2.4 流程中断)
    • [2.5 批准执行](#2.5 批准执行)
      • [2.5.1 情况 1 :不修改状态](#2.5.1 情况 1 :不修改状态)
      • [2.5.2 状态合并 BUG](#2.5.2 状态合并 BUG)
      • [2.5.3 情况 2 :修改状态](#2.5.3 情况 2 :修改状态)
    • [2.6 人工拒绝](#2.6 人工拒绝)
  • [3. interruptsAfter 模式](#3. interruptsAfter 模式)
    • [3.1 简单案例](#3.1 简单案例)
    • [3.2 interruptBeforeEdge 配置](#3.2 interruptBeforeEdge 配置)

1. 概述

1.1 Human-in-the-Loop(人在回路)

Human-in-the-Loop 为智能体的工具调用 增加人工审核机制。当大模型拟执行存在风险的操作(如写入文件、执行 SQL 语句)时,该中间件可暂停流程执行,等待人工决策。

内置三种人工响应中断的决策方式:

决策类型 说明 适用场景示例
✅ 批准(approve) 直接认可原操作,无修改执行 原样发送邮件草稿
✏️ 编辑(edit) 修改工具调用参数后再执行 发送邮件前修改收件人
❌ 拒绝(reject) 驳回工具调用,并在对话中附上拒绝理由 拒绝邮件草稿并说明改写建议

在之前我们介绍过 React 基于 HumanInTheLoopHook 模型后置钩子(模型生成响应后、工具调用执行前触发)实现,处理逻辑:

  1. 智能体调用大模型生成响应
  2. 中间件解析响应中的工具调用
  3. 若需人工介入,构造含操作请求、审核配置的 HITL 请求并触发中断
  4. 智能体挂起,等待人工决策
  5. 根据人工决策:执行批准/编辑的调用、为拒绝操作生成工具消息、将人工回复直接作为工具返回结果,最终恢复流程执行

1.2 Interrupts(执行中断)

执行中断 允许在流程图的指定节点暂停执行,等待外部输入后再继续运行。该机制是实现人在回路 流程的核心,适用于需要外部人工介入才能继续执行的场景。触发中断时,Graph 会通过持久化层保存流程图完整状态,并无限等待直到手动恢复执行。

中断核心价值:暂停执行、等待外部输入

典型应用场景:

  • 审批流程:执行高危操作前暂停(接口调用、数据库变更、金融交易)
  • 多中断并行处理:单次调用批量恢复多个并行分支的中断
  • 审核与编辑:人工审核、修改大模型输出或工具调用参数后再继续
  • 工具调用中断:工具执行前暂停,人工审核编辑后再执行
  • 输入校验:步骤流转前校验人工输入,非法则重新询问

Spring AI Alibaba Graph 提供了提供了两种方式来实现重点:

  • 通过实现 InterruptableAction 接口来控制中断时机,可以在任意时刻返回 InterruptionMetadata 来中断执行
  • 编译 Graph 时指定中断点(interruptBeforeinterruptsAfter),在指定节点执行前后自动中断

两种中断模式对比:

特性 InterruptionMetadata 模式 interruptBefore\ interruptsAfter模式
中断时机 运行时动态决定 编译时预先定义
节点要求 需要实现 InterruptableAction 接口 普通节点即可
灵活性 高,可根据运行时状态动态中断 中等,需在编译时固定中断位置
配置复杂度 较高,需实现接口方法 低,仅配置节点名称即可
适用场景 需依据运行时业务状态动态判断是否中断 业务流程中已知、固定的中断点位

HITL 规定了只能在工具调用前暂停 ,而 Interrupts 更灵活支持任意节点、任意时机、任意触发条件都能暂停

1.3 执行中断 VS 人在回路

对比维度 执行中断(Interrupts) 人在回路(Human-in-the-Loop, HITL)
核心定位 Graph 底层技术原语、基础工具 AI 流程的业务设计模式、人机协作规范
本质 底层实现恢复指令,实现暂停/恢复 受控的人机协作机制,封装了固定的审批逻辑(批准/编辑/驳回/应答)
核心作用 在流程图任意位置暂停执行、保存状态,等待外部输入后恢复运行 将人纳入自动化流程,关键节点(多为工具调用)需人工审批/编辑后才能继续
灵活性 极高,可在任意节点、任意时机、任意条件下触发暂停 较低,仅针对工具调用场景,按预设规则触发暂停,决策方式固定
触发场景 调试断点、多轮对话等待用户输入、任意节点条件暂停(如风险判断)、非工具环节人工确认等 工具调用前审批(如执行SQL、写文件、发邮件)、高危操作把关、合规校验、工具调用参数审核修改
实现方式 执行中断机制,自定义暂停时机和恢复逻辑 集成 HITL 钩子,配置 interrupt_on 定义需审批的工具及允许的决策类型
依赖关系 独立技术能力,不依赖 HITL 依赖 Interrupts 实现暂停功能,是 Interrupts 的业务化封装
核心用途 满足所有需要暂停流程的场景,通用且灵活 生产级安全场景,重点管控工具调用,保障流程合规、安全

一句话总结:

  • InterruptsGraph底层暂停/恢复技术,万能工具
  • HITL :基于 Interrupts人机协作安全模式,专用于执行时人工审批

2. interruptBefore 模式

在编译 Graph 时提前指定中断点,在指定节点执行前后自动中断。这种方式适合已知的中断点,配置简单直接。

优势

  • 配置简单:只需在编译配置中指定中断点
  • 无需修改节点:普通节点即可,不需要实现特殊接口
  • 明确的中断点:中断位置在编译时确定,易于理解和维护

接下来,我们在之前【邮件处理工作流】案例的基础上进行中断演示!


2.1 配置项说明

2.1.1 CompileConfig

CompileConfig 中断配置项说明:

配置项 类型 作用 示例
interruptsBefore Set<String> 在指定节点执行前中断 interruptBefore("llm_node")
interruptsAfter Set<String> 在指定节点执行后中断 interruptAfter("tool_node")
interruptBeforeEdge boolean 恢复执行后动态计算下一个节点(配合 interruptsAfter interruptBeforeEdge(true)

示例:

java 复制代码
CompileConfig config = CompileConfig.builder()
    // 方式1:执行前中断
    .interruptBefore("approval_node", "review_node")
    
    // 方式2:执行后中断
    .interruptAfter("tool_call_node")
    
    // 方式3:边评估前中断(需配合 interruptsAfter)
    .interruptAfter("decision_node")
    .interruptBeforeEdge(true)
    
    .saverConfig(SaverConfig.builder().register(MemorySaver.builder().build()).build())
    .build();

StateGraph graph = new StateGraph(...)
    .compile(config);

2.1.2 RunnableConfig

RunnableConfig流程执行、中断、断点续跑、人工反馈配置载体,所有中断相关的状态、标记、元数据都通过它传递和管理。

定义了中断/续跑场景下的固定元数据 Key,是引擎识别人工反馈、状态更新的唯一标识:

java 复制代码
// 1. 人工反馈标记:标识流程为【断点续跑】模式
public static final String HUMAN_FEEDBACK_METADATA_KEY = "HUMAN_FEEDBACK";
// 2. 状态更新标记:续跑时合并人工输入的状态数据
public static final String STATE_UPDATE_METADATA_KEY = "STATE_UPDATE";

作用:

  1. HUMAN_FEEDBACK:触发断点续跑 的核心标记(GraphRunnerContext 据此初始化续跑上下文)
  2. STATE_UPDATEInterruptableAction 模式专用,续跑时合并人工反馈的业务状态

中断节点状态管理(核心字段):

java 复制代码
// 并发Map:存储【节点ID -> 中断状态】,线程安全
private final Map<String, Object> interruptedNodes;
  • 存储格式:格式化节点ID → true/false
  • true:节点处于中断暂停状态
  • false:节点已恢复执行
  • 线程安全:使用 ConcurrentHashMap,支持多线程场景

中断状态操作核心方法,引擎/业务侧用于标记、判断、恢复、清除节点中断状态:

  • isInterrupted(String nodeId):判断节点是否中断
  • markNodeAsInterrupted(String nodeId):标记节点为中断状态
  • withNodeResumed(String nodeId):将节点标记为已恢复,引擎继续执行
  • removeInterrupted(String nodeId):清空节点的中断标记,流程执行完成,清理中断状态

流程续跑配置方法,用于构建续跑配置,告诉引擎「这是一次中断恢复执行」:

  • withResume():自动添加 HUMAN_FEEDBACK 元数据,触发断点续跑
  • addHumanFeedback(InterruptionMetadata humanFeedback):添加人工反馈,携带中断元数据,恢复时关联原始中断信息
  • addStateUpdate(Map<String, Object> stateUpdate):添加续跑状态更新(InterruptableAction 专用),携带人工输入的状态数据,续跑时自动合并

链式构建中断/续跑配置,业务侧最常用:

java 复制代码
RunnableConfig config = RunnableConfig.builder()
        .threadId("thread_123")          // 会话ID
        .checkPointId("checkpoint_456")  // 断点ID
        .resume()                        // 标记续跑(添加HUMAN_FEEDBACK)
        .addStateUpdate(Map.of("approve", true)) // 添加工单审批状态
        .build();

2.2 配置中断节点

CompileConfig 提供了两种【节点执行前触发中断】的配置方法:

java 复制代码
		/**
		 * 从集合中设置【节点执行前触发】的多个中断点
		 * @param interruptsBefore 存储中断点标识的字符串集合
		 * @return 当前构建器实例,用于方法链式调用
		 */
		public Builder interruptsBefore(Collection<String> interruptsBefore) {
			// 将集合转换为不可修改的Set集合,赋值给中断点配置
			this.config.interruptsBefore = interruptsBefore.stream().collect(Collectors.toUnmodifiableSet());
			return this;
		}

		/**
		 * 通过可变参数方式,设置【节点执行前触发】的单个/多个中断点
		 * @param interruptBefore 一个或多个表示中断点的字符串
		 * @return 当前构建器实例,用于方法链式调用
		 */
		public Builder interruptBefore(String... interruptBefore) {
			// 将可变参数转换为不可变Set集合,赋值给中断点配置
			this.config.interruptsBefore = Set.of(interruptBefore);
			return this;
		}

节点名称就是我们在添加节点时指定的名称:

java 复制代码
workflow.addNode("read_email", readEmail)

ReactAgent 只有模型、工具节点,是在 ReactAgent#initGraph 方法中定义的:

java 复制代码
		graph.addNode(AGENT_MODEL_NAME, node_async(this.llmNode));
		if (hasTools) {
			graph.addNode(AGENT_TOOL_NAME, node_async(this.toolNode));
		}

常量定义在 RunnableConfig 类中:

java 复制代码
	public static final String AGENT_MODEL_NAME = "_AGENT_MODEL_";
	public static final String AGENT_TOOL_NAME = "_AGENT_TOOL_";

自定义状态图、ReactAgent 都需要通过 CompileConfig 配置中断节点,这里的配置标识在 read_email (读取邮件)节点执行前进行中断:

java 复制代码
			 // 自定义状态图
        CompileConfig compileConfig = CompileConfig.builder()
                .saverConfig(saverConfig)
                .interruptBefore("read_email")
                .store(new MemoryStore())
                .build();
        CompiledGraph compiledGraph = workflow.compile(compileConfig);
			  
			  // ReactAgent
        ReactAgent chatAgent = ReactAgent.builder()
                .name("email-chat-agent")
                .observationRegistry(observationRegistry)
                .compileConfig(compileConfig)
                .enableLogging(true)
                .tools()
                .saver(new MemorySaver())
                .model(chatModel)
                .instruction("你是一个邮件处理助手,可以帮助用户处理邮件分类、文档搜索、Bug追踪和回复起草等问题。请用中文回答用户的问题。")
                .build();

2.3 定义中断反馈结果

HITL 中定义了三种工具审批结果:

java 复制代码
	// ==================== FeedbackResult 枚举 ====================

	/**
	 * FeedbackResult - 工具审批结果枚举
	 *
	 * <p>表示用户对工具调用的审批决定。
	 */
	public enum FeedbackResult {

		/**
		 * 已批准 - 允许执行工具
		 */
		APPROVED,

		/**
		 * 已拒绝 - 拒绝执行工具
		 */
		REJECTED,

		/**
		 * 已编辑 - 用户修改了工具参数后批准
		 */
		EDITED;
	}

Interrupts 更加灵活,我们可以自定义多种处理结果,比如在以上三种基础上加:

决策类型 说明 适用场景示例
💬 回复(respond) 跳过节点执行,人工输入内容直接作为节点返回结果 直接回复「询问用户」类提示

自定义中断反馈结果枚举类:

java 复制代码
public enum InterruptFeedbackResult {

    APPROVED("批准", "APPROVED"),
    EDITED("编辑", "EDITED"),
    REJECTED("拒绝", "REJECTED"),
    RESPOND("回复", "RESPOND");

    private final String name;
    private final String code;

    InterruptFeedbackResult(String name, String code) {
        this.name = name;
        this.code = code;
    }

    public String getName() {
        return name;
    }

    public String getCode() {
        return code;
    }

    public static InterruptFeedbackResult fromCode(String code) {
        for (InterruptFeedbackResult action : values()) {
            if (action.getCode().equals(code)) {
                return action;
            }
        }
        return null;
    }
}

后端可以根据不同的审批策略,执行对应逻辑:

java 复制代码
                switch (action) {
                    case APPROVED:
                        // 人工批准:恢复执行

                        break;

                    case EDITED:
                        // 人工编辑:更新状态并设置
                        break;

                    case REJECTED:
                        // 人工拒绝:更新状态标记拒绝后终止执行

                        return Flux.just("❌ 执行已被拒绝终止。");

                    default:
                        throw new IllegalArgumentException("Unknown action: " + action);
                }
            }

2.4 流程中断

执行前中断执行逻辑:

  1. 流程在 interruptBefore 调用前精准挂起
  2. 检查点器保存完整状态,支持后续恢复;生产环境需使用数据库等持久化检查点
  3. 返回 InterruptionMetadata 数据,我们需要根据输出类型进行处理

interruptBefore 模式下只要执行 Graph 到中断节点前,一定会执行自动中断并返回 InterruptionMetadata 数据,我们需要根据输出类型进行处理:

  1. 如果是 InterruptionMetadata 类型说明流程暂停了
  2. 检测到中断点时,输出相关处理页面

部分处理代码:

java 复制代码
        return stream
                .map(output -> {
                    String nodeName = output.node();
                    OverAllState state = output.state();
                    boolean isInterrupted = output instanceof InterruptionMetadata;

                    StringBuilder result = new StringBuilder();

                    // 检测中断点
                    if (isInterrupted) {
                        result.append("⏸️ 执行已中断,等待审核...\n");
                        result.append("节点: ").append(nodeName).append("\n");
                        result.append("sessionId: ").append(sessionId).append("\n");
                        result.append("\n请选择审核操作:\n");
                        result.append("<div class=\"action-buttons\">\n");
                        result.append("  <button class=\"action-btn approved-btn\" onclick=\"handleInterruption('APPROVED')\">");
                        result.append("✅ 批准 - 继续执行</button>\n");
                        result.append("  <button class=\"action-btn rejected-btn\" onclick=\"handleInterruption('REJECTED')\">");
                        result.append("❌ 拒绝 - 终止执行</button>\n");
                        result.append("  <button class=\"action-btn edited-btn\" onclick=\"showEditStateForm()\">");
                        result.append("✏ 编辑 - 修改状态后继续</button>\n");
                        result.append("</div>\n");
                        result.append("<div id=\"editStateForm\" style=\"display:none;margin-top:10px;\">\n");
                        result.append("  <textarea id=\"editedStateInput\" placeholder='输入JSON格式的状态更新,例如:{\"draft_response\":\"修改后的回复\"}' style=\"width:100%;height:80px;padding:8px;border:1px solid #ddd;border-radius:8px;resize:vertical;\"></textarea>\n");
                        result.append("  <button class=\"action-btn confirm-edited-btn\" onclick=\"handleInterruption('EDITED')\" style=\"margin-top:8px;\">确认修改并继续</button>\n");
                        result.append("</div>\n");

                        // 输出当前状态信息供审核
                        state.value("classification").ifPresent(v -> {
                            result.append("\n分类结果: ").append(v).append("\n");
                        });
                        state.value("draft_response").ifPresent(v -> {
                            result.append("\n回复草稿: ").append(v).append("\n");
                        });
                    } 

InterruptionMetadata 数据:

json 复制代码
{
	nodeId = 'read_email',
		state = {
			"OverAllState": {
				"data": {
					"email_id": "7eb310d1-6e77-43a2-9ddb-de63fa9586db",
					"_graph_execution_id_": "041fa408-6131-4b6a-9b2d-b372a2582e95",
					"email_content": "你好",
					"sender_email": "user@example.com"
				}
			}
		},
		metadata = {}
}

前端显示效果:

2.5 批准执行

AI 流程运行到指定审批节点时主动中断暂停,让出执行权给人工;人工完成审批 / 填写参数后,基于原断点恢复流程继续往下执行。

继续往下执行时又分为多种情况:

  1. 不修改状态数据,使用原数据继续执行
  2. 修改状态数据,替换掉原数据后再继续执行

恢复中断关键要点:

  1. 恢复时必须使用触发中断时相同的 thread_id ,为了获取到对应的检查点数据,获取使用默认 $default ,会造成紊乱

  2. 构建 HUMAN_FEEDBACK 元数据,标识这是一个恢复中断操作

  3. 传递 checkPointId 值(检查点 ID ),获取中断时保存的状态数据,否则取最后一条

2.5.1 情况 1 :不修改状态

构建 RunnableConfig

java 复制代码
// 人工批准:恢复执行
RunnableConfig approvedResumeConfig = RunnableConfig.builder()
         .threadId(sessionId) // 会话 ID 
         .resume() // 构建 `HUMAN_FEEDBACK` 元数据
         .build();

支持传递 RunnableConfig 配置的流式方法只有一个:

java 复制代码
	public Flux<NodeOutput> stream(Map<String, Object> inputs, RunnableConfig config) {
		return streamFromInitialNode(stateCreate(inputs), config);
	}

不修改状态可以直接传入 null 会使用原状态数据继续执行(官网中提供的方法):

java 复制代码
stream = emailAgentGraph.stream(null, approvedResumeConfig);

对话结果中,发现原先的状态数据消失了 ,在分类节点时中没有任何数据,说明恢复执行后检查点中的数据没有合并到全局状态中:

可以先查询检查点状态,再次传入:

java 复制代码
StateSnapshot stateSnapshot = emailAgentGraph.getState(config);
Map<String, Object> input = stateSnapshot.state().data();
stream = emailAgentGraph.stream(input, approvedResumeConfig);

再次对话,发现正常了:

2.5.2 状态合并 BUG

在流程恢复执行时,会将原先检查点状态,合并到用户传入的状态中,在上面不修改状态运行中,因为是执行前中断,所以检查点中保存的是 _START 节点的数据:

合并策略只会为将检查点中配置了 Key 策略的数据,添加到新的全局状态中,恢复执行时,我们传入的 null 的全局状态为:

检查点中没有 input 数据,所以导致全局状态完全是空的。按照正常逻辑来说,不应该是以新传入的数据为准:

  • 用户没有传数据时,将检查点中的所有数据都合并到全局状态中
  • 用户传了数据时,以新数据为主,检查点中的数据为辅

GitHub 中可以看到,这是一个 BUG ,把「旧状态」和「新状态」传反了,导致恢复流程时,数据合并(追加 / 累加 / 替换)结果错误,并在 2026-2 月提交了 PR ,最新版本(1.1.2.2)尚未修复:

2.5.3 情况 2 :修改状态

用户提交修改后在恢复执行的场景中,第一步需要显示给用户可编辑操作和内容,比如,这里演示直接将所有的状态数据返回用户,用户点击编辑后,输入新的状态数据恢复执行:

将修改后的状态传递给流程进行恢复执行:

java 复制代码
@SuppressWarnings("unchecked")
Map<String, Object> stateUpdate = (Map<String, Object>) request.get("stateUpdate");
if (stateUpdate == null) {
    stateUpdate = Map.of();
}
RunnableConfig resumeWithEditConfig = RunnableConfig.builder()
        .threadId(sessionId)
        .resume()
        .build();
stream = emailAgentGraph.stream(stateUpdate, resumeWithEditConfig);

2.6 人工拒绝

如果是人工拒绝,可以在状态元数据中添加一个拒绝标识,会调用检查点进行更新和持久化,下次再执行这个会话时,可以提示当前会话流程已终止。

添加元数据:

java 复制代码
emailAgentGraph.updateState(
        config,
        Map.of("human_feedback", "rejected"),
        null
);
log.info("Rejected - terminating execution for sessionId: {}", sessionId);
return Flux.just("❌ 执行已被拒绝终止。");

在恢复执行前,需要自定义判断逻辑:

java 复制代码
var currentState = emailAgentGraph.getState(config);
Map<String, Object> stateData = currentState.state().data();
String human_feedback = stateData.get("human_feedback").toString();
if ("rejected".equals(human_feedback)){
    throw new RuntimeException("当前流程已终止,无法进行恢复!!");
}

3. interruptsAfter 模式

interruptsAfter 用于配置【节点执行后】执行中断,指定节点执行完成结束后再暂停流程。

适用场景:

  • 文案生成、合同解析、内容抽取等节点跑完,暂停供人工校对、修改;
  • 节点产出业务结果后,需要外部系统回调、消费数据,再继续往下走;
  • 子图 / 子流程执行完毕后暂停,等待外部指令再进入主流程下一环节;
  • 数据加工、统计计算节点执行完,需先落库、发通知,再手动恢复续跑。

用法和 interruptBefore 基本一致,很多一样的地方就不赘述了

3.1 简单案例

示例,定义classify_intent (邮件分类)节点执行后进行中断:

java 复制代码
        CompileConfig compileConfig = CompileConfig.builder()
                .saverConfig(saverConfig)
                .interruptAfter("classify_intent")
                .store(new MemoryStore())
                .build();

输入测试:

3.2 interruptBeforeEdge 配置

中断时只暂停不规划,恢复时再动态算下一步,专门为「动态条件路由边」设计,等人工改完数据状态后,再让流程做条件判断,保证路由正确。

对配置了 interruptsAfter 的节点,interruptBeforeEdge 可以配置:

  • false(默认值):中断后,直接计算下一个节点
  • true:中断后,不提前计算下一个业务节点,只把 nextNodeId 标记为 __INTERRUPTED__,恢复后才根据当前状态重新计算真正的下一个节点

配置示例:

java 复制代码
        CompileConfig compileConfig = CompileConfig.builder()
                .saverConfig(saverConfig)
                .interruptAfter("read_email")
                .interruptBeforeEdge(true)
                .store(new MemoryStore())
                .build();

如果有【邮件读取】、【邮件分类】两个节点,当配置了【邮件读取】配置了执行后中断时,并开启了 interruptBeforeEdge = true ,下一个节点设置为(__INTERRUPTED__),而不是【邮件分类】:

【邮件读取】执行完成后会进行中断,在恢复执行时,会再将下一个节点设置为本身应该执行的节点(实时计算):

相关推荐
疯狂成瘾者1 小时前
Spring Boot 项目中的 SMTP 邮件验证码服务技术解析
java·spring boot·后端
y = xⁿ1 小时前
Java并发八股学习日记
java·开发语言·学习
xifangge20251 小时前
【深度排障】从 OS 底层寻址剖析 javac 不是内部或外部命令 核心报错:变量空间隔离与自动化部署终极范式
java·开发语言·jdk·自动化
染指11101 小时前
3.AI大模型-token是什么-大模型底层运行机制
人工智能·算法·机器学习
肖恩想要年薪百万1 小时前
JSP中常用JSTL标签
java·开发语言·状态模式
stsdddd1 小时前
【YOLO算法多类别野生动物识别目标检测数据集】
人工智能·yolo·目标检测
qq_411262421 小时前
四博AI眼罩方案升级:白噪音、音乐助眠、AI情绪陪伴,把智能音箱戴在身
人工智能·智能音箱
han_2 小时前
AI Skill 是什么?一篇讲清楚它和 Prompt、MCP 的区别
人工智能·ai编程·mcp
实习僧企业版2 小时前
从“抢人”到“识人”,回归匹配本质
大数据·人工智能·雇主品牌·招聘技巧