模块 05 · 第 3 课

工具怎么设计

同样三个工具,名字和说明写得含糊,模型 30 次只选对 14 次;写清楚之后 30 次全对。讲清楚工具的名字、说明、参数、返回值和报错信息分别该怎么写。

  • 约 35 分钟
  • 难度:进阶
  • 实测:2026-09-14 deepseek-flash

智能体好不好用,一大半取决于工具。模型能看到的只有工具的名字、说明和参数定义,看不到你的代码。说明书写得含糊,模型就只能猜:这个工具能干什么?什么时候该用它?参数填什么?

这一课先做一个实验,量一量说明书写得好坏的差别,然后讲具体怎么写。

实验:含糊的说明和清楚的说明

同样三个功能:在文档里搜索、读一个文档文件、查 PyPI 上的版本号。写两套说明书。

含糊的一套:名字是通用的动词,说明只有两三个字:

VAGUE = [
    fn("search", "搜索", q="内容"),
    fn("read", "读取", x="要读的东西"),
    fn("lookup", "查找信息", name="名字"),
]

清楚的一套:名字说明操作的对象,说明写清楚能做什么、什么时候用、参数怎么填:

CLEAR = [
    fn("search_docs", "在 httpx 官方文档里按英文关键词全文搜索,返回匹配的文件名和行号。"
       "用户问 httpx 某个功能怎么用、某个参数是什么意思时,先用它。",
       keyword="英文关键词,例如 timeout、proxy、follow_redirects"),
    fn("read_doc", "读取 httpx 文档里某个文件的内容。通常在 search_docs 找到文件名之后使用。",
       path="文档文件路径,例如 advanced/timeouts.md"),
    fn("get_pypi_info", "查询某个 Python 包在 PyPI 上的最新版本号和发布信息。只在用户问版本号、是否已发布新版本时使用。",
       package="PyPI 上的包名,例如 httpx"),
]

fn 是一个生成工具说明书的小函数,完整代码见 code/05-agents/tool_design.py。)

然后准备 10 个问题,每个问题标注第一步应该调用哪个工具。其中"你好"这一题的正确答案是不调用任何工具。每个问题、每套说明书各问 3 次,只看模型第一步选了哪个工具,不真的执行:

QUESTIONS = [
    ("httpx 怎么设置代理?", "search_docs"),
    ("httpx 最新版本是多少?", "get_pypi_info"),
    ("帮我看看 advanced/ssl.md 里写了什么", "read_doc"),
    ("follow_redirects 参数是干什么的?", "search_docs"),
    ("requests 现在出到哪个版本了?", "get_pypi_info"),
    ("httpx 怎么上传文件?", "search_docs"),
    ("你好", None),
    ("把 quickstart.md 的内容给我看一下", "read_doc"),
    ("httpx 有没有发布 1.0 正式版?", "get_pypi_info"),
    ("httpx 的 event hooks 怎么用?", "search_docs"),
]

结果:

含糊的工具:14/30 次选对
    httpx 怎么设置代理?  应该用 search_docs,实际 {'None': 2, 'search_docs': 1}
    httpx 最新版本是多少?  应该用 get_pypi_info,实际 {'search_docs': 3}
    帮我看看 advanced/ssl.md 里写了什么  应该用 read_doc,实际 {'read_doc': 2, 'get_pypi_info': 1}
    follow_redirects 参数是干什么的?  应该用 search_docs,实际 {'search_docs': 1, 'None': 1, 'get_pypi_info': 1}
    requests 现在出到哪个版本了?  应该用 get_pypi_info,实际 {'get_pypi_info': 1, 'search_docs': 2}
    httpx 怎么上传文件?  应该用 search_docs,实际 {'None': 3}
    httpx 有没有发布 1.0 正式版?  应该用 get_pypi_info,实际 {'search_docs': 3}
清楚的工具:30/30 次选对

同一个模型,只是说明书不同,正确率从 47% 变成了 100%。

含糊的说明书错在哪

不知道工具管什么范围。"搜索"是在哪里搜?网页、文档还是代码?模型不知道。于是"httpx 最新版本是多少"它 3 次都选了 search,因为"搜索"听起来最通用。

不知道什么时候该用。"httpx 怎么上传文件",3 次都没调用任何工具,模型直接凭记忆回答了。它不知道 search 能帮它找到更可靠的答案,也就没有理由去用。

名字和功能对不上lookup 其实是查 PyPI 版本的,可"查找信息"这个说明和"读取"、"搜索"区分不开,模型只能随机选。"follow_redirects 参数是干什么的",3 次选了 3 个不同的结果。

