Skip to content

2026-04-02 星期四

【版本控制】Git 移除已追踪文件并忽略的正确步骤


当文件已被 Git 追踪但需要改为忽略时,不能直接修改 .gitignore,需执行以下流程:

  1. 更新 .gitignore: 将目标文件路径加入忽略列表。
  2. 清理暂存区: 执行 git rm --cached <file>
    • 关键参数 --cached: 仅从 Git 索引中移除追踪,保留本地物理文件
  3. 提交变更: 将 .gitignore 的更新和移除追踪的改动一并提交。
bash
# 示例:移除 TODO.md 的追踪
git rm --cached TODO.md
git add .gitignore
git commit -m "chore: 移除 TODO.md 追踪"
最近 claude code 的源码泄露了,网上有很多对源码进行解析讲解的。大多数解析内容都提到了 claude code 中有一个 SYSTEM_PROMPT_DYNAMIC_BOUNDARY ,提示词 “拼装” 机制,静态部分的提示词可以缓存,减少 token 费用,我不明白为什么缓存静态部分就可以节省费用?难道提示词不都是最终要输入给 AI 大模型进行输出的吗?就算是缓存难道不是在服务器后端把静态提示词缓存,然后发送请求的时候自动携带上吗?最终模型接收到的提示词都是完整的啊?为什么说会节省token?这其中的机制我完全不理解!整个提示词拼接机制,帮我讲解明白
好,我现在有了充分的资料,让我为你系统地讲清楚这个机制。这是一个非常精妙的问题,你的困惑也很有代表性。让我从底层原理到上层应用一步步讲透。

---

# 🧠 彻底讲清楚:Prompt Caching + SYSTEM_PROMPT_DYNAMIC_BOUNDARY

---

## 一、你的误解在哪里?先破除核心误区

你的直觉是:
> "就算缓存,不也是最终把完整提示词发给模型吗?模型接收到的 token 是一样多的,为什么说节省了 token?"

这个误解的根源是:**你把"缓存"理解成了传统软件缓存(比如 Redis 缓存 HTTP 响应)**,以为是"存一个字符串,下次复用字符串内容"。

但 **LLM 的 Prompt Caching 缓存的不是文本字符串,而是模型处理这段文本后产生的中间计算结果(KV Cache)**。

---

## 二、必须先理解 LLM 推理的底层机制:KV Cache

要彻底理解这个问题,必须先了解 Transformer 模型推理时内部发生了什么。

### Transformer 的 Attention 机制

LLM 处理 prompt 的核心是 **注意力机制(Attention)**。对于每一个 token,模型需要计算:
- **Query (Q)**:当前 token 在问"我应该关注哪些信息?"
- **Key (K)**:每个 token 在说"我包含什么信息"
- **Value (V)**:每个 token 实际贡献的内容

注意力机制的工作是帮助 LLM 理解 prompt 中每个 token 之间的关系,通过以加权方式组合各 token 的 embedding 来实现。

### 什么是 KV Cache?

当模型处理一段 prompt 时,对每个 token 都会产生对应的 **K 向量和 V 向量**。这个计算是极其昂贵的(需要大量矩阵乘法,消耗 GPU 算力)。

**关键洞察**:如果下一个请求的 prompt **前缀部分完全相同**,那么这部分 token 的 K、V 向量结果是 **完全一致的** —— 根本不需要重新计算!

Prompt caching 的核心思想就是存储与静态 prompt 部分关联的 Key-Value 向量。由于这些部分在请求间不会变化,它们的 K-V 对是一致的。第一次请求时,模型处理整个 prompt 并生成和存储静态部分的 K-V 对;后续请求使用相同 prompt 前缀时,直接检索已缓存的 K-V 对,无需重新计算;只有新的动态部分(如下一条用户消息)需要处理。

### 用一个比喻彻底理解

想象你是老师,每节课要先背诵一份 100 页的教材(static prompt),然后再回答学生的问题。

- **没有缓存**:每次上课都要重新从头到尾背一遍 100 页教材,再回答问题
- **有 KV Cache**:第一次背完后,把你"理解教材"的思维结构(KV 矩阵)存入大脑记忆,下次上课直接从记忆中提取,跳过背诵,直接回答问题

**模型接收到的 prompt 字节确实是完整的,但它不需要重新"思考"静态部分!** 静态部分的算力消耗被跳过了。

---

## 三、Prompt Caching 的收费模型

Cache breakpoint 本身是免费的。你只需要为以下几项付费:将内容写入缓存(比标准输入 token 贵 25%);从缓存读取(只需标准输入 token 价格的 10%);以及未缓存内容的常规输入 token。

用数字来感受一下:

**Cache hit(缓存命中)只需标准输入价格的 10%**,这意味着对于 5 分钟 TTL(1.25 倍写入成本),只需一次缓存读取就能回本;对于 1 小时 TTL(2 倍写入成本),只需两次缓存读取就能回本。

所以:
```
普通 token 费用:  1.00x
缓存写入费用:     1.25x(只有第一次才有)
缓存命中费用:     0.10x  ← 省了 90%!
```

举个极端例子:一个 Coding Agent 跑 50 轮对话,系统提示词有 10,000 token,如果不缓存,你悄悄地为这份每轮都重复发送的指令支付了 500,000 token 的费用。

---

## 四、为什么每次对话都要重发 prompt?

这也是很多人的困惑点。

**每次你发送消息,整个对话历史都会被重新发送给 API。** 第一条消息发送的是:系统 prompt + 工具定义 + CLAUDE.md + 你的消息。第十条消息发送的是以上所有内容,加上前 9 轮对话。第五十条消息发送的是从开始到现在的所有内容,加上 49 轮对话。没有缓存的话,模型每次都要从头重新处理所有 token。

