尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Spring AI Alibaba ——人工介入(Human-in-the-Loop)

Spring AI Alibaba ——人工介入(Human-in-the-Loop)
📅 发布时间:2026/6/23 4:50:46

Spring AI Alibaba ——人工介入(Human-in-the-Loop)

💡核心结论:一句话先记住

如果说 Agent 是个不知疲倦的打工人,那HITL(Human-in-the-Loop,人工介入)就是给它配了一个“拥有一票否决权的主管”。

大白话:当 AI 打算干一些“危险动作”(比如写文件、删数据库、发正式邮件)时,系统会自动按下暂停键,把操作卡住,等你人工看一眼。你点头了(Approve),它才继续干;你觉得不行,可以帮它改参数(Edit),或者直接打回重做(Reject)。

在 Spring AI Alibaba 中,这个机制底层依赖于检查点(Checkpoint)机制:Agent 暂停时会把当前的脑图记忆存进硬盘或内存,等你审批完,再原封不动地取出来继续跑。


🛑一、 老板批奏折的三种决策(Decision Types)

当 Agent 被拦截下来时,你需要给它一个反馈,系统内置了三种决策:

  • ✅approve(批准):没毛病,原样执行。(比如:生成的邮件内容很好,直接发。)
  • ✏️edit(修改):思路对了,但细节有误,我帮你改改参数再执行。(比如:邮件收件人填错了,手动改对后发送。)
  • ❌reject(拒绝):纯属胡扯,打回去并告诉它为什么拒绝,让它重新想。(比如:这封邮件语气太凶了,重写。)

📁二、 怎么给危险工具挂上“审批流”?(基础配置配置)

大白话:第一步,必须要有记忆存储(如MemorySaver),不然暂停后 Agent 就失忆了;第二步,搞一个HumanInTheLoopHook,告诉它遇到哪些工具需要卡住。

💻配置中断代码示例:

import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.agent.hook.hip.HumanInTheLoopHook; import com.alibaba.cloud.ai.graph.agent.hook.hip.ToolConfig; import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; // ⭐ 1. 必须:配置检查点保存器(因为人工介入必须保存案发现场) MemorySaver memorySaver = new MemorySaver(); // ⭐ 2. 核心:创建人工介入 Hook,指定哪些工具需要审批 HumanInTheLoopHook humanInTheLoopHook = HumanInTheLoopHook.builder() // 发现它调用 "write_file" 工具时,给我拦住! .approvalOn("write_file", ToolConfig.builder().description("文件写入操作需要审批").build()) // 发现它调用 "execute_sql" 时,也拦住! .approvalOn("execute_sql", ToolConfig.builder().description("SQL执行操作需要审批").build()) .build(); // 3. 把 Hook 和 Saver 挂载到 Agent 身上 ReactAgent agent = ReactAgent.builder() .name("approval_agent") .model(chatModel) .tools(writeFileTool, executeSqlTool, readDataTool) // 里面包含了各种工具 .hooks(List.of(humanInTheLoopHook)) // 注入审批流 .saver(memorySaver) // 注入记忆 .build();

✋三、 怎么响应中断并给反馈?(完整生命周期)

大白话:当你调用 Agent 发现它没返回答案,而是返回了一个InterruptionMetadata,这就说明“它被卡住了,在等你批示”。

💻1. 发现并查看中断(响应中断示例):

import com.alibaba.cloud.ai.graph.RunnableConfig; import com.alibaba.cloud.ai.graph.NodeOutput; import com.alibaba.cloud.ai.graph.action.InterruptionMetadata; // ⭐ 必须提供 threadId,不然系统不知道这是谁的会话 String threadId = "user-session-123"; RunnableConfig config = RunnableConfig.builder().threadId(threadId).build(); // 运行它! Optional<NodeOutput> result = agent.invokeAndGetOutput("删除数据库中的旧记录", config); // ⭐ 检查是不是被中断了 if (result.isPresent() && result.get() instanceof InterruptionMetadata) { InterruptionMetadata interruptionMetadata = (InterruptionMetadata) result.get(); // 把它打算干的坏事打印出来看看 List<InterruptionMetadata.ToolFeedback> toolFeedbacks = interruptionMetadata.toolFeedbacks(); for (InterruptionMetadata.ToolFeedback feedback : toolFeedbacks) { System.out.println("工具: " + feedback.getName()); System.out.println("参数: " + feedback.getArguments()); System.out.println("描述: " + feedback.getDescription()); } }

💻2. 给出决策并恢复运行(完整示例,接上文):

