前端 Agent 编排中的工具调用拦截器:实现人机协同的确认机制

封面信息图

在把大模型 Agent 接入真实业务系统时,最让安全团队和业务方担心的就是“Agent 失控”——比如 Agent 自作主张地调用了转账接口、删除了线上数据表,或者向客户群发了未经审核的营销短信。

如果完全不给 Agent 工具调用权限,Agent 就退化成了只能聊天的普通问答机器人;如果把所有权限无条件开放给 Agent 全自动执行,又面临巨大的不可控风险。

解决这一矛盾的核心工程架构是:前端工具调用拦截器(Tool Invocation Interceptor)与人机在环协同(Human-in-the-Loop, HITL)。通过在前端与 Agent 运行时之间构建拦截插槽,对于高危或涉及金钱/敏感数据的动作,挂起(Suspend)模型执行流程,弹出交互卡片由人类用户二次确认或修改参数后,再继续后续的执行链路。

工具风险分级与拦截状态机

我们将 Agent 可调用的工具按照破坏性划分为三个等级:

  1. L1 只读无害工具(Auto-Pass):查询天气、搜索文档、读取只读配置等,由 Agent 全自动静默调用;
  2. L2 读写轻量工具(User Notification):创建草稿、添加收藏、发送测试日志等,自动执行但需在前端界面明确提示;
  3. L3 高危敏感工具(User Confirmation Required):发起支付扣款、删除生产资源、修改权限配置等,必须强制拦截并挂起执行
[Agent 规划出 Tool Call: "transferMoney"]
             │
             ▼
[前端 Interceptor 识别为 L3 风险工具] ──> (挂起 Agent 执行管道)
             │
             ▼
[前端 UI 渲染受控授权确认卡片: "是否向账户 X 转账 ¥5000?"]
             │
             ├─ 用户点击【拒绝】──> 向 Agent 回传: { status: "rejected", reason: "User cancelled" }
             └─ 用户点击【确认】──> 真实发起请求,并将成功结果喂回 Agent 继续推理

核心实现:可挂起的前端工具调用拦截器

借助 TypeScript 和 Promise 的挂起机制,我们实现一个完全受控的拦截器引擎:

export type ToolRiskLevel = 'L1' | 'L2' | 'L3';

export interface ToolActionPayload {
  callId: string;
  toolName: string;
  riskLevel: ToolRiskLevel;
  parameters: Record<string, any>;
}

export interface InterceptorDecision {
  action: 'proceed' | 'reject' | 'modify';
  modifiedParams?: Record<string, any>;
  rejectReason?: string;
}

export class AgentToolInterceptor {
  private pendingResolvers = new Map<string, (decision: InterceptorDecision) => void>();
  private onPromptUserCallback?: (payload: ToolActionPayload) => void;

  public registerUserPromptHandler(callback: (payload: ToolActionPayload) => void) {
    this.onPromptUserCallback = callback;
  }

  // 当 Agent 引擎尝试调用工具时触发此拦截钩子
  public async intercept(toolName: string, riskLevel: ToolRiskLevel, params: Record<string, any>): Promise<any> {
    const callId = crypto.randomUUID();

    // 如果是 L1 级低风险工具,直接放行
    if (riskLevel === 'L1') {
      return { status: 'proceed', finalParams: params };
    }

    // L3 级高风险工具:挂起当前执行流,向前端 UI 发出确认事件
    return new Promise<any>((resolve) => {
      this.pendingResolvers.set(callId, (decision) => {
        if (decision.action === 'reject') {
          resolve({
            status: 'cancelled',
            error: decision.rejectReason || 'Operation was rejected by user.',
          });
        } else if (decision.action === 'modify') {
          resolve({
            status: 'proceed',
            finalParams: decision.modifiedParams || params,
          });
        } else {
          resolve({
            status: 'proceed',
            finalParams: params,
          });
        }
      });

      // 通知前端 UI 弹出确认弹窗
      if (this.onPromptUserCallback) {
        this.onPromptUserCallback({
          callId,
          toolName,
          riskLevel,
          parameters: params,
        });
      }
    });
  }

  // 前端用户点击按钮后触发的反馈方法
  public resolveUserAction(callId: string, decision: InterceptorDecision) {
    const resolver = this.pendingResolvers.get(callId);
    if (resolver) {
      resolver(decision);
      this.pendingResolvers.delete(callId);
    }
  }
}

在 React UI 中的受控确认卡片渲染

结合 React Hook,将拦截器事件转化为清晰的用户确认浮层:

import React, { useState, useEffect } from 'react';
import type { AgentToolInterceptor, ToolActionPayload } from './interceptor';

export const AgentHumanConfirmationSlot: React.FC<{ interceptor: AgentToolInterceptor }> = ({
  interceptor,
}) => {
  const [currentRequest, setCurrentRequest] = useState<ToolActionPayload | null>(null);

  useEffect(() => {
    interceptor.registerUserPromptHandler((payload) => {
      setCurrentRequest(payload);
    });
  }, [interceptor]);

  if (!currentRequest) return null;

  const handleConfirm = () => {
    interceptor.resolveUserAction(currentRequest.callId, { action: 'proceed' });
    setCurrentRequest(null);
  };

  const handleReject = () => {
    interceptor.resolveUserAction(currentRequest.callId, {
      action: 'reject',
      rejectReason: '用户在界面主动终止了操作',
    });
    setCurrentRequest(null);
  };

  return (
    <div className="my-3 p-4 bg-amber-50 border border-amber-300 rounded-xl shadow-sm">
      <div className="flex items-center gap-2 text-amber-900 font-semibold text-sm">
        <span>⚠ 高危操作确认请求</span>
        <span className="px-2 py-0.5 text-xs bg-amber-200 text-amber-800 rounded">
          {currentRequest.toolName}
        </span>
      </div>
      <p className="text-xs text-amber-700 mt-1">
        Agent 申请执行敏感操作,请核实以下调用参数:
      </p>
      <pre className="mt-2 p-2 bg-white rounded border border-amber-200 text-xs font-mono overflow-x-auto text-gray-800">
        {JSON.stringify(currentRequest.parameters, null, 2)}
      </pre>
      <div className="mt-3 flex gap-2 justify-end">
        <button
          onClick={handleReject}
          className="px-3 py-1.5 text-xs font-medium bg-white text-gray-700 border border-gray-300 rounded hover:bg-gray-50"
        >
          拒绝授权
        </button>
        <button
          onClick={handleConfirm}
          className="px-3 py-1.5 text-xs font-medium bg-amber-600 text-white rounded hover:bg-amber-700"
        >
          确认执行
        </button>
      </div>
    </div>
  );
};

工程落地收益

  1. 构筑安全信任底线:业务方与风控团队无需再担心大模型由于幻觉乱调接口,所有高危动作均有清晰的人工审批留痕。
  2. 支持参数在线修正:当 Agent 生成的 SQL 查询条件或转账金额有轻微瑕疵时,用户可以在前端直接修改参数后放行,避免推倒重来,大幅提升协同效率。
  3. 完美融入大模型多轮会话:通过拦截器返回的标准状态,模型能够准确理解“用户拒绝了此项操作”并根据反馈调整下一步规划(例如:“检测到您取消了该操作,请问是否需要换一种方式处理?”)。
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