手把手给AI装上13个工具:claude-code-from-scratch工具系统设计与edit_file防坑实战
【免费下载链接】claude-code-from-scratchBuild your own Claude Code from scratch. 🔍 Claude Code 开源了 50 万行代码,读不动?用 ~5000 行 TypeScript / Python 从零复现核心架构,11 章分步教程带你理解 coding agent 精髓项目地址: https://gitcode.com/gh_mirrors/cl/claude-code-from-scratch
Claude Code 开源了 50 万行代码,读不动?claude-code-from-scratch用约 5000 行 TypeScript / Python 代码从零复现了它的核心架构,而工具系统(Tool System)正是让 AI 从"只会聊天"进化为"能干活的 coding agent"的关键一步。本文带你手把手拆解这个开源项目如何给 AI 装上 13 个工具:一个静态数组 + 一个 switch 分发器就搭起了完整工具系统,再深入edit_file工具,看清"改错地方"、"引号不匹配"、"覆盖用户修改"三大坑是如何被逐一堵上的。
为什么 AI 需要工具:一个工具只有三样东西
大模型本身只能"说话",它不能读文件、不能跑测试。工具就是 AI 的"手":模型决定调什么工具、传什么参数,真正干活的函数由你的代码执行。
在 src/tools.ts 中,每个工具只需要三样东西:
| 组成 | 说明 | 作用 |
|---|---|---|
| 名字 | 如read_file、edit_file | 模型调用时用的标识 |
| 给模型看的说明 | description + input_schema | 让模型知道工具能干什么、传什么参数 |
| 干活的函数 | 一个普通函数 | 真正执行文件读写、搜索等操作 |
这个设计极简但威力巨大——模型通过 schema "理解"工具,函数负责落地执行,两者之间只差一层 JSON 参数。
13 个工具全家福:6 核心 + 7 扩展
项目共定义 13 个工具,全部集中在一个 toolDefinitions 静态数组里:
| 分类 | 工具 | 能力 |
|---|---|---|
| 📄 文件读写 | read_file | 读取文件(带行号) |
write_file | 新建 / 覆盖文件(自动创建父目录) | |
edit_file | 精确字符串替换(本章重点 ⭐) | |
| 🔍 探索代码 | list_files | glob 模式列文件 |
grep_search | 正则搜索(优先系统 grep) | |
| ⚡ 执行命令 | run_shell | 跑测试、git、装依赖 |
| 🧩 扩展能力 | skill | 调用.claude/skills/里的技能模板 |
web_fetch | 抓取 URL 并去 HTML 标签 | |
enter_plan_mode/exit_plan_mode | Plan 模式进出(deferred 延迟加载) | |
agent | 派生子 Agent 隔离干活 | |
tool_search | 按需激活延迟加载的工具 |
Python 版功能完全一致,实现在 python/mini_claude/tools.py,两版可对照阅读。
工具系统核心设计:数组 + switch,拒绝过度工程
Claude Code 的 66+ 工具用类体系管理(继承、多态、独立测试),但教程项目里 13 个工具完全够用更简单的方案:
- 定义层:toolDefinitions 静态数组,格式与 Anthropic API 的
tools参数完全一致,零转换直接发送; - 执行层:executeTool 分发器 用一个
switch把工具名分派到对应函数。
// src/tools.ts — 工具执行分发 switch (name) { case "read_file": result = readFile(input); break; case "write_file": result = writeFile(input); break; case "edit_file": result = editFile(input); break; // ... list_files, grep_search, run_shell, web_fetch, tool_search default: return `Unknown tool: ${name}`; }💡设计哲学:错误是数据,不是异常。未知工具返回
Unknown tool: xxx字符串而非抛异常——这段文字会回到模型手里,让它自己发现"我幻觉出了一个不存在的工具名"并纠正。
edit_file 防坑实战:这个工具藏着 3 个真实的坑
edit_file是 13 个工具里唯一"有坑"的。它的工作方式很简单:给一段old_string和一段new_string,把文件里精确匹配到的旧字符串替换掉。听起来没问题?实际运行会撞上三个坑。
坑 1:改错地方 —— 唯一性检查
如果old_string在文件里出现 3 次,静默替换第一个匹配项就是灾难。editFile 实现 先数出现次数,不唯一就直接拒绝:
const count = content.split(actual).length - 1; if (count > 1) return `Error: old_string found ${count} times. Must be unique.`;出现 0 次说明模型"记错了"文件内容(幻觉检测);出现 >1 次则要求模型提供更多信息来唯一定位。宁可失败也不猜测——错误信息会喂回给模型,它会带上更多上下文重试。
坑 2:引号不匹配 —— 引号容错
LLM 的 tokenization 可能把文件里的直引号"生成成弯引号",没有容错的话这类编辑会 100% 失败。项目用 normalizeQuotes + findActualString 解决:先尝试精确匹配,失败后把两边的弯引号统一归一化再找,匹配成功后返回文件中的原始字符串去替换,保持文件原有字符风格不被改写。
细节彩蛋:替换用content.split(actual).join(new_string)而不是String.replace——后者的$有替换符语义,遇到含$的代码会被悄悄改写。
坑 3:覆盖用户正在改的东西 —— read-before-edit + mtime 防护
这是整个工具系统最有价值的防护(executeTool 中的检查逻辑):
| 场景 | 系统行为 |
|---|---|
| AI 没读过就直接改已有文件 | ❌ 拒绝:You must read this file before editing |
| AI 读完后,你在 IDE 里手动改了它 | ⚠️ 警告:modified externally, read_file again |
| 新建文件 | ✅ 跳过检查(新文件无需先读) |
原理:Agent 用一个 Map 记录每个文件读取时的mtime(修改时间戳),写入前再比对一次。时间戳变了 = 文件在 AI 读取后被外部动过。这与 Claude Code 的readFileTimestamps机制对齐——编辑必须基于已知状态,不能"盲写"。
编辑成功还送一份 diff
编辑完成后,工具会生成一段带行号的简易 diff 返回(generateDiff),模型和人都能立刻确认改对了哪几行。
为什么选"字符串替换"而不是行号或 diff?
项目文档 docs/02-tools.md 里有张精彩的方案对比表,值得新手记住:
| 备选方案 | 致命缺陷 |
|---|---|
| 行号编辑 | 插入 3 行后所有后续行号偏移,多步编辑要复杂重算 |
| AST 编辑 | 语法错误的文件恰恰最需要编辑,AST 解析器直接报错 |
| Unified diff | LLM 生成严格格式很差,一个+/-前缀错就废 |
| 全文件重写 | 大文件烧 Token,还可能静默丢掉没改的代码 |
| 字符串替换 | ✅ 且自带"幻觉安全":字符串不存在就直接失败,逼模型重读纠正 |
另外 3 个值得抄作业的工具设计
- 结果截断保头尾:超过 50K 字符时,truncateResult 保留开头和结尾各一半——因为编译错误摘要、测试统计往往在末尾,只砍中间并明确标注"truncated N chars";
- 只读工具并行跑:
read_file、grep_search等无副作用的工具标记为 CONCURRENCY_SAFE_TOOLS,可并发执行,2-3 倍加速; - deferred 延迟加载:不常用的工具(如 plan mode)只把名字发给模型,需要时模型调
tool_search激活完整 schema——工具一多起来,这是省 token 的关键。
动手跑起来:一条命令验证工具系统
仓库是只读的,先 clone 到本地再运行:
git clone https://gitcode.com/gh_mirrors/cl/claude-code-from-scratch cd claude-code-from-scratch npm install node steps/run.mjs 2 # 跑第 2 章工具系统,无需 API key node steps/run.mjs 2 --diff # 看这一章比上一章多了什么 node steps/run.mjs 2 --py # 换 Python 版第 2 章的可运行最小实现位于 steps/canonical/ts/tools.ts,文档与代码由同一真源生成,保证"文档说的和代码对得上"。完整 13 项工具行为可用 test/TEST-GUIDE.md 逐一手动验证。
总结:~5000 行看懂工具系统的精髓
| 要点 | 一句话总结 |
|---|---|
| 工具结构 | 名字 + 说明 + 函数,静态数组定义、switch 分发执行 |
| 错误处理 | 错误是数据:失败信息回喂模型,让它自我纠正 |
| edit_file 三坑 | 唯一性检查 / 引号容错 / read-before-edit + mtime |
| 防上下文爆炸 | 50K 截断保头尾、大结果持久化到磁盘 |
工具定义了 agent 的能力,下一篇可以顺着 docs/03-system-prompt.md 看 System Prompt 如何定义它的行为——什么时候该小心、优先用哪个工具。
💬 想交流 AI Agent 开发心得?可以加入AI Agent 工坊交流群(群号 1090526244),扫码进群一起讨论 coding agent 的实现细节。
【免费下载链接】claude-code-from-scratchBuild your own Claude Code from scratch. 🔍 Claude Code 开源了 50 万行代码,读不动?用 ~5000 行 TypeScript / Python 从零复现核心架构,11 章分步教程带你理解 coding agent 精髓项目地址: https://gitcode.com/gh_mirrors/cl/claude-code-from-scratch
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考