Skip to main content

从数组到 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:3279)—— 调用 splitSysPromptPrefix() 分块,为每个块附加 cache_control

SystemPrompt 品牌类型

// packages/@ant/model-provider/src/types/systemPrompt.ts:4
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'
BOUNDARYSYSTEM_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' 或无缓存)
Boundary 是什么:它把 System Prompt 分成”不变的静态区”和”因用户/会话而异的动态区”。静态区对所有用户相同,可获得 scope: 'global' 跨组织缓存;动态区每次不同,只能 scope: 'org' 或不缓存。它本身是一个特殊字符串,在发送给 API 前被移除,AI 永远看不到。

动态区的 Section 注册表

动态区通过 systemPromptSection() / DANGEROUS_uncachedSystemPromptSection() 注册,这两个工厂函数定义于 src/constants/systemPromptSections.ts
// 缓存式 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 缩减为一行:
`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. OverrideoverrideSystemPrompt 非空完全替换,返回 [override]
1. CoordinatorCOORDINATOR_MODE feature + 环境变量使用协调者专用提示词
2. AgentmainThreadAgentDefinition 存在Proactive 模式:追加到默认提示词尾部;否则:替换默认提示词
3. Custom--system-prompt 参数指定替换默认提示词
4. Default无特殊条件使用 getSystemPrompt() 完整输出
appendSystemPrompt 始终追加到末尾(Override 除外)。

Provider 系统概述

Claude Code 支持多种 API 提供商,分为两大类:
类别Provider环境变量说明
1P (First Party)firstParty默认Anthropic 官方 API 直连
3P (Third Party)bedrockCLAUDE_CODE_USE_BEDROCK=1AWS Bedrock 托管服务
3PvertexCLAUDE_CODE_USE_VERTEX=1Google Vertex AI
3PopenaiCLAUDE_CODE_USE_OPENAI=1OpenAI 兼容层(Ollama/DeepSeek/vLLM)
3PgeminiCLAUDE_CODE_USE_GEMINI=1Google Gemini API
3PgrokCLAUDE_CODE_USE_GROK=1xAI Grok
Provider 决定了:
  • 可用的 beta headers:部分 beta 功能仅限 1P 用户
  • 缓存策略:全局缓存 scope: 'global' 仅 1P 可用
  • Token 计数方式:Bedrock 有独立的 countTokens 端点,OpenAI/Gemini 依赖估算
// src/utils/model/providers.ts:5-13
export type APIProvider =
  | 'firstParty'    // 1P - Anthropic 直连
  | 'bedrock'       // 3P - AWS Bedrock
  | 'vertex'        // 3P - Google Vertex
  | 'foundry'       // 3P - Anthropic Foundry
  | 'openai'        // 3P - OpenAI 兼容层
  | 'gemini'        // 3P - Google Gemini
  | 'grok'          // 3P - xAI Grok

缓存策略:分块、标记、命中

这是 System Prompt 设计中最精密的部分。

Anthropic Prompt Cache 基础

Anthropic API 的 Prompt Cache 允许跨请求复用相同的 System Prompt 前缀,按缓存命中量计费(远低于完整输入价格)。缓存键由内容的 Blake2b 哈希决定——任何字符变化都会导致缓存失效。

splitSysPromptPrefix():三种分块模式

src/utils/api.ts:321 是缓存策略的核心,根据条件选择三种分块模式:

模式 1:MCP 工具存在时(skipGlobalCacheForSystemPrompt=true

[attribution header]    → cacheScope: null     (不缓存)
[system prompt prefix]  → cacheScope: 'org'    (组织级缓存)
[everything else]       → cacheScope: 'org'    (组织级缓存)
MCP 工具列表在会话中可能变化(连接/断开),破坏了跨组织缓存的基础,因此降级为组织级。

模式 2:Global 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 等)对所有用户相同,可跨组织缓存。
Boundary 插入条件SYSTEM_PROMPT_DYNAMIC_BOUNDARY 标记仅在特定条件下插入:
// src/utils/betas.ts:226-229
export function shouldUseGlobalCacheScope(): boolean {
  return (
    getAPIProvider() === 'firstParty' &&
    !isEnvTruthy(process.env.CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS)
  )
}
// src/constants/prompts.ts:574
...(shouldUseGlobalCacheScope() ? [SYSTEM_PROMPT_DYNAMIC_BOUNDARY] : []),
这意味着:
  • 3P 用户(Bedrock/Vertex/OpenAI/Gemini):Boundary 永远不存在,始终使用模式 3
  • 1P 用户禁用实验性功能:设置 CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1,Boundary 不插入
  • 1P 用户默认:Boundary 存在,使用模式 2(最高缓存效率)

模式 3:默认(3P 提供商 或 Boundary 缺失)

[attribution header]    → cacheScope: null     (不缓存)
[system prompt prefix]  → cacheScope: 'org'    (组织级缓存)
[everything else]       → cacheScope: 'org'    (组织级缓存)

getCacheControl():TTL 决策

src/services/api/claude.ts:348 生成的 cache_control 对象:
{
  type: 'ephemeral',
  ttl?: '1h',         // 仅特定 querySource 符合条件时
  scope?: 'global',   // 仅静态区
}
1 小时 TTL 的判定逻辑(should1hCacheTTL(),第 383 行):
  • 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:354)的内容必须放在 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 环境变量时,整个系统提示词会大幅缩减:
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

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

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
// 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 费用。

兼容层:OpenAI 与 Gemini

Claude Code 提供了 OpenAI 和 Gemini 协议的兼容层,允许使用非 Anthropic 端点。

OpenAI 兼容层

通过 CLAUDE_CODE_USE_OPENAI=1 启用,支持任意 OpenAI Chat Completions 协议端点(Ollama、DeepSeek、vLLM 等)。 实现采用流适配器模式
  1. 将 Anthropic 格式请求转换为 OpenAI 格式
  2. 调用 OpenAI 兼容端点
  3. 将 SSE 流转换回 BetaRawMessageStreamEvent
  4. 下游代码完全无感知
src/services/api/openai/
├── client.ts           # OpenAI 客户端配置
├── convertMessages.ts  # 消息格式转换(Anthropic → OpenAI)
├── convertTools.ts     # 工具定义转换
├── streamAdapter.ts    # SSE 流适配(OpenAI → Anthropic)
├── modelMapping.ts     # 模型名称映射
└── index.ts            # 入口函数 queryModelOpenAI()
关键环境变量:
  • CLAUDE_CODE_USE_OPENAI=1 — 启用 OpenAI provider
  • OPENAI_API_KEY — API 密钥
  • OPENAI_BASE_URL — API 端点(默认 https://api.openai.com/v1
  • OPENAI_MODEL — 直接指定模型名

Gemini 兼容层

通过 CLAUDE_CODE_USE_GEMINI=1 启用,支持 Google Gemini API。
src/services/api/gemini/
├── client.ts           # Gemini 客户端配置
├── convertMessages.ts  # 消息格式转换
├── convertTools.ts     # 工具定义转换
├── streamAdapter.ts    # 流适配
├── modelMapping.ts     # 模型名称映射
├── types.ts            # 类型定义
└── index.ts            # 入口函数
关键环境变量:
  • CLAUDE_CODE_USE_GEMINI=1 — 启用 Gemini provider
  • GEMINI_API_KEY — API 密钥
  • GEMINI_BASE_URL — API 端点(默认 https://generativelanguage.googleapis.com/v1beta
  • GEMINI_MODEL — 直接指定模型名
  • GEMINI_DEFAULT_SONNET_MODEL / GEMINI_DEFAULT_OPUS_MODEL — 按能力级别映射

兼容层的限制

使用 3P 兼容层时,部分功能受限:
  • 无精确 token 计数:系统退回到近似估算,影响自动压缩触发时机
  • 无全局缓存:只能使用组织级缓存 scope: 'org'
  • 部分 beta 功能不可用:依赖 Anthropic 特有 beta headers 的功能受限
详见 docs/plans/openai-compatibility.mdCLAUDE.md 中的相关章节。