Agent Tool Execution:四种执行边界
比较模型直连工具、Tool Gateway、Sandbox Worker 与 Human Approval Queue 四种执行架构:谁验证参数、谁持有 credential、动作在哪里执行、怎样避免同一个副作用做两次、批准怎样和最终参数绑定,以及每种边界失守时用户看到什么、怎么恢复。
模型输出 tool call 只是一个 proposal。真正的系统设计从四个问题开始:谁验证参数、谁持有 credential、动作在哪里执行、同一个副作用怎样不做两次。 四个答案决定了一次错误的调用(参数被 prompt injection 改写、模型理解错意图、重试时重复提交)能波及多远。
本章讲的是「一次 tool call 从提议到执行」这一段。工具怎么通过 MCP 暴露给 Agent 见 MCP 工具平台架构;不可信代码关在哪一层见 Sandboxed Agent 执行架构;Agent 代表用户拿什么 token 见 Agent 身份与委托授权;跨小时的长任务怎么恢复见 Durable Agent 执行架构。
有约束的设计问题
一个企业内部助手要接四类工具:查天气、查内部文档这类只读、低风险、每轮对话调很多次的工具;几十个团队各自维护的业务 API,要按租户和用户限权、统一审计;运行模型写出来的 Python 分析用户上传的表格;以及退款、删除数据、对外发邮件这类做了就收不回来的动作。四类工具该走哪种执行边界?
四种执行架构
| 架构 | Credential 在哪里 | State owner | 适合 | 主要风险 |
|---|---|---|---|---|
| Direct Tool Call | app process 的 server-side secret store | app runtime | 低风险、只读、少量工具 | blast radius 与 app 相同 |
| Tool Gateway | gateway vault / delegated token | gateway policy + audit log | 多租户、统一 policy 和审计 | gateway 成为集中瓶颈 |
| Sandbox Worker | broker 传 capability,worker 临时取 scoped token | job store | code、文件、浏览器等高风险执行 | escape、资源耗尽、结果污染 |
| Approval Queue | approval service 保存 proposal 与决定 | approval record | 付款、删除、发布等高影响动作 | approval stale 或用户误判 |
必须画出的 envelope
Tool request 需要包含 tool name、schema-validated arguments、tenant/user scope、trace id、deadline 和 idempotency key。Credential 不放进 model context,也不由 browser 直接调用 provider。Sandbox 接收短期 capability,不接收长期 master key。Approval 发生在执行之前,而且批准内容必须与最终执行参数绑定。
一个具体的 envelope 长这样:
{
"tool": "refund_order",
"call_id": "call_7f3a",
"arguments": { "order_id": "ord_1029", "amount_cents": 4500, "currency": "AUD" },
"scope": { "tenant_id": "t_acme", "user_id": "u_88", "on_behalf_of": "u_88" },
"trace_id": "tr_5c1e",
"deadline_ms": 8000,
"idempotency_key": "run_42:step_7:refund_order",
"arguments_hash": "sha256:9b1d…"
}
每个字段都对应一个失败方式:没有 call_id,结果回不到对应的调用(OpenAI 的 function calling 用 call_id 把工具输出和那次调用对上,Anthropic 的 tool use 用 tool_use_id);没有 scope,工具只能用一个共享账号去查所有租户的数据;没有 deadline_ms,一个卡住的下游把整轮对话拖到超时;没有 idempotency_key,重试就是第二次退款;没有 arguments_hash,人批准的是 45 块、执行的可能是被改过的 4500 块。
一次调用的执行顺序
- 校验形状。 按 JSON Schema 校验参数,拒绝多余字段。OpenAI 的 function calling 支持在工具定义上设
strict: true,让模型输出严格匹配 schema;这只保证形状,不保证语义,amount_cents是合法整数不代表这笔退款该退。 - 校验语义和权限。 用
scope里的用户身份判断这个人能不能对这个订单做这件事,金额是否超过订单金额。这一步在服务端,不能交给模型或前端。 - 决定执行边界。 按工具的风险等级路由到直连、网关、沙箱或审批队列。
- 执行,带幂等键和超时。 先按幂等键查执行记录,已完成就直接返回上次的结果;下游支持幂等键的(例如 Stripe)把键原样传下去。
- 记录。 写执行记录和审计日志:谁、代表谁、哪个工具、脱敏参数、判定、结果大小、耗时。
- 把结果交回模型,当作不可信内容。 截断过大的输出,标明来源。工具结果里的「忽略之前的指令」只是文字,但模型可能照做;OWASP 把这类经由外部内容注入的攻击列为 LLM01。
顺序不能反:权限检查放在执行之后等于没有检查;幂等检查放在执行之后,重复的副作用已经发生了。
1. Direct Tool Call:工具就在应用进程里
模型返回 tool call,应用在同一个进程里调用函数,credential 从服务端 secret store 读。实现最简单,延迟最低,没有额外的一跳。
代价是 blast radius 和应用一样大:工具能访问的,就是应用进程能访问的全部数据和密钥;一个参数注入成功,影响的是应用的全部权限。只适合只读、低风险、参数空间很小的工具,例如查天气、查公开文档。即使是直连,credential 也不进 model context,工具返回的内容也要截断。
2. Tool Gateway:所有调用经过一个策略点
应用把 envelope 发给网关,网关做认证、按租户和工具的策略放行或拒绝、限流、记审计,再用自己保管的 credential 或用户委托的 token 调后端。几十个团队的 API 挂在同一个网关后面,策略和审计只写一份。
关键点在 token:网关调后端时用的应该是为这个后端、这个用户签发的 token,而不是客户端发来的那张。MCP 的授权规范对 MCP server 有同样的要求:必须验证 token 是签给自己的,不能把收到的 token 透传给上游 API。
代价是网关成为集中依赖:它挂了所有工具都不可用,它慢了所有工具都慢。要按无状态服务多副本部署,策略从配置中心读,按工具设独立的超时和并发上限,一个慢后端不能占满网关的全部连接。
3. Sandbox Worker:不可信代码在隔离环境里跑
模型写的代码、要打开的文件、要访问的网页,都在一次性的隔离环境里执行。broker 给 worker 一个短期 capability(例如只能读这一个上传文件、只能写一个输出目录、15 分钟后过期,数字为假设),worker 不持有任何长期密钥。结果写回 job store,由 broker 取回再交给模型。
这一种的风险是边界本身:逃逸、资源耗尽(死循环、fork 炸弹、写满磁盘)、结果污染(代码输出里夹带给模型的指令)。沙箱启动失败时必须硬失败,不能退回到在应用进程里直接跑。选哪一层隔离(操作系统沙箱、加固容器、微型虚拟机、带浏览器的专用 VM)见 Sandboxed Agent 执行架构,那一章也有互动 Lab:/system-design-lab/sandboxed-agent-execution-architectures。
4. Approval Queue:先存提案,人批准后再执行
高影响的动作不直接执行,而是把完整 envelope 存成一条 proposal,给人看参数的可读版本,人点批准后才执行。MCP 的工具规范写明应当始终有人在回路里、能拒绝工具调用;OpenAI 构建 Agent 的安全指南也建议对 MCP 工具保持开启 tool approval,让用户审查每一次操作。
两个细节决定这一层是否真的有效:
- 批准绑定参数。 批准记录里存
arguments_hash,执行前重新计算并比对,不一致就拒绝,要求重新审批。否则批准的和执行的可能不是同一件事。 - 批准有有效期。 proposal 设过期时间(例如 15 分钟,假设),过期要重新提。订单状态、账户余额会变,一小时前合理的退款现在可能已经退过了。
等人批准可能要几小时,执行进程不能一直挂着等。等待状态要持久化,由 checkpoint 或工作流引擎保管,见 Durable Agent 执行架构。
四种架构共同的底线
- Credential 永远不进 model context。 模型看到的只有工具名和参数。
- 工具结果是不可信输入。 截断、标来源、不把它拼进 system 或 developer 指令。OpenAI 的安全指南建议不要把不可信变量放进 developer message,并用结构化输出约束节点之间的数据流。
- 每个副作用带稳定的幂等键。 键由「运行 ID + 步骤 + 工具」派生,重试时不变。
- 每次调用都有超时和输出上限。 超时后向模型返回明确的错误,而不是无限等待。
- 审计记脱敏参数,不记原始 token。
故障与恢复
| 架构 | 故障 | 用户看到什么 | 恢复 |
|---|---|---|---|
| Direct | 网页内容里的注入让模型调用了一个本不该调用的工具 | 助手做了用户没要求的事 | 高风险工具不放在直连层;工具结果当不可信内容;只读工具才直连 |
| Direct | 一个工具调用卡住 | 整轮对话超时 | 每个调用单独的 deadline,超时返回错误给模型 |
| Gateway | 网关把客户端 token 透传给后端 | 后端用错误的受众 token 执行,越权 | 网关为每个后端单独换 token;后端拒绝受众不对的 token |
| Gateway | 一个慢后端占满网关连接 | 所有工具都变慢 | 按工具的并发上限和超时隔离(bulkhead),见 Circuit Breaker |
| Sandbox | 模型写的代码死循环或写满磁盘 | 任务超时,节点变慢 | CPU、内存、磁盘、墙钟时间上限;超限杀掉并返回错误 |
| Sandbox | 沙箱启动失败,代码退回到应用进程执行 | 表面上一切正常,实际边界消失 | 沙箱不可用时硬失败,不做降级执行 |
| Approval | 人批准后,参数在执行前被改写 | 批准了 45 块,退了 4500 块 | 执行前比对 arguments_hash,不一致就拒绝 |
| Approval | 批准几小时后才执行,订单已经退过 | 重复退款 | 批准有效期;执行前重新读业务状态;幂等键 |
| 所有 | 网络超时后重试 | 同一个副作用做了两次 | 执行前查幂等键记录,下游也带幂等键 |
怎么选
- 只读、低风险、参数空间小、调用频繁:Direct Tool Call,credential 在服务端,结果截断。
- 多个团队、多个租户的业务 API,要统一限权和审计:Tool Gateway,按后端换 token,按工具隔离超时和并发。
- 运行模型生成的代码、处理用户上传的文件、操作浏览器:Sandbox Worker,短期 capability,边界失败即失败。
- 不可逆或高影响的动作(钱、删除、对外发布):Approval Queue,批准绑定参数哈希,带有效期,等待状态持久化。
- 一个工具可以同时走两层:退款经过网关做权限和审计,再进审批队列等人。
回到开头的企业助手:查天气和查文档直连;各团队业务 API 走网关;表格分析走沙箱;退款、删除、对外发邮件走网关 + 审批队列。
面试时这样回答
- 复述约束:列出工具,按「只读 / 有副作用 / 执行不可信代码 / 不可逆」分级,并问清多租户和审计要求。
- 点名执行路径:模型提议 → schema 校验 → 服务端按用户权限校验 → 按风险路由(直连、网关、沙箱、审批)→ 幂等检查后执行 → 审计 → 结果作为不可信内容交回模型。
- 说清状态和 credential 归谁:credential 在 secret store 或网关,模型看不到;执行记录和幂等键在 job store;批准在 approval record,绑定参数哈希。
- 说一个代价和一个故障:例如审批让高风险动作慢几分钟到几小时,换来不可逆操作有人把关;故障选「批准后参数被改写」,修法是执行前比对哈希。
一手证据
- OpenAI:Function calling(
call_id、strict: true) - OpenAI:Safety in building agents(不可信输入、结构化输出、tool approvals)
- Anthropic:Tool use overview
- Microsoft:Agent search and tool use architectures
- Model Context Protocol:Architecture
- Model Context Protocol:Tools(human in the loop、输入校验、访问控制)
- Model Context Protocol:Authorization(受众校验、禁止 token 透传)
- OWASP:LLM01:2025 Prompt Injection
- Stripe:Idempotent requests