Skip to content

04A. Tool 能力接口与执行边界

版本:v1.2

最后更新:2026-07-09

适用对象:想把 Agent 的“会调用工具”真正落到接口设计、权限边界、失败处理、运行时治理和评测体系里的产品、研发、平台与架构同学

1. 为什么要把 Tool 单独讲

很多团队说“我们已经接了很多工具”,但真正上线以后问题并不出在“有没有工具”,而出在:

  • 工具定义太大,模型不知道什么时候该调
  • 参数 schema 太松,调用看起来成功,业务实际上已经偏了
  • 读工具、写工具、审批工具混在一起,风险边界不清
  • 工具失败后只返回一段自由文本,系统没法稳定恢复
  • 工具结果原样回灌,把后续推理一起污染

从工程视角看,Tool 不是“顺手接一个 API”这么简单,它是 Agent 系统里最小的可执行能力单元。


2. Tool 到底是什么

可以把 Tool 理解成一句话:

  • Tool = 一个被模型可见、被系统可控、被运行时真正执行的最小能力接口

它至少要回答六个问题:

  1. 这个能力什么时候该用
  2. 允许传什么参数
  3. 真正由谁执行
  4. 成功和失败分别返回什么
  5. 会不会产生真实副作用
  6. 调用结果如何影响下一轮推理

这里最关键的一点是:

  • 模型负责提出调用建议
  • 应用或运行时负责校验、审批和执行

也就是说,Tool 不是“模型自己在操作外部系统”,而是“模型在受控边界内发起一个动作请求”。

2.1 Tool calling 是一个多步闭环,不是单次函数调用

OpenAI 当前官方 Function Calling 指南把 tool calling 描述成一个多步对话闭环:

  1. 你把工具定义连同任务一起发给模型
  2. 模型返回 tool call
  3. 你的应用侧执行工具
  4. 你把 tool output 再发回模型
  5. 模型继续回答,或继续发起更多 tool call

这条闭环很重要,因为很多线上问题不是出在“模型有没有选对工具”,而是出在:

  • tool output 怎么回灌
  • 回灌后模型有没有继续误调
  • 调用失败后系统有没有停住

2.2 Tool 不等于函数,函数只是 tool 的一种

在 OpenAI 当前工具体系里,至少要区分三类对象:

类型输入形态更适合什么
function toolJSON schema参数结构明确、可验证、适合业务接口
custom tool自由文本或 grammar 约束文本输入不适合硬包一层 JSON 的场景
built-in tool平台内建web search、MCP、shell、computer use 等

也就是说,函数只是 tool 的一个子类。

如果团队把所有工具都统称成“函数调用”,后面就会在:

  • 参数校验
  • 运行时所有权
  • 审批边界
  • 成本模型

这些地方一起混掉。

2.3 Tool 结果不只是“执行完了”,还是下一轮上下文的一部分

OpenAI 官方文档明确说明,tool output 会和:

  • 工具定义
  • 原始 prompt
  • 模型 tool call

一起回到模型上下文中,供后续推理消费。

因此 Tool 的工程问题从来不只包括:

  • 这个动作能不能做

还包括:

  • 做完以后,什么结果该继续传播
  • 什么结果该被清洗
  • 什么结果根本不该再喂回模型

2.4 用同一个例子,把 Tool 单独看清

继续拿“事故分诊”举例:

  • Tool 不负责定义整个事故流程
  • Tool 也不负责说明这些能力如何跨宿主暴露
  • Tool 只负责把某个动作做成稳定最小单元

例如:

  • read_recent_alerts(service, window_minutes)
  • search_runbook(query, service)
  • create_incident_ticket(severity, summary, evidence_ids)

这里你真正要讨论的是:

  • 参数有没有冗余
  • 输出是不是稳定结构
  • 哪个动作有副作用
  • 哪个动作必须审批

如果你开始讨论:

  • 这些能力怎么按标准协议接到别的宿主

那你已经进入 MCP 话题了。

