claude-code/docs/context/system-prompt.mdx
weiqianpu cf1707bbb0 feat: 开源发布 — 完整 README、MIT 许可证、22+ 大模型厂商支持
- 重写 README.md:项目简介、快速开始、22+ 厂商列表、功能特性、架构说明、配置指南、FAQ、贡献指南
- 新增 MIT LICENSE
- 包含全部源码与文档
2026-04-02 02:34:24 +00:00

253 lines
12 KiB
Plaintext
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: "System Prompt 动态组装 - AI 工作记忆构建"
description: "深入解析 Claude Code 的 System Prompt 动态组装过程缓存策略、分界标记、Section 注册表、CLAUDE.md 多级合并,以及如何将零散上下文拼装为 API 可消费的缓存友好结构。"
keywords: ["System Prompt", "系统提示词", "动态组装", "CLAUDE.md", "Prompt Cache", "缓存策略"]
---
## 从数组到 API 调用System Prompt 的完整链路
System Prompt 在 Claude Code 中不是一段写死的文本,而是一个 **`string[]` 数组**(品牌类型 `SystemPrompt`,定义于 `src/utils/systemPromptType.ts:8`),经过组装、分块、缓存标记后发送给 API。
### 三阶段管道
```
getSystemPrompt() → string[] (组装内容)
buildEffectiveSystemPrompt() → SystemPrompt (选择优先级路径)
buildSystemPromptBlocks() → TextBlockParam[] (分块 + cache_control 标记)
```
1. **`getSystemPrompt()`**`src/constants/prompts.ts:444`)—— 收集静态段 + 动态段,插入 `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` 分界标记
2. **`buildEffectiveSystemPrompt()`**`src/utils/systemPrompt.ts:41`)—— 按 Override > Coordinator > Agent > Custom > Default 优先级选择
3. **`buildSystemPromptBlocks()`**`src/services/api/claude.ts:3214`)—— 调用 `splitSysPromptPrefix()` 分块,为每个块附加 `cache_control`
## SystemPrompt 品牌类型
```typescript
// src/utils/systemPromptType.ts:8
export type SystemPrompt = readonly string[] & {
readonly __brand: 'SystemPrompt'
}
export function asSystemPrompt(value: readonly string[]): SystemPrompt {
return value as SystemPrompt // 零开销类型断言
}
```
品牌类型branded type防止普通 `string[]` 被意外传入 API 调用——只有通过 `asSystemPrompt()` 显式转换才能获得 `SystemPrompt` 类型。
## getSystemPrompt():内容组装的全景
`src/constants/prompts.ts:444` 是 System Prompt 的核心工厂函数,返回一个有序数组:
| 阶段 | 内容 | 缓存策略 |
|------|------|----------|
| **静态区** | Intro Section、System Rules、Doing Tasks、Actions、Using Tools、Tone & Style、Output Efficiency | 可跨组织缓存(`scope: 'global'` |
| **BOUNDARY** | `SYSTEM_PROMPT_DYNAMIC_BOUNDARY = '__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__'` | 分界标记(不发送给 API |
| **动态区** | Session Guidance、Memory、Model Override、Env Info、Language、Output Style、MCP Instructions、Scratchpad、FRC、Summarize Tool Results、Token Budget、Brief | 每次会话不同(`scope: 'org'` 或无缓存) |
### 动态区的 Section 注册表
动态区通过 `systemPromptSection()` / `DANGEROUS_uncachedSystemPromptSection()` 注册,这两个工厂函数定义于 `src/constants/systemPromptSections.ts`
```typescript
// 缓存式 Section计算一次/clear 或 /compact 后才重新计算
systemPromptSection('memory', () => loadMemoryPrompt())
// 危险:每轮重新计算,会破坏 Prompt Cache
DANGEROUS_uncachedSystemPromptSection(
'mcp_instructions',
() => isMcpInstructionsDeltaEnabled() ? null : getMcpInstructionsSection(mcpClients),
'MCP servers connect/disconnect between turns' // 必须给出破坏缓存的理由
)
```
`resolveSystemPromptSections()` 在每轮查询时解析所有 Section对于 `cacheBreak: false` 的 Section优先使用 `getSystemPromptSectionCache()` 中的缓存值。只有 MCP 指令等真正动态的内容使用 `DANGEROUS_uncachedSystemPromptSection`。
### `CLAUDE_CODE_SIMPLE` 快速路径
当环境变量 `CLAUDE_CODE_SIMPLE` 为真时,整个 System Prompt 缩减为一行:
```typescript
`You are Claude Code, Anthropic's official CLI for Claude.\n\nCWD: ${getCwd()}\nDate: ${getSessionStartDate()}`
```
跳过所有 Section 注册、缓存分块、动态组装——用于最小化 token 消耗的测试场景。
## buildEffectiveSystemPrompt():五级优先级
`src/utils/systemPrompt.ts:41` 决定最终使用哪个 System Prompt
| 优先级 | 条件 | 行为 |
|--------|------|------|
| **0. Override** | `overrideSystemPrompt` 非空 | 完全替换,返回 `[override]` |
| **1. Coordinator** | `COORDINATOR_MODE` feature + 环境变量 | 使用协调者专用提示词 |
| **2. Agent** | `mainThreadAgentDefinition` 存在 | Proactive 模式:追加到默认提示词尾部;否则:替换默认提示词 |
| **3. Custom** | `--system-prompt` 参数指定 | 替换默认提示词 |
| **4. Default** | 无特殊条件 | 使用 `getSystemPrompt()` 完整输出 |
`appendSystemPrompt` 始终追加到末尾Override 除外)。
## 缓存策略:分块、标记、命中
这是 System Prompt 设计中最精密的部分。
### Anthropic Prompt Cache 基础
Anthropic API 的 Prompt Cache 允许跨请求复用相同的 System Prompt 前缀,按缓存命中量计费(远低于完整输入价格)。缓存键由内容的 Blake2b 哈希决定——任何字符变化都会导致缓存失效。
### `splitSysPromptPrefix()`:三种分块模式
`src/utils/api.ts:321` 是缓存策略的核心,根据条件选择三种分块模式:
#### 模式 1MCP 工具存在时(`skipGlobalCacheForSystemPrompt=true`
```
[attribution header] → cacheScope: null (不缓存)
[system prompt prefix] → cacheScope: 'org' (组织级缓存)
[everything else] → cacheScope: 'org' (组织级缓存)
```
MCP 工具列表在会话中可能变化(连接/断开),破坏了跨组织缓存的基础,因此降级为组织级。
#### 模式 2Global Cache + Boundary 存在1P 专用)
```
[attribution header] → cacheScope: null (不缓存)
[system prompt prefix] → cacheScope: null (不缓存)
[static content] → cacheScope: 'global' (全局缓存!跨组织共享)
[dynamic content] → cacheScope: null (不缓存)
```
这是缓存效率最高的模式。`SYSTEM_PROMPT_DYNAMIC_BOUNDARY` 之前的静态内容Intro、Rules、Tone & Style 等)对所有用户相同,可跨组织缓存。
#### 模式 3默认3P 提供商 或 Boundary 缺失)
```
[attribution header] → cacheScope: null (不缓存)
[system prompt prefix] → cacheScope: 'org' (组织级缓存)
[everything else] → cacheScope: 'org' (组织级缓存)
```
### `getCacheControl()`TTL 决策
`src/services/api/claude.ts:359` 生成的 `cache_control` 对象:
```typescript
{
type: 'ephemeral',
ttl?: '1h', // 仅特定 querySource 符合条件时
scope?: 'global', // 仅静态区
}
```
1 小时 TTL 的判定逻辑(`should1hCacheTTL()`,第 394 行):
- **Bedrock 用户**:通过环境变量 `ENABLE_PROMPT_CACHING_1H_BEDROCK` 启用
- **1P 用户**:通过 GrowthBook 配置的 `allowlist` 数组匹配 `querySource`,支持前缀通配符(如 `"repl_main_thread*"`
- **会话级锁定**:资格判定结果在 bootstrap state 中缓存,防止 GrowthBook 配置中途变化导致同一会话内 TTL 不一致
### 缓存破坏Session-Specific Guidance 的放置
`getSessionSpecificGuidanceSection()``src/constants/prompts.ts:352`)的内容必须放在 `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` **之后**。因为它包含:
- 当前会话的 enabledTools 集合
- `isForkSubagentEnabled()` 的运行时判定
- `getIsNonInteractiveSession()` 的结果
这些运行时 bit 如果放在静态区,会产生 2^N 种 Blake2b 哈希变体N = 运行时条件数),完全破坏缓存命中率。源码注释明确警告:
> Each conditional here is a runtime bit that would otherwise multiply the Blake2b prefix hash variants (2^N). See PR #24490, #24171 for the same bug class.
### `CLAUDE_CODE_SIMPLE` 模式
当设置了 `CLAUDE_CODE_SIMPLE` 环境变量时,整个系统提示词会大幅缩减:
```typescript
return [`You are Claude Code, Anthropic's official CLI for Claude.\n\nCWD: ${getCwd()}\nDate: ${getSessionStartDate()}`]
```
## 上下文注入System Context 与 User Context
System Prompt 数组本身不包含运行时上下文git 状态、CLAUDE.md 内容)。上下文通过两个独立的管道注入:
### System Context`src/context.ts:116`
```typescript
export const getSystemContext = memoize(async () => {
return {
gitStatus, // git 分支、状态、最近提交(截断至 MAX_STATUS_CHARS=2000
cacheBreaker, // 仅 ant 用户的缓存破坏器
}
})
```
- 使用 `lodash.memoize` 缓存——**整个会话期间只计算一次**
- Git 状态快照包含 5 个并行 `git` 命令branch、defaultBranch、status、log、userName
- `status` 超过 2000 字符时截断并附加提示使用 BashTool 获取更多信息
- `systemPromptInjection` 变更时,通过 `getUserContext.cache.clear?.()` 清除所有上下文缓存
### User Context`src/context.ts:155`
```typescript
export const getUserContext = memoize(async () => {
return {
claudeMd, // 合并后的 CLAUDE.md 内容
currentDate, // "Today's date is YYYY-MM-DD."
}
})
```
- **CLAUDE.md 禁用条件**`CLAUDE_CODE_DISABLE_CLAUDE_MDS` 环境变量,或 `--bare` 模式(除非通过 `--add-dir` 显式指定目录)
- `--bare` 模式的语义是"跳过我没要求的东西"而非"忽略所有"
### 注入位置
在 `src/query.ts:449`
```typescript
// System Context 追加到 System Prompt 尾部
const fullSystemPrompt = asSystemPrompt(
appendSystemContext(systemPrompt, systemContext) // 简单拼接
)
```
User Context 通过 `prependUserContext()``src/utils/api.ts:449`)注入为 `<system-reminder>` 标签包裹的首条用户消息,放在所有对话消息之前。
## Attribution Header计费与安全
每个 API 请求的 System Prompt 首块是 Attribution Header`src/constants/system.ts:30`),包含:
- **`cc_version`**Claude Code 版本 + 指纹
- **`cc_entrypoint`**入口点标识REPL / SDK / pipe 等)
- **`cch=00000`**NATIVE_CLIENT_ATTESTATION 启用时Bun 原生 HTTP 层在发送前将零替换为计算出的哈希值,服务器验证此 token 确认请求来自真实 Claude Code 客户端
Header 始终 `cacheScope: null`——它因版本和指纹不同而变化,不适合缓存。
## CLAUDE.md项目级知识注入
这是 Claude Code 最巧妙的设计之一。在项目根目录放一个 `CLAUDE.md` 文件,就能让 AI "理解" 你的项目:
- **项目概述**:这个项目做什么、用了什么技术栈
- **开发约定**:代码风格、命名规范、分支策略
- **常用命令**:怎么构建、怎么测试、怎么部署
- **注意事项**:已知的坑、特殊的配置
系统会自动发现并合并多级 CLAUDE.md
```
~/.claude/CLAUDE.md ← 用户全局(个人偏好)
└── /project/CLAUDE.md ← 项目根目录(团队共享)
└── /project/src/CLAUDE.md ← 子目录(模块特定)
```
加载逻辑在 `src/utils/claudemd.ts` 中的 `getClaudeMds()` 和 `getMemoryFiles()` 实现——从 CWD 向上遍历目录树,合并所有匹配的 CLAUDE.md 文件内容。
## 设计洞察:为什么是 `string[]` 而非单个 `string`
将 System Prompt 设计为数组而非单段文本,是为了 **缓存分块**
1. Anthropic Prompt Cache 以 **内容块**TextBlock为缓存单位
2. 将 System Prompt 拆为多个块可以让不变的部分Intro、Rules获得独立的缓存命中
3. 如果是单个 `string`,任何一个字符变化(如日期更新)都会导致整个 System Prompt 的缓存失效
4. `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` 标记允许 `splitSysPromptPrefix()` 精确地将静态区标记为 `scope: 'global'`,动态区不标记或标记为 `scope: 'org'`
这是 Claude Code 在 token 成本优化上的核心设计——一次典型的 System Prompt 约 20K+ tokens通过缓存分块可以节省 30-50% 的输入 token 费用。