什么样的工具定义会让模型乱调,一句话:模型乱调工具,多半不是模型分不清,而是你的定义写得像一段坏代码——名字糊、描述空、参数乱,模型捉摸不准。
前言
「模型调错工具」不能只归咎于「模型能力不行」——「这个模型不会用我的工具」,可能换更强的模型、调温度、加系统提示词……折腾一圈,问题还在。其实大多数「乱调」的根子,在你写的工具定义上。因为模型看不到你的代码,它唯一能看到的,是你塞进上下文的那段 JSON——名字、描述、参数。这段 JSON 就是模型手里的「API 文档」;文档写得烂,再聪明的模型也只能瞎猜。 而工具定义的「烂」,是有迹可循的,下面按四个维度一个个说。
一、名字:模型检索工具的第一信号
名字是模型「要不要用这个工具、这是干嘛的」的第一判断。它不好,模型根本不会点进来,或者点错了对象。
// 坏味道
{ "name": "search", "description": "..." } // 太泛,搜什么?文件?网页?
{ "name": "get_file" }, { "name": "read_file" } // 两个名字几乎一样,模型分不清
{ "name": "process", "description": "..." } // 动作不明确,process 是什么动作?
// 好一点:动词开头 + 明确宾语,一眼看出「做什么 + 动什么」
{ "name": "read_file" }
{ "name": "web_search" }
{ "name": "delete_file" }
取舍点:一个通用的起名规则是「动词 + 宾语」,而且动词要具体——read_file 比 get_file 清楚,delete_file 比 process_file 清楚。两个工具名字太像,是重灾区:模型会「觉得差不多」而乱选。名字不是给人类看的简称,是给模型看的检索关键词。
二、描述:模型决定「用不用、何时用」的依据
如果说名字是「点不点进来」,描述就是「点进来之后,该不该真的用、什么场景用」。描述写不好,工具要么被滥用、要么被闲置。
// 坏味道
{ "name": "search_files", "description": "search files" } // 等于没说
{ "name": "send_email", "description": "当用户要求时发送邮件" } // 废话:模型当然知道「用户要求时」
{ "name": "delete_file", "description": "删除文件" } // 没写副作用
// 好一点:说清「做什么 + 什么时候用 + 有什么后果」
{
"name": "delete_file",
"description": "永久删除指定路径的文件,不可恢复。仅在用户明确要求删除时才调用,不要因为内容冗余就主动删除。"
}
取舍点:一条好的描述,至少回答三件事——做什么、什么时候该用、用了有什么后果(副作用)。尤其是副作用:delete_file、send_email、run_command 这种「不可逆 / 花钱 / 对外发送」的工具,不写明后果,模型会轻率地调用。描述的价值不在「解释工具」,而在「约束模型什么时候不该用」。
三、参数:让模型「填得对」
工具定义的另一半,是参数 schema。模型要往里填值,schema 含糊,填出来的值就乱。
// 坏味道
{ "properties": { "p": { "type": "string" } } } // 参数名是缩写,p 是什么?
{ "properties": { "path": { "type": "string" } } } // 没标必填/可选
{ "properties": { "mode": { "type": "string" } } } // 枚举不列全,模型会编一个
// 好一点:每个参数都有名字、类型、说明、是否必填、枚举/默认值/示例
{
"name": "write_file",
"parameters": {
"type": "object",
"required": ["path", "content"],
"properties": {
"path": { "type": "string", "description": "要写入的文件路径" },
"content": { "type": "string", "description": "要写入的完整内容" },
"mode": {
"type": "string",
"enum": ["overwrite", "append"],
"default": "overwrite",
"description": "覆盖写入还是追加写入"
}
}
}
}
取舍点:三个最容易被忽略的点——必填要显式标 required(否则模型不确定哪些能省);枚举要给全 enum(否则模型会自己编一个不在列表里的值);给默认值或示例(能显著降低模型乱填的概率)。参数的描述不是装饰,是模型「填值的依据」。
四、粒度:一个工具只干一件事
最后一个坏味道,是「粒度」——工具切得太粗或太细,都会让模型困惑。
// 太粗:一个工具干太多,行为取决于参数组合,模型很难预测它到底会做什么
{ "name": "do_file_operation", "description": "读写删改文件都靠它" }
// 太细:三个工具其实是同一件事,模型不知道选哪个
{ "name": "read_file" }, { "name": "open_file" }, { "name": "load_file" }
取舍点:好工具的粒度,跟好函数一样——一个工具一个明确动作。太粗,模型得先推理「这一堆参数怎么组合出我想要的动作」,容易组合错;太细,模型在几个等价工具之间反复横跳。判断标准:一个工具能不能用一句话说清「它只做一件事」。说不清,就说明该拆或该并。
五、原点:工具定义,就是「写给模型的 API 文档」
把四节收拢成一个判断:
模型不看你写的代码,只看你塞进上下文的那段 JSON 定义。所以「设计工具」本质上是「给模型写 API 文档」——名字是标题、描述是正文、参数是字段说明。
这也是为什么工具设计的坏味道,和代码的坏味道几乎一一对应:名字含糊(像坏变量名)、描述空(像没注释)、参数乱(像没类型)、粒度烂(像上帝函数)。区别只在于:代码的坏味道,你迟早会重构;工具定义的坏味道,你却常常以为「是模型不行」,结果一直怪错了对象。
所以判断一个工具的「定义」写得好不好,就一条:把一个不认识这个工具的人(或者模型)叫来,光看这段 JSON,能不能说出「它做什么、什么时候用、参数怎么填」。能,就合格;不能,就是坏味道。
结语
坏味道就四样——名字糊、描述空、参数乱、粒度烂。对应的药方也就四条:名字「动词 + 宾语」、描述「做什么 + 何时用 + 副作用」、参数「类型 + 必填 + 枚举 + 示例」、粒度「一个工具一件事」。
工具定义是模型手里唯一的「API 文档」,你把文档写清楚了,模型才可能「调得对」。而这件事,恰恰是 Agent 工程里最便宜、也最被低估的一环。
参考: