Skip to content

🐣 小a的困惑

上一章,小a学会了 function calling——模型能"下单"了。但它马上面临新问题:到底该怎么定义工具,模型才会用得又准又好?

老z:"工具是 Agent 的手。手好不好用,直接决定 Agent 能不能干活。这一章,我们专门讲 Tool——怎么设计一把趁手的兵器。"


6.1 Tool 是什么:把能力包装成"菜单"

🧙 Tool(工具)是把 function calling 包装成的、模型可调用的完整能力单元。一个 Tool 包含三样东西:

  • 名字 + 描述:告诉模型"这个工具能干嘛、什么时候该用"
  • 参数 schema:告诉模型"调用它要传什么参数、什么类型"
  • 执行函数(execute):你写的代码,真正干活的那个函数

老z打比方:function calling 是"下单的语法",Tool 是"菜单上的一道菜"——菜有名字、有做法、有食材清单,服务员(模型)按菜单点,厨房(你的 execute 函数)照做。

Tool vs Function Calling

function callingTool
是什么模型吐结构化调用的机制一个具体的、可调用的能力
层次协议层应用层
例子模型输出 {"name":"read_file",...}read_file 这个工具(名字+描述+schema+execute)

🐣 小a:"所以 function calling 是'语法',Tool 是'词汇'?"

🧙 "对! 语法(机制)只有一种,词汇(工具)可以无限多。你的 Agent 能干多少事,取决于你给它配了多少把好工具。"


6.2 一个好 Tool 的灵魂:描述

🧙 Tool 最容易被忽略、却最关键的是描述。 模型完全靠描述来决定"什么时候用这个工具、传什么参数"。

对比两个 read 工具的描述:

一个糟糕的:{ name: "read", description: "读文件" } → 模型不知道:什么时候该读?能读什么?读多大多小的文件?

一个出色的: { name: "read", description: "读取指定路径的文件内容。当你需要查看文件内容、理解现有代码、确认配置时使用。支持文本和图片。", parameters: { path: "文件的绝对或相对路径" } } → 模型清楚知道:用途(什么时候用)、边界(读什么)、参数(传什么)

🧙 写描述的三个要点:

  1. 说清用途:什么时候该用(场景)
  2. 说清边界:什么时候不该用、有什么限制
  3. 说清参数:每个参数是什么类型、什么含义、给个例子

🐣 小a恍然:"所以描述本质是在教模型怎么用这个工具?"

🧙 "对。 描述写得好,模型用得准;写得烂,模型要么不用、要么乱用。这就是为什么 pi 的每个工具都有精心写的描述。"


6.3 参数 schema:工具的"表单"

🧙 参数 schema 定义工具接受什么参数——它是模型的"填表规范"。

一份好 schema 要:

  • 类型准确:path 是 string,count 是 number
  • 必填可选分清:required 数组标出必填项
  • 描述到位:每个字段说明含义和例子
  • 尽量简单:参数越少越好,模型越不容易填错
json
{
  "name": "edit_file",
  "description": "修改文件中的一段文本。用于修改现有文件内容。",
  "parameters": {
    "type": "object",
    "properties": {
      "path": { "type": "string", "description": "要修改的文件路径" },
      "old_text": { "type": "string", "description": "要替换的原文(必须精确匹配文件内容)" },
      "new_text": { "type": "string", "description": "替换后的新文本" }
    },
    "required": ["path", "old_text", "new_text"]
  }
}

🧙 schema 设计的反例:

  • ❌ 参数过多(模型填错概率高)
  • ❌ 类型模糊(全用 string,模型不知道该传啥)
  • ❌ 没有必填约束(模型漏传参数)

工具参数越少、越明确,模型越容易用对。 这是"最小工具原则"。


6.4 工具设计原则

