在 Claude Code 向大模型发送的提示词中,除了用户自行编写的部分,还内置了一套完整的系统提示词。本文将从源码角度深入拆解这套系统提示词的生成逻辑与核心作用。

在 Claude Code 源码 prompts.ts 中,存在一个名为 getSystemPrompt 的函数,专门用于生成系统提示词。该函数接收四个参数:
tools:当前对话中可用的工具列表model:当前对话所使用的模型名称,例如 "glm-5.2"additionalWorkingDirectories:用户通过--add-dir指定的额外工作目录mcpClients:当前已连接的 MCP 服务器客户端列表
这四个参数全部参与系统提示词的构建。那么问题来了:系统提示词中为何需要显式传递 tools?
通常,Claude Code 在调用大模型时,请求中已经包含了所有工具的定义——名称、描述、参数均已嵌入。模型已经能够感知这些工具的存在,系统提示词中再次提及,似乎多此一举。
实际上,系统提示词的核心任务是从提示词层面告诉模型“如何正确使用这些工具”——也就是设定清晰的调用规则。这些规则可分为两类:
- 通用规则:无论当前工具列表包含哪些,都必须遵守。例如“读取文件请使用专用工具,不要用 cat”、“优先使用专用工具而非 Bash”。
- 动态规则:根据当前启用的工具动态调整。例如,如果工具列表中包含任务管理工具(如
TaskCreate),系统提示词就会额外插入“使用任务工具拆解任务”的指导。
换句话说,仅将工具定义交给模型是不够的,还需要在提示词层面教会模型:什么优先、如何配合、何时使用哪个。而其中部分规则需要根据本次启用的工具动态变化,因此必须将 tools 传入,让代码进行条件判断。
原理清晰后,接下来我们深入代码,看看实际提示词是如何生成的。
tools 传入后,第一步是将其转换为工具名集合,后续判断“是否存在某个工具”完全依赖该集合:
const enabledTools = new Set(tools.map(_ => _.name))
随后,该集合被传入 getUsingYourToolsSection 函数,负责生成系统提示词中“Using your tools”部分的内容。
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`)
}
这段代码实际运行后,在系统提示词中呈现如下(假设未传入任务工具):
# 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 工具后,整份提示词只多出如下一行:
- 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':
features: ['BUDDY', 'DUMP_SYSTEM_PROMPT'],
第二步:重新构建项目。
bun devkit/build.ts
第三步:导出系统提示词。
将提示词重定向至文件,便于后续对比:
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 的工具列表即可:
// 修改前
const prompt = await getSystemPrompt([], model);
// 修改后
const prompt = await getSystemPrompt([{ name: 'TaskCreate' }], model);
修改完成后重新构建并再次导出,使用 diff 工具对比两个文件:
bun devkit/build.ts
bun dist/cli.js --dump-system-prompt > b.txt
diff a.txt b.txt
这次 diff 结果中会多出一行——正是“使用 TaskCreate 拆解任务”的指导语句。动态规则的生成效果一目了然。
