文章目录
- [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-Loop(HITL,人在回路)是一种人工智能系统设计模式,核心思想是在 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 的工具调用进行人工监督与审批,其工作原理如下:
- 拦截
Agent生成的所有工具调用请求; - 与预设的审批策略(哪些工具需要审批)进行比对;
- 若工具调用需要人工介入,触发 执行中断 ,暂停
Agent运行; - 通过检查点(
Checkpoint)保存当前图(Graph)的执行状态; - 接收人工决策(批准/编辑/拒绝)后,恢复
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 最佳实践
- 检查点必须配置:无论测试还是生产,HITL 依赖检查点保存状态,生产环境优先使用
RedisSaver或PostgresSaver; - 明确审批策略:只对高危工具(如
execute_sql、write_file)配置审批,避免过度干预影响 Agent 效率; - 提供清晰描述:在
ToolConfig中添加详细的审批说明,帮助人工审查者快速理解操作目的; - 统一线程
ID:中断与恢复必须使用相同的threadId,建议使用用户会话 ID 或请求 ID,确保状态关联正确; - 实现超时机制:人工审批可能长时间未响应,需添加超时逻辑,避免 Agent 一直处于暂停状态;
- 最小化编辑:编辑工具参数时,尽量只修改必要内容,避免破坏模型的原始意图。
4.2 常见注意事项
- 检查点共享:
Workflow与嵌套Agent必须使用同一个检查点保存器,否则无法恢复状态; - 工具名称一致:
approvalOn中的toolName必须与工具的name完全匹配(大小写敏感); - 空输入恢复:恢复执行时,输入参数可传入空
Map,因为状态已保存在检查点中; - 多工具中断:若多个工具同时触发中断,需为每个工具单独提供决策,不可批量处理;
- 异常处理:需捕获中断恢复时的异常(如检查点连接失败),避免执行异常导致状态丢失。