ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Claude Code源码解析(一):系统提示词里 tools 字段的配置骨架与验证

Claude Code源码解析(一):系统提示词里 tools 字段的配置骨架与验证 1. 从一次“工具明明在模型却不用”的排查说起如果你正在读 Claude Code 的源码或者准备基于它的思路做自己的 Agent 框架getSystemPrompt这个函数大概率是你绕不开的一站。它负责把本轮对话要用到的系统提示词拼装出来而它接收的第一个参数就是tools——本轮可用的工具列表。很多人第一次看到这里会卡住请求体里不是已经把每个工具的名字、描述、参数 schema 都发给模型了吗模型按理说已经“看得见”这些工具了为什么生成系统提示词的时候还要再把tools传一遍这个问题不搞清楚后面读prompts.ts会一直别扭。我把它拆成一句人话工具定义告诉模型“有什么”系统提示词告诉模型“怎么用、优先用谁、什么时候别用”。前者是能力清单后者是使用说明书。而这份说明书里有一部分内容是动态的——取决于本轮到底启用了哪些工具。比如你启用了任务管理工具提示词里才会多出一条“用任务工具拆解工作”没启用这条就不该出现。所以getSystemPrompt必须拿到tools才能判断该不该插入这些动态规则。这篇文章就沿着getSystemPrompt的源码路径把tools字段的配置骨架讲清楚再给你一段可复制的settings.json片段最后用--dump-system-prompt亲手验证一次“动态规则”是怎么冒出来的。适合已经能跑起 Claude Code、想往源码层再走一步的读者。如果你还没配好模型接入可以先用 TaoToken 的模型对话把环境跑通再回来跟源码。2. TaoToken 前置把模型接入这层先铺平读源码归读源码但你总得有个能实际发请求的模型端点不然--dump-system-prompt导出来的东西没法验证改了cli.tsx也不知道跑起来对不对。TaoToken 在这里的角色就是帮你把“模型接入”这层铺平让你把精力放在提示词和工具配置上而不是卡在鉴权和端点上。它的接入方式兼容 Anthropic 风格的调用Claude Code 这类工具配置起来比较直接。你需要准备的东西不多一个 API Key一个 base 地址然后把它写进环境变量或配置文件。官网入口在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end API 地址是 https://taotoken.net/api 注意这个 API 地址后面不加任何查询参数保持干净。具体到操作先去控制台把 Key 建出来入口是 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite Key 的管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite 。建好之后先别急着往 Claude Code 里塞用模型对话页发一条最简单的消息确认 Key 是活的、额度是通的页面在 https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite 。这一步花不了一分钟但能帮你排除掉后面一大半“到底是提示词问题还是接入问题”的扯皮。如果你打算长期用 Claude Code 做编码或者搭 Agent可以顺手看一下 Coding Plan入口是 https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite 它更适合高频调用的场景。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite 遇到字段对不上时翻一下比瞎试快。Claude Code 相关的接入说明单独有一页https://taotoken.net/ClaudeCodeAnthropic?utm_sourcetaotoken_aicg_blog_endutm_contentClaudeCodeAnthropicutm_campaignrewrite 。提示先把模型对话跑通再动源码。很多人一上来就改prompts.ts结果请求根本发不出去白白浪费一晚上。3. tools 字段的配置骨架从 enabledTools 到动态规则现在进入正题。getSystemPrompt接收四个参数tools、model、additionalWorkingDirectories、mcpClients。这一篇只盯tools。它进来之后的第一步不是直接拼字符串而是先转成一个集合const enabledTools new Set(tools.map(_ _.name))这个enabledTools就是后面所有“有没有某个工具”判断的依据。为什么要转成 Set因为后面要反复查“TaskCreate 在不在”“TodoWrite 在不在”用 Set 查是 O(1)比每次遍历数组干净。这个设计本身也说明了一件事系统提示词的生成逻辑是以工具名为条件做分支的。接着这个集合被传进getUsingYourToolsSection这个函数专门生成提示词里# Using your tools那一段。它的骨架可以概括成三层第一层是通用规则不管工具列表里有什么都会出现。比如“读文件用 Read别用 cat/head/tail/sed”“改文件用 Edit别用 sed/awk”“找文件用 Glob别用 find/ls”“搜内容用 Grep别用 grep/rg”。这些规则的本质是当专用工具存在时不要退回 Bash 去干同样的活。因为专用工具的输出模型更容易理解用户也更容易 review。第二层是动态规则靠enabledTools判断。源码里是这么写的const taskToolName [TASK_CREATE_TOOL_NAME, TODO_WRITE_TOOL_NAME].find(n enabledTools.has(n))它在TaskCreate和TodoWrite里找第一个被启用的。找到了就插入一条“用这个工具拆解和管理工作”找不到这条就是null被.filter(item item ! null)过滤掉。这就是为什么你导出空工具版本的系统提示词时看不到任务管理那条规则。第三层是并行调用规则也是通用规则“你可以在一次响应里调用多个工具如果没有依赖关系就并行调用”。这条不依赖具体工具但属于“怎么用”的范畴所以也放在这一段。把这三层拼起来getUsingYourToolsSection返回的就是一个以# Using your tools开头的 Markdown 片段。你可以把它理解成一份给模型的工具使用公约公约的条款分“永远适用”和“按需适用”两类而tools参数就是判断“按需”那部分要不要生效的开关。这里有个容易忽略的点tools传进来的是完整工具对象数组但生成提示词时只用了name。描述和参数 schema 不参与提示词拼装它们走的是请求体里的tools字段。所以系统提示词和请求体里的工具定义是两份数据、两个用途不要混为一谈。前者管“怎么用”后者管“有什么”。4. 可复制的 settings.json 片段与验证动作理解了骨架接下来动手。先给你一段可以直接抄的settings.json片段把模型接入和工具相关的配置放进去。注意路径和字段名按你自己的环境调整{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的Key }, permissions: { allow: [ Read, Edit, Write, Glob, Grep, Bash ] }, tools: { enableTaskTools: true } }这里tools.enableTaskTools不是 Claude Code 官方字段而是给你一个思路如果你在自己的封装里想控制任务工具是否进入tools数组就在这一层做开关。真正决定动态规则出不出现的是getSystemPrompt收到的tools数组里有没有TaskCreate或TodoWrite。配置写好后验证分两步。第一步确认模型接入是通的用模型对话发一条消息即可。第二步导出系统提示词做对比。导出入口被一个编译期开关门控默认不开。打开devkit/build.ts在features那行加上DUMP_SYSTEM_PROMPTfeatures: [BUDDY, DUMP_SYSTEM_PROMPT],然后重新打包bun devkit/build.ts导出空工具版本bun dist/cli.js --dump-system-prompt a.txt打开a.txt你会看到完整的系统提示词大约 25 KB、200 行出头。此时# Using your tools段里只有通用规则没有任务管理那条。接着改src/entrypoints/cli.tsx把传给getSystemPrompt的空数组换成带TaskCreate的列表// 改前 const prompt await getSystemPrompt([], model) // 改后 const prompt await getSystemPrompt([{ name: TaskCreate }], model)重新打包再导出bun devkit/build.ts bun dist/cli.js --dump-system-prompt b.txt diff a.txt b.txt这次 diff 会多出一行正是“用 TaskCreate 拆解任务”那条动态规则。到这一步你就亲眼看到了tools参数是怎么影响系统提示词内容的。验证完记得恢复把features里的DUMP_SYSTEM_PROMPT去掉把cli.tsx里那行改回[]再重新打包。别把调试开关留在生产构建里。5. 本篇常见错排查导出文件是空的或者报错。先确认bun devkit/build.ts有没有跑成功dist/cli.js是否存在。如果打包报错多半是features数组写错了检查引号和逗号。另外确认你改的是devkit/build.ts而不是别的构建脚本。diff 没有任何变化。最常见的原因是改了cli.tsx但没重新打包跑的还是旧的dist/cli.js。另一个原因是工具名写错了TaskCreate的大小写要和源码里的常量一致写成taskcreate是匹配不上的。还有一种情况是你改的getSystemPrompt调用点不是--dump-system-prompt实际走的那条路径回去确认cli.tsx里的调用位置。系统提示词里工具规则和实际工具对不上。比如提示词说“用 Read 读文件”但你的tools数组里根本没有Read。这说明你的工具列表和提示词生成逻辑脱节了。getUsingYourToolsSection里的通用规则是无条件插入的它假设这些专用工具默认存在。如果你在自己的封装里裁剪了工具要么同步裁剪提示词要么确保这些基础工具始终启用。改了settings.json但没生效。检查文件路径对不对Claude Code 读的是项目级还是用户级的配置。另外环境变量ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY如果同时在 shell 里导出了可能会覆盖配置文件里的值用echo确认一下当前生效的是哪个。请求发出去返回鉴权错误。回到 TaoToken 的 API Keys 页面确认 Key 状态再用模型对话页发一条消息验证。如果模型对话能通但 Claude Code 不通多半是 base 地址写错了注意是https://taotoken.net/api不要多加路径或者查询参数。6. 把 tools 理解成“提示词的条件变量”读到这里你应该能回答开头那个问题了tools之所以必须传进getSystemPrompt是因为系统提示词里有一部分规则是条件生成的而条件就是“某个工具在不在本轮的工具列表里”。enabledTools这个 Set 是判断的载体getUsingYourToolsSection是执行的场所TaskCreate/TodoWrite是最直观的例子。这个设计思路其实可以迁移到你自己的 Agent 项目里把工具定义和工具使用规则分开管理规则里需要动态判断的部分统一从工具列表派生而不是硬编码在提示词模板里。这样你增删工具时提示词会自动跟着变不用手动维护两份可能不一致的清单。下一步你可以继续往下读getSystemPrompt的另外三个参数——model、additionalWorkingDirectories、mcpClients它们各自也会影响提示词的不同段落。尤其是mcpClients它决定了 MCP 相关规则要不要出现逻辑和tools判断任务工具是同一套思路。把这一篇的验证方法复用过去你就能把整个系统提示词的生成逻辑摸一遍。
返回列表