Spring AI Alibaba 1.x 系列【33】Human-in-the-Loop(人在回路)演示

文章目录

  • [1. 概述](#1. 概述)
    • [1.1 什么是 Human-in-the-Loop](#1.1 什么是 Human-in-the-Loop)
    • [1.2 Spring AI Alibaba HITL](#1.2 Spring AI Alibaba HITL)
  • [2. 入门案例](#2. 入门案例)
    • [2.1 配置需要审批的工具](#2.1 配置需要审批的工具)
    • [2.2 构建 HITL Hook](#2.2 构建 HITL Hook)
    • [2.3 创建 MemorySaver](#2.3 创建 MemorySaver)
    • [2.4 构建 Agent](#2.4 构建 Agent)
    • [2.5 测试](#2.5 测试)
      • [2.5.1 删除文件](#2.5.1 删除文件)
      • [2.5.2 执行 Shell](#2.5.2 执行 Shell)
  • [3. 使用进阶](#3. 使用进阶)
    • [3.1 前端接入](#3.1 前端接入)
      • [3.1.1 判断是否返回 HITL 中断](#3.1.1 判断是否返回 HITL 中断)
      • [3.1.2 提交审批结果](#3.1.2 提交审批结果)
    • [3.2 三种审批决策](#3.2 三种审批决策)
    • [3.3 多工具审批](#3.3 多工具审批)
    • [3.4 Workflow 中嵌套 Agent](#3.4 Workflow 中嵌套 Agent)
    • [3.5 HITL 决策简化工具类](#3.5 HITL 决策简化工具类)
  • [4. 最佳实践与注意事项](#4. 最佳实践与注意事项)
    • [4.1 最佳实践](#4.1 最佳实践)
    • [4.2 常见注意事项](#4.2 常见注意事项)

1. 概述

AI Agent 实际生产应用中,部分高风险操作(如文件写入、SQL 执行)需要人工监督,避免模型误操作导致数据丢失或安全问题

1.1 什么是 Human-in-the-Loop

Human-in-the-LoopHITL,人在回路)是一种人工智能系统设计模式,核心思想是在 AI 自主执行关键操作之前,引入人类监督和决策机制。该模式确保 AI 系统在执行敏感、高风险或重要任务时,必须获得人类的明确批准或指导。

核心价值

  • 风险控制 - 防止 AI 执行危险操作(删除文件、执行 SQL、调用付费 API
  • 合规审计 - 满足企业合规要求,所有关键操作可追溯
  • 人机协作 - 结合 AI 效率和人类判断力,提升决策质量
  • 调试开发 - 在开发阶段观察 AI 决策过程,便于调试和优化

应用场景

场景分类 具体应用 审批工具示例
数据安全 数据库操作、文件删除 execute_sql, delete_file, drop_table
财务支出 支付、转账、订单处理 process_payment, create_order, transfer_funds
外部通信 发送邮件、短信、通知 send_email, send_sms, push_notification
系统变更 配置修改、服务部署 update_config, deploy_service, restart_server
敏感查询 个人隐私数据访问 query_user_pii, access_medical_record
内容发布 文章发布、社交媒体 publish_article, post_to_social_media
高风险操作 权限变更、账户管理 change_user_role, disable_account

1.2 Spring AI Alibaba HITL

Spring AI Alibaba 中,HITL 的核心作用是 对 Agent 的工具调用进行人工监督与审批,其工作原理如下:

  1. 拦截 Agent 生成的所有工具调用请求;
  2. 与预设的审批策略(哪些工具需要审批)进行比对;
  3. 若工具调用需要人工介入,触发 执行中断 ,暂停 Agent 运行;
  4. 通过检查点(Checkpoint)保存当前图(Graph)的执行状态;
  5. 接收人工决策(批准/编辑/拒绝)后,恢复 Agent 执行。

执行流程如下:

复制代码
AI 执行流程
    ↓
┌─────────────────────────────────────┐
│  LLM 推理生成工具调用                  │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│  HumanInTheLoopHook 检测              │
│  - 识别需要审批的工具                │
│  - 构建中断元数据                    │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│  中断执行,等待人类决策               │
│  - APPROVED: 批准执行               │
│  - EDITED: 编辑后执行               │
│  - REJECTED: 拒绝执行               │
└─────────────────────────────────────┘
    ↓
┌─────────────────────────────────────┐
│  从中断点恢复执行                     │
│  - 应用人类决策                      │
│  - 执行/跳过工具                     │
└─────────────────────────────────────┘

核心模块支持:

模块 HITL 支持 说明
spring-ai-alibaba-graph-core ✅ 底层支持 提供 InterruptableAction 接口和 InterruptionMetadata 数据结构
spring-ai-alibaba-agent-framework ✅ 核心实现 提供 HumanInTheLoopHook 和完整的审批流程
spring-ai-alibaba-studio ✅ 可视化支持 提供 HITL 审批 UI 界面,可在 Web 界面进行审批操作
spring-ai-alibaba-admin ✅ 平台支持 企业管理平台支持 HITL 审批流配置和审计日志

2. 入门案例

2.1 配置需要审批的工具

当你的 AI 智能体想要执行高危工具:

  • 调用 delete_file → 删除文件
  • 调用 shell → 在服务器执行命令

框架会强制触发人工审批:

  • 弹出你配置的提示语
  • 必须用户手动确认同意,AI 才能执行这个操作
  • 如果用户不同意,操作直接取消
java 复制代码
// 1. 创建一个【工具名称 → 工具安全配置】的集合
Map<String, ToolConfig> approvalOn = Map.of(
        // 2. 配置第一个高危工具:删除文件
        "delete_file", ToolConfig.builder()
                // 审批提示语:AI调用这个工具时,弹出这句话让用户确认
                .description("⚠️ 删除文件是危险操作,请确认")
                .build(),
        
        // 3. 配置第二个高危工具:执行Shell命令
        "shell", ToolConfig.builder()
                // 审批提示语
                .description("⚠️ 执行 Shell 是危险操作,请确认")
                .build()
);
代码部分 含义
approvalOn 开启人工审批的工具列表
delete_file 删除文件工具(高危)
shell 执行服务器命令工具(高危)
description 审批时的提示文案

2.2 构建 HITL Hook

构建 HumanInTheLoopHook 并配置【需要人工审批的工具】的集合:

java 复制代码
    HumanInTheLoopHook hitlHook = HumanInTheLoopHook.builder()
        .approvalOn(approvalOn)
        .build();

支持三种重载方法:

  • approvalOn(String toolName, ToolConfig toolConfig)
  • approvalOn(String toolName, String description)
  • approvalOn(Map<String, ToolConfig> approvalOn)

2.3 创建 MemorySaver

必须配置 MemorySaver 用于状态保存:

java 复制代码
MemorySaver memorySaver = new MemorySaver();

2.4 构建 Agent

完整示例:

java 复制代码
/**
 * 创建 技能型React智能体 Bean
 * 整合了本地技能注册、Shell工具、人工审批安全管控
 */
@Bean("skillAgent")
public ReactAgent skillAgent(ChatModel dashscopeChatModel) throws GraphRunnerException {

    // 配置需要人工审批的高危工具:删除文件、执行Shell命令
    Map<String, ToolConfig> approvalOn = Map.of(
            "delete_file", ToolConfig.builder()
                    .description("⚠️ 删除文件是危险操作,请确认")
                    .build(),
            "shell", ToolConfig.builder()
                    .description("⚠️ 执行 Shell 是危险操作,请确认")
                    .build()
    );

    // 构建人工介入钩子:AI调用高危工具前必须人工确认
    HumanInTheLoopHook humanInTheLoopHook = HumanInTheLoopHook.builder()
            .approvalOn(approvalOn)
            .build();

    // 内存记忆存储器:保存智能体对话上下文
    MemorySaver memorySaver = new MemorySaver();

    // 构建技能注册中心:加载本地文件系统中的技能包
    SkillRegistry registry = FileSystemSkillRegistry.builder()
            .userSkillsDirectory(System.getProperty("user.home") + "/saa/skills") // 用户目录技能
            .projectSkillsDirectory("E:\\TD\\icloud\\study-spring-ai\\spring-ai-alibaba-studio-01\\src\\main\\resources\\skills") // 项目目录技能
            .autoLoad(true) // 自动加载技能
            .build();

    // 技能代理钩子:关联技能注册中心,支持自动重载
    SkillsAgentHook skillsHook = SkillsAgentHook.builder()
            .skillRegistry(registry)
            .autoReload(true)
            .build();

    // Shell工具钩子:提供执行服务器命令的能力
    ShellToolAgentHook shellHook = ShellToolAgentHook.builder().build();

    // 构建并返回React架构的智能体
    return ReactAgent.builder()
            .name("skill_agent") // 智能体名称
            .instruction("请全程使用中文交流")
            .methodTools(new DeleteFileTool()) // 删除文件工具
            .model(dashscopeChatModel) // 绑定通义千问大模型
            .hooks(skillsHook, shellHook,humanInTheLoopHook) // 注册三大钩子:技能、Shell、人工审批
            .saver(memorySaver) // 绑定记忆存储
            .enableLogging(true) // 开启日志
            .build();
}

2.5 测试

2.5.1 删除文件

输入:

java 复制代码
删除 G:\\image1.jpg\

大模型要求调用删除工具时,提示需要审批:

点击 APPROVED 后才会执行删除操作:

2.5.2 执行 Shell

输入:

java 复制代码
解析 D:\表格1.pdf

在使用 pdf-extractor 技能执行 Shell 命令时,会弹出审批页面:

点击 REJECTED 后:

3. 使用进阶

3.1 前端接入

前面使用了 Studio 提供的审批页面支持,是实际开发中,怎么实现前后端交互呢?

3.1.1 判断是否返回 HITL 中断

NodeOutput 表示图节点的输出,有三种类型

  • StreamingOutput<T> :流式输出,用于 Agent 执行过程中的消息流
  • StateSnapshot :状态快照,用于 checkpoint 恢复
  • InterruptionMetadata :中断元数据,用于 human-in-the-loop 场景

如果返回 InterruptionMetadata 表示需要进行 HITL 中断处理:

java 复制代码
    @GetMapping("/chat")
    public Object chat() {
        // 定义待执行的指令消息
        var message = "删除 G:\\image1.jpg";
        // 定义用户会话ID,用于区分不同用户的对话上下文
        var threadId = "user_123456";

        // 构建Agent执行配置,指定会话线程ID
        RunnableConfig config = RunnableConfig.builder()
                .threadId(threadId)
                .build();

        Optional<NodeOutput> result = null;
        try {
            // 调用技能Agent执行指令,获取执行结果
            result = skillAgent.invokeAndGetOutput(message, config);
        } catch (GraphRunnerException e) {
            // 捕获Agent执行异常,包装为运行时异常抛出
            throw new RuntimeException(e);
        }

        // 判断Agent是否返回有效结果,无结果则返回错误响应
        if (result.isEmpty()) {
            return HitlResponse.error("Agent 未返回任何结果");
        }

        // 获取Agent执行的有效输出
        NodeOutput output = result.get();

        // 判断是否触发HITL人工中断(需要人工审批)
        if (output instanceof InterruptionMetadata interruption) {
            log.info("触发 HITL 中断,需要人工审批");
            return interruption;
        }
        
        // 执行正常,返回Agent最终响应结果
        return output;
    }
java 复制代码
var message = "删除 G:\\image1.jpg";
        var threadId = "user_123456";

        RunnableConfig config = RunnableConfig.builder()
                .threadId(threadId)
                .build();

InterruptionMetadata 中包含了当前需要审批的工具信息:

前端需要判断返回的是中断信息时,弹出审批页面,比如,可以直接显示 description 中的信息

java 复制代码
The AI is requesting to use the tool: delete_file.
Description: ⚠️ 删除文件是危险操作,请确认
With the following arguments: {"filePath": "G:\\image1.jpg"}
Do you approve?

3.1.2 提交审批结果

构建完成后的 InterruptionMetadata 对象,添加审批意见,通过元数据恢复执行:

java 复制代码
// 1. 将输出对象强转为 HITL 中断元数据对象,获取中断详情
InterruptionMetadata feedbackInterruptionMetadata= (InterruptionMetadata)output;
// 2. 获取中断信息中第一个待审批的工具调用信息
InterruptionMetadata.ToolFeedback toolFeedback = feedbackInterruptionMetadata.toolFeedbacks().get(0);
// 3. 构建审批后的工具反馈:复制原工具信息,手动设置审批结果为【同意】
InterruptionMetadata.ToolFeedback feedback = InterruptionMetadata.ToolFeedback.builder()
        .id(toolFeedback.getId())
        .name(toolFeedback.getName())
        .arguments(toolFeedback.getArguments())
        .result(InterruptionMetadata.ToolFeedback.FeedbackResult.APPROVED)// 模拟人工审批:同意执行工具
        .description(toolFeedback.getDescription())
        .build();

// 4. 构建新的中断元数据:绑定执行节点ID + 注入审批完成的工具反馈
InterruptionMetadata interruptionMetadata = InterruptionMetadata.builder()
        .nodeId(output.node())
        .addToolFeedback(feedback)
        .build();

// 5. 创建 Agent 恢复执行配置:指定会话ID + 传入人工审批的元数据
RunnableConfig resumeConfig = RunnableConfig.builder()
        .threadId(threadId)
        .addMetadata(RunnableConfig.HUMAN_FEEDBACK_METADATA_KEY, interruptionMetadata)
        .build();

// 6. 携带人工审批结果,继续执行被中断的 Agent 流程(空消息:无需新指令,仅恢复执行)
Optional<NodeOutput> response = skillAgent.invokeAndGetOutput("", resumeConfig);

可以看到最终返回结果:

也支持以下方式创建恢复配置:

java 复制代码
            RunnableConfig resumeConfig = RunnableConfig.builder()
                    .threadId(threadId)
                    .addHumanFeedback(interruptionMetadata)
                    .build();

3.2 三种审批决策

HITL 定义了三种人工响应中断的决策类型,每种类型对应不同的业务场景,可通过配置灵活指定:

决策类型 英文标识 描述 适用场景示例
✅ 批准 APPROVED 操作被原样批准并执行,不做任何更改 完全按照模型生成的内容发送邮件、查询合法数据
✏️ 编辑 EDITED 工具调用参数被修改后执行 修正邮件收件人、调整 SQL 查询条件后执行
❌ 拒绝 REJECTED 工具调用被拒绝,向对话中添加拒绝原因 拒绝危险 SQL 操作(如 DROP TABLE),并提示模型重新生成

代码示例:

java 复制代码
// 1. 批准
InterruptionMetadata.ToolFeedback approved = InterruptionMetadata.ToolFeedback
    .builder(feedback)
    .result(InterruptionMetadata.ToolFeedback.FeedbackResult.APPROVED)
    .build();

// 2. 拒绝
InterruptionMetadata.ToolFeedback rejected = InterruptionMetadata.ToolFeedback
    .builder(feedback)
    .result(InterruptionMetadata.ToolFeedback.FeedbackResult.REJECTED)
    .description("不用使用这个工具,你自己完成写作。")
    .build();

// 3. 编辑
InterruptionMetadata.ToolFeedback edited = InterruptionMetadata.ToolFeedback
    .builder(feedback)
    .arguments(feedback.getArguments().replace("。\"", "。By Spring AI Alibaba\""))
    .result(InterruptionMetadata.ToolFeedback.FeedbackResult.EDITED)
    .build();

3.3 多工具审批

InterruptionMetadata 中的 ToolFeedback 是集合类型,是支持同时多个工具进行审批处理的:

java 复制代码
public final class InterruptionMetadata extends NodeOutput implements HasMetadata<InterruptionMetadata.Builder> {

	private final Map<String, Object> metadata;

	private List<AssistantMessage.ToolCall> toolsAutomaticallyApproved;

	private List<ToolFeedback> toolFeedbacks;

代码示例:

java 复制代码
// 场景:三个工具同时请求,分别采用不同的决策
InterruptionMetadata.Builder builder = InterruptionMetadata.builder()
    .nodeId(metadata.node())
    .state(metadata.state());

List<InterruptionMetadata.ToolFeedback> feedbacks = metadata.toolFeedbacks();

for (int i = 0; i < feedbacks.size(); i++) {
    InterruptionMetadata.ToolFeedback feedback = feedbacks.get(i);
    
    if (i == 0) {
        // 第一个工具:批准
        builder.addToolFeedback(InterruptionMetadata.ToolFeedback
            .builder(feedback)
            .result(InterruptionMetadata.ToolFeedback.FeedbackResult.APPROVED)
            .build());
    } else if (i == 1) {
        // 第二个工具:编辑
        builder.addToolFeedback(InterruptionMetadata.ToolFeedback
            .builder(feedback)
            .arguments("\"newValue\"")
            .result(InterruptionMetadata.ToolFeedback.FeedbackResult.EDITED)
            .build());
    } else {
        // 第三个及后续工具:拒绝
        builder.addToolFeedback(InterruptionMetadata.ToolFeedback
            .builder(feedback)
            .result(InterruptionMetadata.ToolFeedback.FeedbackResult.REJECTED)
            .description("不允许此操作")
            .build());
    }
}

InterruptionMetadata mixedFeedback = builder.build();

也可以分批审批:

java 复制代码
// 第一次审批:只批准第一个工具
InterruptionMetadata partialFeedback = InterruptionMetadata.builder()
    .nodeId(metadata.node())
    .state(metadata.state())
    .addToolFeedback(InterruptionMetadata.ToolFeedback
        .builder(feedbacks.get(0))
        .result(FeedbackResult.APPROVED)
        .build())
    // 注意:不添加其他工具的反馈
    .build();

// 系统会继续中断,等待剩余工具的审批

3.4 Workflow 中嵌套 Agent

在复杂业务中,常需要在 StateGraph 工作流中嵌套 ReactAgent,此时 HITL 中断需要在工作流级别处理,核心是 共享检查点、统一线程 ID

工作原理:

  • 嵌套的 ReactAgent 配置 HITL Hook 后,触发中断时,整个工作流会暂停;
  • 工作流的状态通过检查点保存,恢复执行时需在 CompiledGraph 层面传递人工决策;
  • 工作流与嵌套 Agent 必须使用 同一个检查点保存器,确保状态一致性。

完整配置示例:

java 复制代码
public class HITLWorkflowNestedExample {
    public static void main(String[] args) {
        // 1. 定义工具(搜索工具,需要人工审批)
        ToolCallback searchTool = FunctionToolCallback
                .builder("search", (args) -> "搜索结果:AI Agent是能够感知环境、自主决策并采取行动的智能系统。")
                .description("搜索工具,用于查找相关信息")
                .inputType(String.class)
                .build();

        // 2. 配置检查点保存器(工作流与Agent共享)
        MemorySaver saver = new MemorySaver();

        // 3. 创建带 HITL Hook 的 ReactAgent(嵌套到工作流中)
        ReactAgent qaAgent = ReactAgent.builder()
                .name("qa_agent")
                .model(chatModel) // 替换为自己的 ChatModel
                .instruction("你是问答专家,负责回答用户问题,需要时使用search工具。用户问题:{cleaned_input}")
                .outputKey("qa_result")
                .saver(saver) // 与工作流共享检查点
                .hooks(HumanInTheLoopHook.builder()
                        .approvalOn("search", ToolConfig.builder()
                                .description("搜索操作需要人工审批,请确认是否执行")
                                .build())
                        .build())
                .tools(searchTool)
                .build();

        // 4. 定义工作流节点(预处理、验证)
        // 预处理节点:清理用户输入
        class PreprocessorNode implements NodeAction {
            @Override
            public Map<String, Object> apply(OverAllState state) throws Exception {
                String input = state.value("input", "").toString();
                return Map.of("cleaned_input", input.trim());
            }
        }

        // 验证节点:验证问答结果是否有效
        class ValidatorNode implements NodeAction {
            @Override
            public Map<String, Object> apply(OverAllState state) throws Exception {
                Optional<Object> qaResultOpt = state.value("qa_result");
                if (qaResultOpt.isPresent() && qaResultOpt.get() instanceof String message) {
                    return Map.of("is_valid", message.length() > 30);
                }
                return Map.of("is_valid", false);
            }
        }

        // 5. 定义状态管理策略
        KeyStrategyFactory keyStrategyFactory = () -> {
            HashMap<String, KeyStrategy> strategies = new HashMap<>();
            strategies.put("input", new ReplaceStrategy());
            strategies.put("cleaned_input", new ReplaceStrategy());
            strategies.put("qa_result", new ReplaceStrategy());
            strategies.put("is_valid", new ReplaceStrategy());
            return strategies;
        };

        // 6. 构建工作流
        StateGraph workflow = new StateGraph(keyStrategyFactory);
        workflow.addNode("preprocess", node_async(new PreprocessorNode()));
        workflow.addNode("validate", node_async(new ValidatorNode()));
        workflow.addNode(qaAgent.name(), qaAgent.asNode(true, false)); // 嵌套Agent节点

        // 定义工作流执行顺序:预处理 → Agent处理 → 验证
        workflow.addEdge(StateGraph.START, "preprocess");
        workflow.addEdge("preprocess", qaAgent.name());
        workflow.addEdge(qaAgent.name(), "validate");

        // 条件分支:验证通过则结束,否则重新执行Agent
        workflow.addConditionalEdges(
                "validate",
                edge_async(state -> {
                    Boolean isValid = (Boolean) state.value("is_valid", false);
                    return isValid ? "end" : qaAgent.name();
                }),
                Map.of(
                        "end", StateGraph.END,
                        qaAgent.name(), qaAgent.name()
                )
        );

        // 7. 编译工作流(必须注册检查点保存器)
        CompiledGraph compiledGraph = workflow.compile(
                CompileConfig.builder()
                        .saverConfig(SaverConfig.builder().register(saver).build())
                        .build()
        );

        // 8. 执行工作流并处理中断
        String threadId = "workflow-hilt-001";
        Map<String, Object> input = Map.of("input", "请解释量子计算的基本原理");

        // 第一次调用:触发Agent的搜索工具审批中断
        Optional<NodeOutput> nodeOutputOptional = compiledGraph.invokeAndGetOutput(
                input,
                RunnableConfig.builder().threadId(threadId).build()
        );

        // 处理工作流级别中断
        if (nodeOutputOptional.isPresent() && nodeOutputOptional.get() instanceof InterruptionMetadata interruptionMetadata) {
            System.out.println("工作流被中断,等待人工审批:");
            System.out.println("中断节点: " + interruptionMetadata.node()); // 输出:qa_agent(嵌套Agent的节点名称)

            // 打印需要审批的工具信息
            List<InterruptionMetadata.ToolFeedback> feedbacks = interruptionMetadata.toolFeedbacks();
            for (InterruptionMetadata.ToolFeedback feedback : feedbacks) {
                System.out.println("工具名称: " + feedback.getName());
                System.out.println("工具参数: " + feedback.getArguments());
            }

            // 模拟人工批准
            InterruptionMetadata approvalMetadata = HITLHelper.approveAll(interruptionMetadata);

            // 恢复工作流执行
            RunnableConfig resumableConfig = RunnableConfig.builder()
                    .threadId(threadId)
                    .addHumanFeedback(approvalMetadata) // 工作流恢复用 addHumanFeedback 方法
                    .build();

            // 恢复执行(输入为空,状态已保存在检查点)
            nodeOutputOptional = compiledGraph.invokeAndGetOutput(Map.of(), resumableConfig);
            if (nodeOutputOptional.isPresent()) {
                System.out.println("工作流执行完成,最终结果: " + nodeOutputOptional.get());
            }
        }
    }

    // 复用 HITLHelper 工具类(同 5 节)
    public static class HITLHelper {
        public static InterruptionMetadata approveAll(InterruptionMetadata interruptionMetadata) {
            InterruptionMetadata.Builder builder = InterruptionMetadata.builder()
                    .nodeId(interruptionMetadata.node())
                    .state(interruptionMetadata.state());

            interruptionMetadata.toolFeedbacks().forEach(toolFeedback -> {
                builder.addToolFeedback(
                        InterruptionMetadata.ToolFeedback.builder(toolFeedback)
                                .result(InterruptionMetadata.ToolFeedback.FeedbackResult.APPROVED)
                                .build()
                );
            });
            return builder.build();
        }
    }
}

3.5 HITL 决策简化工具类

为避免重复编码,可封装 HITLHelper 工具类,快速实现"批准所有""拒绝所有""编辑工具参数"等常见决策,直接复用即可。

java 复制代码
import com.alibaba.cloud.ai.graph.action.InterruptionMetadata;
import java.util.List;

/**
 * HITL 人工决策工具类,简化中断处理逻辑
 */
public class HITLHelper {

    /**
     * 批准所有需要审批的工具调用
     */
    public static InterruptionMetadata approveAll(InterruptionMetadata interruptionMetadata) {
        InterruptionMetadata.Builder builder = InterruptionMetadata.builder()
                .nodeId(interruptionMetadata.node())
                .state(interruptionMetadata.state());

        interruptionMetadata.toolFeedbacks().forEach(toolFeedback -> {
            builder.addToolFeedback(
                    InterruptionMetadata.ToolFeedback.builder(toolFeedback)
                            .result(InterruptionMetadata.ToolFeedback.FeedbackResult.APPROVED)
                            .build()
            );
        });
        return builder.build();
    }

    /**
     * 拒绝所有需要审批的工具调用,并添加拒绝原因
     */
    public static InterruptionMetadata rejectAll(
            InterruptionMetadata interruptionMetadata,
            String reason) {
        InterruptionMetadata.Builder builder = InterruptionMetadata.builder()
                .nodeId(interruptionMetadata.node())
                .state(interruptionMetadata.state());

        interruptionMetadata.toolFeedbacks().forEach(toolFeedback -> {
            builder.addToolFeedback(
                    InterruptionMetadata.ToolFeedback.builder(toolFeedback)
                            .result(InterruptionMetadata.ToolFeedback.FeedbackResult.REJECTED)
                            .description(reason) // 拒绝原因,会返回给Agent
                            .build()
            );
        });
        return builder.build();
    }

    /**
     * 编辑指定工具的参数,其他工具默认批准
     * @param toolName 需要编辑的工具名称
     * @param newArguments 新的工具参数(JSON格式字符串)
     */
    public static InterruptionMetadata editTool(
            InterruptionMetadata interruptionMetadata,
            String toolName,
            String newArguments) {
        InterruptionMetadata.Builder builder = InterruptionMetadata.builder()
                .nodeId(interruptionMetadata.node())
                .state(interruptionMetadata.state());

        interruptionMetadata.toolFeedbacks().forEach(toolFeedback -> {
            if (toolFeedback.getName().equals(toolName)) {
                // 编辑目标工具的参数
                builder.addToolFeedback(
                        InterruptionMetadata.ToolFeedback.builder(toolFeedback)
                                .arguments(newArguments)
                                .result(InterruptionMetadata.ToolFeedback.FeedbackResult.EDITED)
                                .build()
                );
            } else {
                // 其他工具默认批准
                builder.addToolFeedback(
                        InterruptionMetadata.ToolFeedback.builder(toolFeedback)
                                .result(InterruptionMetadata.ToolFeedback.FeedbackResult.APPROVED)
                                .build()
                );
            }
        });
        return builder.build();
    }
}

// 使用示例
// 1. 批准所有
InterruptionMetadata approval = HITLHelper.approveAll(interruptionMetadata);
// 2. 拒绝所有,添加原因
InterruptionMetadata reject = HITLHelper.rejectAll(interruptionMetadata, "该SQL操作存在数据泄露风险");
// 3. 编辑SQL工具参数,将删除改为查询
InterruptionMetadata edit = HITLHelper.editTool(
        interruptionMetadata,
        "execute_sql",
        "{\"query\": \"SELECT * FROM records WHERE created_at < NOW() - INTERVAL '30 days' LIMIT 10\"}"
);

4. 最佳实践与注意事项

4.1 最佳实践

  1. 检查点必须配置:无论测试还是生产,HITL 依赖检查点保存状态,生产环境优先使用 RedisSaverPostgresSaver
  2. 明确审批策略:只对高危工具(如 execute_sqlwrite_file)配置审批,避免过度干预影响 Agent 效率;
  3. 提供清晰描述:在 ToolConfig 中添加详细的审批说明,帮助人工审查者快速理解操作目的;
  4. 统一线程 ID:中断与恢复必须使用相同的 threadId,建议使用用户会话 ID 或请求 ID,确保状态关联正确;
  5. 实现超时机制:人工审批可能长时间未响应,需添加超时逻辑,避免 Agent 一直处于暂停状态;
  6. 最小化编辑:编辑工具参数时,尽量只修改必要内容,避免破坏模型的原始意图。

4.2 常见注意事项

  1. 检查点共享:Workflow 与嵌套 Agent 必须使用同一个检查点保存器,否则无法恢复状态;
  2. 工具名称一致:approvalOn 中的 toolName 必须与工具的 name 完全匹配(大小写敏感);
  3. 空输入恢复:恢复执行时,输入参数可传入空 Map,因为状态已保存在检查点中;
  4. 多工具中断:若多个工具同时触发中断,需为每个工具单独提供决策,不可批量处理;
  5. 异常处理:需捕获中断恢复时的异常(如检查点连接失败),避免执行异常导致状态丢失。
相关推荐
今天你TLE了吗2 小时前
LLM到Agent&RAG——AI概念概述 第五章:Skill
人工智能·笔记·后端·学习
网安情报局2 小时前
弹性云服务器跟游戏行业有什么关系?
人工智能
難釋懷2 小时前
Redis服务器端优化-内存划分和内存配置
java·redis·spring
两年半的个人练习生^_^2 小时前
每日一学:设计模式之适配器模式
java·设计模式·适配器模式
人工智能AI技术2 小时前
梯度下降基础:AI 模型自我优化的核心方法
人工智能
kishu_iOS&AI2 小时前
深度学习 —— 梯度下降法的优化方法
人工智能·pytorch·python·深度学习
MobotStone2 小时前
拼多多为什么弱化购物车?
人工智能
Rabbit_QL2 小时前
【权重】离线环境怎么用预训练权重
人工智能·pytorch
程序员老邢2 小时前
【技术底稿 18】FTP 文件处理 + LibreOffice Word 转 PDF 在线预览 + 集群乱码终极排查全记录
java·经验分享·后端·pdf·word·springboot