如果你开始讨论:

  • 事故分诊这个任务应该先查告警、再读 runbook、最后决定是否建单

那你已经进入 Skill 或 workflow 话题了。

2.5 一个像样 Tool,最好先长成稳定契约

下面这个例子比“随手暴露后端接口”更接近模型真正适合调用的最小 Tool 面:

json
{
  "type": "function",
  "name": "create_incident_ticket",
  "description": "在确认存在真实故障且证据充分后,创建事故工单。不要在只有猜测时调用。",
  "strict": true,
  "parameters": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "severity": {
        "type": "string",
        "enum": ["sev1", "sev2", "sev3"]
      },
      "summary": {
        "type": "string",
        "description": "面向值班同学的简短事故摘要"
      },
      "evidence_ids": {
        "type": "array",
        "items": { "type": "string" }
      }
    },
    "required": ["severity", "summary", "evidence_ids"]
  }
}

这个例子真正重要的点不是 JSON 长什么样,而是它已经把几件事说清了:

  • 什么时候该用,什么时候不该用
  • 工单级别只能填哪些值
  • 不能偷偷塞额外字段
  • 证据是必填,而不是“感觉差不多就建单”

3. 一个成熟 Tool 的最小契约

一个能在线上长期运行的 Tool,通常至少要包含下面这些字段或等价信息:

维度需要回答的问题
name模型怎么唯一识别它
description什么时候该用,什么时候不该用
input schema参数有哪些、哪些必填、允许什么枚举或格式
output schema成功时返回什么结构,失败时返回什么结构
side effects它是只读、写入、外呼、还是高风险动作
auth / approval哪些角色能用,哪些情况要审批
retry semantics超时后能不能自动重试,是否幂等
observability调用、参数、结果、耗时、失败原因如何记录

如果只剩下“函数名 + 参数列表”,那还不算一个成熟的 Tool 契约。

3.1 namedescription 其实是工具发现层接口

很多团队只重视参数 schema,却低估了工具名和描述的重要性。

但模型一开始真正能依赖来做路由判断的,恰恰是:

  • name
  • description

如果这两者不清楚,模型就容易:

  • 误选相近工具
  • 把写工具当读工具
  • 在无关任务里滥用某个万能工具

3.2 input schemaoutput schema 都要当成正式契约

线上常见误区是:

  • 输入很严格
  • 输出靠自由文本“解释一下”

这会直接导致:

  • 下游节点无法稳定消费
  • 结果分类和回放困难
  • 评测口径不稳定

更稳的做法通常是把以下三者一起管住:

  • 输入 schema
  • 成功输出 schema
  • 错误输出 schema

3.3 call_idnamespace 和结果关联字段不能丢

OpenAI 当前官方 Function Calling 指南里,tool output 必须能关联到具体 tool call。

这意味着在工程实现上,至少要保留:

  • call_id
  • tool name
  • 如有命名空间,还要保留 namespace

如果这层关联丢了,后面会很难回答:

  • 这条结果是哪次调用产生的
  • 哪个 tool call 失败了
  • 某轮多工具并发时到底是谁污染了结果

4. Tool 设计最容易踩错的三件事

4.1 把后端 API 原样暴露给模型

后端接口通常是给工程师调的,不是给模型调的。常见问题有:

  • 参数太多
  • 同一个接口既能查又能改
  • 错误码只对后端同学友好
  • 返回结果太脏,下一轮模型很难稳定消费

更稳的做法通常是:

  • 给模型重做一层更小、更清晰的 Tool 面
  • 把复杂业务接口收敛成少量原子动作
  • 把内部字段名和外部能力名分开

4.2 把读工具和写工具混在同一层暴露

读工具和写工具的治理要求完全不同:

  • 读工具更关注召回、过滤、成本和噪音
  • 写工具更关注审批、幂等、补偿和审计

如果在同一个任务阶段把两类工具一起放给模型,通常会导致:

  • 非必要写入动作过早暴露
  • 模型在多个相近工具之间误选
  • 审批链被迫后置

