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

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


### 1、核心原理：中断与延续

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

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



### 2、核心组件说明

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

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


### 3、快速接入

通过 `HITLInterceptor` 声明式地注册需要人工审核的工具。


* 关键准备示例

```java
// 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 控制器完整示例


```java

@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 拦截
        if (resp.getTrace().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<String, Object> args) {
        AgentSession session = getSession(sid);
        HITLTask task = HITL.getPendingTask(session);
        if (task == null) return Result.failure("任务不存在");

        // 构建决策对象
        HITLDecision decision = new HITLDecision(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 层，你可以探知到这个挂起的任务：

```java
// 获取当前会话中被拦截的任务
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 填错了账号）：


```java
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。








