📑 本页目录(点开跳转)
07 · 工具设计:能力的上限
⏱ 35 分钟 | ⭐⭐ 核心 | 🔨 有可直接抄的代码
🎯 一句话
「Agent 的能力上限由我们给它的工具决定。」 工具设计是一门新学科:ACI(Agent-Computer Interface)——像做 UI 一样认真地做模型的界面。
🧠 一、核心认知转变
| 传统 API | Agent 工具 | |
|---|---|---|
| 使用者 | 确定性程序 | 非确定性模型 |
| 调用方式 | 严格按文档 | 可能换着花样、可能理解错、可能压根不调 |
| 错误处理 | 调用方负责 | 工具要引导模型自我修复 ⭐ |
| 设计目标 | 完备、正交 | 贴合任务意图、省 token、防呆 |
🔑 一句话:API 的用户会读文档,工具的用户会「猜」。 好工具让猜对的概率最大化,让猜错的代价最小化。
📌 SWE-bench 冲榜团队公开表示:「花在优化工具上的时间比优化整体提示词还多。」
🤔 先把一个几乎每个人都会卡住的问题解决掉:「Function Calling 和 MCP 到底是什么关系?」 很多人以为它们是竞品、要二选一。不是——它们在三个不同的层上,你会同时用到全部三个。 🔗 答案在 08 · MCP 第二节,那里有 Function Calling / MCP / A2A 的三层分工表。 ⭐ 先记住结论,这一章后面才读得顺: Function Calling 是模型和你的程序之间的协议(模型说"我要调
get_weather",也就是这一章在设计的那个东西); MCP 是你的程序和外部工具之间的协议(怎么把get_weather这个能力接进来,不用为每个工具写一遍胶水)。 所以这一章的五条设计原则在两边都成立——你自己写的函数要遵守,别人的 MCP Server 也一样。 ⚠️ 唯一的差别是:接第三方 MCP Server 时你往往改不了它的工具描述。 那就只能在自己这侧包一层——重写描述、裁掉用不上的参数,再暴露给模型。 (这和 08 章第五节 ⑥「第三方返回臃肿就包一层做裁剪」是同一招的两半:描述进去的那半在这一章的原则 ⑤,返回出来的那半在 08 章。)
🖐️ 二、五条设计原则
① 选对工具,而非更多工具
核心矛盾:Agent 的上下文有限,计算机的内存无限——别把内存的活儿丢给上下文。
| ❌ 反上下文的工具 | ✅ 围绕工作流的工具 | 省了多少 |
|---|---|---|
list_contacts() 返回全部 5000 条 |
search_contacts(query) 只返回相关的 |
5000 → 5 条 |
read_logs() 返回整个日志文件 |
search_logs(pattern) 返回匹配行 + 上下文 |
50000 → 20 行 |
list_users() + list_events() + create_event() |
schedule_event(attendees, duration) 内部处理找空档 |
3 轮 → 1 轮 |
判断标准:
一个工具应该对应【人类的一个任务意图】
而不是【数据库的一张表】或【REST API 的一个端点】
⭐ 自检:「用户会说『帮我___』吗?」
会 → 这是个好工具边界
不会 → 你在暴露实现细节
② 命名空间
asana_search / asana_create / asana_update
jira_search / jira_create / jira_update
⭐ 好处:
· 几十个工具时,前缀帮模型快速缩小范围
· 明确边界:模型不会拿 asana 的 id 去调 jira
③ 返回有意义的上下文
# ❌ 技术标识符,模型抄写会出错
{"user_id": "a3f8b21c-4e5d-11ee-be56-0242ac120002",
"mime_type": "application/vnd.ms-excel", "ts": 1753574400}
# ✅ 语义化 + 短索引
{"idx": 0, "name": "张伟", "file_type": "Excel 表格", "time": "2026-07-27 09:00"}
三条具体规则:
| 规则 | 理由 |
|---|---|
| 用语义名称替代 UUID ⭐ | 模型抄写长随机串会出错(幻觉),短索引 0,1,2 不会 |
| 用人类可读格式 | "Excel 表格" 比 "application/vnd.ms-excel" 省 token 也更准 |
提供 response_format 枚举 |
让模型自选详略,实测省约 1/3 token |
def search_docs(query: str, response_format: str = "concise"):
"""
response_format:
"concise" - 只返回标题和一句话摘要(默认,省 token)
"detailed" - 返回完整正文
"""
④ 为 token 效率优化
四个手段:分页、过滤、范围选择、带指引的截断。
# ✅ 截断时要告诉模型"还有更多"以及"怎么拿"
def format_results(results, limit, formatted):
if len(results) > limit:
return (f"{formatted[:limit]}\n\n"
f"[还有 {len(results)-limit} 条结果未显示。"
f"用更具体的 query 缩小范围,或用 offset={limit} 翻页]")
⑤ ⭐ 工具描述就是提示词
像给新入职同事写说明一样写:把隐性知识显式化、参数命名无歧义、给用法示例和边界。
# ❌ 开发者视角的描述
"""Query the events table."""
# ✅ 使用者视角的描述
"""查询日历事件。
用于回答"某人某天有什么安排""会议室X什么时候空闲"这类问题。
- 时间范围最长 90 天,超过会报错
- 不包含已取消的事件(除非 include_cancelled=True)
- 返回按开始时间升序排列
示例:查询张伟下周的会议
query_events(attendee="张伟", start="2026-08-04", end="2026-08-10")
"""
📌 官方战绩:仅仅精化工具描述,就曾让 Claude Sonnet 3.5 拿下 SWE-bench Verified 的 SOTA。 这是全领域投入产出比最高的优化之一——不用改代码,只改文档字符串。
🛡️ 三、防呆设计(Poka-yoke)
让错误用法在结构上不可能发生,而不是在文档里警告:
| 容易出错的设计 | 防呆改法 | 为什么 |
|---|---|---|
| 接受相对路径 | 强制绝对路径 | 模型搞不清"当前目录"是哪 |
| 自由字符串参数 | 改成 enum: ["draft","published"] |
拼错直接被 schema 拦住 |
create() 后要记得 commit() |
合并成一个原子操作 | 模型会忘第二步 |
| 危险操作直接执行 | 加 confirm: bool 必填参数 |
强制模型显式表达意图 |
| 时间用字符串 | 给出明确格式 + 示例,或用相对描述 "next monday" |
格式歧义是高频错误源 |
# ✅ 防呆示例:三重保护
def delete_records(
table: str, # enum 限定
where: str, # 必填,不允许空(防止全表删除)
confirm: bool, # 必填 True,强制显式确认
max_rows: int = 100, # 硬上限,超过直接拒绝
):
if not where.strip():
return "错误:where 不能为空。如需清空全表请用 truncate_table 工具。"
...
🩺 四、错误信息:写给模型看的教学材料
这是最容易被忽略、收益却极高的一环。
# ❌ 给人看的报错
raise ValueError("Invalid input")
# ✅ 给模型看的教学
def bad_expression_error():
return ( "错误:expression 参数含不支持的字符 ['s','i','n']。\n"
"本工具只支持数字和 + - * / ** ( ),不支持函数。\n"
"如需三角函数请用 advanced_calc 工具。\n"
"正确示例:calculator(expression='3.14 * 2**2')")
好错误信息的四要素:
① 错在哪 「含不支持的字符 ['s','i','n']」
② 规则是什么 「只支持数字和 + - * / ** ( )」
③ 该怎么办 ⭐ 「如需三角函数请用 advanced_calc」
④ 正确示例 「calculator(expression='3.14 * 2**2')」
🔑 判断标准:模型看完这条错误,能不能一次就改对? 能 → 好错误信息。不能 → 它会瞎试,浪费好几轮。
🔁 五、评估驱动的工具改进循环
工具好不好,不靠感觉,靠这个循环:
① 快速做出原型工具
↓
② 造一批【真实的多步评估任务】(不是 demo 题)
↓
③ 跑评估,收集完整转录
↓
④ 读转录:模型在哪困惑?哪里绕弯?哪个参数总用错?
↓
⑤ ⭐ 让 Claude 读转录,自己重构工具和描述
↓
⑥ 留出测试集验证 → 回到 ③
📌 官方实测:让 Claude 根据转录优化过的工具,性能超过了专家手写的版本。 用 Agent 改进 Agent 的工具,是 2026 年的标准操作。
第 ④ 步该盯什么(读转录的检查表):
| 症状 | 说明它需要什么 |
|---|---|
| 反复调同一个工具改参数 | 描述里没说清参数含义/取值范围 |
| 调了工具但没用返回结果 | 返回格式不好懂,或信息不是它要的 |
| 手动组合多个工具做一件事 | 该提供一个组合工具(原则 ①) |
| 一直不调某个工具 | 描述没说清它什么时候有用 |
| 调用后报错然后放弃 | 错误信息没告诉它怎么改 |
🎭 六、「think」工具:一个值得知道的小历史
2025 年官方发现:给 Agent 一个什么都不做的 think 工具
(唯一作用是让模型写下思考),在复杂多步任务上显著提效。
def think(thought: str) -> str:
"""记录你的思考过程。不执行任何操作,仅用于整理思路。"""
return "已记录。" # ← 真的什么都不做
为什么有用:给了模型一个显式的暂停-整理位(第 4 章:思考需要 token 承载)。
⚠️ 后来的模型有了原生扩展思考,这个技巧多数场景已被取代。 但它是「工具可以塑造行为,而不只是提供能力」的经典例证—— 这个思路今天仍然有用(比如给一个
plan工具强制它先规划)。
🔨 七、动手:给一个工具做体检
拿你系统里(或第 1 章里)的一个工具,过六关:
| # | 检查 | 修法 |
|---|---|---|
| 1 | 返回里有模型用不上的字段吗? | 砍掉或加 response_format |
| 2 | 有裸 UUID / 时间戳 / MIME 类型吗? | 换语义化名称或短索引 |
| 3 | 错误信息告诉模型怎么改了吗? | 按四要素重写 |
| 4 | 描述拿给没见过这系统的同事,他能用对吗? | 补隐性知识 + 加示例 |
| 5 | 有哪种误用可以从结构上堵死? | 防呆化(枚举/必填/绝对路径) |
| 6 | 这个工具对应一个任务意图还是一张数据表? | 按意图重新划分边界 |
🕳️ 八、六个常见坑
| 坑 | 症状 | 修 |
|---|---|---|
| 工具太多 | 模型选错工具、上下文被定义撑爆 | 合并成意图级工具;上 Tool Search(第 8 章) |
| 返回体积失控 | 前几轮就烧光预算 | 分页/过滤/带指引的截断 |
| 描述是给开发者写的 | 模型用不对 | 改成使用者视角 + 示例 |
| 错误信息无法行动 | 模型报错后瞎试 | 四要素重写 |
| 参数含义靠猜 | 反复调同一工具改参数 | 参数名 + docstring 明确取值范围 |
| 一比一镜像 API | 简单意图要调三个工具 | 按意图重新分组 |
📚 延伸阅读
- 用 Agent 为 Agent 写有效的工具
- 「think」工具
- 构建有效的 Agent(附录:ACI 与防呆)
👉 接进真实产品时:工具的入参出参怎么用 Schema 约束住、校验失败怎么带着错误重试,在 16c · 接进真实产品 —— 那一章还讲了「Schema 只保证格式对,不保证内容对」这个最容易漏的坑。
✅ 检查点
- 工具的用户和 API 的用户本质区别是什么?
- 「返回全部联系人」的工具坏在哪?判断工具边界的自检问题是什么?
- 为什么返回里的 UUID 要换掉?
- 好的错误信息有哪四个要素?判断标准是什么?
- 评估驱动改进循环的第 ⑤ 步是什么?为什么它有效?
- 读转录时,「模型反复调同一个工具改参数」说明什么问题?
think工具为什么有用?它今天还有意义吗?
👀 答案
- API 用户是确定性程序、会读文档严格调用;工具用户是模型,它会「猜」——可能换花样、理解错、或压根不调。好工具让猜对概率最大、猜错代价最小。
- 把计算机内存的活儿丢给了有限的上下文——模型被迫逐 token 读完 5000 条。自检问题:「用户会说『帮我___』吗?」会 → 好边界;不会 → 你在暴露实现细节。
- 模型抄写长随机串会出错(幻觉)。用语义名称或短索引
0,1,2可显著减少。 - ①错在哪 ②规则是什么 ③该怎么办 ④正确示例。判断标准:模型看完能不能一次改对。
- 让 Claude 读评估转录后自己重构工具和描述。有效因为转录里有真实的困惑证据,且官方实测 AI 优化版超过专家手写版。
- 描述里没说清参数含义或取值范围——它在靠试错猜参数。
- 给模型一个显式的暂停-整理位,让思考有 token 承载。原生扩展思考出现后多数场景已被取代,但「工具可以塑造行为而不只提供能力」这个思路仍然有用。
🛑 可以停在这里
⚡ 走神救援
工具决定能力上限,这门学科叫 ACI,要像做 UI 一样认真做模型的界面。核心认知:API 用户读文档,工具用户会猜——好工具让猜对概率最大、猜错代价最小(冲榜团队说过:花在优化工具上的时间比优化提示词还多)。五原则:①按任务意图选工具不按数据表(自检:用户会说"帮我___"吗;Agent 上下文有限而计算机内存无限——返回全部 5000 条联系人要改成只返回 5 条,5 万行日志要改成 20 行匹配行,三个工具 3 轮要并成 1 轮)②命名空间(asana_/jira_ 前缀帮模型缩小范围,也防它拿 asana 的 id 调 jira)③语义化返回(UUID 换短索引防幻觉,「Excel 表格」比长 MIME 又省 token 又准,加 response_format 省 1/3 token)④token 效率(分页/过滤/带指引的截断——要告诉它还剩多少条、怎么拿)⑤描述就是提示词(像给新同事写说明;仅改描述曾拿下 SWE-bench Verified 的 SOTA,不改代码只改文档字符串,投产比最高)。防呆:让误用结构上不可能(绝对路径/枚举/原子操作/必填 confirm/where 不许为空+max_rows 硬上限)。错误信息四要素:错在哪+规则+该怎么办+示例,标准是"模型能否一次改对",不能它就瞎试烧掉好几轮。改进靠循环:原型→造真实多步评估任务→读转录(反复改参数=没说清取值范围;调了不用结果=返回难懂;手动拼工具=该给组合工具;一直不调=没说清何时有用;报错就放弃=错误信息没说怎么改)→⭐让 Claude 自己重构工具,官方实测超过专家手写版。
下一节 👉 08-MCP-接入外部世界.md