读与搜 · read_file 与 grep 的门道
看似最简单的两个工具——读文件、搜代码——在 grok 里藏着不少门道:行号投影、多模态(PDF/图片/PPTX)、字符对齐的流式、包 ripgrep 的预算早停。这一章顺带澄清一个易混点:search_tool 搜的是「工具」不是代码。
读与搜 · read_file 与 grep 的门道
进具体工具。先看最基础的两个——读文件、搜代码。别小看它们,生产实现里门道不少。
read_file:不只是读文本
id read_file(kind Read,只读)。基本参数 target_file(schema 里从 path 改名)、offset(起始行)、limit(行数),输出带行号(行号→内容),默认上限 1000 行 / 25000 token。行号很关键:后面的编辑工具靠它定位。
真正有意思的是多模态:靠魔数嗅探(bytes_to_metadata),read_file 能读 PDF、PPTX、Jupyter notebook、图片。图片会被 compress_image_for_conversation 压到模型能吃的尺寸(≤768KB base64、≤1024² 像素,JPEG 质量阶梯降级)再嵌进对话;PDF 支持按页范围(pages: "1-5";超过 10 页必须给,一次最多 20 页),默认把页面渲染成图片,format: "text" 则改为抽取文本。一个「读文件」工具,顺手把「看图看文档」也办了。
还有个流式细节:execute() 把格式化后的投影按字符对齐地分块流出(4KB 一块),保证前端拼接起来和最终卡片逐字节一致——只有文本行流式(且要客户端声明了 stream_tool_progress),图片/PDF/PPTX 都是终止性的。
grep:包着 ripgrep
id grep(kind Search,只读)。它不自己实现搜索,而是包 rg 二进制:正则 pattern、glob、上下文 -A/-B/-C、head_limit(省略时内容模式默认 200 行、文件/计数模式默认 500 条;显式传入也会被夹在 2000 行/10000 条以内)。两个工程细节值得看:
- 预算早停:stdout 读到
head_limit+1行(多读一行用来判断「是否被截断」)就提前 kill 掉rg——而且是在排空 stderr 之前 kill,免得在大目录树遍历上阻塞。 - 尊重
.gitignore(除非你给了很宽的 glob)。
澄清:search_tool 搜的是「工具」不是代码
这是个极易混淆的点:search_tool 不是代码搜索,而是工具协议那章提过的 MCP 工具发现元工具(相当于「工具的搜索引擎」)。grok CLI 从不把 MCP 工具直接列给模型——不管挂了几个,这套机制都常开:模型先用 search_tool 按 BM25 搜出相关工具(默认返回 5 条),再用 use_tool 调。输出里还带 total_hidden_tools 告诉模型「还有多少没显示」。搜代码是 grep,搜工具是 search_tool——别弄反。
这套「一个 read_file 吃下多模态 + 字节级一致的流式 + grep 包 ripgrep 还带预算早停」,是「读和搜」这两件最朴素的事在生产里的真实复杂度。教学实现里它们各是三五行;这里每一个都得考虑大文件、二进制、超时、截断、流式一致性。
和《从 LLM 到 Coding Agent》对照
对照 tool-use:最小实现的 read/grep 就是 fs.readFile 和 child_process.exec('grep')。grok 的增量在多模态、流式一致性、外部二进制的预算控制,以及把「工具发现」本身也做成一个工具。下一章看更硬的:同一件「改文件」,grok 为什么要三种模型协议。