早期 Agent 原型为了方便,把数据库和第三方 API key 直接放进执行进程。模型生成工具名和参数,代码几乎原样调用。演示阶段它能查数据、发消息,真实接入后很快出现两个问题:模型把测试项目 ID 带到生产工具;一次超时重试重复创建了外部任务。

我们把工具执行独立成权限网关。模型不持有密钥,也不能直接访问生产网络;它只提交结构化意图。网关用确定性规则决定参数是否有效、当前主体能否执行、是否需要审批以及怎样去重。

模型、权限网关与外部系统之间的工具调用隔离架构

图 1:推理可以不确定,执行边界必须确定、最小授权并且可审计。

工具定义包含效果与风险

type ToolDefinition<Input, Output> = {
  name: string;
  version: number;
  inputSchema: JsonSchema<Input>;
  effect: "read" | "write" | "external_side_effect";
  risk: "low" | "medium" | "high";
  idempotency: "native" | "gateway" | "none";
  timeoutMs: number;
  execute(ctx: ToolContext, input: Input): Promise<Output>;
};

工具不是一段描述文字。版本、输入边界、副作用等级和幂等能力都进入注册表。模型请求不存在的字段会在 Schema 层失败,不会被执行器“尽量理解”。

策略基于主体、资源和上下文

type ToolContext = {
  actorId: string;
  tenantId: string;
  taskId: string;
  approvedScopes: string[];
  environment: "sandbox" | "production";
};

策略检查:用户是否能访问目标资源;任务契约是否允许该工具;环境是否匹配;参数影响范围是否超过限制;当前调用是否已有审批。模型说“用户要求我这么做”不是授权证据。

调用 默认策略
读取当前租户公开文档 自动执行,结果仍做权限过滤
创建草稿 自动执行,返回可撤销草稿 ID
发送外部邮件 展示收件人和正文,人工确认
删除生产资源 默认禁止或双人审批
执行任意 SQL/Shell 不作为通用生产工具暴露

高风险审批绑定 planHash

Agent 先生成规范化计划,网关计算 hash。审批界面显示工具、资源、参数、预计影响和补偿动作。批准签名绑定 planHash;模型后来修改任何参数,都必须重新审批。

const canonicalPlan = stableStringify({
  tool: request.tool,
  version: request.version,
  input: request.input,
  tenantId: context.tenantId,
});
const planHash = sha256(canonicalPlan);

只批准一句自然语言“允许发邮件”范围太大,也无法证明实际执行内容与人看到的一致。

执行使用短期、下放权限的凭证

网关按单次调用向凭证服务申请几分钟有效、只允许指定资源和动作的 token。外部系统日志能识别真实 actor 与 taskId。长期密钥留在凭证服务,不进入模型上下文、工具结果或普通日志。

重试必须服从副作用语义

读工具可以有限重试;写工具需要 idempotencyKey;不支持幂等且结果未知的外部副作用不能自动重放,进入 unknown 状态等待查询或人工处理。

requested -> authorized -> executing -> succeeded
                               |-> failed_retryable
                               |-> failed_terminal
                               |-> outcome_unknown

网关保存规范化输入 hash、策略版本、审批、凭证范围、外部回执与耗时。工具输出回到模型前再做大小限制和敏感字段过滤,防止外部内容把秘密带入上下文。

这层网关看起来增加了延迟和代码,却让 Agent 从“拿着万能钥匙的脚本”变成一个受控调用方。模型擅长决定可能要做什么,真正执行仍需要软件工程里熟悉的 Schema、授权、事务、幂等和审计。

判断“哪些动作需要人在中途确认”不在网关内部完成,而在它上游的策略里:审批应该绑定不可变的执行计划、绑定 planHash、在计划变化时重新授权——这是人工审批不是弹窗整篇的内容。执行环境本身的隔离(文件、网络、密钥、资源)则是编码 Agent 的沙箱的主题,那里把这里的工具边界下沉到了进程级别。