跳到主要内容

HITL交互界面

核对日期:2026-08-26。

1. 定义与边界

HITL 交互界面是人类在环(Human-in-the-loop)的前端投影:当策略引擎把 run 停在 waiting_approval 时,界面必须让审批人看懂要做什么、为什么、影响是什么,并能批准、拒绝、改参数或升级。

策略、超时、双人复核见 ../01-Agent基础理论/Human-in-the-loop.md../12-安全与治理/人类审批.md。本文件只写界面信息架构、状态恢复和常见空确认框如何避免。

AI SDK 提供 addToolApprovalResponse({ approved: true }) 这类 API。它能接线,不能当产品设计:只有 Approve / Deny 的按钮,等于把责任推给看不懂上下文的人。

2. 为什么重要

审批失败通常不是模型绕过了按钮,而是人点了看不懂的按钮:

  • 只显示「是否确认」时,审批人无法承担后果。
  • 批准后不展示将执行的最终参数,编辑过的字段会静默丢失。
  • 前端允许点击,但服务端不复检策略,等于把授权放在浏览器。
  • 拒绝后 Agent 换一个同义工具继续跑,界面看起来「已拒绝」,系统仍在写。

3. 核心机制

界面是策略的展示,不是策略本身。浏览器里的「已批准」只是一次用户意图;执行前必须用同一套策略再算一遍。

4. 架构模式

4.1 审批卡片必含字段

字段作用缺失时发生什么
用户目标判断动作是否服务于原任务批准无关副作用
工具名与风险等级识别写 / 资金 / 外发把退款当成查询
最终参数人看到的就是将执行的模型参数与展示不一致
证据摘要说明依据来自哪些只读工具凭感觉批准
影响范围条数、金额、外部收件人低估爆炸半径
可回滚性能否补偿把不可逆当可逆
选项approve / reject / edit / escalate只有确认 / 取消
过期时间超时策略可见人离开后静默执行或静默丢弃
run_id / approval_id审计与回放事故无法对齐

4.2 审批载荷

interface ApprovalCard {
approvalId: string;
runId: string;
traceId: string;
toolCallId: string;
toolName: string;
riskLevel: 'medium' | 'high';
goal: string;
argumentsPreview: Record<string, unknown>;
evidence: Array<{ toolName: string; summary: string }>;
impact: {
resourceType: string;
resourceCount: number;
reversible: boolean;
};
options: Array<'approve' | 'reject' | 'edit' | 'escalate'>;
expiresAt: string;
}

前端可以编辑 argumentsPreview 的白名单字段,提交时必须整包回传。服务端以回传包为准再 schema + policy,而不是以模型原始参数为准。

5. 工程实现

5.1 Vue 3 审批卡片

<script setup lang="ts">
interface ApprovalCardView {
approvalId: string;
runId: string;
toolName: string;
riskLevel: 'medium' | 'high';
goal: string;
argumentsPreview: Record<string, unknown>;
evidence: Array<{ toolName: string; summary: string }>;
impact: { resourceType: string; resourceCount: number; reversible: boolean };
expiresAt: string;
}

const props = defineProps<{ card: ApprovalCardView }>();

const emit = defineEmits<{
decide: [payload: { approvalId: string; action: 'approve' | 'reject' | 'escalate'; editedArgs?: Record<string, unknown> }];
}>();

function decide(action: 'approve' | 'reject' | 'escalate'): void {
emit('decide', {
approvalId: props.card.approvalId,
action,
editedArgs: action === 'approve' ? props.card.argumentsPreview : undefined,
});
}
</script>

<template>
<aside class="approval-card" role="dialog" aria-modal="true">
<p>目标:{{ card.goal }}</p>
<p>动作:{{ card.toolName }}({{ card.riskLevel }})</p>
<p>影响:{{ card.impact.resourceCount }} 个 {{ card.impact.resourceType }},可回滚:{{ card.impact.reversible ? '是' : '否' }}</p>
<p>过期:{{ card.expiresAt }}</p>
<ul>
<li v-for="item in card.evidence" :key="item.toolName">
{{ item.toolName }}:{{ item.summary }}
</li>
</ul>
<pre>{{ JSON.stringify(card.argumentsPreview, null, 2) }}</pre>
<footer>
<button type="button" @click="decide('approve')">批准并执行</button>
<button type="button" @click="decide('reject')">拒绝</button>
<button type="button" @click="decide('escalate')">升级人工</button>
</footer>
</aside>
</template>

