1 吐字的脑子,怎么「动手」?
先停在一件你早就习以为常、却越想越不对劲的事上——你在 Claude Code 里按下回车,它真的会操作你本机上的内容:读取 src/ 目录下的文件、执行构建命令、修改代码。可真正完成这些动作的到底是什么?它又是如何“动手”干活的?
答案藏在一个关键词里——工具。说得更直白一点:工具,就是大模型的手和脚。脑子(模型)本身只会生成文字、做推理,碰不到外部世界;真正去读文件、跑命令、改代码的,其实是工具。
2 拆开一只手
你平时使用 Claude Code,看到的通常只是它调用某个工具、再拿回结果;至于工具内部到底长什么样,大多数人并不会专门去看。现在我们就拆开一只“手”——挑一个最简单、也最常用的 Glob:给它一个 pattern(比如 src/**/*.ts),它就会把匹配到的文件全部列出来。
打开 Glob 的源码(src/tools/GlobTool/GlobTool.ts),你会发现它自带的内容其实分成两半:一半是给脑子(模型)看的,另一半才是真正执行任务的部分。
上半部分——工具名称、描述信息、输入参数——都是说给模型听的;下半部分,才是真正落地执行。一共五样,我们一项一项来看。
工具名称(name)。 这只手叫什么——Glob(代码里就是常量 GLOB_TOOL_NAME = 'Glob')。但只有名字还不够:Claude Code 里有几十种工具,各自都有不同名字——Read、Bash、Grep、Glob……模型每一轮都要从中选一只来用,光凭名字根本不够判断。所以每只手还必须带上一段描述,明确说明自己负责什么。
描述说明(description / prompt)。 这部分是专门写给模型看的——告诉它这个工具能做什么、适合在什么场景下使用。Glob 的描述原文就是这几行:
- Fast file pattern matching tool that works with any codebase size- Supports glob patterns like "**/*.js" or "src/**/*.ts"- Returns matching file paths sorted by modification time- Use this tool when you need to find files by name patterns- When you are doing an open ended search that may require multiple rounds of globbing and grepping, use the Agent tool instead
真正需要抓住的,其实是后面这两句:一句是在说明它该在什么场景出场——当需求是按文件名模式搜索文件时,就用它;另一句是在划清边界——如果是开放式搜索,而且很可能要反复进行 glob 和 grep,那就不要继续硬用这个工具,而是直接改用 Agent。每次任务开始执行时,模型都会把所有工具说明从头到尾过一遍,再结合当前步骤的目标,自行判断该调用哪一个。换句话说,用哪只“手”,是模型自己决定的,而不是别人替它选。
关键还在输入参数,也就是 inputSchema。这其实是在定义调用工具时必须提供的“材料”。以 Glob 为例,它最核心的参数是 pattern,相当于给工具下达搜索条件,比如 src/**/*.ts 就指定了要匹配的文件模式。此外,还可以传入 path 来限制搜索范围;如果留空,工具就默认在当前目录下执行。举个更直观的例子,当需要查找 src 目录下的所有 TypeScript 文件时,模型传递的参数结构大致如下:
{ "pattern": "src/**/*.ts" }
到这里还只是把参数准备好,真正的活还没有开始做。
工具调用(call)。 这一步才是真正干活。call 拿着填好的 pattern 去扫描文件系统——调用 glob 引擎,按照 src/**/*.ts 这种模式做文件匹配,把符合条件的文件一个一个找出来。它拿回来的原始结果是一个结构化对象,大致像这样:
{ filenames: ["src/Tool.ts", "src/context.ts", "src/cost-tracker.ts", …] }
前面三样都还只是纸面定义;只有 call 真正执行时,工具才算真的碰到了你的磁盘和文件系统。
结果返回(mapToolResult)。(源码里它叫 mapToolResultToToolResultBlockParam,名字太长,这里简写。)但模型并不会直接接收 call 返回的那种原始对象——它需要的是一份格式规整的“回执”。mapToolResult 做的就是最后这一步:把上一步得到的文件路径一行一个整理好,交回去的结果大概就是这样:
src/Tool.tssrc/context.tssrc/cost-tracker.ts…
模型最终收到的,就是这份整理后的结果。规则很简单——每调用一次工具,就必须返回这样一份回执,缺一个都不行。
五样讲完,一只手的完整样子也就清楚了——说到底,工具本质上就是一个普通对象,并没有什么神秘感:
# 一只手的极简版:Glob 按名字模式找文件Glob = {"name": "Glob",# 叫什么"description": "按名字模式找文件",# 给脑子看的"inputSchema": {"pattern": "src/**/*.ts"},"call": lambda inp: glob_files(inp["pattern"]), # 真干活"mapToolResult": lambda files: "n".join(files),# 结果装回执}
现在我们回到开头那个问题:一个只会吐字的东西,凭什么能真正读取你的文件?答案就在这个对象里——模型只负责做一件事:吐出一句「用 Glob 找 src/**/*.ts」。剩下的,无论是真正扫描文件系统,还是把结果整理好交回来,甚至包括「工具叫什么名字」「应该接收什么参数」,全都已经写在这只手自身的定义里。
3 工具是怎么被调起来的
拆开一只手看过之后,那它在实际运行时到底是怎么被调用起来的?把整个过程绕一圈看下来,其实核心只有四步。
四步拆开看:
- Harness 送清单:把所有工具中那些给模型看的信息收集起来(
name、description、inputSchema),再和你的提问一起发送给模型。也就是说,模型每次回答前,都会先收到一份工具清单:有哪些工具、每个工具负责什么、需要填写哪些参数。它先读完这份清单,才知道这一轮有哪些能力可以调用。 - 模型挑工具:模型读完工具列表后,自己选择一个最合适的工具,吐出一条
tool_use——也就是「我要调用 Glob,参数是src/**/*.ts」。选哪个工具、参数怎么填,都是模型自己判断;但它依然只是在“吐字”,真正去接触硬盘和执行动作的并不是它。 - Harness 执行工具:接到这条
tool_use之后,Harness 代替模型运行工具的call。在真正执行之前,它还会经过几道检查关卡(比如参数是否合法、是否具备权限、是否安全),这一部分下一节会细看。 - Harness 送回结果:
call的执行结果,会经过mapToolResult整理成回执,再塞回对话上下文;模型拿到结果后,继续往下走——如果还需要调用工具,就回到第 2 步;如果任务已经完成,就进入收尾。
一句话概括:模型下指令,Harness 真执行。模型只负责输出 tool_use,真正完成工具调用的是 Harness。至于完整执行链路里 hook、用户审批、流式返回、错误处理这些更细的机制,我们后面再讲;这里先抓住最核心、最基础的工具调用流程。
4 能不能放心让手干活?
上一节提到,Harness 在执行工具之前还要过几道关卡——而负责把关的,正是工具自带的几个属性。这些属性可以分成两类来看。
两样标签——它到底是一只什么样的手:
isReadOnly(只读吗)——判断它会不会修改你的内容。Glob标的是 true:它只负责查找文件,不会改动任何东西;像写文件、执行命令这类可能产生变更的工具,一般会标 false。isConcurrencySafe(能并行吗)——判断它能不能和别的工具一起同时运行。Glob标 true:它只是只读地搜索文件,多个同时跑也比较安全;会产生修改的工具通常标 false,需要排队执行,避免互相干扰。
两道检查——真正执行前必须经过的关卡:
validateInput(校验参数)——执行之前先检查输入参数。比如你传了path,它会先确认这个目录是否存在、是不是一个有效目录;如果不存在,就会直接打回,报一句「Directory does not exist: ...」,根本不会让call继续执行。checkPermissions(检查权限)——动手之前先过权限控制。只读工具通常比较宽松,可以自动放行;而会改动内容的工具,则要按照你设定的权限策略来处理——例如写文件时弹出提示问你「允许修改src/foo.ts吗?」,只有你确认后它才会执行,否则就会被拦下。
这些属性里,最值得注意的是默认值。Harness 给所有工具都设了一套默认配置(TOOL_DEFAULTS),其中 isReadOnly 和 isConcurrencySafe 默认都是 false——也就是说,除非某个工具主动声明「我是只读的」「我是可并行的」,否则系统一律先把它当成会修改内容、也不能并行执行。宁可让一个其实无害的工具老老实实排队,也绝不轻易冒险让它乱改环境:本质上,这是一种偏保守、偏安全的默认策略。
5 能力长在手和脚上
拆到这里,再回头想一件你一直默认接受的事。
你看着 Claude Code 读取文件、执行构建、修改代码,一路都很顺——于是很容易产生一种感觉:是它“聪明”,是它“有执行力”。但把 Glob 工具摊开来看,恰恰会发现事实并不是这样:这些具体工作,模型本身一件都没有真正做过——它只是负责吐字和做选择。
大模型本身没有手——它所谓“动手”的全部能力,实际上都长在 Harness 提供的工具体系上。
6 手,比你以为的多得多
拆完 Glob 这一只手,你可能会以为:所谓工具,无非就是查查文件、读写几行内容、跑一条命令。
远远不止。
在 Claude Code 的体系里,「工具」这个词覆盖的范围,比很多人想象得更大:做计划——是一种工具(EnterPlanMode);记录待办——是一种工具(TodoWrite);甚至开一个 git 分支开始工作——也是一种工具(EnterWorktree)。它们和 Glob、Bash 一样,并排出现在同一个 getAllBaseTools() 数组里,结构也和 Glob 没区别。模型既分不出这些工具的“类别”,其实也不需要分——对它来说,这些统统都是工具,调用方式没有本质差别。
至于这些“手”究竟是谁造出来的(有些是内置工具,有些来自 MCP,还有些由 Skill 转化而来),那就是后话了。下一站,我们会把「工具」这个词的边界彻底掀开。
