react - 人工介入(HITL)
2026年8月3日 下午5:12:18
在自动化程度极高的 AI Agent 应用中,人工介入(Human-In-The-Loop, HITL) 是确保业务安全与合规的最后一道防线。(工具调用时)对于涉及资金退款、敏感数据删除或重要邮件发送等操作,我们需要在 Agent 执行前获得人类的明确许可,甚至允许人类修正 AI 的参数。
Solon AI 通过标准化的 HITLInterceptor,实现了 “任务探知 - 决策回填 - 断点续传” 的工业级管控流程。
1、核心原理:中断与延续
HITL 的本质是利用 ReAct 协议的生命周期钩子进行“切面管控”:
- 任务挂起: 当 Agent 尝试执行敏感工具调用(Tool Call)时,拦截器在
onActionStart(批级预检)捕捉到这一动作,将callUuid、工具名、参数封装成HITLTask快照存入 Session,随即通过session.pending(true, summary)挂起会话并写入 Final Answer(源码中并不存在trace.interrupt()方法)。流式输出下还会推送HITLPendingEvent供前端渲染审批卡片。 - 断点续传: 当人类完成审批并回填
HITLDecision后,再次调用agent.prompt().session(session).call(),拦截器在onAgentStart检测到“全部待批任务均已决策”,强制路由回 ACTION 并应用决策,驱动流程从中断点继续运行(HITLDecidedEvent用于关闭/更新审批卡片)。
2、核心组件说明
最新架构引入了四个核心类,实现了业务与 Agent 逻辑的彻底解耦:
HITL: 交互助手。提供定位任务(getPendingTasks/getPendingTask/getPendingTaskByCallUuid/getPendingTaskByToolName等)与提交决策(submit/approve/reject/skip,均面向HITLTask)的静态 API。4.0.4 起决策主键统一为callUuid,按 toolName 提交的旧接口已@Deprecated。HITLTask: 任务快照。以callUuid(= ToolCall.uuid)为主键,记录了“哪个调用实例、想调用哪个工具、具体参数是什么”,供 UI 界面展示给审核员。HITLDecision: 决策实体。承载人类的最终裁决(批准、拒绝、跳过)及参数修正(modifiedArgs)、“始终允许”(alwaysAllow)信息。HITLInterceptor: 管控引擎。在onActionStart做批级预检:存在未决策项则整批挂起(零执行);全部已决策则统一应用改参 / skip / reject。
3、快速接入
通过 HITLInterceptor 声明式地注册需要人工审核的工具。
- 关键准备示例
// 1. 定义并配置 HITL 拦截器
HITLInterceptor hitl = new HITLInterceptor()
// 快速注册敏感工具(触发时自动挂起)
.onSensitiveTool("refund_money", "delete_database")
// 自定义策略:例如只有退款金额超过 100 时才需要人工介入
.onTool("refund_money", (trace, args) -> {
double amount = Double.parseDouble(args.get("amount").toString());
return amount > 100 ? "大额退款需人工审核" : null;
});
// 2. 注入到 Agent
ReActAgent agent = ReActAgent.of(chatModel)
.defaultToolAdd(...)
.defaultInterceptorAdd(hitl)
.build();
- HITL Web 控制器完整示例
@Controller
@Mapping("/ai/hitl")
public class HitlWebController {
private final Map<String, AgentSession> agentSessionMap = new ConcurrentHashMap<>();
private AgentSession getSession(String sid) {
return agentSessionMap.computeIfAbsent(sid, k -> InMemoryAgentSession.of(k));
}
// 1. 初始化带 HITL 拦截器的 Agent
private final ReActAgent agent = ReActAgent.of(LlmUtil.getChatModel())
.defaultInterceptorAdd(new HITLInterceptor()
.onSensitiveTool("transfer_money") // 只要调此工具就拦截
.onTool("send_msg", (trace, args) -> args.size() > 2 ? "复杂指令需审核" : null))
.build();
/**
* 执行/续传接口
* 无论初次提问还是审批后恢复,均调用此接口。不传 prompt 则视为“断点续传”
*/
@Post
@Mapping("call")
public Result call(String sid, String prompt) throws Throwable {
AgentSession session = getSession(sid);
// 核心:调用 agent。如果是审批后恢复,prompt 传 null 即可
ReActResponse resp = agent.prompt(prompt).session(session).call();
// 检查是否被 HITL 拦截(挂起状态在 Session 上,Trace 不提供 isPending)
if (resp.getSession().isPending()) {
return Result.failure(403, "审批拦截", HITL.getPendingTask(session));
}
return Result.succeed(resp.getContent());
}
/**
* 决策提交接口
* 由管理员或业务系统调用,提交批准、拒绝或修正参数
*/
@Post
@Mapping("submit")
public Result submit(String sid, int action, @Body Map args) {
AgentSession session = getSession(sid);
HITLTask task = HITL.getPendingTask(session);
if (task == null) return Result.failure("任务不存在");
// 构建决策对象(4.0.4 推荐工厂方法;action 常量仍可用)
HITLDecision decision = HITLDecision.approve().modifiedArgs(args);
if (action == HITLDecision.ACTION_REJECT) decision.comment("安全合规性拒绝");
// 回填决策(主路径面向 HITLTask;按 toolName 提交的旧接口已废弃)
HITL.submit(session, task, decision);
return Result.succeed("决策已提交,请重新请求 call 接口触发续传");
}
}
4、 业务闭环流程
人工介入在实际开发中分为三个标准阶段:
第一阶段:触发拦截
当用户发送“帮我退款 200 元”,Agent 推理出需要调用 refund_money。拦截器检测到触发条件,执行中断。
在 Controller 层,你可以探知到这个挂起的任务:
// 获取当前会话中被拦截的任务
HITLTask task = HITL.getPendingTask(session);
if (task != null) {
System.out.println("等待审批:" + task.getToolName());
System.out.println("AI 拟调用的参数:" + task.getArgs());
}
第二阶段:人工决策
审核员在管理后台看到任务快照后,通过 HITL 工具类提交决策。
- 批准并执行:
HITL.approve(session, task); - 拒绝并终止:
HITL.reject(session, task, "理由:账户异常"); - 参数修正(人类发现 AI 填错了账号):
HITLTask task = HITL.getPendingTaskByToolName(session, "refund_money"); // 批内同名须唯一
Map fixedArgs = Collections.singletonMap("account", "correct_888");
HITL.submit(session, task, HITLDecision.approve().modifiedArgs(fixedArgs));
第三阶段:恢复执行
业务系统再次调用 agent.prompt().session(session).call()(无需再次传入 Prompt)。此时拦截器会读取 HITLDecision 并应用:
- 如果是 Approve:拦截器将
modifiedArgs合并进工具参数(未修正则按原参执行),执行工具并继续后续推理;带alwaysAllow时触发onApproved回调注入会话级规则,后续同类操作不再弹确认。 - 如果是 Reject:仅命中单个敏感工具时,直接路由 END,以拒绝理由作为最终答复(不再继续思考);同一批内存在多个敏感工具时,仅把拒绝理由写入该工具的 Observation,流程继续处理其余工具。
- 如果是 Skip:跳过真实工具执行,返回一条“人工已处理”的观测结果给 Agent。