4.3 只约束参数,不约束结果

很多团队会认真写输入 schema,却放任工具返回一大段自由文本。结果是:

  • 下一轮模型被污染
  • trace 很难复盘
  • 工作流节点无法做稳定分支判断

所以 Tool 契约至少要同时治理:

  • 输入结构
  • 输出结构
  • 错误结构

5. Function tool、custom tool 和 built-in tool 怎么选

5.1 Function tool 适合参数清晰、动作边界稳定的能力

如果你的工具输入天然是结构化字段,例如:

  • customer_id
  • ticket_id
  • refund_reason

那 function tool 通常最合适。

原因很直接:

  • schema 明确
  • 更适合 strict mode
  • 更适合日志校验、审批和回放

5.2 Custom tool 适合不想强包 JSON 的文本型输入

OpenAI 当前官方文档把 custom tool 定义得很清楚:

  • custom tool 允许模型返回任意字符串作为输入
  • 也可以再叠加 grammar 约束

这类工具更适合:

  • 生成代码片段
  • 生成表达式
  • 输入本身天然是文本而不是对象字段

但风险也更大,因为:

  • 结构化验证更弱
  • 更容易混入无关文本
  • 更需要 grammar 或下游清洗兜底

5.3 Built-in tool 更像平台级执行层,不是你的业务函数

内建工具例如:

  • web search
  • MCP
  • shell
  • local shell
  • computer use

它们回答的通常不是“你的业务系统要做什么动作”,而是:

  • 模型怎样访问平台级能力或执行环境

所以不要把:

  • 业务读写动作
  • 平台执行能力

揉成同一层概念。


6. Strict mode、Structured Outputs 和 schema 质量

6.1 OpenAI 官方建议:function tool 尽量开 strict: true

OpenAI 当前 Function Calling 官方文档明确建议:

  • function tool 的 strict mode 尽量始终开启

因为 strict mode 会利用 Structured Outputs,让模型对函数参数更可靠地遵循 schema,而不是“尽力而为”。

6.2 开 strict 不是只写个布尔值,还要满足 schema 前提

OpenAI 当前官方文档还写明了 strict mode 的关键要求:

  1. 每个 object 都要设置 additionalProperties: false
  2. properties 里的字段都要在 required 里显式标记

也就是说,很多团队以为自己“开了严格模式”,其实 schema 本身还没准备好。

6.3 Function tool 的 schema 质量会直接决定工具稳定性

Structured Outputs 官方文档给出的建议很值得直接拿来用:

  • key 要命名清晰
  • title 和 description 要清楚
  • 用 evals 去找最合适的结构

从工程角度看,这意味着 Tool schema 设计不是一次性工作,而是持续优化对象。

6.4 JSON 合法不等于契约可靠

很多团队看到模型能输出合法 JSON,就以为 Tool 稳了。

其实真正关键的是:

  • 枚举是否合法
  • 必填字段是否齐
  • 值域是否合理
  • 失败时是否有统一结构

合法 JSON 只是底线,不是上线标准。


7. Tool 最好怎么分层

最常见、也最实用的分层方式是四层:

7.1 原子只读 Tool

例如:

  • search_runbook
  • get_ticket
  • list_recent_alerts

它们的特点是:

  • 不产生副作用
  • 更适合高频自动调用
  • 更适合做缓存和结果压缩

7.2 原子写入 Tool

例如:

  • create_ticket
  • update_order_status
  • send_notification

这类 Tool 要重点补:

  • 幂等键
  • 风险级别
  • 审批要求
  • 超时和补偿策略

7.3 两阶段写 Tool

高风险动作最好拆成:

  • draft_*
  • execute_*

这样模型先产出执行草案,再由系统或人工确认后执行。

7.4 编排层不要伪装成单一 Tool

如果一个能力内部已经包含多步状态推进、回滚、审批和人机协同,它更像 workflow 或 skill,不再只是一个普通 Tool。


