Appearance
🐣 小a的困惑
上一章,小a学会了 function calling——模型能"下单"了。但它马上面临新问题:到底该怎么定义工具,模型才会用得又准又好?
老z:"工具是 Agent 的手。手好不好用,直接决定 Agent 能不能干活。这一章,我们专门讲 Tool——怎么设计一把趁手的兵器。"
6.1 Tool 是什么:把能力包装成"菜单"
🧙 Tool(工具)是把 function calling 包装成的、模型可调用的完整能力单元。一个 Tool 包含三样东西:
- 名字 + 描述:告诉模型"这个工具能干嘛、什么时候该用"
- 参数 schema:告诉模型"调用它要传什么参数、什么类型"
- 执行函数(execute):你写的代码,真正干活的那个函数
老z打比方:function calling 是"下单的语法",Tool 是"菜单上的一道菜"——菜有名字、有做法、有食材清单,服务员(模型)按菜单点,厨房(你的 execute 函数)照做。
Tool vs Function Calling
| function calling | Tool | |
|---|---|---|
| 是什么 | 模型吐结构化调用的机制 | 一个具体的、可调用的能力 |
| 层次 | 协议层 | 应用层 |
| 例子 | 模型输出 {"name":"read_file",...} | read_file 这个工具(名字+描述+schema+execute) |
🐣 小a:"所以 function calling 是'语法',Tool 是'词汇'?"
🧙 "对! 语法(机制)只有一种,词汇(工具)可以无限多。你的 Agent 能干多少事,取决于你给它配了多少把好工具。"
6.2 一个好 Tool 的灵魂:描述
🧙 Tool 最容易被忽略、却最关键的是描述。 模型完全靠描述来决定"什么时候用这个工具、传什么参数"。
对比两个 read 工具的描述:
一个糟糕的:
{ name: "read", description: "读文件" }→ 模型不知道:什么时候该读?能读什么?读多大多小的文件?
一个出色的:
{ name: "read", description: "读取指定路径的文件内容。当你需要查看文件内容、理解现有代码、确认配置时使用。支持文本和图片。", parameters: { path: "文件的绝对或相对路径" } }→ 模型清楚知道:用途(什么时候用)、边界(读什么)、参数(传什么)
🧙 写描述的三个要点:
- 说清用途:什么时候该用(场景)
- 说清边界:什么时候不该用、有什么限制
- 说清参数:每个参数是什么类型、什么含义、给个例子
🐣 小a恍然:"所以描述本质是在教模型怎么用这个工具?"
🧙 "对。 描述写得好,模型用得准;写得烂,模型要么不用、要么乱用。这就是为什么 pi 的每个工具都有精心写的描述。"
6.3 参数 schema:工具的"表单"
🧙 参数 schema 定义工具接受什么参数——它是模型的"填表规范"。
一份好 schema 要:
- 类型准确:path 是 string,count 是 number
- 必填可选分清:required 数组标出必填项
- 描述到位:每个字段说明含义和例子
- 尽量简单:参数越少越好,模型越不容易填错
json
{
"name": "edit_file",
"description": "修改文件中的一段文本。用于修改现有文件内容。",
"parameters": {
"type": "object",
"properties": {
"path": { "type": "string", "description": "要修改的文件路径" },
"old_text": { "type": "string", "description": "要替换的原文(必须精确匹配文件内容)" },
"new_text": { "type": "string", "description": "替换后的新文本" }
},
"required": ["path", "old_text", "new_text"]
}
}🧙 schema 设计的反例:
- ❌ 参数过多(模型填错概率高)
- ❌ 类型模糊(全用 string,模型不知道该传啥)
- ❌ 没有必填约束(模型漏传参数)
工具参数越少、越明确,模型越容易用对。 这是"最小工具原则"。
6.4 工具设计原则
🧙 五条设计原则(老z的兵器谱):
- 单一职责:一个工具只干一件事。
read只读、write只写,别搞"读写一体"。- 描述优先:宁可多花时间写描述,不要省。描述是工具的使用说明。
- 幂等性:同一参数调用两次,结果应该一致(不会产生副作用叠加)。
- 失败可读:工具出错时,返回的错误信息要能告诉模型"为什么失败、怎么改"。
- 输出受控:工具输出别太长(会撑爆上下文),要截断或结构化。
🐣 小a问:"为什么要'失败可读'?"
🧙 "因为模型要靠错误信息自我纠正。 工具返回
文件不存在比返回Error: ENOENT有用得多——模型看到前者就知道'路径写错了,换一个',后者它看不懂。工具的错误输出,是喂给模型的'反馈信号'。"
6.5 从"单工具"到"工具箱"
🧙 单个工具是小菜,工具箱才是正餐。一个编程 Agent 的工具箱通常是组合:
能力 工具 为什么需要 读 read查看文件、理解代码 搜 grep/find定位代码、找引用 改 edit/write修改/创建文件 跑 bash执行测试、装依赖 列 ls看目录结构 工具之间要有配合。 模型先
grep找位置,再read看内容,再edit修改,再bash验证——一套组合拳。这就是为什么 Agent 需要"循环"来串起这些工具(下一章)。
6.6 什么时候该做成工具?什么时候不该?
"最后一个问题,也是最重要的判断。"老z说,"不是所有东西都该做成工具。"
🧙 做成工具的三类理由:
- 需要实时信息:文件内容、命令输出、网络数据——这些必须"现场拿",模型凭记忆猜不到 → 工具
- 需要确定性执行:计算、校验、格式转换——要让程序精确执行,不能靠模型"口算" → 工具
- 需要副作用:写文件、发请求、跑命令——动作必须真的发生,而且只能发生一次 → 工具
反过来,纯静态的知识(比如"公司的代码规范是什么")不该做成工具——它不随时间变、不需要执行,塞进 prompt 或打包成 Skill(第 11 章)更划算,又省一轮调用又少一次出错机会。
🐣 小a:"那我是不是工具越多越好?"
🧙 "恰恰相反。工具越多,模型越容易选错。 想象你面前摆着 50 把刀,切菜时你也得犹豫半天。模型也一样——工具太多,它要么选错、要么干脆不敢选。"
🧙 工具箱要克制:
- 只给够用的工具:完成当前任务需要 5 个,就别挂 50 个
- 现代做法:按需加载——不是一次把所有工具都塞给模型,而是根据任务只把相关的给它(pi 的 coding-agent 就是按需装配,Part II 第 20 章会看到)
- 判断标准:多一个工具,是帮模型干成事,还是给它添乱?
工具数量是个工程权衡:太少,模型无工具可用;太多,模型用错工具。 好的工具箱,是"刚好够用"。
本章小结
🐣 小a的兵器课
┌──────── Tool 与工具设计 ────────┐ │ │ │ • Tool = 名字+描述+schema+执行 │ │ (function calling 的"词汇") │ │ │ │ • 描述是灵魂:用途/边界/参数 │ │ → 写好模型才用得准 │ │ │ │ • schema 是表单:类型/必填/简单 │ │ → 参数越少越不容易错 │ │ │ │ • 五原则:单一职责/描述优先/ │ │ 幂等/失败可读/输出受控 │ │ │ │ • 工具箱:读搜改跑列 组合拳 │ └─────────────────────────────────┘
关键认知:工具是 Agent 的手。设计工具的功夫,决定 Agent 干活的水平。 描述、schema、错误信息——这些"细节"才是工具能不能被模型用好的关键。
课后实验
- 评价工具描述:找两个真实工具(read/bash)的描述,评价它们是否符合"用途/边界/参数"三要点。
- 写一个工具:为你手头的项目写一个
ls工具,注意描述和 schema 怎么写才算好。 - 体验失败可读:故意让工具返回一个模糊错误,再看一个清晰的错误,体会模型"自我纠正"的差异。