前端 Agent 编排中的工具调用拦截器:实现人机协同的确认机制
在把大模型 Agent 接入真实业务系统时,最让安全团队和业务方担心的就是“Agent 失控”——比如 Agent 自作主张地调用了转账接口、删除了线上数据表,或者向客户群发了未经审核的营销短信。
如果完全不给 Agent 工具调用权限,Agent 就退化成了只能聊天的普通问答机器人;如果把所有权限无条件开放给 Agent 全自动执行,又面临巨大的不可控风险。
解决这一矛盾的核心工程架构是:前端工具调用拦截器(Tool Invocation Interceptor)与人机在环协同(Human-in-the-Loop, HITL)。通过在前端与 Agent 运行时之间构建拦截插槽,对于高危或涉及金钱/敏感数据的动作,挂起(Suspend)模型执行流程,弹出交互卡片由人类用户二次确认或修改参数后,再继续后续的执行链路。
工具风险分级与拦截状态机
我们将 Agent 可调用的工具按照破坏性划分为三个等级:
- L1 只读无害工具(Auto-Pass):查询天气、搜索文档、读取只读配置等,由 Agent 全自动静默调用;
- L2 读写轻量工具(User Notification):创建草稿、添加收藏、发送测试日志等,自动执行但需在前端界面明确提示;
- 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> ); };工程落地收益
- 构筑安全信任底线:业务方与风控团队无需再担心大模型由于幻觉乱调接口,所有高危动作均有清晰的人工审批留痕。
- 支持参数在线修正:当 Agent 生成的 SQL 查询条件或转账金额有轻微瑕疵时,用户可以在前端直接修改参数后放行,避免推倒重来,大幅提升协同效率。
- 完美融入大模型多轮会话:通过拦截器返回的标准状态,模型能够准确理解“用户拒绝了此项操作”并根据反馈调整下一步规划(例如:“检测到您取消了该操作,请问是否需要换一种方式处理?”)。