Human Approval UI
Human Approval UI 是前端展示 Agent 高风险操作审批请求的组件。它让用户在 Agent 执行不可逆操作前进行确认、拒绝或修改。
#concept
#evergreen
#frontend
#ai
[!info] related notes
- 所属 MOC: AI Agent Application MOC
- 上游概念: Human-in-the-loop
- 相关: Tool Call UI, Chat UI
- 后端: Approval Checkpoints
Human Approval UI
一句话定义
Human Approval UI 是前端展示 Agent 高风险操作审批请求的组件。执行器在需要新增授权的操作前暂停,UI 展示请求,让用户确认、拒绝或修改操作参数。
它解决什么问题
Agent 调用高风险工具时,需要人类审批。但审批不是简单的”确定/取消”弹窗:
- 用户需要看到完整操作内容(调用什么工具、传了什么参数)
- 用户需要理解风险(这个操作的影响范围)
- 用户可能需要修改参数(不是拒绝,而是改一下再执行)
- 审批需要超时处理(用户长时间不响应怎么办)
核心原理
审批卡片的信息结构
┌─────────────────────────────────────────┐
│ ⚠️ 需要确认 │
│ │
│ Agent 请求执行以下操作: │
│ │
│ 🔧 删除记录 │
│ record_ids: ["id_1", "id_2", "id_3"] │
│ count: 3 条 │
│ │
│ ⚠️ 此操作不可撤销 │
│ │
│ [✅ 批准] [❌ 拒绝] [✏️ 修改参数] │
│ │
│ ⏱️ 距服务端审批截止时间: 60s │
└─────────────────────────────────────────┘
审批状态机
interrupt_received → pending → approved / rejected / expired(服务端原子决策)
│
└→ 修改草稿 → 新提案版本 → 重新校验与审批
approved → 执行器重新检查并消费授权 → 记录执行结果
典型工程实现
Approval Card 组件
该组件展示服务端请求,父组件按 request.id/version 设置 key 并刷新权威状态。后端仍必须鉴权、校验期限/版本并原子处理重复提交;UI 防双击不能替代这些检查。summary 是经服务端筛选的可审阅摘要,不直接展示可能含秘密的整个参数对象。
import { useRef, useState } from "react";
type Props = {
request: { id: string; version: number; summary: string; status: string };
onDecision: (id: string, version: number, decision: "approve" | "reject") => Promise<void>;
onEdit: (id: string) => void;
};
export function ApprovalCard({ request, onDecision, onEdit }: Props) {
const sending = useRef(false);
const [busy, setBusy] = useState(false);
const [error, setError] = useState("");
const disabled = busy || request.status !== "pending";
async function submit(decision: "approve" | "reject") {
if (sending.current || request.status !== "pending") return;
sending.current = true;
setBusy(true);
setError("");
try {
await onDecision(request.id, request.version, decision);
// 父组件随后用服务端权威状态替换 pending。
} catch {
setError("提交结果未确认,请刷新请求状态后重试。");
} finally {
sending.current = false;
setBusy(false);
}
}
return (
<section aria-label="操作审批">
<p>{request.summary}</p>
<p>状态:{request.status}</p>
<button disabled={disabled} onClick={() => void submit("approve")}>批准</button>
<button disabled={disabled} onClick={() => void submit("reject")}>拒绝</button>
<button disabled={disabled} onClick={() => onEdit(request.id)}>修改提案</button>
{error && <p role="alert">{error}</p>}
</section>
);
}
常见设计模式
1. 一键审批
只有策略要求新增确认时展示;已授权的普通查询不应为了流程完整而反复弹窗。
2. 参数修改
编辑器保留原始文本草稿,提交时解析并验证 schema;修改后的参数产生新提案/版本,重新做策略与授权检查。不能在每次按键时 JSON.parse,也不能把修改直接当作已批准执行。
3. 批量审批
多个低风险操作打包成一个审批请求。
4. 上下文关联
审批卡片显示相关上下文(如要删除的记录详情)。
常见坑
- 审批信息不足: 用户看不到完整参数,无法判断
- 不做超时处理: 用户永远不响应,Agent 永远卡住
- 审批点太多: 每个操作都要审批,用户体验差
- 误称可撤销:执行前可撤回未消费授权;副作用完成后通常只能按业务做补偿,不能保证撤销
- 移动端适配差: 审批卡片在手机上显示不全
和其他概念的关系
- vs Human-in-the-loop: HITL 是概念,Approval UI 是前端实现
- vs Tool Call UI: Tool Call UI 展示所有工具调用,Approval UI 专注审批场景
- vs Approval Checkpoints: Checkpoints 是后端机制,Approval UI 是前端组件