有效的工具描述
你在Agent 循环》中写第一篇工具描述时,用了两个部分:WHEN TO USE和WHEN NOT TO USE。这足以让路由用三个工具运行起来。
再加一个工具。再加五分之一。添加子 Agent、编辑、写作和待办事项。模型又开始模糊了。它会为read应该句柄的内容挑选bash,或者跳过grep,进入一个探索性的read循环,打开二十个文件。
修复方法和之前一样。我们只是需要更多。
学习成果
你Harness中的每个工具都有五个部分的描述契约(WHEN到USE,WHEN NOT到USE,DO NOT USE FOR,USAGE,EXAMPLES),模型会正确地穿越模糊提示词。
快速路径
- 将每个工具的描述扩展为5个部分
- 添加USAGE用于参数指导,添加EXAMPLES用于具体调用
- 保留双重负片(WHEN NOT TO USE并做 NOT USE FOR),因为没有它,每个模型都会漏回去
bash
动手练习 2.1
在index.ts重构每个工具的描述,以充分利用完整契约。
要求:
- 每个描述都以一行简要的工具功能和输出格式的总结开头
- WHEN USE 列出 2-4 个具体场景,使用关键词,模型将在提示词中看到
- WHEN NOT TO USE 按名称重定向到其他工具
- DO NOT USE FOR会重复负面引导作为硬边界
- USAGE解释了参数约束和默认值
- EXAMPLES显示了2-3个具体调用,输入
实现提示:
- WHEN NOT TO USE 是软的(“prefer X”)。DO NOT USE FOR很难(“永远不要用这个做Y”)。你想要两者都想要
- 当描述薄弱时,模型默认会对所有内容
bash。重复的负方是反力 - USAGE在参数具有模型无法从schema推断的约束(如大写、默认值、编码)时才值得使用。
为什么要用双重负片
你可能会把WHEN NOT TO USE和 DO NOT USE FOR并排对比,觉得它们说的是重复的。他们确实如此。这正是重点。
在我们的测试中:
- Haiku读WHEN NOT是“TO USE”,但在歧义下忽略了它
- Sonnet 会遵守 WHEN NOT TO USE,但加入 DO NOT USE FOR 的强化约束后效果更好
- Opus 句柄都很好,重复也无害
我们测试过的每个模型都倾向于描述薄弱时bash。我们调用这种撞击引力,那种普遍的吸引力,指向最通用的工具。说一次“不要用这个搜索”并不总是足够。说两遍几乎总是有问题。
完整契约
以下是每个部分在 grep 上的应用:
const grep = tool({
description: `Search file contents using regex. Returns matching lines with file paths.
WHEN TO USE: finding patterns across multiple files, locating function definitions,
searching for imports, finding TODOs or error messages.
WHEN NOT TO USE: reading a known file (use read instead).
Running commands (use bash instead).
DO NOT USE FOR: reading files (use read), listing directories (use bash),
modifying files (use edit).
USAGE: pattern is a regex string. glob filters by file extension.
Results are capped at 50 matches.
EXAMPLES:
- Find all TODO comments: pattern "TODO" glob "*.ts"
- Find function definitions: pattern "function \\w+" glob "*.ts"
- Find imports of a package: pattern "from 'express'" glob "*.ts"`,
// ... inputSchema and execute unchanged
});以下是read的同样处理:
const read = tool({
description: `Read a file from the project. Returns numbered lines.
WHEN TO USE: viewing file contents, checking configurations, reading source code,
examining specific lines with offset/limit.
WHEN NOT TO USE: searching for patterns across files (use grep instead).
Running commands (use bash instead).
DO NOT USE FOR: searching code (use grep), executing commands (use bash),
modifying files (use edit or write).
USAGE: path is relative to working directory. offset and limit are optional.
Output is capped at 500 lines.`,
// ... rest unchanged
});bash:
const bash = tool({
description: `Execute a shell command in the working directory.
WHEN TO USE: running build commands, installing packages, running tests,
git operations, directory listings.
WHEN NOT TO USE: reading file contents (use read instead).
Searching for patterns (use grep instead).
DO NOT USE FOR: reading files (use read), searching code (use grep).
USAGE: command is a single shell string. Commands not in the safe-prefix
allowlist are blocked and return a clear error message.
EXAMPLES:
- List files: command "ls -la"
- Check git status: command "git status"
- Run a test suite: command "npm test"`,
// ... rest unchanged
});为什么每个部分都值得被认可
| 部分 | 作用 |
|---|---|
| 首行 | 工具的功能,以及它返回的 |
| WHEN到USE | 提示词会用到的具体场景和关键词 |
| WHEN NOT到USE | 柔性引导到正确工具 |
| 一定要NOT USE FOR | 再次强调的硬边界 |
| USAGE | schema无法捕捉的约束(大写、默认值、编码) |
| EXAMPLES | 模型模式匹配的具体调用 |
描述会越来越长。没关系。工具描述存放在系统提示词中,SDK在回合间缓存。你已经付了一次token。
动手试试
每个工具形状运行一个提示词,并验证路由:
bun run index.ts . "Find all TODO comments in this project"
bun run index.ts . "Read the package.json"
bun run index.ts . "List all files in this directory"你应该看看:
- TODO 提示词调用
grep - package.json 提示词调用
read - 列表文件
ls提示词调用bash
npx tsc --noEmit**注意:每个形状用一个提示词来验证**
选一个提示词能恰好指向一个工具。搜索形状表示grep,文件形状表示read,壳形状表示bash。混合提示词(比如“显示我 package.json 内容”)有时会用 cat 路由到 read 或 bash,这不是路由 bug,而是模糊的提示词。
提交
git add index.ts
git commit -m "feat(tools): expand descriptions to full 5-section contract"完成标准
- [ ] 这三种工具都有包含全部5个部分的描述
- [ ] TODO搜索提示词
grep - [ ] 文件读取后提示词路由到
read - [ ] 壳牌列表提示词
bash的路由 - [ ]
npx tsc --noEmit
**注意:找到最薄弱的环节**
选你写的描述开始脱衣。放开EXAMPLES。NOT USE FOR掉落DO。放下USAGE。每试一次后,进行三次测试提示词。路由在什么时候会中断?它最先断在哪里?第一个被翻转的模型就是你找到的那个地板。
参考实现
const read = tool({
description: `Read a file from the project. Returns numbered lines.
WHEN TO USE: viewing file contents, checking configurations, reading source code,
examining specific lines with offset/limit.
WHEN NOT TO USE: searching for patterns across files (use grep instead).
Running commands (use bash instead).
DO NOT USE FOR: searching code (use grep), executing commands (use bash),
modifying files (use edit or write).
USAGE: path is relative to working directory. offset and limit are optional.
Output is capped at 500 lines.`,
// ... inputSchema and execute from Module 1
});
const grep = tool({
description: `Search file contents using regex. Returns matching lines with file paths.
WHEN TO USE: finding patterns across multiple files, locating function definitions,
searching for imports, finding TODOs or error messages.
WHEN NOT TO USE: reading a known file (use read instead).
Running commands (use bash instead).
DO NOT USE FOR: reading files (use read), listing directories (use bash),
modifying files (use edit).
USAGE: pattern is a regex string. glob filters by file extension.
Results are capped at 50 matches.
EXAMPLES:
- Find all TODO comments: pattern "TODO" glob "*.ts"
- Find function definitions: pattern "function \\w+" glob "*.ts"
- Find imports of a package: pattern "from 'express'" glob "*.ts"`,
// ... inputSchema and execute from Module 1
});
const bash = tool({
description: `Execute a shell command in the working directory.
WHEN TO USE: running build commands, installing packages, running tests,
git operations, directory listings.
WHEN NOT TO USE: reading file contents (use read instead).
Searching for patterns (use grep instead).
DO NOT USE FOR: reading files (use read), searching code (use grep).
USAGE: command is a single shell string. Commands not in the safe-prefix
allowlist are blocked and return a clear error message.
EXAMPLES:
- List files: command "ls -la"
- Check git status: command "git status"
- Run a test suite: command "npm test"`,
// ... inputSchema and execute from Module 1
});