Agent Tool 设计指南:模型用不好工具,往往是你定义错了
一句话结论:给 Agent 加工具很容易,让 Agent 正确使用工具很难。绝大多数"模型不会用工具"的抱怨,根因不在模型,而在工具本身——名字含糊、描述空洞、参数有歧义、返回值缺信息、错误不可理解。工具是模型与真实世界之间唯一的接口,接口定义得差,再强的模型也白搭。
引言:工具为什么会被误用
一个典型的失败现场:你给 Agent 注册了 search 和 query_documents 两个工具,期望它用前者查网络、后者查内部库。结果它要么两个混着用,要么在明确该查内部库时偏偏去搜网络。
大多数人的第一反应是"模型太笨",然后开始写更长的提示词去纠正。但真正的问题通常出在工具的定义上:两个工具的描述都写着"用于搜索信息",模型凭什么区分?
工具调用是模型能力最直接的放大器,也是最容易被忽视的工程环节。本文是该系列第五篇,前面讲了 Skill 封装、上下文架构、项目约定、评测,这一篇讲:手里这些能被模型调用的函数,到底该怎么定义。
一、先分清 Tool 和 Skill
这两个概念经常被混为一谈,混淆会直接导致设计错位。
| 维度 | Tool(工具) | Skill(技能) |
|---|---|---|
| 本质 | 一个可调用的函数/接口 | 一套完成任务的指令与流程 |
| 形态 | 结构化参数 + 返回值 | 自然语言说明 + 可选脚本 |
| 谁执行 | 平台执行,模型只决定"调不调" | 模型按指令自己执行 |
| 粒度 | 原子,做一件事 | 可能是多步 |
一句话区分:Tool 是"手",Skill 是"操作手册"。 Skill 告诉模型"完成某类任务要分几步、注意什么",Tool 提供具体的动作能力。一个 Skill 经常需要调用多个 Tool;而一个 Tool 也可能被多个 Skill 复用。
设计纪律:能力下沉到 Tool 层,流程留在 Skill 层。 把"怎么调接口、传什么参数"的细节写进 Skill 的指令里,是耦合过重的信号——一旦接口变了,整个 Skill 都要改。
二、什么该做成 Tool,什么不该
不是所有能力都适合暴露成 Tool。判断标准是原子性 + 可判定:
适合做成 Tool:
- 有明确输入输出的原子操作(检索、计算、读写文件、发请求)。
- 需要访问模型外部状态的操作(数据库、网络、文件系统)。
- 结果可结构化返回、可验证的操作。
不适合做成 Tool:
- 纯推理类任务("分析这段代码")——模型自己能做,包成工具反而绕远。
- 需要多步交互、带流程的过程——应该做成 Skill,内部再调 Tool。
- 粒度极粗的"万能工具"(如
do_everything(action=...))——参数立刻变成迷宫。
一个反直觉但重要的判断:如果这个能力去掉 Tool 模型也能完成,那就别加。 工具越多,模型的选择负担越重,误选概率越高。
三、工具定义的三要素
一个工具的定义由三块组成,三块都要精心设计,缺一块都会出问题。
1. name——用动词+宾语,别用抽象名词
- 差:
search、process、handler、util - 好:
search_web、read_file、send_email、query_station_by_country
名字是模型做选择时最先看到的信息。抽象名词无法传达"这个工具到底干什么",名字本身就该携带语义。
2. description——这是给模型看的说明书,不是备注
description 是模型决定"什么时候该用这个工具"的唯一依据,它的重要性远超大多数人的认知。
好的 description 必须同时包含三段:
- 做什么:一句话说清功能。
- 何时用:触发条件的正例("当需要查询最新的网络信息时")。
- 何时不用:反例与边界("不适用于查询内部知识库,那应该用 query_documents")。
只写正例、不写反例,是导致工具误用的头号原因。给模型划清"这不是给你的",和告诉它"这是给你的"一样重要。
3. parameters——用 schema 约束,别用自然语言描述
参数定义要用严格的 JSON Schema:
- 类型明确(string/number/boolean/array)。
- 必填与选填标注清楚。
- 有限取值用
enum(如order只能是votes/name/random),别让模型自由发挥。 - 每个参数都要有 description,说明格式与取值范围。
模糊的参数是 bug 的温床。"limit 传多少合适?""时间格式是 ISO 还是时间戳?"——这些必须由 schema 回答,而不是让模型猜。
四、参数设计的细节
- 默认值放在服务端,不放在描述里。 与其让模型"记得不传时用 60",不如工具内部处理默认值,减少模型出错面。
- 避免布尔陷阱:
include_archived=true/false这种参数容易被模型忽略。能用两个独立动作表达就别用布尔开关。 - 拒绝"参数大杂烩":一个工具带 8 个可选参数,模型很难全用对。拆成几个语义清晰的小工具,通常比一个大工具更稳。
- 参数名要自解释:
q不如keyword,n不如limit,d不如date。模型对缩写的理解远不如人类。
五、返回值设计:模型靠它做判断
很多人精心设计了参数,却对返回值随意——返回一大坨原始 JSON,或者干脆返回一句话。这会毁掉后续推理,因为模型要靠返回值决定下一步。
好的返回值应满足:
- 结构化:字段命名清晰,便于模型解析。
- 信息充分但不过载:既要有模型判断所需的关键字段,又不要把无关的原始数据全倾倒进去。比如返回搜索结果时,附上
total与has_more,模型才知道要不要翻页。 - 错误可理解:失败时给出模型能据此行动的信息,而不是堆栈或错误码。
"未找到匹配记录,请尝试放宽筛选条件"远好于{"error": -1}。 - 可追溯:涉及外部数据时附来源与时间,帮助模型判断信息是否可信、是否过时。
一条原则:返回值的编写视角,是"模型下一步要拿它做什么",而不是"方便前端展示"。
六、工具的数量与粒度
- 少了不够用,多了选择困难。 研究表明工具数量超过十几个后,模型选错、选漏的概率明显上升。宁精勿多。
- 语义要互斥。 两个工具如果功能重叠,模型必然混用。要么合并,要么用 description 把边界划到清楚可判定。
- 按场景分组加载。 如果工具集很大,考虑按当前任务动态暴露相关子集(比如"检索类任务只挂载检索工具"),降低干扰。这与上下文工程里的"按需加载"是同一个思路。
七、安全与幂等:工具是"真实能力"
Tool 一旦被执行,就是真实副作用。必须考虑:
- 危险操作要确认:删除、转账、发邮件、修改线上配置,要么要求明确确认,要么设计成两步(先预览、再执行)。
- 幂等性:网络重试场景下,非幂等工具(如"发消息")可能被执行两次。要么工具内部保证幂等,要么用幂等键去重。
- 权限最小化:每个工具只给完成任务所需的最小权限。一个只读工具就别带写权限。
- 输入校验:永远不要相信"模型传的一定合法"。参数进入副作用前,服务端必须校验,别把模型当可信用户。
八、常见误区
- description 写得太笼统:
"用于获取信息"——模型根本无法判断该不该用。 - 只写正例不写反例:导致最典型的多工具误选。
- 参数无 schema 约束:自由文本参数让模型输出千奇百怪,后处理苦不堪言。
- 返回值信息不足或过载:前者让模型"猜",后者污染上下文。
- 工具粒度过粗或过细:万能工具参数迷宫,原子碎片工具调用链过长。
- 错误信息对模型无意义:
"500"或异常堆栈,模型无法据此重试或改参。 - 忽视幂等与权限:重试导致重复副作用,或权限过大引发安全事故。
- 用提示词硬压工具误用:治标不治本,根因在工具定义,应该改定义而不是加提示词。
总结:可照抄的检查单
新增或修订一个 Tool 前,逐条对照:
- [ ] 名字用"动词+宾语",语义自解释
- [ ] description 含"做什么 + 何时用 + 何时不用(反例)"
- [ ] 参数用严格 schema,有限取值用 enum,每个参数有描述
- [ ] 默认值放服务端,不靠模型记忆
- [ ] 返回值结构化、信息充分、错误可理解、可追溯
- [ ] 工具语义互斥,数量精简,避免重叠
- [ ] 工具粒度原子;流程性逻辑放 Skill,不塞进单个工具
- [ ] 危险操作有确认机制,关键工具幂等
- [ ] 权限最小化;输入在服务端校验
- [ ] 工具集过大时按场景动态加载
- 点赞
- 收藏
- 关注作者
评论(0)