作者:前端转 AI 深度实践者
【省流助手/核心观点】:Agent 真正进入业务系统后,最大风险不是它不会调用工具,而是它太容易调用工具。查询订单、搜索政策这类只读工具可以相对宽松,但取消订单、发起退款、修改权限、发送邮件这类有副作用的工具,必须经过 3 道闸门:权限检查、风险分级、用户确认。模型可以提出动作建议,但程序必须掌握最终执行权。
做到第 28 篇,我们的 Agent 已经有了不少工程能力。
它能调用工具。
它有工具 Schema。
它能多步执行。
它会失败处理。
它还有 trace、结构化日志和运行报告。
这时候,一个很自然的问题会出现:
能不能让它开始真正办事?
比如:
text
帮我取消订单 A1001。
text
帮我给用户发一封延迟补偿邮件。
text
帮我把这张工单升级成 P0。
从能力上说,当然可以。
但从工程安全上说,必须非常谨慎。
Agent 一旦能调用有副作用的工具,就不再只是"回答系统"。
它开始变成"操作系统"。
而操作系统必须有权限、确认和审计。
1. 痛点:模型可以建议动作,但不能拥有最终执行权
先看一个危险写法:
ts
async function runModelDecision(modelOutput: {
toolCall: {
toolName: string;
args: Record<string, unknown>;
};
}) {
return runTool(modelOutput.toolCall);
}
这意味着:
text
模型说调用什么,程序就调用什么。
如果工具都是查询类,还勉强可以接受。
但如果工具里有:
cancelOrderrefundPaymentdeleteFilesendEmailchangeUserRole
这就很危险。
因为模型可能误解用户意图。
用户说:
text
这个订单是不是能取消?
模型可能误判成:
json
{
"toolName": "cancelOrder",
"args": {
"orderId": "A1001"
}
}
用户只是问能不能取消,不是要立刻取消。
所以先建立一个原则:
模型可以提出动作建议,但最终能不能执行,必须由程序决定。
2. 错误做法:只靠 Prompt 提醒模型"谨慎执行"
很多人会在 Prompt 里写:
text
如果用户没有权限,不要调用取消订单工具。
遇到高风险操作时要谨慎。
这有帮助,但不够。
Prompt 是软约束。
权限检查、风险分级、二次确认必须是程序层硬约束。
否则一旦模型误判、提示词被绕过、上下文被污染,系统就可能执行真实副作用。
Agent 安全边界不能靠模型自觉。
3. 正确做法:先把工具分成风险等级
不是所有工具风险都一样。
可以先分三类:
ts
type RiskLevel = "low" | "medium" | "high";
type ToolDefinition = {
name: string;
riskLevel: RiskLevel;
requiredPermission: string;
requiresConfirmation: boolean;
handler: (args: Record<string, unknown>) => Promise<unknown>;
};
低风险工具通常是只读:
- 查询订单状态。
- 查询政策文档。
- 查询库存。
- 计算退款金额。
中风险工具通常会创建记录,但影响可控:
- 创建客服工单。
- 添加备注。
- 保存草稿。
高风险工具通常会改变真实业务状态:
- 取消订单。
- 发起退款。
- 修改用户权限。
- 删除资料。
- 发送外部邮件。
风险等级的意义是:不同工具走不同执行规则。
text
low -> 可以自动执行,但要记日志
medium -> 需要权限检查
high -> 需要权限检查 + 用户确认 + 审计日志
这不是把系统搞复杂。
这是让 Agent 进入真实业务时不乱来。
4. 权限检查必须在程序层
先定义用户上下文:
ts
type AgentUser = {
id: string;
permissions: string[];
};
function hasPermission(user: AgentUser, permission: string) {
return user.permissions.includes(permission);
}
再定义工具:
ts
const cancelOrderTool: ToolDefinition = {
name: "cancelOrder",
riskLevel: "high",
requiredPermission: "order:cancel",
requiresConfirmation: true,
handler: async (args) => {
return {
cancelled: true,
orderId: args.orderId
};
}
};
const toolRegistry: Record<string, ToolDefinition> = {
cancelOrder: cancelOrderTool
};
执行前必须检查权限:
ts
function checkPermission(user: AgentUser, tool: ToolDefinition) {
if (hasPermission(user, tool.requiredPermission)) {
return null;
}
return {
ok: false as const,
errorType: "permission_denied",
message: `缺少权限:${tool.requiredPermission}`
};
}
这才是可靠边界。
AI 工程有个朴素但重要的原则:
不能靠模型自觉来保护系统安全。
5. 高风险工具必须先生成确认请求
取消订单这类动作,不能直接执行。
应该先生成一个确认请求:
ts
type ConfirmationRequest = {
confirmationId: string;
toolName: string;
argsSummary: string;
riskLevel: RiskLevel;
message: string;
status: "pending" | "approved" | "rejected";
};
function createConfirmation(tool: ToolDefinition, args: Record<string, unknown>) {
const orderId = String(args.orderId ?? "");
return {
confirmationId: crypto.randomUUID(),
toolName: tool.name,
argsSummary: JSON.stringify({ orderId }),
riskLevel: tool.riskLevel,
message: `该操作将取消订单 ${orderId},是否确认继续?`,
status: "pending"
} satisfies ConfirmationRequest;
}
此时 Agent 的回答应该是:
text
取消订单 A1001 是高风险操作。请确认是否继续。
而不是:
text
已为你取消订单。
前端同学可以把它理解成危险操作的确认弹窗:
text
你确定要删除这个项目吗?
[取消] [确认删除]
Agent 只是把按钮点击换成了自然语言流程,但安全原则没有变。
6. 前端确认组件:让暂停有地方停
高风险操作必须能暂停,也必须能恢复。
前端可以展示确认卡片:
tsx
function ToolConfirmationCard({
confirmation,
onApprove,
onReject
}: {
confirmation: ConfirmationRequest;
onApprove: (id: string) => void;
onReject: (id: string) => void;
}) {
return (
<section>
<h3>需要确认</h3>
<p>{confirmation.message}</p>
<pre>{confirmation.argsSummary}</pre>
<button
type="button"
onClick={() => onReject(confirmation.confirmationId)}
>
取消
</button>
<button
type="button"
onClick={() => onApprove(confirmation.confirmationId)}
>
确认执行
</button>
</section>
);
}
真实系统里,这个确认请求可能对应:
- 前端确认弹窗。
- 审批流。
- 管理后台任务。
- 人工审核队列。
- 消息通知。
重点是:高风险操作不能直接消失在模型输出和工具执行之间。
它必须被暂停、展示、确认、记录。
7. 安全版工具执行器:像网关一样把关
安全版 runTool 应该像一个执行网关。
它不只是把工具跑起来,还要检查:
- 工具是否存在。
- 用户是否有权限。
- 工具风险等级。
- 是否需要确认。
- 是否已经确认。
ts
type ToolCall = {
toolName: string;
args: Record<string, unknown>;
confirmationId?: string;
};
type SafeToolResult =
| {
ok: true;
toolName: string;
data: unknown;
}
| {
ok: false;
toolName?: string;
errorType:
| "unknown_tool"
| "permission_denied"
| "confirmation_required"
| "tool_error";
message: string;
confirmation?: ConfirmationRequest;
};
async function runToolSafely(
user: AgentUser,
toolCall: ToolCall,
approvedConfirmations: Set<string>
): Promise<SafeToolResult> {
const tool = toolRegistry[toolCall.toolName];
if (!tool) {
return {
ok: false,
toolName: toolCall.toolName,
errorType: "unknown_tool",
message: `未知工具:${toolCall.toolName}`
};
}
const permissionError = checkPermission(user, tool);
if (permissionError) {
return {
...permissionError,
toolName: tool.name
};
}
const needsConfirmation =
tool.requiresConfirmation &&
(!toolCall.confirmationId ||
!approvedConfirmations.has(toolCall.confirmationId));
if (needsConfirmation) {
const confirmation = createConfirmation(tool, toolCall.args);
return {
ok: false,
toolName: tool.name,
errorType: "confirmation_required",
message: confirmation.message,
confirmation
};
}
try {
const data = await tool.handler(toolCall.args);
return {
ok: true,
toolName: tool.name,
data
};
} catch (error) {
return {
ok: false,
toolName: tool.name,
errorType: "tool_error",
message: error instanceof Error ? error.message : "工具执行失败"
};
}
}
这就是 Agent 的安全执行层。
它让模型从"执行者"退回到"建议者"。
程序才是最后的把关者。
8. 审计日志:高风险操作必须留痕
高风险工具即使确认后执行,也要记录审计日志。
ts
type AuditLog = {
traceId: string;
userId: string;
toolName: string;
argsSummary: string;
confirmationId?: string;
resultStatus: "success" | "failed";
createdAt: string;
};
function createAuditLog(input: Omit<AuditLog, "createdAt">): AuditLog {
return {
...input,
createdAt: new Date().toISOString()
};
}
审计日志回答的是:
- 谁执行了?
- 执行了什么?
- 参数摘要是什么?
- 是否经过确认?
- 结果是什么?
- 什么时候执行?
这对真实业务很重要。
因为高风险操作不能只看最终状态,还要能追溯过程。
9. 生产环境避坑指南
1. 查询类工具和写入类工具分开注册
不要把只读工具和写入工具混在一个默认可执行池里。
查询类可以宽一些,写入类必须严格走权限和确认。
2. confirmed 不能只信前端传参
不要因为前端传了 confirmed: true 就执行。
后端必须校验 confirmationId 是否存在、是否属于当前用户、是否仍然有效、是否已批准。
3. 确认页要展示操作摘要
不要只写"是否确认执行"。
要展示工具名、关键参数、风险说明和可能影响。
4. 高风险操作要防重复提交
用户可能重复点击确认,模型也可能重复发起同一个工具调用。
需要通过 confirmationId、幂等 key 或业务单号防止重复执行。
5. 审计日志不要存完整敏感参数
审计日志要可追溯,但不代表要裸存所有字段。
建议记录参数摘要、脱敏字段或哈希。
10. 常见误区
误区 1:Prompt 里写"谨慎执行"就够了
不够。Prompt 是软约束,权限和确认必须在程序层实现。
误区 2:只有删除才算高风险
不是。发邮件、退款、改权限、批量修改、取消订单都可能是高风险。
误区 3:用户说了"帮我取消",就可以直接取消
仍然不建议。真实业务里应该展示操作摘要,让用户明确确认。
误区 4:确认后就不用记录了
不对。确认只是执行前边界,审计日志是执行后追溯。
11. 给前端开发者的落地清单
如果你要让 Agent 调用真实业务工具,可以先按这份清单检查:
- 所有工具都有风险等级。
- 所有工具都有所需权限。
- 所有写入类工具默认不可直接执行。
- 高风险工具必须二次确认。
- 确认请求包含工具名、参数摘要、风险说明。
- 用户确认后才带
confirmationId恢复执行。 - 权限检查在程序层完成。
- Prompt 不能替代权限系统。
- 高风险操作写审计日志。
- 拒绝、取消、确认、执行都要有 trace。
这份清单听起来很像后台系统安全规范。
没错,Agent 进入业务系统后,它就是后台系统的一部分。
结语
Agent 不能"模型说执行就执行"。
模型擅长理解意图,擅长生成建议,但它不应该拥有最终执行权。
真正可靠的 Agent,需要把工具分级,把权限做硬,把高风险操作停下来确认,把执行过程写入审计。
这不是给智能泼冷水。
这是让智能能进入真实世界。
因为真实世界里,能办事的系统必须先能负责。