工具设计的坏味道:什么样的定义会让模型乱调

0 阅读5分钟

什么样的工具定义会让模型乱调,一句话:模型乱调工具,多半不是模型分不清,而是你的定义写得像一段坏代码——名字糊、描述空、参数乱,模型捉摸不准。

前言

「模型调错工具」不能只归咎于「模型能力不行」——「这个模型不会用我的工具」,可能换更强的模型、调温度、加系统提示词……折腾一圈,问题还在。其实大多数「乱调」的根子,在你写的工具定义上。因为模型看不到你的代码,它唯一能看到的,是你塞进上下文的那段 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_fileget_file 清楚,delete_fileprocess_file 清楚。两个工具名字太像,是重灾区:模型会「觉得差不多」而乱选。名字不是给人类看的简称,是给模型看的检索关键词。

二、描述:模型决定「用不用、何时用」的依据

如果说名字是「点不点进来」,描述就是「点进来之后,该不该真的用、什么场景用」。描述写不好,工具要么被滥用、要么被闲置。

// 坏味道
{ "name": "search_files", "description": "search files" }        // 等于没说
{ "name": "send_email", "description": "当用户要求时发送邮件" }  // 废话:模型当然知道「用户要求时」
{ "name": "delete_file", "description": "删除文件" }             // 没写副作用
// 好一点:说清「做什么 + 什么时候用 + 有什么后果」
{
  "name": "delete_file",
  "description": "永久删除指定路径的文件,不可恢复。仅在用户明确要求删除时才调用,不要因为内容冗余就主动删除。"
}

取舍点:一条好的描述,至少回答三件事——做什么、什么时候该用、用了有什么后果(副作用)。尤其是副作用:delete_filesend_emailrun_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 工程里最便宜、也最被低估的一环。


参考: