2026-06-08 23:08:57 +08:00
|
|
|
|
---
|
2026-06-09 23:15:17 +08:00
|
|
|
|
tags:
|
|
|
|
|
|
- system-prompt
|
|
|
|
|
|
- 缓存策略
|
|
|
|
|
|
- 动态组装
|
|
|
|
|
|
- Prompt-Cache
|
|
|
|
|
|
create time: 2026-06-09 22:15
|
2026-06-08 23:08:57 +08:00
|
|
|
|
---
|
|
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
# System Prompt 动态组装 - AI 工作记忆构建
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
## 概述
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
Claude Code 的 System Prompt 不是一段写死的文本,而是通过三阶段管道动态组装的 `string[]` 数组,支持分块缓存、五级优先级选择、CLAUDE.md 多级合并和多种 Provider 适配。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
## 正文
|
|
|
|
|
|
|
|
|
|
|
|
### 从数组到 API 调用: System Prompt 的完整链路
|
|
|
|
|
|
|
|
|
|
|
|
System Prompt 在 Claude Code 中是一个 **`string[]` 数组**(品牌类型 `SystemPrompt`,定义于 `src/utils/systemPromptType.ts:8`),经过组装、分块、缓存标记后发送给 API。
|
|
|
|
|
|
|
|
|
|
|
|
```mermaid
|
|
|
|
|
|
flowchart TD
|
|
|
|
|
|
A["getSystemPrompt string[] 组装内容"] --> B["buildEffectiveSystemPrompt SystemPrompt 选择优先级路径"]
|
|
|
|
|
|
B --> C["buildSystemPromptBlocks TextBlockParam[] 分块+cache_control标记"]
|
2026-06-08 23:08:57 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
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:3279`)—— 调用 `splitSysPromptPrefix()` 分块,为每个块附加 `cache_control`
|
|
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
### SystemPrompt 品牌类型
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
|
|
|
|
|
```typescript
|
2026-06-09 23:15:17 +08:00
|
|
|
|
// packages/@ant/model-provider/src/types/systemPrompt.ts
|
2026-06-08 23:08:57 +08:00
|
|
|
|
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` 类型。
|
|
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
### getSystemPrompt(): 内容组装的全景
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
`src/constants/prompts.ts:444` 是 System Prompt 的核心工厂函数,返回一个有序数组:
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
|
|
|
|
|
| 阶段 | 内容 | 缓存策略 |
|
|
|
|
|
|
|------|------|----------|
|
|
|
|
|
|
| **静态区** | Intro Section、System Rules、Doing Tasks、Actions、Using Tools、Tone & Style、Output Efficiency | 可跨组织缓存(`scope: 'global'`) |
|
2026-06-09 23:15:17 +08:00
|
|
|
|
| **BOUNDARY** | `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` | 分界标记(不发送给 API,仅用于分割静态区与动态区) |
|
|
|
|
|
|
| **动态区** | Session Guidance、Memory、Model Override、Env Info、Language 等 | 每次会话不同(`scope: 'org'` 或无缓存) |
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
> [!info] Boundary 的作用
|
|
|
|
|
|
> 它把 System Prompt 分成"不变的静态区"和"因用户/会话而异的动态区"。静态区对所有用户相同,可获得 `scope: 'global'` 跨组织缓存;动态区每次不同,只能 `scope: 'org'` 或不缓存。它本身是一个特殊字符串,在发送给 API 前被移除,AI 永远看不到。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
#### 动态区的 Section 注册表
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
动态区通过 `systemPromptSection()` / `DANGEROUS_uncachedSystemPromptSection()` 注册(`src/constants/systemPromptSections.ts`):
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
|
|
|
|
|
```typescript
|
2026-06-09 23:15:17 +08:00
|
|
|
|
// 缓存式 Section: 计算一次,/clear 或 /compact 后才重新计算
|
2026-06-08 23:08:57 +08:00
|
|
|
|
systemPromptSection('memory', () => loadMemoryPrompt())
|
|
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
// 危险: 每轮重新计算,会破坏 Prompt Cache
|
2026-06-08 23:08:57 +08:00
|
|
|
|
DANGEROUS_uncachedSystemPromptSection(
|
|
|
|
|
|
'mcp_instructions',
|
|
|
|
|
|
() => isMcpInstructionsDeltaEnabled() ? null : getMcpInstructionsSection(mcpClients),
|
|
|
|
|
|
'MCP servers connect/disconnect between turns' // 必须给出破坏缓存的理由
|
|
|
|
|
|
)
|
|
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
`resolveSystemPromptSections()` 在每轮查询时解析所有 Section,对于 `cacheBreak: false` 的 Section,优先使用缓存值。只有 MCP 指令等真正动态的内容使用 `DANGEROUS_uncachedSystemPromptSection`。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
#### CLAUDE_CODE_SIMPLE 快速路径
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
当环境变量 `CLAUDE_CODE_SIMPLE` 为真时,整个 System Prompt 缩减为一行,跳过所有 Section 注册、缓存分块、动态组装——用于最小化 token 消耗的测试场景。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
### buildEffectiveSystemPrompt(): 五级优先级
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
`src/utils/systemPrompt.ts:41` 决定最终使用哪个 System Prompt:
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
|
|
|
|
|
| 优先级 | 条件 | 行为 |
|
|
|
|
|
|
|--------|------|------|
|
|
|
|
|
|
| **0. Override** | `overrideSystemPrompt` 非空 | 完全替换,返回 `[override]` |
|
|
|
|
|
|
| **1. Coordinator** | `COORDINATOR_MODE` feature + 环境变量 | 使用协调者专用提示词 |
|
2026-06-09 23:15:17 +08:00
|
|
|
|
| **2. Agent** | `mainThreadAgentDefinition` 存在 | Proactive 模式: 追加到默认提示词尾部;否则: 替换默认提示词 |
|
2026-06-08 23:08:57 +08:00
|
|
|
|
| **3. Custom** | `--system-prompt` 参数指定 | 替换默认提示词 |
|
|
|
|
|
|
| **4. Default** | 无特殊条件 | 使用 `getSystemPrompt()` 完整输出 |
|
|
|
|
|
|
|
|
|
|
|
|
`appendSystemPrompt` 始终追加到末尾(Override 除外)。
|
|
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
### Provider 系统概述
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
Claude Code 支持多种 API 提供商:
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
|
|
|
|
|
| 类别 | Provider | 环境变量 | 说明 |
|
|
|
|
|
|
|------|----------|---------|------|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
| **1P** | `firstParty` | 默认 | Anthropic 官方 API 直连 |
|
|
|
|
|
|
| **3P** | `bedrock` | `CLAUDE_CODE_USE_BEDROCK=1` | AWS Bedrock 托管服务 |
|
2026-06-08 23:08:57 +08:00
|
|
|
|
| **3P** | `vertex` | `CLAUDE_CODE_USE_VERTEX=1` | Google Vertex AI |
|
|
|
|
|
|
| **3P** | `openai` | `CLAUDE_CODE_USE_OPENAI=1` | OpenAI 兼容层(Ollama/DeepSeek/vLLM) |
|
|
|
|
|
|
| **3P** | `gemini` | `CLAUDE_CODE_USE_GEMINI=1` | Google Gemini API |
|
|
|
|
|
|
| **3P** | `grok` | `CLAUDE_CODE_USE_GROK=1` | xAI Grok |
|
|
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
Provider 决定了可用的 beta headers、缓存策略(全局缓存仅 1P 可用)和 Token 计数方式。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
### 缓存策略: 分块、标记、命中
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
|
|
|
|
|
这是 System Prompt 设计中最精密的部分。
|
|
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
**Anthropic Prompt Cache 基础**: 允许跨请求复用相同的 System Prompt 前缀,按缓存命中量计费。缓存键由内容的 Blake2b 哈希决定——任何字符变化都会导致缓存失效。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
#### splitSysPromptPrefix(): 三种分块模式
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
`src/utils/api.ts:321` 是缓存策略的核心:
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
**模式 1: MCP 工具存在时**(`skipGlobalCacheForSystemPrompt=true`)
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
|
|
|
|
|
```
|
2026-06-09 23:15:17 +08:00
|
|
|
|
[attribution header] -> cacheScope: null (不缓存)
|
|
|
|
|
|
[system prompt prefix] -> cacheScope: 'org' (组织级缓存)
|
|
|
|
|
|
[everything else] -> cacheScope: 'org' (组织级缓存)
|
2026-06-08 23:08:57 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
MCP 工具列表在会话中可能变化,破坏了跨组织缓存的基础,因此降级为组织级。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
**模式 2: Global Cache + Boundary 存在**(1P 专用)
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
|
|
|
|
|
```
|
2026-06-09 23:15:17 +08:00
|
|
|
|
[attribution header] -> cacheScope: null (不缓存)
|
|
|
|
|
|
[system prompt prefix] -> cacheScope: null (不缓存)
|
|
|
|
|
|
[static content] -> cacheScope: 'global' (全局缓存! 跨组织共享)
|
|
|
|
|
|
[dynamic content] -> cacheScope: null (不缓存)
|
2026-06-08 23:08:57 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
这是缓存效率最高的模式。`SYSTEM_PROMPT_DYNAMIC_BOUNDARY` 之前的静态内容对所有用户相同,可跨组织缓存。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
> [!tip] Boundary 插入条件
|
|
|
|
|
|
> `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` 标记仅在 `getAPIProvider() === 'firstParty'` 且未禁用实验性功能时插入。3P 用户(Bedrock/Vertex/OpenAI/Gemini)永远不存在 Boundary,始终使用模式 3。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
**模式 3: 默认**(3P 提供商或 Boundary 缺失)
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
所有内容块使用 `cacheScope: 'org'`(组织级缓存)。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
#### getCacheControl(): TTL 决策
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
`src/services/api/claude.ts:348` 生成的 `cache_control` 对象支持 1 小时 TTL(通过 GrowthBook 配置的 allowlist 匹配 `querySource`),会话级资格判定结果在 bootstrap state 中缓存防止中途变化。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
#### 缓存破坏: Session-Specific Guidance 的放置
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
`getSessionSpecificGuidanceSection()` 的内容必须放在 `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` **之后**,因为它包含运行时条件(enabledTools、`isForkSubagentEnabled()`、`getIsNonInteractiveSession()`)。这些运行时 bit 如果放在静态区,会产生 2^N 种 Blake2b 哈希变体,完全破坏缓存命中率。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
### 上下文注入: System Context 与 User Context
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
System Prompt 数组本身不包含运行时上下文。上下文通过两个独立的管道注入:
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
**System Context**(`src/context.ts:116`): 使用 `lodash.memoize` 缓存,整个会话期间只计算一次。包含 git 状态(5 个并行 git 命令的快照)和可选的缓存破坏器。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
**User Context**(`src/context.ts:155`): 同样使用 `memoize` 缓存。包含合并后的 CLAUDE.md 内容和当前日期。禁用条件: `CLAUDE_CODE_DISABLE_CLAUDE_MDS` 环境变量或 `--bare` 模式。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
**注入位置**: System Context 追加到 System Prompt 尾部(`src/query.ts:449`);User Context 通过 `prependUserContext()` 注入为 `<system-reminder>` 标签包裹的首条用户消息。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
### Attribution Header: 计费与安全
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
每个 API 请求的 System Prompt 首块是 Attribution Header(`src/constants/system.ts:30`),包含版本标识、入口点标识和可选的客户端证明 token。Header 始终 `cacheScope: null`——它因版本和指纹不同而变化,不适合缓存。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
### CLAUDE.md: 项目级知识注入
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
在项目根目录放一个 `CLAUDE.md` 文件,就能让 AI "理解" 你的项目——项目概述、开发约定、常用命令、注意事项。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
系统会自动发现并合并多级 CLAUDE.md:
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
```mermaid
|
|
|
|
|
|
flowchart TD
|
|
|
|
|
|
A["~/.claude/CLAUDE.md 用户全局 个人偏好"] --> B["/project/CLAUDE.md 项目根目录 团队共享"]
|
|
|
|
|
|
B --> C["/project/src/CLAUDE.md 子目录 模块特定"]
|
2026-06-08 23:08:57 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
加载逻辑在 `src/utils/claudemd.ts` 中的 `getClaudeMds()` 和 `getMemoryFiles()` 实现——从 CWD 向上遍历目录树,合并所有匹配的 CLAUDE.md 文件内容。
|
|
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
### 设计洞察: 为什么是 string[] 而非单个 string
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
将 System Prompt 设计为数组而非单段文本,是为了**缓存分块**:
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
1. Anthropic Prompt Cache 以内容块(TextBlock)为缓存单位
|
|
|
|
|
|
2. 将 System Prompt 拆为多个块,可以让不变的部分获得独立的缓存命中
|
|
|
|
|
|
3. 如果是单个 `string`,任何一个字符变化都会导致整个 System Prompt 的缓存失效
|
|
|
|
|
|
4. `SYSTEM_PROMPT_DYNAMIC_BOUNDARY` 标记允许精确地将静态区标记为 `scope: 'global'`
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
一次典型的 System Prompt 约 20K+ tokens,通过缓存分块可以节省 30-50% 的输入 token 费用。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
### 兼容层: OpenAI 与 Gemini
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
|
|
|
|
|
Claude Code 提供了 OpenAI 和 Gemini 协议的兼容层,允许使用非 Anthropic 端点。
|
|
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
**OpenAI 兼容层**(`CLAUDE_CODE_USE_OPENAI=1`): 支持任意 OpenAI Chat Completions 协议端点(Ollama、DeepSeek、vLLM 等)。采用流适配器模式: 将 Anthropic 格式请求转换为 OpenAI 格式 -> 调用端点 -> 将 SSE 流转换回 `BetaRawMessageStreamEvent`。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
**Gemini 兼容层**(`CLAUDE_CODE_USE_GEMINI=1`): 支持 Google Gemini API,同样通过流适配器模式屏蔽差异。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
> [!warning] 兼容层的限制
|
|
|
|
|
|
> 使用 3P 兼容层时,部分功能受限: 无精确 token 计数(退回到近似估算)、无全局缓存、部分 beta 功能不可用。
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
## 关联笔记
|
2026-06-08 23:08:57 +08:00
|
|
|
|
|
2026-06-09 23:15:17 +08:00
|
|
|
|
- [[compaction]] - 上下文压缩与 Session Memory
|
|
|
|
|
|
- [[token-budget]] - Token 预算动态计算
|
|
|
|
|
|
- [[project-memory]] - 项目记忆系统与 CLAUDE.md
|
|
|
|
|
|
- [[../conversation/the-loop]] - Agentic Loop 中的 System Prompt 使用
|