工具 Schema、权限门与幂等设计

参数类型写成字符串,还没有说明它是不是允许访问的路径;调用执行成功,也没有说明重复一次是否会造成损失。本章分别讲格式、权限和幂等,把这些经常混在一起的问题拆开处理。

1. 输入 Schema 要表达语义

仅写 type: string 远远不够。一个文件读取工具应明确:

  • 路径是相对工作区还是绝对路径;
  • 是否允许 glob;
  • 编码;
  • 最大字节数;
  • 行号是否从 1 开始;
  • 路径必须存在且位于哪些根目录。
json
{ "type": "object", "properties": { "path": { "type": "string", "minLength": 1, "maxLength": 500, "description": "Path relative to the workspace root" }, "line_start": { "type": "integer", "minimum": 1 }, "line_end": { "type": "integer", "minimum": 1 } }, "required": ["path"], "additionalProperties": false }

2. 权限是确定性层

Permission Gate 在执行前检查主体、动作、资源和上下文:

Text
allow = policy(subject, tool, normalized_arguments, session, environment)

常见策略:

  • 已预先授权、限定资源范围的低风险只读工具可自动通过;“只读”不等于没有风险,读取私人文件后发往外部也可能泄露信息,因此仍检查调用者、目标路径、数据归属与结果去向;
  • 工作区内可逆写入按策略通过;
  • 外部发布、发送、删除、付款需要确认;
  • 密钥只由运行时注入,模型上下文只出现引用名;
  • 子 Agent 的权限应是父任务权限的子集。

权限决策要进入 trace,包括命中的规则、请求范围和最终决定。

3. 幂等键

非幂等工具遇到超时时,系统不知道动作是否已发生。为创建订单、发消息、触发部署等动作加入幂等键:

Text
idempotency_key = hash(session_id + task_id + logical_action)

重试时复用同一个键。若外部服务不支持幂等键,执行前后分别查询状态,或在本地写入 outbox 记录。

4. 参数规范化

权限与幂等计算前先:

  • 解析并规范化路径;
  • 统一 URL、域名和端口;
  • 对 JSON key 排序;
  • 去除无语义空白;
  • 明确默认值;
  • 拒绝类型混淆。

否则 a/../b、大小写、尾斜杠等差异可能绕开重复检测或策略匹配。

5. 测试

  • Schema 的正例、边界值、缺字段和多余字段;
  • 权限允许、拒绝、确认三条路径;
  • 同一幂等键重复调用;
  • 工具执行后响应丢失;
  • 子 Agent 请求超出委派范围;
  • 路径规范化和符号链接边界;
  • 敏感字段是否出现在日志或模型消息。

6. Schema:从语法约束到业务约束

json
{ "type": "object", "additionalProperties": false, "required": ["document_id", "expected_version", "content"], "properties": { "document_id": { "type": "string", "minLength": 1 }, "expected_version": { "type": "integer", "minimum": 0 }, "content": { "type": "string", "maxLength": 200000 } } }

additionalProperties: false 可以减少模型凭空增加字段;expected_version 支持乐观并发控制。但 schema 通过后,还要检查调用者是否有该文档写权限、版本是否仍匹配、内容是否满足业务规则。

7. 幂等不是简单“重试安全”

幂等键应由“调用者 + 工具版本 + 规范化参数 + 任务范围”生成并持久化。首次执行写入 pending,成功后保存可复用结果;相同键再次到达时返回原结果。对于发送邮件、支付、发布等动作,若外部系统响应丢失,状态可能是 unknown,此时应先查询外部状态,而不是再次提交。

8. 最小权限的落点

最小权限要同时落在工具暴露、参数范围、凭据、运行目录和网络目标上。不要把一个全能 run_command(command: string) 当作所有能力入口;优先暴露语义清晰、输入受限、结果结构化的工具,并对高影响动作设置独立审批。

审批通过的究竟是哪一次调用

用户确认“关闭工单 42”,不应被解释成“以后所有工单都可以关闭”。审批至少要能对应到具体工具调用和参数;如果目标改成工单 43,应重新判断是否仍在授权范围内。

Agents SDK 的当前文档还区分了单次审批和持续审批。特别是本地 ShellTool、ApplyPatchTool 的持续决定可能按工具类型和名字共享,不能把换了一个 Agent 当成权限自动隔离。工具执行器不同、权限不同的场景,应认真检查共享审批状态。

练习可以完全使用假工具:第一次请求关闭 42,获得许可;第二次改成 43;第三次拿旧 call ID 重放。分别检查授权范围与重复动作。通过 JSON Schema 只解决参数形状,审批与业务幂等仍要独立检查。

本节的核验依据

2026-10-25 查阅。下面的链接支持本节的机制说明;练习场景和选型建议是本教程的教学设计。