批准按钮文案应包含动作,而不是「确认」。资金类应用「批准退款」而不是「OK」。

5.2 提交与恢复

interface ApprovalDecision {
approvalId: string;
runId: string;
action: 'approve' | 'reject' | 'edit' | 'escalate';
editedArgs?: Record<string, unknown>;
reason: string;
}

async function submitApproval(decision: ApprovalDecision): Promise<void> {
const response = await fetch(`/api/agent/runs/${decision.runId}/approvals/${decision.approvalId}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(decision),
});
if (!response.ok) {
throw new Error(`approval_http_${response.status}`);
}
}

服务端顺序:

  1. 校验审批人身份与 approval_id 未过期、未被消费。
  2. edit,用 editedArgs 替换待执行参数。
  3. schema 校验。
  4. policy 复检。
  5. 执行工具或写入拒绝状态,禁止同类写工具在本 run 自动改道。
  6. 恢复 Agent Loop,并从 waiting_approval 继续。

LangGraph interrupt、OpenAI Agents SDK HITL 都是这个状态机的实现,不是另一种产品逻辑。

5.3 和 AI SDK 审批 API 的关系

useChataddToolApprovalResponse 适合把「工具执行前停住」接到 React / Vue。生产上仍要:

  • 自己渲染目标、证据、影响,而不是只渲染 SDK 示例里的 Approve / Deny。
  • 拒绝原因、编辑字段写入审计和评测集。
  • 自动批准(isAutomatic)必须在界面上标明「策略自动通过」,避免人以为自己点过。

6. 生产实践

实践说明
阻塞式模态高风险审批出现时禁止继续发新消息改同一 run
参数 diff人编辑后展示与模型原稿的字段差
脱敏卡片展示打码值,执行仍用服务端持有的原文 token
移动端小程序里用全屏页而不是 toast 确认
超时可见倒计时;超时结果与策略一致(升级而非默认批准)
多审批人队列里显示当前处理人,避免两人同时批同一 approval_id

7. 常见反模式

反模式表现后果修正
空确认框「是否让助手继续」无人能负责必含目标 / 参数 / 证据 / 影响
前端授权点批准就直接调业务 API绕过策略只提交意图,服务端执行
批准后不复检参数在等待期间被篡改越权执行approve 后 policy + schema
拒绝后改道send_mail_v2治理失效拒绝写入状态,禁止同风险类工具
全部动作弹窗读操作也打断审批疲劳,人开始盲点按风险分级
审批不进 trace没有 approval_id无法回放审批 span 绑定 run

8. 评测方法

指标说明
Approval Context Completeness卡片是否含必含字段
Blind Confirm Rate打开后 3 秒内批准且无滚动 / 无展开参数的比例
Edit Rate人修改参数的比例;过低可能是不敢看或看不懂
Post-Approval Incident Rate批准后仍出事故
Dual Display Mismatch界面参数与实际执行参数不一致,目标为 0
Mobile Completion Rate小程序 / H5 审批完成率

评测集应收录真实拒绝理由,见 HITL 主文的 label_for_eval 示例。

9. 安全与治理

  • 审批卡片中的证据文本来自工具返回,按不可信内容渲染。
  • 人类批准不能扩大权限:数据 ACL 仍由身份系统执行。
  • 展示层脱敏,执行层用保险库 / 服务端秘密,不把明文 API Key 放进卡片。
  • 高风险可要求二次认证(已登录不等于已批准资金)。
  • 审批日志不可被 Agent 工具改写。
  • 浏览器 / Computer Use 的审批必须在每个动作 block 之前,并展示当前 URL 与截图或控件树,见 ../20-Computer-Use与浏览器Agent/凭证与HITL.md

10. 权威资料