import com.alibaba.cloud.ai.graph.agent.ReactAgent; import com.alibaba.cloud.ai.graph.agent.hook.hip.HumanInTheLoopHook; import com.alibaba.cloud.ai.graph.agent.hook.hip.ToolConfig; import com.alibaba.cloud.ai.graph.RunnableConfig; import com.alibaba.cloud.ai.graph.NodeOutput; import com.alibaba.cloud.ai.graph.action.InterruptionMetadata; import com.alibaba.cloud.ai.graph.checkpoint.savers.MemorySaver; public class HumanInTheLoopExample { public static void main(String[] args) throws Exception { // ... (此处省略前面初始化 Agent 和 Hook 的代码) ... String threadId = "user-session-001"; RunnableConfig config = RunnableConfig.builder().threadId(threadId).build(); // 🔴 第一次调用:期望它被拦住 Optional<NodeOutput> result = agent.invokeAndGetOutput("帮我写一首100字左右的诗", config); if (result.isPresent() && result.get() instanceof InterruptionMetadata interruptionMetadata) { System.out.println("检测到中断,需要人工审批"); // ⭐ 1. 模拟老板批奏折:构建审批意见(这里选择全部 APPROVED 批准) InterruptionMetadata.Builder feedbackBuilder = InterruptionMetadata.builder() .nodeId(interruptionMetadata.node()) .state(interruptionMetadata.state()); interruptionMetadata.toolFeedbacks().forEach(toolFeedback -> { InterruptionMetadata.ToolFeedback approvedFeedback = InterruptionMetadata.ToolFeedback.builder(toolFeedback) .result(InterruptionMetadata.ToolFeedback.FeedbackResult.APPROVED) // 决策:批准! .build(); feedbackBuilder.addToolFeedback(approvedFeedback); }); InterruptionMetadata approvalMetadata = feedbackBuilder.build(); // ⭐ 2. 将圣旨(反馈意见)塞进 Config 里 RunnableConfig resumeConfig = RunnableConfig.builder() .threadId(threadId) .addMetadata(RunnableConfig.HUMAN_FEEDBACK_METADATA_KEY, approvalMetadata) // 带着反馈意见 .build(); // 🟢 第二次调用:带着老板的决定,恢复执行!(注意传的 input 是空串,因为状态已经记在脑子里了) Optional<NodeOutput> finalResult = agent.invokeAndGetOutput("", resumeConfig); if (finalResult.isPresent()) { System.out.println("最终结果: " + finalResult.get()); } } } }

🌪️四、 终极复杂场景:Workflow(工作流)里的连环套

大白话:有时候 Agent 不是单打独斗,而是被嵌套在一个巨大的StateGraph(图工作流)里的一个小小节点。这时候怎么审批?

区别在于:记忆(Saver)必须注册在工作流全局的CompileConfig上,中断和恢复也是对着CompiledGraph发号施令。

💻工作流嵌套审批代码:

// ... 省略 imports ... // 1. 创建工具和 Saver ToolCallback searchTool = FunctionToolCallback.builder("search", (args) -> "搜索结果...").build(); MemorySaver saver = new MemorySaver(); // ⭐ 全局共享的 Saver // 2. 创建带审批 Hook 的 Agent ReactAgent qaAgent = ReactAgent.builder() .name("qa_agent") .model(chatModel) .saver(saver) // Agent 要挂载 .hooks(HumanInTheLoopHook.builder() .approvalOn("search", ToolConfig.builder().description("搜索操作需审批").build()) .build()) .tools(searchTool).build(); // ... 省略 PreprocessorNode 和 ValidatorNode 的定义 ... // 3. 构建工作流 StateGraph workflow = new StateGraph(keyStrategyFactory); workflow.addNode("preprocess", node_async(new PreprocessorNode())); workflow.addNode("validate", node_async(new ValidatorNode())); // ⭐ 把 Agent 作为一个 Node 塞进图里 workflow.addNode(qaAgent.name(), qaAgent.asNode(true, false)); // ... 省略连接边的代码 ... // 4. 编译工作流!⭐ 关键:必须在 CompileConfig 中注册检查点保存器 CompiledGraph compiledGraph = workflow.compile(CompileConfig.builder() .saverConfig(SaverConfig.builder().register(saver).build()) .build()); // 5. 执行工作流并处理中断 String threadId = "workflow-hilt-001"; Map<String, Object> input = Map.of("input", "请解释量子计算"); // 🔴 第一次调用 Graph Optional<NodeOutput> nodeOutputOptional = compiledGraph.invokeAndGetOutput(input, RunnableConfig.builder().threadId(threadId).build()); if (nodeOutputOptional.isPresent() && nodeOutputOptional.get() instanceof InterruptionMetadata interruptionMetadata) { System.out.println("工作流被中断,等待人工审核。中断节点: " + interruptionMetadata.node()); // ⭐ 构建同意的反馈(同上) InterruptionMetadata.Builder feedbackBuilder = InterruptionMetadata.builder() .nodeId(interruptionMetadata.node()) .state(interruptionMetadata.state()); interruptionMetadata.toolFeedbacks().forEach(toolFeedback -> { feedbackBuilder.addToolFeedback(InterruptionMetadata.ToolFeedback.builder(toolFeedback) .result(InterruptionMetadata.ToolFeedback.FeedbackResult.APPROVED).build()); }); InterruptionMetadata approvalMetadata = feedbackBuilder.build(); // 🟢 第二次调用:恢复工作流执行 RunnableConfig resumableConfig = RunnableConfig.builder() .threadId(threadId) // 必须是同一个线程 .addHumanFeedback(approvalMetadata) .build(); // 传入空 Map,因为状态已保存在全局检查点中 nodeOutputOptional = compiledGraph.invokeAndGetOutput(Map.of(), resumableConfig); }

🛠️五、 官方送你的“批奏折”快捷键 (HITLHelper)

大白话:每次遇到中断,都要写一堆Builder去循环构建同意/拒绝,太啰嗦了!你可以封装一个HITLHelper实用工具类,一键审批!

💻HITLHelper 工具类代码:

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).build()); }); return builder.build(); } /** 修改特定工具的参数,其他的统统批准 */ 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(); } } // 实际使用起来爽多了: InterruptionMetadata approvalMetadata = HITLHelper.approveAll(interruptionMetadata); // 一键全过 InterruptionMetadata rejectMetadata = HITLHelper.rejectAll(interruptionMetadata, "操作不安全"); // 一键打回 InterruptionMetadata editMetadata = HITLHelper.editTool(interruptionMetadata, "execute_sql", "{\"query\": \"SELECT * FROM records LIMIT 10\"}"); // 手动改个分页再跑

