🍊
Claude Code 源码拆解 harness 设计 - 工具设计
- 总结
- 万能工具是兜底而不是首选
- 模型的工作偏向用最少的工具做最多的事,一定要显示的写出哪些常见用哪些工具,而不靠模型判断
- 工具按需发现
- 工具输出限制预算
- 不要假设输出是可控的
- 不要直接截断,会导致信息丢失,给模型一个摘要+指针,让模型按需/分段去读
- 好的工具设计,不只是给模型更多能力,也要给模型更清晰的边界
- 完美符合之前提到的 上下文窗口中的内容不是 装不装的下 而不是 该不该装进去
- Claude Code 工具系统包含
- 输入校验
- 权限检查
- 输出格式化
- 并发安全
- 分类器摘要
- 搜索关键词
- 核心设计
- 万能工具是兜底而不是首选
- 最反直觉的设计,在 bash 工具中 的使用说明有一个规则,不要这个工具来执行读文件、搜索、列目录等,除非已经确认专用工具无法完成任务
- 列出文件工具偏好
- 文库搜索用 Glob 工具而不是首选 find 命令
- 内容搜索用 Grep 工具而不是首选 grep 命令
- 读文件用 Read 而不是 cat 命令
- 编辑用 Edit 而不是 sed / awk 命令
- 为什么要这样设计
- 万能工具是安全和可控性的敌人
- 所有操作都走同一个工具入口,权限系统、输出管理等等设计都会被绕过
- 专用工具的价值
- 专用工具的价值不在于能做什么,而是它把操作限制在一个可控的范围内
- Read 工具只能读文件,因此能精确控制它的输出格式、做去重、token预算检查
- Edit 工具只能编辑文件,所以能做 diff 展示,路径保护,权限细分等等
- 模型的工作偏向用最少的工具做最多的事,因此必须在万能工具的说明中引导
- 工具的按需发现
- 加载的工具太多浪费上下文空间,并且影响模型的性能
- Claude Code 的做法不是拆分多个Agent 而是将工具分类
- 核心工具:启动时直接加载,模型直接可用
- 命令行、文件读取、文件编辑、文件写入、搜索、Agent
- 延迟工具:模型只知道工具的名字,不知道参数格式,无法直接调用(按需发现)
- tool search
- 当需要使用延迟工具时,先调用 tool search 工具传入关键词,tool search 返回匹配的工具的完整定义,包括参数格式,拿到定义后就能直接调用
- searchHint
- 每个工具除了完整的名字和使用说明之外还有一个 searchHint 3~10词的能力摘要
- 专门为关键词匹配优化,用的词不和工具名重复,覆盖可能搜索的各种表述
- 例如文件读取工具的 searchHint 是 “读取文件、图片、PDF、文本”
- 优先级规则
- 工具名精确匹配权重最高、searchHint匹配次优先、完整工具说明中的关键词匹配最低
- 这样就能保证模型用简短的关键词就能找到工具
- 缓存稳定性(排序策略)
- 源码中将内置工具排在前面作为连续前缀,外部插件排在后面,这样即使插件工具发生变化,内置工具前缀不变,服务端的工具提示词缓存不会失效(关键在于缓存断点 见详解)
详解
- LLM处理文本是自回归 从左到右的,当模型读取到第 n 个 token 时 它的状态依赖于前面的所有token
- 只要前 n 个 token 的序列 完全一模一样,模型前面计算出的数学矩阵就可以直接复用
- 当第 n+1 个 token 发生变化,会导致第 n+1 个位置开始重新计算前n个token
- Prompt 的结构通常是 Tools → System Prompt → Messages
- Claude Code 为了最大化缓存命中率,采用 严格分组排序 + 插入缓存断点 的策略
- 将工具进行严格物理排序,静态→按需发现→动态/插件
- Claude Code 会在 最后一个静态内置工具 的结尾处,打上缓存断点
- 最后的prompt 结构为
- 缓存区(命中缓存)
- [内置工具A] -> [内置工具B] -> [Tool Search(按需发现)] -> [内部工具C + 缓存断点]
- 计算区(正常计算)
- [新增插件工具] -> [系统提示词中的动态变量 (如当前工作目录、时间)] -> [用户的提问]
- 如果混合排序,每次插件工具变化都会打乱前缀,缓存直接废了
- 总结:如果工具太多就应该考虑 分级加载,核心工具直接给,其余按需发现
- 最简单的实现:直接给出所有工具的名字+简短的描述 按需发现(skills)
- 工具输出预算限制
- 大部分工具是 10w 字符,文件读取 无限大
- 当工具输出超过上限,系统不会直接阶段,丢失信息,而是将输出放在磁盘文件,给模型一个 预览(文件的前几千字节内容)+ 文件路径,让模型用工具按需去读取
- 文件读取为什么是无限大
- 如果设置一个阈值,存放到本地后,Read读回来,又会触发阈值,陷入死循环
- 它有自己的 token预算检查,超过预算直接报错,模型会尝试使用偏移和行数参数分段读取
- 搜索工具
- 搜索结果默认限制在 250条,总共2w字符
- 如果模型需要更多信息,可以申请适当解除限制