Human Approval UI

Human Approval UI 是前端展示 Agent 高风险操作审批请求的组件。它让用户在 Agent 执行不可逆操作前进行确认、拒绝或修改。

#type / concept #status / evergreen #tech / frontend #tech / ai

[!info] related notes

Human Approval UI

一句话定义

Human Approval UI 是前端展示 Agent 高风险操作审批请求的组件。它在 Agent 执行不可逆操作(删除、支付、发布)前暂停,让用户确认、拒绝或修改操作参数。

它解决什么问题

Agent 调用高风险工具时,需要人类审批。但审批不是简单的”确定/取消”弹窗:

  • 用户需要看到完整操作内容(调用什么工具、传了什么参数)
  • 用户需要理解风险(这个操作的影响范围)
  • 用户可能需要修改参数(不是拒绝,而是改一下再执行)
  • 审批需要超时处理(用户长时间不响应怎么办)

核心原理

审批卡片的信息结构

┌─────────────────────────────────────────┐
│ ⚠️ 需要确认                             │
│                                         │
│ Agent 请求执行以下操作:                  │
│                                         │
│ 🔧 删除记录                             │
│   record_ids: ["id_1", "id_2", "id_3"] │
│   count: 3 条                           │
│                                         │
│ ⚠️ 此操作不可撤销                        │
│                                         │
│ [✅ 批准]  [❌ 拒绝]  [✏️ 修改参数]      │
│                                         │
│ ⏱️ 自动拒绝倒计时: 60s                   │
└─────────────────────────────────────────┘

审批状态机

interrupt_received → showing_approval → approved → resume
                                       → rejected → resume (with rejection)
                                       → modified → resume (with modifications)
                                       → timeout → auto_reject → resume

典型工程实现

Approval Card 组件

function ApprovalCard({ interrupt, onResume }: ApprovalCardProps) {
  const [countdown, setCountdown] = useState(60);
  const [modifying, setModifying] = useState(false);
  const [modifiedArgs, setModifiedArgs] = useState(interrupt.tool_call.arguments);

  // 超时自动拒绝
  useEffect(() => {
    const timer = setInterval(() => {
      setCountdown(prev => {
        if (prev <= 1) {
          onResume({ decision: 'rejected', reason: 'timeout' });
          return 0;
        }
        return prev - 1;
      });
    }, 1000);
    return () => clearInterval(timer);
  }, []);

  return (
    <div className="approval-card">
      <div className="approval-header">
        <span className="warning-icon">⚠️</span>
        <span>需要确认</span>
      </div>

      <div className="approval-body">
        <p>Agent 请求执行以下操作:</p>
        <div className="tool-call-preview">
          <strong>{interrupt.tool_call.name}</strong>
          <pre>{JSON.stringify(interrupt.tool_call.arguments, null, 2)}</pre>
        </div>
        {interrupt.message && (
          <p className="risk-message">⚠️ {interrupt.message}</p>
        )}
      </div>

      {modifying ? (
        <div className="modify-form">
          <textarea
            value={JSON.stringify(modifiedArgs, null, 2)}
            onChange={e => setModifiedArgs(JSON.parse(e.target.value))}
          />
          <button onClick={() => onResume({
            decision: 'approved',
            modifications: modifiedArgs,
          })}>
            修改后执行
          </button>
        </div>
      ) : (
        <div className="approval-actions">
          <button className="approve" onClick={() => onResume({ decision: 'approved' })}>
            ✅ 批准
          </button>
          <button className="reject" onClick={() => onResume({ decision: 'rejected' })}>
            ❌ 拒绝
          </button>
          <button className="modify" onClick={() => setModifying(true)}>
            ✏️ 修改参数
          </button>
        </div>
      )}

      <div className="countdown">
        ⏱️ 自动拒绝倒计时: {countdown}s
      </div>
    </div>
  );
}

常见设计模式

1. 一键审批

低风险操作(如查询)只需一键确认。

2. 参数修改

用户可以修改工具参数后再执行。

3. 批量审批

多个低风险操作打包成一个审批请求。

4. 上下文关联

审批卡片显示相关上下文(如要删除的记录详情)。

常见坑

  1. 审批信息不足: 用户看不到完整参数,无法判断
  2. 不做超时处理: 用户永远不响应,Agent 永远卡住
  3. 审批点太多: 每个操作都要审批,用户体验差
  4. 没有撤销机制: 批准后发现错了无法撤销
  5. 移动端适配差: 审批卡片在手机上显示不全

和其他概念的关系

参考资料

创建于 2026/6/30 更新于 2026/7/15