🛡️最佳实践(避坑指南)

  1. 没脑子千万别审批:必须使用检查点(Saver),否则恢复时找不到状态。
  2. 话要说清楚:配置ToolConfig时,描述写清楚点,不然人工审核的时候看着一团代码懵圈。
  3. 不要落下任何一只手:遇到多个并发的工具中断,必须对每一个ToolFeedback都给出决策(是杀是留)。
  4. 认准唯一单号:恢复执行的时候,threadId必须和之前中断的完全一致。
  5. 处理死等:最好加个超时机制,不能让 Agent 等老板审批等一整天。

🎯终极秒记口诀

智能 Agent 爱自由,危险动作需防守;

加入 HITL 做拦截,Saver 留存防弄丢;

Approve 批准任它走,Edit 帮你修一修;

要是胡扯全 Reject,批完奏折再回头!

相关新闻

  • (2026最新)惠州防水补漏正规公司甄选推荐:漏水检测维修-暗管漏水精准定位检测漏水点-卫生间/厨房/屋顶/阳台/渗漏水维修-本地人必选的正规测漏公司 - 即刻修防水
  • 从零到一万并发:Apipost接口压力测试全流程实战指南
  • (2026最新)德阳防水补漏正规公司甄选推荐:漏水检测维修-暗管漏水精准定位检测漏水点-卫生间/厨房/屋顶/阳台/渗漏水维修-本地人必选的正规测漏公司 - 即刻修防水

最新新闻

  • MedFlow:面向临床落地的多模态医疗AI工程化基座
  • OpenClaw不是多Agent框架,而是Skill共享总线
  • Edge AI与TinyML:电子行业AI落地实践
  • Hy-MT2混合指令调优:大模型翻译的工业级定制化实践
  • 盘点2026年数控弯管机制造商,伟博机械怎么样 - mypinpai
  • 电焊培训中心哪家性价比高?顺鑫职业技术培训学校分析 - mypinpai

日新闻

  • Arduino-ESP32项目深度解析:解锁隐藏芯片支持与架构演进
  • 2026年 系统窗厂家/品牌推荐榜单:隔音系统窗+高端系统门窗的核心优势与选购指南 - 品牌发掘
  • NVBench:首个双语非言语发声语音合成评测基准详解与实践

周新闻

  • Visual C++运行库修复终极指南:5分钟快速解决Windows软件启动错误
  • 手把手教你构建统计局地区经济数据爬虫:从环境搭建到数据持久化全指南
  • 2026多Agent深度解析:用AI团队替代单一模型,四种架构实战落地

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号