8. Tool 不只是“能调通”,还要“能收得住”

8.1 错误返回要可操作

不要只返回:

  • error
  • failed

更适合工程治理的返回通常会说明:

  • 失败类别
  • 是否可重试
  • 是否需要换参数
  • 是否需要人工接管

例如:

字段含义
error_code稳定错误分类
retryable是否允许自动重试
requires_approval是否要进入审批链
user_action_required是否需要补证据或补参数

8.2 写 Tool 必须回答重试问题

只要会产生副作用,就要提前说清:

  • 自动重试是否安全
  • 并发调用是否安全
  • 超时后是否可能已经部分成功
  • 如何补偿

这类问题如果不写进契约,就会在事故里用生产系统替你回答。

8.3 审批不是 UI 补丁,而是 Tool 契约的一部分

高风险 Tool 最好从定义层就表达:

  • 风险级别
  • 所需审批角色
  • 执行前要展示哪些证据
  • 审批后如何恢复运行

这比在页面上额外加一个“确定吗”按钮可靠得多。

8.4 Tool 结果最好分 raw、sanitized、model-facing 三层

虽然这部分在结构化输出专题里已经展开讲过,但从 Tool 视角也很关键。

比较稳的三层通常是:

  • raw:外部系统原始返回
  • sanitized:做过脱敏、白名单、字段收缩
  • model-facing:真正回灌给模型的最小结果

这样你才能同时满足:

  • 审计
  • 回放
  • 模型稳定消费

8.5 有些结果应该拒绝传播,而不是继续总结

如果结果包含:

  • 敏感数据
  • 跨租户混入
  • 注入性文本
  • 高风险失败堆栈

更稳的做法通常不是“让模型自己总结一下”,而是:

  • 直接阻断回灌
  • 只回传结构化失败标签
  • 转人工或审批链

9. Parallel tool calls、tool_choice 和执行约束

9.1 并发工具调用不是默认越多越好

OpenAI 当前 Function Calling 官方文档明确说明:

  • 模型可能在一轮里选择多个函数
  • 你可以通过 parallel_tool_calls: false 限制成零个或一个

这条配置非常适合高风险工具或强状态依赖工具。

9.2 Built-in tools 场景下并行限制和 function tool 不完全一样

OpenAI 当前文档还明确说:

  • 使用 built-in tools 时,不支持 parallel function calling 那套并发模式

这也再次说明:

  • function tool
  • built-in tool

不能简单套用同一套运行假设。

9.3 对写工具来说,串行通常比并行更稳

对于写操作,开启并发最常见的问题是:

  • 多个副作用同时落地
  • 回滚顺序变复杂
  • 审批和证据链难配对

所以一个很实用的经验是:

  • 读工具更适合考虑并行
  • 写工具默认优先串行

9.4 tool_choice 是收束风险的重要开关

虽然很多系统喜欢让模型自由选工具,但在某些阶段你更适合显式控制:

  • 必须不用工具
  • 必须用某个工具
  • 只能在已加载子集里选

特别是在 tool search 后只加载了某个子集时,tool_choice 可以帮助你把模型收在更小的工具面里。


10. Tool surface 太大时,不要靠 prompt 撑住

OpenAI 当前 Function Calling 和 Tool Search 官方文档都明确写到:

  • 当函数很多、schema 很大时,可以配 tool search
  • 只有 gpt-5.4 及以后支持 tool_search

10.2 defer_loading 解决的是上下文和成本问题

Tool Search 官方文档给出的关键机制是:

  • 对 function 设置 defer_loading: true
  • 或对 MCP server 设置 defer_loading: true
  • 再把 tool_search 加进 tools

这样模型起步时只看到:

  • 名称
  • 描述

而不是所有细节 schema 都先塞进上下文。

10.3 Namespace 比“平铺 100 个函数”更适合搜索

OpenAI 当前 Tool Search 官方建议很清楚:

  • 尽可能用 namespace
  • namespace 描述要清晰
  • 最好把 namespace 控制在较小规模