清楚的说明书把这些都写明了:搜的是"httpx 官方文档","用户问 httpx 某个功能怎么用时,先用它";查版本的工具"只在用户问版本号时使用"。模型不需要猜。

名字

  • 说清楚操作的对象search_docssearch 好,get_pypi_infolookup 好。工具多了以后,通用的名字很容易撞在一起。
  • 用动词开头,前后一致get_search_read_create_,一套规则用到底。
  • 别用缩写gpi 你自己知道是什么,模型不知道。

说明

一个好的说明,回答三个问题:

  1. 它能做什么? "在 httpx 官方文档里按英文关键词全文搜索,返回匹配的文件名和行号。"
  2. 什么时候该用它? "用户问 httpx 某个功能怎么用时,先用它。"
  3. 什么时候不该用它? "只在用户问版本号、是否已发布新版本时使用。"

工具之间容易混淆时,第三点尤其重要。另外,说明里可以写工具之间的配合关系,比如 read_doc 的"通常在 search_docs 找到文件名之后使用",模型就知道先搜后读。

参数

  • 每个参数都写说明,最好带例子。"英文关键词,例如 timeout、proxy、follow_redirects"。例子能告诉模型格式,也暗示了"文档是英文的,要用英文搜"。
  • 参数越少越好。一个工具有七八个参数,模型很容易漏填或填错。能有默认值的给默认值。
  • 用枚举限定取值。参数只能取几个固定值时,在 schema 里用 enum 列出来(第 02 模块第 4 课用过)。
  • 名字和类型要明确xqname 这样的参数名,不如 pathkeywordpackage

返回值

工具的返回值会原样进入上下文,后面每一步都要为它付费。所以:

  • 只返回有用的信息。上一课的 get_pypi_info 只挑了版本号、简介、Python 版本要求四个字段,没有把 PyPI 返回的几十 KB 原始 JSON 全部塞进去。
  • 限制长度。上一课的 grep_docs 最多返回 20 条,read_doc 一次最多 80 行,循环里还有一道 3000 字符的截断。
  • 方便模型下一步使用grep_docs 返回"文件名:行号:内容",模型可以直接拿文件名和行号去调用 read_doc
  • 结果为空时说清楚。返回"没有找到 pool timeout",而不是一个空字符串。空字符串让模型困惑:是工具坏了,还是真的没有?

报错信息

报错信息是写给模型看的,要能帮它改正:

错误:没有这个文件 advanced/timeout.md,请先用 list_docs 查看有哪些文件

这一句说清楚了三件事:出了什么错、错在哪个参数、下一步该怎么做。对比 Python 默认的 FileNotFoundError: [Errno 2] No such file or directory,模型也能看懂,但不知道该调用哪个工具去找正确的文件名。

工具多了怎么办

工具越多,模型越难选对,每次请求的说明书也越长。一些经验:

  • 合并功能相近的工具。如果 search_docssearch_api_reference 的区别连你都说不清,就合并成一个,加一个参数区分。
  • 按场景分组。不同的任务只给相关的那几个工具,第 6 课的多智能体会用到这个思路。
  • 看轨迹。经常被用错的工具,就是说明书需要改的工具。

练习

  1. tool_design.py 的清楚组里,把 search_docs 说明里的"用户问 httpx 某个功能怎么用时,先用它"删掉,重新运行。"httpx 怎么上传文件"这一题的结果有变化吗?
  2. 给两套工具都加一个功能:list_docs,列出所有文档文件。含糊的一组叫 list、说明是"列表",清楚的一组照本课的方法写。再加两个问题测试它。
  3. 找一个你以前写过的函数,按本课的方法给它写一份工具说明书,让模型来调用它。

自测

1. 一个好的工具说明应该回答哪几个问题?

它能做什么;什么时候该用它;什么时候不该用它(尤其是和别的工具容易混淆时)。可以再写上它和其他工具怎么配合,比如"通常在 search_docs 之后使用"。

2. 为什么工具的返回值要尽量短?

返回值会原样放进消息列表,智能体后面的每一步都要带着它调用模型,都要为它付费。返回值太长还会让模型抓不住重点,甚至撑满上下文窗口。只返回有用的字段,并设置长度上限。

3. 工具的报错信息写成什么样最好?

写给模型看,说清楚出了什么错、错在哪里、下一步该怎么做。比如"没有这个文件 X,请先用 list_docs 查看有哪些文件"。这样模型能自己改正,而不是反复犯同样的错误。

提问与讨论

这一课没看懂的地方,在这里问。看到别人的问题,也欢迎你来回答。

提问 +3 积分,回答别人 +6 积分。内容经审核后公开。

正在加载讨论…