作者:互联网 时间: 2026-09-02 12:19:54
大模型只会吐文字——那它如何「动手」干活?的重点在于把前置条件、操作顺序和容易误判的地方分清楚。
先停在一件你早就习以为常、却越想越怪的事上——你在 Claude Code 里按下回车,它实实在在地动了你机器上的东西:读了 src/ 下的文件,跑了构建,改了代码。可干这事的是什么?它是怎么动手干活的?
答案藏在一个词里——工具。说穿了:工具,就是大模型的手和脚。脑子(模型)只会说、只会想,碰不到外面的世界;真正去读文件、跑命令、改代码的,是工具。
你平时用 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
最关键的是后两句:一句说什么时候该用它(需要按名字找文件时),一句还点出什么时候该换别的工具(开放式搜索、要反复找,改用 Agent)。每次开工,模型把所有工具的描述挨个读一遍,自己判断这一轮该用哪个——选哪只手是模型自己定的,没人替它拍板。
输入参数(inputSchema)。 就是调用这个工具要传什么参数进去。Glob 要你传一个 pattern,告诉它找哪种文件,比如 src//*.ts;还可以传一个 path 指定在哪个目录找,不传就是当前目录。比如要找 src 下所有 ts 文件,模型传进来的就是:
{"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」。剩下的,真翻文件系统、把结果装好交回去,连「该接什么参数」「叫什么名字」,全是这只手自己带着的。
拆开一只手看过了,那它平时是怎么被用起来的?一圈下来其实就四步。
四步拆开看:
name、description、inputSchema),跟你的提问一起喂给模型。模型每次回答前,都先收到一份工具清单:有哪些工具、各自干嘛、要填什么参数。它读一遍,就知道这轮有什么能用。tool_use——就是「我要用 Glob,参数 src/**/*.ts」。选哪个、填什么,都由模型决定;可它只会吐字,真去碰硬盘的不是它。tool_use,Harness 替模型运行工具的 call。跑之前还过几道关卡(参数对不对、有没有权限、安不安全),下一节细看。call 的结果经 mapToolResult 装成回执,塞回对话;模型收到结果,接着下一步——还要用工具就回第 2 步,活干完了就收尾。一句话:模型下令,Harness 动手。模型只管吐 tool_use,真执行的是 Harness。至于完整的执行链路—— hook、用户审批、流式、出错怎么处理——我们后面再讲,这里我们先看基本核心的流程。
上一节说到 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——除非工具主动声明「我只读、我安全」,否则一律先当它会改东西、当它不能并行。宁可让一个其实无害的工具老实排会儿队,也绝不冒险让它乱改一气:也就是默认往坏处想。
拆到这里,回头再想一件你一直默认的事。
你看着 Claude Code 读文件、跑构建、改代码,顺顺当当——很容易觉得:是它「聪明」,是它「能干」。可 Glob 工具摆在这儿,告诉你恰恰相反:这些活,模型一样都没真干——它只会吐字。
大模型没有手——它「动手」的全部本事,都长在 Harness 的工具上。
拆完 Glob 这一只手,你大概以为:手嘛,无非就是找找文件、读写几行、跑条命令。
远不止。
在 Claude Code 眼里,「工具」这个词覆盖的面,大得有点出乎意料:做计划——是一种工具(EnterPlanMode);记待办——是一种工具(TodoWrite);连开个 git 分支干活——也是一种工具(EnterWorktree)。它们和 Glob、Bash 肩并肩,挤在同一个 getAllBaseTools() 数组里,跟 Glob 长一个样。模型分不出来,也不需要分——对它来说,这些都是工具,调起来都一样。
至于这些手是谁造的(有些内置、有些来自 MCP、有些由 Skill 变身而来),那是后话。下一站,「工具」这个词的边界,会被彻底掀开。