这是 LLM API 的工作方式——它是无状态的,每次 API 调用都是独立的,没有"记忆"上一次调用的状态。

---

## 五、SYSTEM_PROMPT_DYNAMIC_BOUNDARY 的具体机制

现在你理解了 KV Cache,再来看 Claude Code 的设计就会豁然开朗。

### 系统提示词的结构

系统提示词通过一个特殊标记 `__SYSTEM_PROMPT_DYNAMIC_BOUNDARY__` 分割为两部分:标记之前(静态部分)是:身份定义、工具描述、编码风格规则、安全指南——每次 API 调用都完全相同的字节,可以被缓存,费用极低。

标记之后(动态部分)是:MCP 服务器连接信息、记忆文件(CLAUDE.md)、环境信息——在不同请求间会发生变化,但不会破坏静态部分的缓存。

用图来表示:

```
┌─────────────────────────────────────────────┐
│           STATIC(静态部分)                  │
│  - Claude 的身份定义                          │
│  - 工具使用规则(Bash, Read, Write...)        │
│  - 编码风格指南                               │
│  - 安全规则                                  │
│  所有用户共享同一份,全局缓存,TTL: 1小时      │
├─────────────────────────────────────────────┤
│   __SYSTEM_PROMPT_DYNAMIC_BOUNDARY__         │ ← 分界线
├─────────────────────────────────────────────┤
│           DYNAMIC(动态部分)                 │
│  - CLAUDE.md 项目记忆                        │
│  - Git 状态                                  │
│  - MCP 服务器指令                            │
│  - 环境信息(语言、时间等)                   │
│  每个 Session 独立缓存,TTL: 5分钟            │
└─────────────────────────────────────────────┘
```

静态部分(身份、行为规则、工具指令)在**所有用户之间全局共享缓存**,TTL 为 1 小时;动态部分(CLAUDE.md、Git 状态、MCP 指令、记忆)是**每个 Session 独立的缓存**,TTL 为 5 分钟。

### 这意味着什么?

**所有 Claude Code 用户共享同一份系统提示词缓存**;同一项目里所有人共享同一份 CLAUDE.md 缓存;只有对话历史是每个 Session 独有的。

这是一个极其聪明的设计——Anthropic 只需要把一份静态 prompt 的 KV 矩阵放在服务器内存里,全球所有 Claude Code 用户的请求都能复用!

### 实际省了多少钱?

没有缓存时,一次长时间的 Opus Coding Session(100 轮对话)可能耗费 $50-100 的输入 token 费用;有了缓存,只需 $10-19。这正是 Claude Code Pro 每月 $20 在经济上可行的原因。

---

## 六、缓存失效的陷阱(工程师必须知道)

改变哪怕一个字节都会使缓存失效。

缓存遵循以下层级:工具(tools)→ 系统提示词(system)→ 消息(messages)。每一层的改变都会使该层及之后所有层的缓存失效。

Claude Code 团队踩过的坑:

导致缓存失效的例子包括:在静态系统 prompt 里放了精确的时间戳、工具定义的顺序被随机打乱、工具参数发生了更新(比如 AgentTool 可以调用哪些子 Agent)等。

**解决方案很精妙**:

当 prompt 中的某些信息过期了(比如时间或用户修改了文件),你可能会想直接更新 prompt,但这会导致缓存未命中,对用户来说可能相当昂贵。更好的做法是在下一轮的消息中传入这些信息。

这就是为什么 Claude Code 要设计 `DYNAMIC_BOUNDARY`:把所有会变化的信息都放到边界之后,让边界之前的部分永远保持稳定,永远命中缓存。

另一个反直觉但精彩的例子:

Plan Mode 的实现:直觉上的做法是进入 Plan Mode 时换掉工具集,只保留只读工具。但这会破坏缓存。实际做法是:始终在请求中保留所有工具,把 `EnterPlanMode` 和 `ExitPlanMode` 本身也做成工具。当用户切换 Plan Mode 时,Agent 通过系统消息得知自己进入了 Plan Mode。

---

## 七、完整的请求流程总结

现在把所有东西串起来:

```
用户输入一条消息

Claude Code 组装完整的 API 请求:
┌─────────────────────────────────────────┐
│ [tools]    工具定义(全局缓存命中 ✅)    │
│ [system]   静态 prompt(全局缓存命中 ✅)│
│            ───DYNAMIC_BOUNDARY───        │
│            动态 prompt(Session 缓存 ✅)│
│ [messages] 历史对话(自动滑动缓存 ✅)   │
│            用户新消息(新计算 ❌)        │
└─────────────────────────────────────────┘

发送给 Anthropic API

API 服务器检查哪些 token 有 KV Cache
- 静态部分:直接从 GPU 显存读取 KV 矩阵,跳过计算
- 新消息:正常计算

以极低的 token 费用(10%)返回结果
```

---

## 八、一句话总结

**Prompt Caching 节省的不是"传输的 token 数量",而是"服务器计算 token 所消耗的 GPU 算力"。** 缓存的是模型内部处理这些 token 之后产生的 KV 矩阵(中间计算结果),命中缓存就等于跳过了最昂贵的 Attention 计算步骤,从而实现了 **90% 的费用节省** 和 **85% 的延迟降低**。

`SYSTEM_PROMPT_DYNAMIC_BOUNDARY` 就是 Claude Code 团队为了最大化缓存命中率而设计的架构边界线,把"永远不变的部分"和"每次可能变化的部分"物理隔离,确保前者永远稳定,永远命中缓存。

记录学习,分享技术