Solon v3.9.3

react - 人工介入(HITL)

</> markdown
2026年2月3日 下午8:39:13

在自动化程度极高的 AI Agent 应用中,人工介入(Human-In-The-Loop, HITL) 是确保业务安全与合规的最后一道防线。对于涉及资金退款、敏感数据删除或重要邮件发送等操作,我们需要在 Agent 执行前获得人类的明确许可,甚至允许人类修正 AI 的参数。

Solon AI 通过标准化的 HITLInterceptor,实现了 “任务探知 - 决策回填 - 断点续传” 的工业级管控流程。

1、核心原理:中断与延续

HITL 的本质是利用 ReAct 协议的生命周期钩子进行“切面管控”:

  • 任务挂起: 当 Agent 尝试执行敏感工具时,拦截器会捕捉到这一动作,并将当前工具名、参数封装成 HITLTask 快照存入 Session,随即通过 trace.interrupt() 中断执行。
  • 断点续传: 当人类完成审批并回填 HITLDecision 后,再次调用 agent.call(session),拦截器会识别到决策指令,驱动流程从中断点继续运行。

2、核心组件说明

最新架构引入了四个核心类,实现了业务与 Agent 逻辑的彻底解耦:

  • HITL: 交互助手。提供 approve、submit 等静态 API。
  • HITLTask: 任务快照。记录了“谁想调用哪个工具”以及“具体参数是什么”,供 UI 界面展示给审核员。
  • HITLDecision: 决策实体。承载人类的最终裁决(批准、拒绝、跳过)及参数修正信息。
  • HITLInterceptor: 管控引擎。负责监听 Action 阶段并执行拦截或决策分发。

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)
    .defaultInterceptorAdd(hitl)
    .build();
  • HITL Web 控制器完整示例
@Controller
@Mapping("/ai/hitl")
public class HitlWebController {
    // 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 = InMemoryAgentSession.of(sid);
        
        // 核心:调用 agent。如果是审批后恢复,prompt 传 null 即可
        ReActResponse resp = agent.prompt(prompt).session(session).call();

        // 检查是否被 HITL 拦截
        if (resp.getTrace().isInterrupted()) {
            return Result.failure(403, "审批拦截", HITL.getPendingTask(session));
        }

        return Result.succeed(resp.getContent());
    }

    /**
     * 决策提交接口
     * 由管理员或业务系统调用,提交批准、拒绝或修正参数
     */
    @Post
    @Mapping("submit")
    public Result submit(String sid, int action, @Body Map<String, Object> args) {
        AgentSession session = InMemoryAgentSession.of(sid);
        HITLTask task = HITL.getPendingTask(session);
        if (task == null) return Result.failure("任务不存在");

        // 构建决策对象
        HITLDecision decision = new HITLDecision().action(action).modifiedArgs(args);
        if (action == HITLDecision.ACTION_REJECT) decision.comment("安全合规性拒绝");

        // 回填决策,Agent 会在下一次 call 时识别并自动处理
        HITL.submit(session, task.getToolName(), 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, "refund_money");
  • 拒绝并终止:HITL.reject(session, "refund_money", "理由:账户异常");
  • 参数修正(人类发现 AI 填错了账号):
Map<String, Object> fixedArgs = Collections.singletonMap("account", "correct_888");
HITL.submit(session, "refund_money", HITLDecision.approve().modifiedArgs(fixedArgs));

第三阶段:恢复执行

业务系统再次调用 agent.call(session)(无需再次传入 Prompt)。此时拦截器会读取 HITLDecision:

  • 如果是 Approve:拦截器自动替换/合并修正后的参数,执行工具并继续后续推理。
  • 如果是 Reject:拦截器终止工具执行,将拒绝理由反馈给 Agent 产生最终回答。
  • 如果是 Skip:跳过真实工具执行,返回一条“人工已处理”的观测结果给 Agent。