Claude Code源码解析(一):为什么Claude Code系统提示词中需要有tools?

Claude Code每次给发给大模型的提示词中,除开有用户提示词,还有就是系统提示词了,这篇文章来分析一下Claude Code中的系统提示词是如何构成的。

系列文章请关注公众号:IT周瑜

在Claude Code源码prompts.ts中,有一个函数叫做getSystemPrompt,就是用来生成系统提示词的,它接收四个参数:

  1. tools:本轮对话可用的工具列表
  2. model:本轮对话所使用的模型,比如 "glm-5.2"
  3. additionalWorkingDirectories:用户用 --add-dir 添加的额外工作目录
  4. mcpClients:当前连上的 MCP 服务器客户端列表

这四个都是用来生成系统提示词的。

那么,我们先来分析一下,为什么系统提示词中需要有tools呢?

在Claude Code调用大模型时,会在请求中带上所有工具的定义,比如工具的名字、描述、参数定义,那既然请求中已经有了工具信息,也就是说大模型已经能拿到所有工具的定义了,为什么在生成系统提示词时,还需要工具信息呢?

这是因为,Claude Code需要在系统提示词中,设置工具的调用规则,也就是要在系统提示词中教模型怎么调用工具,规则分为两类:

  • 一类是通用规则,不管当前工具列表中有什么工具,都需要遵守此类规则,比如"读文件要用专用工具,别用 cat"、"优先用专用工具而不是 Bash"。
  • 一类是动态规则,比如当前工具列表中如果存在任务管理工具,比如TaskCreate工具,那么系统提示词中就会多出一条 "用任务工具拆解任务"。

换句话说,模型光收到工具定义还不够,系统提示词还得在提示词层面告诉它用什么、怎么用、什么优先 ,而其中一部分要根据这次启用了哪些工具来动态调整,所以需要把 tools 工具信息传进来。

上面说的是原理,下面看看实际代码和真实的提示词长什么样。

tools 进来后,第一步是把它变成一个工具名集合,后面判断「有没有某个工具」就靠它:

typescript 复制代码
const enabledTools = new Set(tools.map(_ => _.name))

然后这个集合传给getUsingYourToolsSection函数,这个函数是用来生成系统提示词中「Using your tools」部分的。

typescript 复制代码
function getUsingYourToolsSection(enabledTools: Set<string>): string {
  
  // 看启用了哪个任务管理工具(动态规则的判断条件)
  const taskToolName = [TASK_CREATE_TOOL_NAME, TODO_WRITE_TOOL_NAME].find(n =>
    enabledTools.has(n),
  )

  const providedToolSubitems = [
    `To read files use ${FILE_READ_TOOL_NAME} instead of cat, head, tail, or sed`,   // 通用规则
    `To edit files use ${FILE_EDIT_TOOL_NAME} instead of sed or awk`,
    // ...Glob、Grep、Bash 等同样是通用规则
  ]

  const items = [
    `Do NOT use the ${BASH_TOOL_NAME} to run commands when a relevant dedicated tool is provided...`,   // 通用规则
    providedToolSubitems,
    taskToolName
      ? `Break down and manage your work with the ${taskToolName} tool...`   // 动态规则的判断,只有启用了任务工具才有这条
      : null,
    `You can call multiple tools in a single response...`,   // 通用规则
  ].filter(item => item !== null)

  return [`# Using your tools`, ...prependBullets(items)].join(`\n`)
}

这段代码实际跑出来,在系统提示词里就是这个样子(先没有传任务工具):

plain 复制代码
# Using your tools
 - Do NOT use the Bash to run commands when a relevant dedicated tool is provided. Using dedicated tools allows the user to better understand and review your work. This is CRITICAL to assisting the user:
  - To read files use Read instead of cat, head, tail, or sed
  - To edit files use Edit instead of sed or awk
  - To create files use Write instead of cat with heredoc or echo redirection
  - To search for files use Glob instead of find or ls
  - To search for the content of files, use Grep instead of grep or rg
  - Reserve using the Bash exclusively for system commands and terminal operations that require shell execution...
 - You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make all independent tool calls in parallel...

