4.2 工具描述的写法决定了调用成功率
一句话结论:工具描述不是文档,而是模型的决策依据。它要回答的不是"这个函数做了什么",而是"在什么情况下应该用它、什么情况下不该用"。写错这个,模型的工具选择就会系统性出错。
读这篇之前
需要先读 4.1(Function Calling 是结构化输出)。这一篇是工具设计里投入产出比最高的一篇:改描述比改代码便宜得多,效果却更明显。
一、先看一个失败案例
假设你有一个工具:
name: "search"
description: "搜索"
用户问:"帮我看看这个项目的依赖有没有安全隐患。"
模型的困境:search 搜什么?搜代码?搜网页?搜数据库?"搜索"两个字没有提供任何判断依据。
结果通常有三种:不调用、调用后传错参数、或者调用了一个不该调的工具。
再看一个写得好的:
name: "search_code"
description: "在项目代码库中按关键词搜索源码。适用于:查找某个函数/变量在哪里定义、
某个字符串出现在哪些文件。不适用于:查找文档说明(用 search_docs)、
或搜索互联网(用 web_search)。"
parameters:
query: "要搜索的关键词或标识符,如 getUserById"
path: "限定搜索的目录,默认整个项目根目录"
差别不在"详细程度",而在它明确了边界:
| 要素 | 作用 |
|---|---|
| 做什么 | 在代码库按关键词搜源码 |
| 什么时候用 | 找定义、找引用 |
| 什么时候不用 | 找文档 → 用别的工具;搜网络 → 用别的工具 |
| 参数说明 + 示例 | 告诉模型怎么填 |
第三条是最关键、也最少人写的一条。
二、为什么"什么时候不用"如此重要
回到 2.1 的机制:模型在做的是从上下文里检索模式、选最匹配的那个。
当你有多个相似工具时,模型要判断"该选哪个"。如果每个工具的 description 只说"我做什么",那么:
- 三个工具都描述得像"搜索"
- 模型只能靠 name 猜
- 猜错的概率很高
而如果你在每个工具里都写了"什么时候不该用我,该用谁",那么模型有了区分依据。
这本质上是把"工具选择"从推理题变成了检索题——而模型擅长后者(2.1、3.3 反复出现的规律)。
三、工具描述的六个要素模板
一个可靠的工具描述,覆盖六项:
| # | 要素 | 写法示例 |
|---|---|---|
| 1 | 一句话能力 | "在项目代码库中按关键词搜索源码" |
| 2 | 适用场景 | "查找函数/变量定义、查找字符串出现位置" |
| 3 | 不适用场景 + 指名替代 | "找文档用 search_docs;搜网络用 web_search" |
| 4 | 返回什么 | "匹配文件路径 + 行号 + 该行内容,最多 50 条" |
| 5 | 副作用等级 | "只读,不修改任何文件" |
| 6 | 失败情形 | "参数为空或路径不存在时返回错误说明" |
第 4、5、6 项常被忽略,但它们各自解决一个真实问题:
- 第 4 项(返回什么):让模型知道"调用后能拿到什么",避免它为了获取不存在的信息反复调用
- 第 5 项(副作用):让模型知道"这个操作危险不危险",也方便你在工具层做分级(2.5)
- 第 6 项(失败情形):提前告诉它失败长什么样,减少把错误结果当成功用的情况
一个完整写法
name: "update_user_status"
description: |
修改指定用户的状态(启用/禁用)。
适用:用户要求启用、禁用、冻结某个账号。
不适用:修改用户资料(昵称、头像)请用 update_user_profile。
影响:写操作,会立即生效并记录审计日志。
返回:成功时返回用户 ID 与新状态;用户不存在返回 error。
注意:禁用操作需要用户明确指定目标用户,禁止批量禁用。
parameters:
user_id: "目标用户 ID(数字)"
status: "目标状态,只能是 active 或 disabled"
这份描述里,.parameters.status 用了枚举(只能是两个值)——这是 4.3 要展开的关键技巧。
四、四个具体的写法技巧
技巧一:参数用枚举,不要用自由文本
差:action: "要执行的操作"
好:action: "操作类型,只能是 create / update / delete 之一"
自由文本意味着无穷多的可能性,模型可能自创一个不存在的操作。枚举把它限制在有限集合里,既提升准确率,又天然防越权(第 14 章)。
技巧二:给示例值,不要只给类型
差:city: "城市名称,string"
好:city: "城市名称,如:北京、上海"
原因同 4.1:示例是可检索的模式。
技巧三:把"必须"和"可选"分开写清楚
差:parameters 里没有 required 字段
好:required: ["user_id"],并在描述里说明 status 默认值
漏写 required,模型会以为都可以省,于是关键参数缺失。
技巧四:长度要够,但不要变成手册
一个反直觉的实测规律:工具描述太短(一两个词)会明显降低准确率,但超过一定长度后收益就饱和了。
太短:「搜索」 → 模型无法判断
合适:一句话能力 + 场景 + 边界 → 最优
太长:500 字的产品说明书 → 占上下文、无额外收益(2.2)
判断标准:让一个不了解你系统的同事读这段描述,他能不能说清"什么情况下该用、什么情况下不该用"。能,就够长了。
五、工具命名也在影响选择
命名不是风格问题,是准确率问题:
| 差 | 好 | 原因 |
|---|---|---|
search |
search_code / search_docs / web_search |
名字自带边界 |
run |
run_python / run_tests / run_shell |
区分执行对象 |
get |
get_weather / get_user_info |
区分获取什么 |
do |
create_user / update_user / delete_user |
动词前缀暴露副作用等级 |
最后一条特别有用:用动词前缀让副作用等级"写在名字里"。
read_* / get_* / list_* → 只读
create_* / update_* → 写入
delete_* / drop_* / deploy_* → 危险
这样即便模型没仔细读 description,它也能从名字上感知风险。而你的工具层也可以按前缀做权限分级(2.5 第五节)。
六、一个常见反模式:工具集"大而全"
差的设计:
execute(sql) // 万能,无法校验
manage_user(action, data) // 万能,参数组合爆炸
exec_command(cmd) // 万能,等于交出 shell
好的设计:
query_readonly(sql) // 只读,可静态校验
create_user(name, email, role) // 参数明确,可枚举 role
list_users(page, size) // 无副作用
disable_user(user_id) // 单一动作,可审批
大而全的工具看起来"灵活",实际上带来了三个问题:
| 问题 | 后果 |
|---|---|
| 参数组合空间巨大 | 模型容易传错,校验困难 |
| 无法分级副作用 | 一个工具既读又写,无法单独审批 |
| 描述无法写清边界 | 什么都"能"做,等于什么都说不清 |
这条原则在 2.5 和后面 4.5 都会再出现:宁可多几个明确的小工具,不要一个模糊的大工具。
七、常见误区
误区一:工具描述是给人看的文档。
它的第一读者是模型。写给人看的详细文档可以另做。
误区二:描述越短越好,省 Token。
太短会让模型无法判断。描述是必要成本,不是浪费。
误区三:只写"我做什么"就够了。
不够。多个相似工具时,"什么时候不该用我"才是区分依据。
误区四:名字随便起,反正有描述。
名字是模型最先扫描的信号,且直接影响你的权限分级设计。
误区五:一个工具能覆盖越多场景越好。
参数空间越大,出错率越高,越难校验。
误区六:描述写好了就不管了。
工具是演进的,描述的边界要随实现同步更新。描述和实现不一致,比没有描述更危险。
八、动手:把一段烂描述改成好描述
改写前
name: "search"
description: "搜索"
模型能做什么? 只能猜。结果通常是:不调用,或者调用后传错参数。
改写后
name: "search_code"
description: |
在项目源码中按关键词搜索。
适用于:
- 查找某个函数/类/变量在哪里定义
- 查找某个字符串出现在哪些文件
不适用于:
- 查找文档说明(用 search_docs)
- 搜索互联网(用 web_search)
- 按文件路径读内容(用 read_file)
只读操作,不修改任何文件。
返回:匹配的路径 + 行号 + 该行内容,最多 50 条。
若无匹配,返回空列表并说明。
parameters:
query:
type: string
description: "搜索关键词或标识符,如 getUserById、TOKEN_EXPIRED"
path:
type: string
description: "限定搜索目录,默认项目根目录,如 src/services"
七个改动点
| # | 从 | 到 |
|---|---|---|
| 1 | search |
search_code(名字自带边界) |
| 2 | 无 | 适用场景 |
| 3 | 无 | 不适用场景 + 指名替代 |
| 4 | 无 | 副作用声明(只读) |
| 5 | 无 | 返回什么 + 数量上限 |
| 6 | 无 | 无结果时返回什么 |
| 7 | 参数无说明 | 参数带示例值 |
第 3 条最关键——它把"选哪个工具"从推理题变成检索题(见本篇第二节)。
一个检查方法
把你的工具描述给一个不了解这个项目的人看,问他:
- "什么情况下你会用这个?"
- "什么情况下你不会?"
两个都答不出来,说明描述还不够。
一个反例:描述写太长也会失效
差:一个工具的描述写了 800 字,包含实现细节、历史沿革、参数逐个举例
问题:模型需要的是"什么时候用",不是"它内部怎么实现"。
判断标准:描述里每一句都应该能回答"这影响我用不用它吗"。不能的,删掉。
九、一个容易忽略的点:描述里的"否定"要谨慎
工具描述里写"不要做什么",有时会起反效果。
风险写法:「不要用这个工具删除文件」
问题:模型可能把"删除文件"当成使用场景之一
更安全的写法:
好:「本工具只读。删除请用 delete_file(需审批)。」
原理:模型对"否定"的把握不如"肯定"(2.3 幻觉研究中的常见现象)。指向替代工具,比单纯禁止更有效。
三条描述写作规则
| 规则 | 好 | 差 |
|---|---|---|
| 用肯定句描述边界 | "只读" | "不要写" |
| 指名替代工具 | "搜索文档用 search_docs" | "不要用它搜文档" |
| 描述副作用 | "会通知仓库成员" | "注意有影响" |
十、一个实战总结:描述写不好的三种典型后果
| 后果 | 表现 | 根因 |
|---|---|---|
| 该用不用 | 模型自己瞎猜而不调工具 | 描述没写清"何时用" |
| 不该用硬用 | 简单问题也去调工具 | 描述太泛 |
| 用另一个 | 调了相似但错的工具 | 缺"不适用时用谁" |
三种后果都指向同一件事:描述里缺"边界"。
一个修法模板
【做什么】一句话
【何时用】2-3 个具体场景
【何时不用】+ 指名替代工具 ← 最关键
【副作用】只读 / 写 / 不可逆
【返回什么】结构 + 数量上限
【失败时】返回什么
六个小节,每次都写全。 写顺了之后,它比"两句话描述"只多花三分钟,但省掉大量调试。
十一、一个补充:描述长度的实测建议
| 工具类型 | 建议描述长度 | 理由 |
|---|---|---|
| 单一动作、名字自解释 | 1-2 句 | 多了是噪声 |
| 与别的工具有重叠 | 必须写清边界(3-5 句) | 否则误选 |
| 副作用不明确 | 必须写副作用 | 影响审批 |
| 参数复杂 | 每个参数一句 | 减少参数错 |
没有统一字数,只有统一标准:
让读者能判断"何时用、何时不用、用完会怎样"。
满足这三条就够,多写的都是噪声(2.2 的信噪比)。
十二、一句话收尾
工具描述是"写给模型的说明书",不是"写给同事的文档"。
写文档时可以假设读者会通读、会查资料;写工具描述时只能假设模型只读这一遍,就决定用不用、怎么填参数。
这一个假设差异,决定了所有写法细节。
十三、下一篇
描述解决了"该不该用、用哪个"。下一个问题是:模型真的调用了,但参数传错了怎么办?
这看起来是个小问题,实际上是 Agent 稳定性的主要来源之一。下一篇讲参数校验与失败回填——包括怎么让模型自己把错误改过来。