Claude 之工具
先分清三个概念
先说一件我自己遇到的事:在输入框敲 /webfetch www.baidu.com,回车,系统提示没有这个命令。WebFetch 明明存在,但它不是命令,是工具——命令是敲给 Claude Code 这个程序看的,由程序在本地处理;工具是模型在干活过程中自己调用的。两份清单互不相通。这件小事正好暴露了几个容易混淆的概念:提示词(prompt)、工具(tool)、技能(skill),以及和它们形似却不同的命令(command)。
用 /context 的实际输出给它们划清边界(本会话数据,glm-5.3[1m],1M 窗口):
| 概念 | 是什么 | 谁发起 | 占用 |
|---|---|---|---|
| 系统提示词 | 固定的行为规范:角色、纪律、环境 | 框架拼装,会话全程不变 | 2.1k |
| 工具 | 用 JSON Schema 定义的函数 | 模型调用,先过权限 | 常驻 15 个共 7.2k;延迟 14 个只挂名字,不占 |
| 技能 | 某类任务的完整指令包 | 用户敲 / 或模型自行判断 |
13 个共 2k,只挂名字和一行触发条件 |
| (命令) | 框架本地处理的用户指令 | 用户敲 / |
不经过模型,不占上下文 |
提示词从会话一开始就固定加载,内容不变:模型是什么(编程代理)、遵守什么(安全规则、汇报纪律)、环境如何(工作目录、操作系统)。它不增加能力,只约束行为。
工具是模型可以调用的函数,每个都有名字、参数和类型约束,另外配一篇很长的说明。这篇说明是写给模型看的——普通软件的 API 文档写给人看,这里的 API 文档写给模型看,同时承担三件事:说明用法、规定使用时机、提醒已知的坑,一份动辄上千 token。工具分两档:Bash、Read、Edit 这类高频工具的 Schema 一直加载着;Monitor、WebFetch 这类低频工具只挂一个名字,模型需要时先用 ToolSearch 把 Schema 拉进来才能调用。这笔账在 /context 里能看到:延迟工具的 4.9k 列在表里,但不计入已用的 12.4k——因为还没加载;拉取之后才变成实际占用。开销跟着使用频率走。
技能解决”怎么做”的问题。/code-review、/dataviz 这些,平时只保留名字和一行触发描述,调用时才把完整指令注入对话。技能不给模型新能力——能用的工具还是那些——只提供做某类任务的方法和步骤。
命令在用户这一侧,框架自己处理,比如 /clear、/context,完全不经过模型。技能是两边之间的桥梁:用户敲 /code-review,框架把技能的指令注入对话,再由模型照着执行。所以”没有 /webfetch”很正常:WebFetch 登记在模型的工具表里,从来没有登记在用户的命令表里。
概念清楚了,下面逐个分析五个工具:AskUserQuestion 用来向用户提问,Bash 用来执行命令,Grep 用来搜索,Monitor 用来监视后台任务,WebFetch 用来抓网页。引文全部逐字来自导出的工具说明原文。
AskUserQuestion:把提问变成选择题
模型遇到拿不准的岔路可以问用户,但直接问是开放式问题,用户要打一段字回答。AskUserQuestion 把提问变成选择题:一次最多 4 个问题,每题 2-4 个选项,系统自动附赠一个 “Other” 自由输入,支持多选,支持预览。
它的说明第一句就划了门槛:”Use this tool only when you are blocked on a decision that is genuinely the user’s to make”——只在卡住、而且这个决定必须由用户来做的时候才用。什么算必须由用户做?后面给了标准:答案会改变下一步行动的才问;有惯例默认值的、或者模型自己能在代码里查到的事实,选一个合理的、说一声、继续做。打断用户是有成本的,要划算——这条思路来自 1999 年 Horvitz 关于混合主动权界面的研究。
三个细节值得注意:
- 问题要能独立读懂。”The user may see only the question dialog”——弹窗打开时,本轮更早的输出可能已经不在屏幕上。所以问题本身要带上下文:依赖什么就简述什么,问哪个文件就写明文件名。这是一条界面约束,但写在了给模型看的行为规范里。
- 预览(preview)。比较界面草图、代码片段、配置写法时,每个选项可以带一块等宽显示的内容,界面变成左右对照——用户直接看到差别,而不是听模型描述差别。只有单选题支持。
- 工具之间有分工。计划模式下想问”这个计划行不行”?说明里明确禁止——这件事归 ExitPlanMode 管。工具之间的边界像函数签名一样不重叠。
还有一条惯例:模型倾向哪个选项,就把它排在第一位并标注 “(Recommended)”——弹窗里的第一个选项常常就是模型的倾向。
Bash:规矩最多的工具
Bash 能执行任意命令,是能力最强、也最危险的工具,所以它的说明最长、限制最密。整篇说明大致五个部分:
环境声明。 它跑的是 Git Bash(POSIX sh),不是 PowerShell,所以先规定语法:/dev/null 不是 NUL,$VAR 不是 %VAR%。跨平台的通用性需要用条款来换。
劝退清单。 说明明确要求不要用 Bash 跑 find、grep、cat、head、tail、sed、awk、echo——不是这些命令不好,而是存在只读的专用工具(Grep、Glob、Read)。专用工具是只读的,可以安全地设置成免询问;Bash 什么都能干,每次都得过权限。结果是最通用的工具反而被自己的说明劝退——权限制度反过来影响了工具的使用方式。
输出归属。 “Command output is displayed to you, not reliably to the user”——命令输出给模型看,用户不一定看得见。所以结论要转述,不能默认用户已经看到。
后台任务。 后台任务退出时会收到通知,说明特意加了句”不要轮询输出文件”;前台 sleep 被禁止,等待条件应该用 Monitor。整个体系都在坚持同一个原则:等通知,不要反复查看。
Git 部分的事故记录。 PR 里的 @名字 会触发真实通知,所以示例里的占位符(@reviewer、@types/node)一律加反引号或去掉 @,防止从项目文件里抄来的写法误伤真人。这类条款没有理论出处,只有事故出处。
最值得注意的是它的必填参数 description:一句给用户看的、主动语态的命令说明,还禁用 “complex”、”risk” 这类词。命令为什么还需要一段描述?因为权限弹窗里用户看到的就是它。这不是给模型的备注,是给用户做决定用的说明——用户看懂了才知道该不该批准;看不懂的话,要么一律拒绝,要么一律放行,两种结果都偏离了权限制度本来的目的。安全设计里有个原则叫”心理可接受性”:安全机制必须好理解,否则用户会想办法绕过它,落点就在这个字段上。
参数里还有一个不加掩饰的名字:dangerouslyDisableSandbox——危险操作连修饰都不做,直接把 dangerous 写在名字里。
Grep:重点是控制拿回多少
Grep 表面上是 ripgrep 的包装,但看参数表会发现,它的设计重心不在搜索本身,而在控制输出——每个参数都在防止搜索结果把上下文撑爆:
| 设计 | 参数 | 意图 |
|---|---|---|
| 三档输出 | output_mode:content / files_with_matches / count,默认只列文件名 |
最常见的问题是”哪些文件里有”,不是”每行内容是什么” |
| 默认限额 | head_limit 默认 250 条 |
一次搜索返回上万行等于把上下文撑爆;原文写明 “0 = 不限,慎用——大结果集浪费上下文” |
| 分页 | offset 跳过前 N 条 |
结果太多就分批取 |
| 只取命中部分 | -o 只输出匹配到的文本 |
行里其余部分多半不需要 |
一个搜索工具,四个参数全在省 token。背景是 Lost in the Middle(Liu et al., 2023)的发现:长上下文里位于中间部分的内容,模型的检索表现明显差于首尾,呈 U 型曲线。既然窗口里的注意力是稀缺的,每次取回就应该尽量少。这也解释了 Bash 里那条”别跑 grep 命令,用 Grep 工具”的劝退:专用工具只读、输出可控、可以免询问,值得单独做一个。
还有一个细节:说明特意提醒这是 ripgrep 的语法、不是 grep 的——字面量的花括号要转义(interface\{\})。同一个名字,两套工具,语法有差别,得先纠正过来。
Monitor:后台监视和它的三条教训
Monitor 的说明是全部工具里最长的一份,内容密度也最高,读下来接近一份运维值班手册。它是延迟工具,要用时才加载——说明书太重,常驻不划算。
基本机制:脚本在后台运行,stdout 每一行是一个事件、对应一条通知;脚本退出,监视结束;每个监视器都有时限(默认 5 分钟,最长 30 分钟),到期可以重新布置。200 毫秒内的多行输出合并成一条通知。
使用前先做选择,说明给了一个三分法:
- 只要一次通知(”构建完成时告诉我”)→ 不用 Monitor,用 Bash 后台任务加 until 循环,条件满足自动退出;
- 每次发生都要通知、直到监视过期(”每出现一行 ERROR 就叫我”)→ Monitor 配不会退出的命令;
- 每次都要、到某个终点为止(”逐个报告 CI 步骤,跑完结束”)→ Monitor 配会退出的命令。
选错的后果也写了:拿 tail -f 配”只要一次通知”,事件发生之后监视器不会停,会一直挂到超时。
真正有价值的是三条事故教训:
第一,SIGPIPE 陷阱。 tail -f log | grep -m 1 "Ready" 看起来命中一条就会停,实际上如果日志随后安静下来,tail 永远收不到 SIGPIPE 信号,整条管道就挂在那里——看似可用的命令,实际是死的。
第二,行缓冲。 管道的每一级都要逐行刷新:grep 要加 --line-buffered,awk 要调 fflush(),head 根本不支持刷新。过滤器不刷新,事件就一直憋在缓冲区里——监视器什么都没报,不是没出事,是没看见。
第三,沉默不等于成功。 “silence is not success”——过滤器必须覆盖所有可能的结束状态,不能只盯着成功路径。只匹配成功标记的监视器,在崩溃循环、进程挂死、意外退出时全程安静,而安静和正常运行看起来一模一样。原文给了对照:
1 | |
布置前的自检只有一句话:”if this process crashed right now, would my filter emit anything?”——如果进程现在崩了,过滤器会有输出吗?不会,就把条件放宽。
另外还有一笔经济账:stdout 每行都是一条会话消息,事件本身也消耗 token,所以过滤器要挑”你会据它行动的行”——挑得准指的是把该行动的挑出来,不是只报好消息。事件太频繁的监视器会被自动停掉。
WebFetch:让小模型代读网页
WebFetch 的说明最短,机制却最值得拆开看。流程:抓取 URL → 转成 Markdown → 一个小而快的模型根据你的 prompt 阅读内容并作答 → 主模型只看到答案,看不到原文。
这是”按任务难度分配模型”的一个缩影:网页动辄几万 token,整页放进主模型的上下文非常浪费;让便宜的小模型读页面、只返回答案,主模型的窗口基本不受影响。代价有两个:答案质量取决于 prompt 写得是否具体;小模型可能漏掉你没有问到的内容。
边界条件都是明确的工程判断,每条都有原因:
- 需要登录的页面直接失败——不要让工具碰运气,改用带凭证的 MCP 工具;
- localhost 和不带点号的主机名失败——本地服务器用 Bash 里的 curl;
- HTTP 自动升级成 HTTPS;
- 跨主机重定向不自动跟随,而是把重定向地址返回给模型、要求再调一次——防止一条重定向链把请求带到意想不到的地方,每次跳转都要确认;
- 同一个 URL 的结果缓存 15 分钟。
WebFetch 和 WebSearch 各管一头:WebFetch 抓一个页面,WebSearch 搜一批结果(支持域名黑白名单,要求答案附来源清单)。WebFetch 常见的失败场景也很实际:靠 JavaScript 渲染的页面、登录墙、反爬。这些时候的替代路线是 Bash 里的 curl,或者专门的抓取 MCP。
收尾
五个工具,五种典型设计:AskUserQuestion 管提问——把打断收敛成选择题,只在真正的岔路口用;Bash 管执行——能力最强所以限制最多,连给用户看的说明都是必填项;Grep 管搜索——难点不在找到,而在少拿;Monitor 管等待——通知系统最危险的故障是毫无声音;WebFetch 管外部世界——用小模型挡在主模型和网页之间。
它们共同应对三个前提:模型会犯错(所以要权限、要审查);上下文有限(所以要按需加载、要限额分页);外部环境不可全信(所以要确认重定向、要防路径劫持)。所谓 agent 框架,不只是换一个更聪明的模型,而是围绕这三个前提搭起来的一整套工程。提示词、工具、技能的分层,本质是把行为规范、能力和方法分开管理——《Claude 之用法》里那些指令的奇怪设计,到这里就都能对上了。
评论