都是通用规则。

然后把TaskCreate 工具加上,整份提示词只多了这一行:

plain 复制代码
 - Break down and manage your work with the TaskCreate tool. These tools are helpful for planning your work and helping you track your progress. Mark each task as completed as soon as you are done with the task. Do not batch up multiple tasks before marking them as completed.

这正是代码里 taskToolName ? 'Break down...' : null 判断出来的,动态的。

到这,大家应该就能理解为什么系统提示词中需要有tools了,因为系统提示词中需要设置工具的调用规则,从而需要动态的判断某些工具在没在工具列表里,如果在就动态生成对应的规则,否则就不需要了。

实操:自己导出系统提示词看看

文章里这些「真实提示词」都是用源码自带的 --dump-system-prompt 导出来的,你也可以自己跑,换个条件就能看到提示词哪里变、哪里不变。

第一步:打开 dump 开关。

这个入口被一个编译期开关门控,默认不开。打开 devkit/build.ts,在 features 那行加上 'DUMP_SYSTEM_PROMPT'

typescript 复制代码
features: ['BUDDY', 'DUMP_SYSTEM_PROMPT'],

第二步:重新打包。

bash 复制代码
bun devkit/build.ts

第三步:导出来。

把提示词重定向到文件,方便等下对比:

bash 复制代码
bun dist/cli.js --dump-system-prompt > a.txt

打开 a.txt 就是完整的系统提示词,大约 25 KB、200 行出头。

第四步:给 dump 传上工具列表。

上面默认导出的是空工具版本,所以那条动态规则没出现。

--dump-system-prompt 在源码 src/entrypoints/cli.tsx 里调的是 getSystemPrompt([], model),把那个空数组换成带上 TaskCreate 的工具列表就行:

typescript 复制代码
// 改前
const prompt = await getSystemPrompt([], model);
// 改后
const prompt = await getSystemPrompt([{ name: 'TaskCreate' }], model);

改完重新打包、再导出一次,和空工具版 diff:

bash 复制代码
bun devkit/build.ts
bun dist/cli.js --dump-system-prompt > b.txt
diff a.txt b.txt

这次 diff 就会多出那一行------正是「用 TaskCreate 拆解任务」那条。这下「动态规则」就亲眼看到了。

看完把 features 里的 'DUMP_SYSTEM_PROMPT' 去掉、把 cli.tsx 里那行改回 [],再重新打包,恢复默认构建。

系列文章请关注公众号:IT周瑜

相关推荐
转转技术团队1 小时前
没有测试的核心代码,怎么交给 AI 重构
人工智能
爱读源码的大都督2 小时前
Claude Code源码分析(三):为什么系统提示词中需要有tools呢?
前端·人工智能·后端
半个落月3 小时前
LLM如何预测下一个Token?一文拆解Transformer核心流程
人工智能
触底反弹3 小时前
🔥 2026 年爆火的 Harness Engineering 到底是什么?从原理到实战一文讲透
javascript·人工智能·程序员
user4465117917913 小时前
源码深读 XAgent:6 个 Agent 怎么分工?工具失败不崩、死循环怎么防?
人工智能
魏祖潇3 小时前
SDD 完整指南——Spec 端打底、Story 端交付、留白区
人工智能·后端
常丛丛3 小时前
5.9 式输出:实时查看 LangGraph Agent 思考过程
人工智能
Token炼金师3 小时前
从节点图到低秩矩阵:ComfyUI 推理引擎与 LoRA 适配机制拆解
人工智能·aigc
武子康3 小时前
调查研究-210 Netflix 用 AI 复刻 Gene Wilder 的声音:语音克隆的下半场,不是模型,而是权利
人工智能·aigc·openai