对 Tool 设计的启发是:

  • 工具分组本身就是工程设计的一部分
  • 不要等到工具炸到上百个再想起分命名空间

10.4 Hosted tool search 和 client-executed tool search 解决的问题不同

Tool Search 官方文档还区分了两种路径:

  • hosted tool search:已知候选工具全集时最省心
  • client-executed tool search:当工具发现依赖项目态、租户态或你自己的系统时更灵活

这意味着不要笼统说“我们用了 tool search”,而要讲清:

  • 搜索是谁执行的
  • loaded subset 怎么回灌
  • search result 是不是可信

11. Observability、回放和评测字段不能等事故后再补

11.1 至少记录这些最小观测字段

一个可回放的 tool call 事件,至少应该保留:

  • trace_id
  • response_id
  • call_id
  • tool name
  • tool namespace
  • 参数快照
  • 输出快照
  • 状态
  • 耗时
  • 错误分类

11.2 Tool 评测不该只看“最后任务是否成功”

从 Tool 视角,至少还应该单独看:

  • 工具选择正确率
  • 参数合法率
  • 非必要工具调用率
  • 工具重试率
  • 高风险动作审批命中率
  • 失败后假装成功率

如果只看最终成功率,很容易漏掉:

  • 路径错了但最后看起来还能说通
  • 写工具风险过高但被结果掩盖
  • 工具成本和错误恢复正在恶化

11.3 Tool output 最好可回放,而不是只剩最终摘要

出了问题以后,团队真正需要的是:

  • 当时给了什么定义
  • 模型发了什么参数
  • 工具真实返回了什么
  • 哪一步开始被污染

所以 Tool 系统一定要先对“回放”友好,再谈“摘要看起来多漂亮”。


12. Tool 和 MCP、Skill 到底怎么区分

这里最容易混:

  • Tool 讲的是“系统能执行什么动作”
  • MCP 讲的是“这些动作如何按统一协议暴露出去”
  • Skill 讲的是“在某类任务里如何组织这些动作、上下文和策略”

一句话记忆:

  • Tool 是能力接口
  • MCP 是接入协议层
  • Skill 是场景封装层

如果你发现自己在讨论:

  • 参数 schema
  • 输出结构
  • 幂等与副作用

那你讨论的是 Tool。


13. 什么时候该优先补 Tool,而不是继续调 Prompt

如果线上问题是这些,通常先看 Tool,不是先堆 Prompt:

  • 模型经常选错工具
  • 参数总是填错字段
  • 同类失败不断重试
  • 工具返回太脏导致后续推理漂移
  • 高风险动作没有清晰审批边界

因为这类问题本质上不是“模型没听懂”,而是“能力接口本身不适合稳定调用”。

13.1 一个很常见的反模式:schema 有问题,却继续补自然语言说明

OpenAI 当前 Structured Outputs 官方文档里其实已经把方向讲得很清楚:

  • 能用 schema 解决的,优先用 schema 解决
  • 不要只靠越来越长的 prompt 去弥补契约缺陷

这条在 Tool 设计里尤其关键,因为很多工具问题本质上是:

  • 字段设计错
  • 枚举设计错
  • 结果结构错

而不是“说明文字还不够多”。


14. Tool 的落地检查清单

  • 是否把后端 API 重新收敛成模型可理解的最小能力单元
  • 是否区分了只读、写入和高风险动作
  • 是否同时定义了输入 schema、输出 schema 和错误结构
  • 是否尽量开启了 strict: true 并满足 strict mode 的 schema 前提
  • 是否写清了幂等、并发、超时和重试语义
  • 是否为 tool output 设计了 raw / sanitized / model-facing 三层
  • 是否能在失败后给出可操作的处理信号
  • 是否为高风险动作预留审批、确认和补偿机制
  • 是否为大工具面考虑了 namespace、defer_loading 和 tool search
  • 是否记录了稳定的 tool call 观测字段和审计字段

15. 推荐联读


16. 参考资料