一个 Agent 能调用函数,并不意味着它已经拥有了“好用的工具”。
真实项目里,我更常见到另一种情况:API 本身没有问题,模型也确实发起了调用,但系统仍然在工具边界上失控。
搜索工具一次返回几百条结果,把下一轮 Context 塞满;
工具只返回一句 request failed ,模型不知道该重试、改参数还是停止;
工具越来越多,光 Schema 就占掉大量输入,选择反而更不稳定。
这些不是单纯的 Prompt 问题,而是 Tool Engineering 问题。
本文是 AI Agent 工程进阶 第 5 篇。上一篇 Harness Engineering 解决“谁负责组织模型、工具、状态和停止”;这一篇继续深入最容易产生真实副作用的接口: 一个工具怎样既便于模型理解,又能被运行时可靠约束。
已经实现 function calling / MCP,开始遇到参数错误、越权、重复写入或工具膨胀的开发者
Python 3.10+,零第三方依赖,不需要 API Key
3e891ff
Tool Registry、11 个边界案例、结构化错误、幂等回执、分页输出和对照报告
重放固定、可检查的工具提案,验证运行时契约;不调用真实模型,不测模型选工具准确率
先说明本文所说的“工具”
这里的工具不是只有一个 Python 函数。它包含两部分:一部分是提供给模型的名称、描述和输入 Schema;另一部分是运行时掌握的权限、审批、副作用、超时、幂等、错误和输出约束。前者帮助模型提出候选调用,后者决定调用能否安全落地。
一分钟概览
如果只保存这篇文章的结论,可以记住十二点:
工具是确定性系统与非确定性 Agent 之间的契约。
模型可见契约与运行时契约要分开。
名称、描述、Schema 帮助选择;权限、审批和幂等必须由代码执行。
先按用户意图设计工具,再考虑复用后端 API。
宽工具和窄工具没有绝对答案。
宽工具减少 Schema 体积,窄工具通常让意图、副作用和权限更清楚。
Schema 要让无效状态尽量无法表达。
必填字段、枚举、边界和 additionalProperties: false 都有价值。
预览和写入最好有清楚分界。
对高风险动作,独立 preview_* 往往比一个容易被忽略的 dry_run 布尔值更直观。
审批不是授权。
用户批准某次动作,不代表调用者拥有该资源的写权限;两者都要检查。
幂等不是“见到相同 key 就返回旧结果”。
同一 key 配不同参数应返回冲突,否则错误请求会被悄悄掩盖。
retryable 不是自动重试开关。
还要判断副作用、结果是否已知、是否有幂等键,以及预算是否允许。
工具输出也是 Context。
返回稳定 ID、摘要、游标和证据,不要默认倾倒整个对象或完整日志。
工具越多,选择成本和 Context 成本越高。
Namespace、deferred loading 和 tool search 是规模化手段,不是第一天就必需的架构。
本地契约测试不能替代模型 Eval。
11/11 只能证明运行时守住了边界;模型是否更会选工具,要用真实任务、重复运行和 held-out 集合验证。
图 1:Tool Engineering 把模型提案送入类型化 Tool Registry,再依次经过 Schema、权限、审批、幂等、执行和输出验证
1. 工具不是给模型看的 API 文档
传统 API 的调用者通常是确定性程序。只要函数签名和协议稳定,调用方会按代码路径提供参数:
代码
record_followup( ticket_id="T-102", note="Customer asked for a callback.", )
Agent 不同。模型面对的是自然语言目标、若干工具描述和一段会变化的 Context。它可能:
Anthropic 在 Writing effective tools for agents 里把工具描述为确定性系统与非确定性 Agent 之间的新型契约。这个判断很关键: Tool Engineering 不是替底层 API 补一份说明,而是设计一个适合 Agent 感知、选择、调用和恢复的动作表面。
因此,一个后端端点不一定对应一个 Agent 工具:
提示词
后端 API:面向系统复用、字段完整、兼容多个调用方 Agent Tool:面向任务意图、参数受限、输出有预算、失败可恢复
有时三个 API 应该被组合成一个工具,因为它们总是连续执行;有时一个通用 API 应拆成三个工具,因为读取、预览和写入拥有完全不同的风险。
2. 一个可运营工具至少有两层契约
我把工具契约拆成两层。
图 2:模型可见契约负责可理解性,运行时契约负责真实控制,两层共同包住业务处理器
第一层:模型可见契约
模型通常会看到:
提示词
name description input schema 有些平台还支持 output schema、namespace 或调用方式
它回答的是:
提示词
这个工具做什么? 什么时候应该用? 什么时候不应该用? 必须提供哪些参数? 返回什么字段?
第二层:运行时契约
这层不应只靠模型自觉遵守:
提示词
required_permission effect: read / write / destructive approval policy timeout and concurrency idempotency semantics retry policy input and output validation audit and redaction
它回答的是:
提示词
当前调用者真的有权执行吗? 这次写入是否已批准? 重复调用会发生什么? 超时以后结果是失败、未知还是可安全重试? 工具是否返回了符合契约的结果? 哪些信息允许进入下一轮 Context?
最危险的实现,是把第二层偷偷塞进描述:
提示词
“这个工具会修改数据,请只在获得批准后调用。”
这句话有助于模型做判断,但没有形成安全边界。真正的边界应该是:
代码
if spec.required_permission not in actor.permissions: return permission_denied() if spec.approval == "required" and not approved: return approval_required()
模型描述负责减少错误提案,运行时负责阻止错误提案变成真实事故。
3. 宽工具还是窄工具
假设工单系统已经有一个通用接口:
代码
{ "name": "ticket_operation", "arguments": { "operation": "get | preview | record | list | slow", "payload": {} } }
它的优点很直接:Schema 小,适配快,后端容易复用。问题也很明显:真正意图藏在 operation 和自由形态的 payload 里;读取与写入共享一个权限入口;描述要同时解释很多分支;输出也很容易变成“什么都可能返回”。
另一种设计是拆成:
提示词
get_ticket preview_ticket_followup record_ticket_followup list_tickets slow_ticket_lookup
图 3:同一组工单能力既可通过一个宽工具暴露,也可按读取、预演、写入、分页和慢依赖拆成窄工具
这不是“工具越小越好”。更实用的判断表是:
动作总是按固定顺序执行
更适合合并: 是,把编排下沉到代码
更适合拆分: 否
参数、返回值和权限高度相似
更适合合并: 是
更适合拆分: 否
读取、写入、删除风险不同
更适合合并: 否
更适合拆分: 是
用户会单独要求预览
更适合合并: 否
更适合拆分: 是
一个枚举分支已经很多
更适合合并: 通常否
更适合拆分: 是
拆分后产生大量近义工具
更适合合并: 谨慎
更适合拆分: 可能应重新分组或加 namespace
工具目录已经很大
更适合合并: 合并相关能力或延迟加载
更适合拆分: 只拆真正需要独立选择的意图
本篇 Lab 选择拆分,不是为了证明拆分一定更优,而是为了让权限、审批、幂等和输出边界可以独立表达。实验也如实记录了代价:模型可见的工具目录从 247 字节 增至 2505 字节 。
这约 10 倍的差距不是 token 账单,只是稳定的 UTF-8 字节代理;但它提醒我们: 更清楚的契约会消耗 Context,工具设计必须同时看可靠性和暴露成本。
4. 名称和描述是在做路由,不是在写文案
OpenAI 的 Function calling 指南 建议使用清晰、详细的函数名、参数说明和调用指令,并明确什么时候使用、什么时候不使用。Anthropic 的工具工程文章同样强调 namespace、返回有意义的 Context、控制 token 和通过 Eval 改进描述。
一个弱描述通常只有能力名:
提示词
record_ticket_followup Record a follow-up.
更可操作的描述应该补齐边界:
提示词
Append one approved follow-up to a support ticket. This tool writes data, requires ticket:write, and deduplicates repeated calls by action_id. Do not use it for previews.
这里包含四个路由信号:
提示词
动作:append one follow-up 副作用:writes data 前置条件:approved + ticket:write 反例:do not use for previews
但要再次强调:这四句话只帮助模型做选择。 ticket:write 和审批仍然由 Harness 检查。
命名检查
用动词表达动作: get_ 、 list_ 、 preview_ 、 record_ ;
让相似工具的区别出现在名字里,而不是藏在说明最后;
不要混用 manage 、 handle 、 process 这类边界不清的词;
通过 namespace 表达领域,例如 ticket.get 与 billing.refund ;
名称重构要配合 Eval,因为它会直接改变模型路由表面。
描述检查
5. Schema 的目标是让无效状态难以表达
在 OpenAI function tool 中, parameters 使用 JSON Schema, strict 可以约束函数调用;官方示例同时使用 required 和 additionalProperties: false 。MCP 的稳定规范也为工具定义 inputSchema ,并支持可选 outputSchema 。
本篇 Lab 的写工具 Schema 如下:
代码
input_schema = { "type": "object", "properties": { "ticket_id": { "type": "string", "minLength": 5, }, "note": { "type": "string", "minLength": 1, }, "action_id": { "type": "string", "minLength": 6, }, }, "required": ["ticket_id", "note", "action_id"], "additionalProperties": False, }
它至少阻止三类问题:
提示词
漏掉 note 传入空 note 偷偷带入未声明字段
如果一个状态可以用结构表达,就不要只写在说明里:
提示词
弱:status: string,描述里说只允许 open 或 closed 强:status: enum[open, closed] 弱:on: bool + off: bool 强:state: enum[on, off] 弱:amount + currency 都可选 强:两者都 required,或使用明确的联合结构
不过,Schema 也不能承担所有业务规则。 ticket_id 是否存在、当前用户是否能访问该工单、金额是否超出额度,仍需要运行时查真实数据。
6. 权限、审批和预演是三个不同问题
这三个概念经常被压成一个 confirm=True ,但它们回答的问题不同:
权限
回答的问题: 调用者是否有资格做这类事
典型证据: 角色、scope、资源 ACL
审批
回答的问题: 这一次具体动作是否被确认
典型证据: call id、参数摘要、批准人、时间
预演
回答的问题: 如果执行,会改变什么
典型证据: diff、影响对象、预计副作用
一次用户点击“批准”,不应该把只读身份升级成写权限。反过来,拥有写权限也不表示每一笔高风险操作都无需确认。
本篇 Lab 的顺序是:
图 4:工具调用从输入验证开始,依次经过权限、审批、超时、幂等和业务执行,最后再验证输出
提示词
1. 找到工具 2. 验证输入 Schema 3. 验证权限 4. 验证审批 5. 检查超时边界 6. 检查幂等回执或冲突 7. 执行业务处理器 8. 验证输出 Schema
为什么使用独立 preview 工具
常见设计是:
代码
{ "name": "record_ticket_followup", "arguments": { "ticket_id": "T-102", "note": "...", "dry_run": true } }
它适合人类调用的 API,也方便共享实现。但对高风险 Agent,我更倾向:
提示词
preview_ticket_followup -> 永远只读 record_ticket_followup -> 永远写入,必须审批
好处是副作用出现在工具身份上,而不是一个可能被漏掉或传错的布尔字段里。代价是多一个工具定义。若你的工具目录很大、预演逻辑与执行必须严格一致,也可以保留 dry_run ,但运行时仍要把它当成硬契约,而不是描述建议。
7. 幂等要同时校验 key 和请求指纹
超时以后,调用方往往不知道:
提示词
请求根本没到后端? 后端已经写入,但响应丢了? 写到一半失败?
这就是写工具需要幂等语义的原因。最小实现通常使用稳定的 action_id :
代码
if action_id in receipts: return receipts[action_id] result = write_once(arguments) receipts[action_id] = result return result
但这仍有一个漏洞:如果第二次调用沿用相同 action_id ,却改变了 ticket_id 或 note ,直接回放旧结果会把冲突藏起来。
Lab 0.5.0 为参数生成稳定指纹:
代码
fingerprint = json.dumps( arguments, sort_keys=True, separators=(",", ":"), ) if action_id in receipts: old_fingerprint, old_output = receipts[action_id] if fingerprint != old_fingerprint: return idempotency_conflict(action_id) return replay(old_output)
所以有三种明确结果:
提示词
新 key + 新请求 -> 执行一次并保存回执 旧 key + 相同请求 -> 回放回执,不重复写 旧 key + 不同请求 -> idempotency_conflict
生产实现还应让回执落在与业务写入一致的持久化边界里。只存在进程内存中的幂等表无法跨重启保护;这会在下一篇 Durable Loop 继续处理。
8. 错误要告诉 Agent 下一步,而不是只说失败
工具错误不是日志文案。它会进入下一轮 Context,影响 Agent 是改参数、请求批准、重试、换工具还是停止。
Lab 使用统一结构:
代码
{ "code": "tool_timeout", "category": "dependency", "message": "Tool exceeded its 500 ms timeout.", "retryable": true, "details": { "timeout_ms": 500, "observed_ms": 900 } }
图 5:结构化错误按验证、策略、依赖、冲突和内部错误分类,并映射到修参数、审批、重试或停止
一组够用的错误分类
validation
示例 code: invalid_arguments
Agent 的候选动作: 根据 violations 修参数
默认自动重试: 否
policy
示例 code: permission_denied
Agent 的候选动作: 停止或请求正确身份
默认自动重试: 否
policy
示例 code: approval_required
Agent 的候选动作: 暂停并展示待审批动作
默认自动重试: 否
not_found
示例 code: ticket_not_found
Agent 的候选动作: 核对 ID 或重新查询
默认自动重试: 否
conflict
示例 code: idempotency_conflict
Agent 的候选动作: 对账,生成新动作或人工处理
默认自动重试: 否
dependency
示例 code: tool_timeout
Agent 的候选动作: 在满足条件时有限重试
默认自动重试: 可能
internal
示例 code: invalid_tool_output
Agent 的候选动作: 停止并报警
默认自动重试: 否
retryable: true 仍然不够
真正重试前至少检查:
提示词
错误是否被分类为瞬时? 工具是读取还是写入? 写入结果是否确定? 是否提供稳定幂等键? 重试次数、deadline 和成本预算是否还有余量?
特别是“写请求超时且结果未知”不能因为 retryable=true 就直接再写一次。正确做法通常是先查询回执或业务状态,再决定重试。
MCP 稳定规范还有一个值得借鉴的细节:工具自身产生的错误应通过工具结果并标记 isError 返回,让模型能够看到并自我修正;找不到工具、服务不支持调用等协议级异常,再使用协议错误。换句话说, 业务失败与传输失败要分层。
9. 输出不是越完整越好
工具输出会成为下一轮模型输入,所以返回 500 条记录不只是网络浪费,也是 Context Architecture 问题。
本篇 Lab 的宽工具忽略 limit=3 ,返回 25 条完整工单;候选工具只返回:
代码
{ "items": [ {"id": "T-101", "status": "open", "subject": "..."}, {"id": "T-102", "status": "open", "subject": "..."}, {"id": "T-103", "status": "waiting", "subject": "..."} ], "next_cursor": "3" }
设计输出时可以逐项检查:
Anthropic 的文章把冗余调用和无效参数错误视为工具设计信号:重复查询可能意味着分页或 token 参数不合适,大量参数错误可能意味着描述和示例不清。工具输出不是一次设计完就结束,它应该和 Trace、Eval 一起迭代。
10. Direct、Programmatic 与 MCP 解决的不是同一层
工具工程容易把“工具怎么定义”和“工具通过什么通道执行”混在一起。可以用下面的表区分:
Direct function call
适合什么: 每次结果都会影响模型下一步;写操作;审批;需要保留原生引用
仍然需要什么: Schema、权限、审批、幂等、错误和输出预算
Programmatic Tool Calling
适合什么: 有界的筛选、去重、聚合、并行读取,代码可在中间压缩结果
仍然需要什么: 明确输入输出、允许的 caller、资源和停止边界
MCP / Connector
适合什么: 跨应用或跨服务标准化发现和调用工具
仍然需要什么: 信任判断、allowed tools、审批、数据边界和服务版本治理
OpenAI 当前的 Programmatic Tool Calling 允许受支持的 Responses 模型生成 JavaScript,在托管运行时内调用被允许的工具并汇总中间结果。官方部署建议也明确:当每个结果会改变下一步、动作需要批准,或最终回答必须保留引用与原生产物时,继续使用 direct call 更合适。
所以它不是“更先进的 function calling”,而是另一种编排路径:
提示词
大量只读查询 -> 代码过滤/连接/去重 -> 小结果回到模型
不应轻易把需要人工批准的写工具放进一个模型生成的批处理程序。
MCP 是传输和发现协议,不是安全证明
MCP 为工具提供 name 、 description 、 inputSchema 、可选 outputSchema 和 annotations。规范同时提醒:来自不受信任服务器的 tool annotations 不能被客户端当作可信安全事实。
例如:
提示词
readOnlyHint: true idempotentHint: true
是提示,不是你可以跳过审计和策略检查的证明。一个 MCP Server 仍然可能升级版本、改变行为或返回带注入内容的数据。客户端/Harness 需要独立控制允许的服务器、允许的工具、审批和日志。
版本说明
截至 2026-07-31,MCP 官方仓库已发布 2026-07-28-RC ,但页面明确标为尚未最终定稿的 release candidate。本文关于 inputSchema 、 outputSchema 、structured content 和 annotations 信任边界的引用,按稳定版 2025-11-25 核对,不把 RC 行为写成已经普遍落地的事实。
公众号发布复核(2026-08-04)
MCP 2026-07-28 已发布为正式稳定版;上面的段落按原博客发布时间原样保留。最新版 Tools 规范 仍保留 inputSchema 、可选 outputSchema 、structured content 与 annotations 信任边界,同时新增了工具列表缓存、输入补充和 Stateful Tools 等能力。SDK 是否采用该版本仍需按实际客户端核对。
11. 本篇实验:同一能力,两种工具表面
代码仍然放在同一个 Agent Reliability Lab 中,版本从 0.4.0 升到 0.5.0 。
提示词
agent-reliability-lab/ ├─ agent_lab/ │ ├─ harness.py │ ├─ tools.py │ └─ tool_reporting.py ├─ datasets/ │ └─ tool-cases.jsonl ├─ reports/ │ ├─ tool-comparison.json │ ├─ tool-comparison.md │ ├─ tool-failures.md │ └─ tool-runs.jsonl ├─ tests/ │ └─ test_tools.py └─ run_lab.py
你也可以直接下载本站打包的 Tool Engineering Lab 0.5.0。
实验控制变量是同一个内存工单后端,变化的是 Agent 面向的工具表面与运行时契约。
对照组: wide-tool-v1
提示词
一个 ticket_operation 自由 payload 无独立权限策略 无审批闸门 无幂等回执 通用 tool_failed 列表返回全部数据
候选组: typed-registry-v2
提示词
五个意图明确的工具 输入和输出 Schema 每工具权限与副作用 写操作审批 action_id + 参数指纹 结构化错误与 retryable limit + next_cursor
十一个边界案例
read-ticket
preview-before-write
write-needs-approval
invalid-arguments
permission-boundary
duplicate-action
idempotency-conflict
transient-timeout
bounded-list
ticket-not-found
invalid-cursor
为了保持零第三方依赖,Lab 只实现了这些案例所需的 JSON Schema 子集,用来展示边界顺序,而不是替代完整标准校验器。生产项目应使用平台 SDK 或成熟 JSON Schema 实现,并为实际使用的关键字增加兼容性测试。
12. 跟着运行 Lab 0.5.0
命令
git clone -b agent-engineering-series https://github.com/RalfNick/ai-agent-learn.git cd ai-agent-learn/phase-7-agent-engineering/agent-reliability-lab python run_lab.py tool-eval python -m unittest discover -s tests -v
正常结果应包含:
提示词
version: 0.5.0 wide-tool-v1: 1 / 11 cases passed typed-registry-v2: 11 / 11 cases passed gate_passed: true 41 tests: OK
报告会写入:
提示词
reports/local/ ├─ tool-comparison.json ├─ tool-comparison.md ├─ tool-failures.md └─ tool-runs.jsonl
建议阅读顺序:
先看 tool-comparison.md ,确认总结果和 Schema 成本;
再看 tool-failures.md ,定位宽工具为什么失败;
最后看 tool-runs.jsonl ,检查每次调用、错误、grader 和副作用数。
13. Tool Registry 真正做了什么
Tool Spec 同时保存两类信息:
代码
@dataclass(frozen=True) class ToolSpec: # Model-facing contract name: str description: str input_schema: dict output_schema: dict # Runtime contract required_permission: str effect: str = "read" approval: str = "never" idempotency_key: str | None = None retry_policy: str = "never" timeout_ms: int = 500
model_schema() 只导出模型需要看到的部分;权限和幂等规则留在受信任运行时。这一点很重要: 不要为了让模型“知道一切”,把内部 ACL、密钥或策略实现全部塞进 Context。
调用路径集中在 Registry:
代码
def invoke(call, actor_permissions, approved): spec = find_tool(call.name) validate(call.arguments, spec.input_schema) require_permission(spec, actor_permissions) require_approval(spec, approved) enforce_timeout(spec, call.arguments) replay_or_reject_idempotency_conflict(spec, call) result = handler(call.arguments) validate(result.output, spec.output_schema) save_receipt_if_needed(spec, call, result) return result
集中分派带来两个好处:
提示词
每个工具都无法绕过相同的策略顺序 每个失败都能进入统一报告和 Trace
生产系统不一定需要自研 Registry。OpenAI Agents SDK 的 function tools、timeouts、approval、tool guardrails,LangGraph 的 ToolNode,或其他框架都可以承载这些能力。Lab 的价值是让边界可见,便于你判断框架是否真的覆盖需求。
14. 怎样正确阅读 1/11 与 11/11
图 6:Lab 0.5.0 的契约结果与同时增加的模型 Schema 成本
本地结果是:
case pass rate
wide-tool-v1: 9.1%
typed-registry-v2: 100%
unsafe side effects
wide-tool-v1: 4
typed-registry-v2: 0
duplicate side effects
wide-tool-v1: 2
typed-registry-v2: 0
actionable error rate
wide-tool-v1: 0%
typed-registry-v2: 100%
model-facing schema bytes
wide-tool-v1: 247
typed-registry-v2: 2505
这个结果能证明:
提示词
给定这些固定调用提案,typed-registry-v2 会执行 Schema、权限、 审批、幂等、超时和输出约束,并生成预期的结构化结果。
它不能证明:
提示词
某个模型看到五个工具后,选择准确率一定更高; 所有真实业务错误都已经覆盖; 内存回执足以支持生产重启; 某个 SDK 或 Provider 比另一个更可靠。
对照组里的错误预览调用是刻意构造的对抗样本,不是从某个 Provider 跑出来的统计数据。把这类本地契约测试写成“工具选择准确率提升到 100%”,会把两种完全不同的证据混为一谈。
15. 真正的模型工具 Eval 应该怎样补
如果要验证名称、描述、Schema 与工具数量是否改善模型表现,可以在本地门禁之外增加 provider adapter:
数据集至少覆盖四类任务
提示词
必须调用某个工具 不应该调用任何工具 相似工具二选一 需要连续多个工具并根据中间结果调整
每条任务记录
提示词
expected tool or abstain required argument constraints forbidden side effects expected evidence maximum calls / latency / tokens
重复运行并分开统计
提示词
tool selection accuracy argument validity task success unnecessary calls unsafe proposals tool error recovery input tokens from tool schemas latency and cost
保留 held-out 集合
工具描述很容易针对已知失败样本过拟合。Anthropic 的实践建议使用贴近真实工作的任务,并用 held-out test set 检查改写后是否只是记住了训练样本。
一个推荐的证据顺序是:
提示词
单元测试 -> handler 和 Schema 正确 契约案例 -> 运行时能拦住风险 模型 Eval -> 模型能正确选择和恢复 线上 Trace -> 真实分布、成本和长尾失败
16. 工具数量变大以后怎么办
OpenAI 官方指南提醒,工具定义会进入模型输入,占用 Context 并计费;当前文档给出的软建议是回合开始时尽量保持较少的初始工具,并通过评估决定数量。工具面较大时,可以使用:
Namespace
提示词
ticket.get ticket.list ticket.record billing.invoice.get billing.refund.preview billing.refund.submit
先让模型识别领域,再在领域内选择动作。
Deferred loading / tool search
不常用工具只暴露高层 namespace,模型确认需要后再加载完整 Schema。OpenAI 当前的 tool search 支持 deferred function、namespace 和 hosted MCP surface;发布前应重新核对模型与 SDK 支持范围。
根据身份动态启用
没有写权限的用户根本不应看到写工具,既减少误调用,也降低工具目录大小。但“隐藏”不能替代运行时授权,因为请求可能被伪造或从旧状态恢复。
收敛近义工具
如果出现:
提示词
search_ticket find_ticket query_ticket lookup_ticket
问题往往不是模型不够聪明,而是工具边界本身不可区分。先合并语义,再优化描述。
17. 从 Function Tool 映射到 MCP 与框架
名称与描述
OpenAI Function / Agents SDK: function name / description
Anthropic / Claude: tool name / description
MCP: name / description
LangGraph: tool metadata
输入契约
OpenAI Function / Agents SDK: JSON Schema、strict
Anthropic / Claude: input_schema
MCP: inputSchema
LangGraph: Python schema / tool args
输出契约
OpenAI Function / Agents SDK: output schema 或应用校验
Anthropic / Claude: tool result 结构
MCP: outputSchema + structuredContent
LangGraph: state / tool message 校验
超时
OpenAI Function / Agents SDK: function tool timeout / Harness
Anthropic / Claude: 应用侧或 SDK 运行时
MCP: Server / Client 实现
LangGraph: ToolNode 外围策略
审批
OpenAI Function / Agents SDK: needs_approval 、HITL
Anthropic / Claude: permission / hook / app flow
MCP: Client approval policy
LangGraph: interrupt / human node
错误
OpenAI Function / Agents SDK: failure handler / tool guardrail
Anthropic / Claude: tool result error
MCP: isError result
LangGraph: handle_tool_errors
大工具面
OpenAI Function / Agents SDK: namespace / tool search
Anthropic / Claude: MCP 与工具设计优化
MCP: tools/list / list changed
LangGraph: 动态 tool set
“Skills、MCP、Function Calling 是否相通”这个问题,可以更精确地回答:
提示词
Skills 主要沉淀工作方法、资料和脚本; Tools 提供可调用的数据与动作; MCP 标准化客户端如何发现和调用外部工具; Harness 决定这些能力如何被组织、约束、记录和恢复。
它们可以协作,但不能互相替代。
18. 把现有 API 改成 Agent Tool 的六步练习
第 1 步:列出真实用户意图
不要先复制 API 路由。写出:
提示词
用户要读取什么? 用户要比较或预演什么? 用户要真正改变什么? 哪些动作总是连续发生?
产物:意图到后端能力的映射表。
第 2 步:标记副作用和权限
为每个候选工具填写:
提示词
read / write / destructive required scope resource-level ACL approval policy
产物:运行时策略表,而不是描述段落。
第 3 步:收紧输入和输出 Schema
至少检查:
提示词
required enum min/max additionalProperties output IDs pagination
产物:一组合法与非法样本。
第 4 步:设计错误与恢复动作
每个错误写清:
提示词
code category retryable result known or unknown next action
产物:错误决策表。
第 5 步:补幂等和预演
写工具至少回答:
提示词
稳定 action_id 从哪里来? 相同 key、不同参数怎么办? 重启后回执在哪里? 用户批准前怎样看到 diff?
产物:重复调用与冲突测试。
第 6 步:分两层评估
先跑确定性契约案例,再接真实模型做重复 Eval。不要用一层测试替另一层背书。
产物:契约报告 + 模型选择报告。
19. 接入生产前的 Tool Engineering 清单
可理解性
[ ] 近义工具已经合并或放入 namespace
输入与输出
[ ] 输入 Schema 有 required、enum 和额外字段策略
[ ] 业务 ID、权限和资源状态在运行时重新验证
副作用
[ ] 工具明确标记 read / write / destructive
[ ] 高风险动作支持 preview 或 dry-run
错误与恢复
[ ] 错误有稳定 code、category 和 details
[ ] retryable 不会绕过副作用与预算判断
[ ] 失败会进入 Trace 和 Eval 数据集
工具规模
[ ] 统计模型可见工具数和 Schema 输入成本
结语:好工具让错误提案停在边界上
Tool Engineering 的目标,不是保证模型永远不会选错。只要调用者是非确定性 Agent,错选、漏参和错误恢复就不可能完全消失。
更现实的目标是:
提示词
让正确工具更容易被理解和选择; 让无效参数在业务执行前失败; 让越权和未审批写入无法落地; 让重复请求只产生一次副作用; 让错误携带足够的下一步信息; 让输出保持在可控 Context 预算内; 让每次改动都能由 Eval 和 Trace 验证。
如果只记住一句:
模型负责提出工具调用,Tool Registry 负责证明这次调用有资格、可执行、可恢复,而且返回了可信结果。
下一篇进入 Durable Loop 。现在我们已经有 Harness 和有契约的工具,接下来要处理更难的现实问题:进程重启、网络抖动、结果未知、退避重试、取消、租约和断点恢复。
参考资料
OpenAI
Programmatic Tool Calling
OpenAI Agents SDK:Guardrails
OpenAI Agents SDK:Human-in-the-loop
Anthropic、MCP 与 LangGraph
Anthropic:Writing effective tools for agents
Model Context Protocol:Tools,stable 2025-11-25
LangChain / LangGraph:Tools and ToolNode
本文代码与证据
Agent Reliability Lab 3e891ff