🧙 五条设计原则(老z的兵器谱):

  1. 单一职责:一个工具只干一件事。read 只读、write 只写,别搞"读写一体"。
  2. 描述优先:宁可多花时间写描述,不要省。描述是工具的使用说明。
  3. 幂等性:同一参数调用两次,结果应该一致(不会产生副作用叠加)。
  4. 失败可读:工具出错时,返回的错误信息要能告诉模型"为什么失败、怎么改"。
  5. 输出受控:工具输出别太长(会撑爆上下文),要截断或结构化。

🐣 小a问:"为什么要'失败可读'?"

🧙 "因为模型要靠错误信息自我纠正。 工具返回 文件不存在 比返回 Error: ENOENT 有用得多——模型看到前者就知道'路径写错了,换一个',后者它看不懂。工具的错误输出,是喂给模型的'反馈信号'。"


6.5 从"单工具"到"工具箱"

🧙 单个工具是小菜,工具箱才是正餐。一个编程 Agent 的工具箱通常是组合:

能力工具为什么需要
read查看文件、理解代码
grep / find定位代码、找引用
edit / write修改/创建文件
bash执行测试、装依赖
ls看目录结构

工具之间要有配合。 模型先 grep 找位置,再 read 看内容,再 edit 修改,再 bash 验证——一套组合拳。这就是为什么 Agent 需要"循环"来串起这些工具(下一章)。


6.6 什么时候该做成工具?什么时候不该?

"最后一个问题,也是最重要的判断。"老z说,"不是所有东西都该做成工具。"

🧙 做成工具的三类理由:

  1. 需要实时信息:文件内容、命令输出、网络数据——这些必须"现场拿",模型凭记忆猜不到 → 工具
  2. 需要确定性执行:计算、校验、格式转换——要让程序精确执行,不能靠模型"口算" → 工具
  3. 需要副作用:写文件、发请求、跑命令——动作必须真的发生,而且只能发生一次 → 工具

反过来,纯静态的知识(比如"公司的代码规范是什么")不该做成工具——它不随时间变、不需要执行,塞进 prompt 或打包成 Skill(第 11 章)更划算,又省一轮调用又少一次出错机会。

🐣 小a:"那我是不是工具越多越好?"

🧙 "恰恰相反。工具越多,模型越容易选错。 想象你面前摆着 50 把刀,切菜时你也得犹豫半天。模型也一样——工具太多,它要么选错、要么干脆不敢选。"

🧙 工具箱要克制:

  • 只给够用的工具:完成当前任务需要 5 个,就别挂 50 个
  • 现代做法:按需加载——不是一次把所有工具都塞给模型,而是根据任务只把相关的给它(pi 的 coding-agent 就是按需装配,Part II 第 20 章会看到)
  • 判断标准:多一个工具,是帮模型干成事,还是给它添乱?

工具数量是个工程权衡:太少,模型无工具可用;太多,模型用错工具。 好的工具箱,是"刚好够用"。


本章小结

🐣 小a的兵器课

┌──────── Tool 与工具设计 ────────┐
│                                 │
│  • Tool = 名字+描述+schema+执行 │
│    (function calling 的"词汇") │
│                                 │
│  • 描述是灵魂:用途/边界/参数   │
│    → 写好模型才用得准           │
│                                 │
│  • schema 是表单:类型/必填/简单 │
│    → 参数越少越不容易错         │
│                                 │
│  • 五原则:单一职责/描述优先/    │
│    幂等/失败可读/输出受控        │
│                                 │
│  • 工具箱:读搜改跑列 组合拳     │
└─────────────────────────────────┘

关键认知:工具是 Agent 的手。设计工具的功夫,决定 Agent 干活的水平。 描述、schema、错误信息——这些"细节"才是工具能不能被模型用好的关键。


课后实验

  1. 评价工具描述:找两个真实工具(read/bash)的描述,评价它们是否符合"用途/边界/参数"三要点。
  2. 写一个工具:为你手头的项目写一个 ls 工具,注意描述和 schema 怎么写才算好。
  3. 体验失败可读:故意让工具返回一个模糊错误,再看一个清晰的错误,体会模型"自我纠正"的差异。