DEEP DIVE / PERFORMANCE OPTIMIZATION
Agent 上下文与 Token 缓存深度解析
在以 Agent 为核心的 AI 辅助编码时代,许多开发者虽然每天都在使用 Cursor、Claude Code、Aider 等工具,但对 AI 的"上下文"和底层的 Token 计费与响应原理缺乏系统认知。
你可能会发现:有时候 AI 响应快如闪电,有时候却需要你等待十几秒才开始吐字;有时候账单里产生了天量的 Token 消耗,AI 却开始胡言乱语;还有的时候,AI 会莫名其妙地遗忘关键的代码规范。
这些现象的背后,都指向了同一个核心课题:**上下文管理 (Context Engineering) 与 Token 缓存 (Prompt Caching) 的调优**。本文将从内部机理到实战调优,为您全面揭开 Agent 上下文黑盒。
---
## 1. 什么是 AI 编码的"上下文 (Context)"?
我们通常所说的"上下文",本质上是**大模型(LLM)进行推理计算时的输入信息流**。
在基于 Transformer 架构的大模型中,它并没有类似人类大脑的持久化记忆。对于模型而言,每一次请求都是一次**完全独立、从零开始**的前向传播计算(Forward Pass)。为了让你感觉 AI 在"进行连续的对话"或者"理解你的整个项目",工具必须在每次发送 Prompt 时,把之前所有的对话历史、关联的代码文件、项目规范等,打包成一个庞大的输入文本,一次性投喂给大模型。
这就是为什么**上下文永远不是无限的水桶**。上下文越长,大模型在计算时需要处理的矩阵维度就呈线性甚至平方级增长,导致推理首字延迟 (TTFT) 大幅增加、算力消耗与 Token 费用急剧飙升、注意力机制被无关杂音稀释而幻觉概率增加(Context Noise)。
---
## 2. 哪些内容会放入上下文?
当我们向 IDE 中的 Coding Agent(如 Cursor 或 Claude Code)发送一句指令时,工具在背后默默将多种信息打包进了输入上下文。主要包含以下四类:
* **静态指令(System & User Rules)**:IDE 默认的系统提示词(System Prompt)、项目级规则文件(如 `CLAUDE.md`、`AGENT.md`、`.cursorrules`)以及个人偏好设置。
* **会话历史(Session History)**:当前聊天窗口中你与 AI 的所有对话往来,以及 AI 之前生成的代码片段及工具调用日志(如读取文件、执行终端命令的返回结果)。
* **活动代码文件与光标上下文(Active Context)**:你当前在 IDE 中打开的所有 Tab 标签页、你的光标当前停留的函数或代码段、你手动选中的代码行。
* **动态检索的知识(Retrieved Context)**:工具通过解析 AST 或 LSP 服务自动生成的项目结构大纲(Repo Map)、通过向量数据库检索到的相似代码片段、依赖定义文件(如 `package.json`、`pom.xml`)、终端的报错堆栈与单元测试日志。
### 💡 实战解析:Agent 发送给大模型的 JSON Payload 示例
在日常开发中,当我们通过 IDE(如 Cursor)输入一句话时,Coding Agent 会将系统提示、本地规则、活动文件和用户提问组合成如下结构的 JSON Payload 发送给 API 端。通过以下示例,可以直观地看清上下文在传输时的真实样貌:
```json
{
"model": "claude-3-5-sonnet-20241022",
"system": "你是一个高水平的 Coding Agent。你必须遵守项目的核心开发规范手册...\n\n[长期规则: AGENTS.md / CLAUDE.md]\n- 必须启用 TS 严格模式,禁止使用 any。\n- 统一返回格式:{ code: number, data: T, msg: string }",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "【依赖与活动文件上下文】\n[package.json]\n{\n \"dependencies\": { \"typescript\": \"^5.0.0\" }\n}\n\n[活动文件: src/services/userService.ts]\nexport class UserService {\n // 待实现注册校验逻辑\n}"
}
]
},
{
"role": "assistant",
"content": "我已读取项目规则与关联文件上下文。请问今天需要针对 `userService.ts` 进行什么开发?"
},
{
"role": "user",
"content": "【当前指令】请帮我实现用户注册密码强度的正则校验功能,并在注册方法中调用。"
}
]
}
```
---
## 3. 哪些内容该放?哪些内容不该放?
管理上下文的第一步是明确**投喂标准**。高质量的上下文管理应该遵循 **High ROI(投资回报率)** 原则。
### 应当放入的内容(高价值、低噪声)
* **长期项目规范**:如 API 统一返回格式、数据库设计禁令、核心 UI 组件命名规则(放入 `AGENT.md`)。这是最稳定的上下文,是 Prompt Cache 命中的最大赢家。
* **任务验证脚本**:如具体的单测运行命令 `npm run test:unit`。这能让 AI 遇到错误时直接在终端验证,而不是问你。
* **强相关的接口定义与 Schema**:如果你在编写一个 Service 页面,它所调用的第三方 API 定义或对应的 SQL 数据库 DDL 应该优先放入。
* **精准的代码变更靶区**:只放入你需要修改的几个类,以及它们直接调用的上下游类。
### 严禁放入的内容(高噪声、无价值)
* **巨大的构建产物与缓存**:如 `node_modules/`、`dist/`、`.next/`、`target/`、`.git/` 等目录。这些文件会瞬间塞爆上下文,引发严重的 Token 污染。
* **无关的平行模块**:例如当你正在修改前端 UI 时,不要将无关的后端 DB Migration SQL 或大数据清洗脚本塞给 AI。
* **超长的冗余历史会话**:如果当前任务是"修改页面按钮颜色",但在之前的会话中你已经和 AI 讨论了 30 轮关于"重构系统登录逻辑"的复杂细节,那么这段历史就成了巨大的上下文噪声。
* **过时的文档或草稿文件**:已经被废弃的代码、未被使用的 `.bak` 或临时草稿,AI 会因为读取了它们而写出陈旧的 API。
---
## 4. 如何查看、分析与清理上下文?
优秀的 Vibe Coding 开发者必须学会监控 Agent 的上下文健康状况。
### 如何查看上下文?
* **命令行 Agent(如 Claude Code)**:在每次发送任务或执行动作前,命令行中会清晰打印出 `Read X files`,你可以通过日志查看它读取的每一个文件路径。
* **IDE 工具(如 Cursor)**:在聊天框的输入栏下方,会列出当前会话已经挂载的文件(如 `@UserController.java`)。点击它们可以看到具体挂载的代码行数。
### 分析与定位问题
如果 AI 表现出以下迹象,说明它的上下文很可能已经被污染或遗失:**胡言乱语**(开始编造项目里根本没有的类,或者突然用起了在其他会话里讨论过的方案)、**响应极慢**(在发送指令后,光标卡着过了十几秒甚至半分钟才开始慢吞吞地输出)、**遗忘规则**(反复教导它的规范在几轮对话后再次犯错)。
### 常用清理与优化手段
* **善用屏蔽配置文件**:在根目录下创建 `.claudeignore` 或 `.cursorignore`,将所有大体积无用文件夹(如 `build/`、`node_modules/`、日志文件等)严密锁死,防止 Agent 工具在执行模糊搜索时误读。
* **适时重开会话(Session Compaction)**:一旦一个复杂的子任务完成(例如"登录模块开发完毕且通过单测"),**立刻归档或关闭当前 Chat**,开启一个干净的全新 Chat 来做下一项任务。
* **精准 `@` 目标文件**:在向 AI 发送提问时,不要使用"帮我重构代码"这种模糊表述,而要使用类似"重构 `@UserService.java` 中的 `login` 方法,参考 `@UserEntity.java` 的属性"这种精确指向。
---
## 5. Token 缓存命中 (Prompt Caching) 深度剖析
在大型项目中使用 AI 编码,最核心的成本与速度调优技术就是 **Prompt Caching(提示词缓存)**。它是当前所有顶尖 Coding Agent 能在大型项目上落地的基石。
### 什么是 Token 缓存命中?
在大模型 API 交互中,每一次请求都是无状态的。如果你的项目包含 100KB 的代码(约 75,000 个 Token),你每问 AI 一个问题,AI 都要全量读取这 75,000 个 Token。
为了解决这个问题,大模型服务商(如 Anthropic、DeepSeek、Gemini)推出了 **Prompt Caching** 服务:
> **如果前后两次请求的 Prompt 前缀(Prefix)部分完全一致,服务端会直接复用上一次计算好的中间状态(KV Cache),跳过对这部分重复内容的重新计算。**
### 💡 实战解析:API 返回中的 Token 缓存统计示例
当我们在支持 Prompt Caching 的大模型 API(以 Anthropic Claude API 为例)中执行请求后,返回体中会包含一个具体的 `usage` 节点。这个 JSON 片段能够最直接地证明"缓存是否命中"以及"命中了多少 Token":
```json
{
"id": "msg_01Xxxxxxxxxxxxxxx",
"type": "message",
"role": "assistant",
"content": [
{
"type": "text",
"text": "已在 `userService.ts` 中完成了密码强度的正则校验逻辑,注册接口已成功调用该校验。"
}
],
"usage": {
"input_tokens": 85240,
"output_tokens": 420,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 83120
}
}
```
在上面的真实返回数据中,各项指标具体含义如下:
* **`input_tokens` (85,240)**:本次请求中输入的大上下文总 Token 量。
* **`cache_read_input_tokens` (83,120)**:**缓存命中部分**。说明这次输入有 83,120 个 Token(主要是系统提示词、`AGENTS.md` 和长会话历史)直接在服务端的显存里复用了缓存,无需重新计算(按 1/10 价格计费且首字零延迟吐字)。
* **`cache_creation_input_tokens` (0)**:本次请求新写入缓存的 Token 量。由于直接复用了现成缓存,本次未创建新缓存边界。
* **`output_tokens` (420)**:AI 吐出来的回答所消耗的 Token 数。
---
## 6. 多轮对话中的缓存命中演进
多轮对话是 Coding Agent 的核心使用形态。在多轮交互中,**由于之前的历史消息(User 指令和 Assistant 回答)也被当做前缀发送,因而它们也会被自动滑入缓存中**。
为了讲透多轮对话中缓存是如何在"无声中演进"的,我们来看以下两个连续请求的对比示例。
### 场景设定
* **项目宪法(System Prompt & AGENTS.md)**:共 80,000 Token。
* **第一轮提问**:用户提问"找出 userService 中的所有 Bug",AI 回答共耗费 880 Token。
* **第二轮提问**:用户继续追问"把刚才发现的第 2 个 Bug 修复掉"。
---
### 📌 第一轮对话:建立初始缓存(首次冷启动)
在首轮请求时,大上下文(80,120 Token)被发送,由于这是首次输入,我们需要在服务端**创建**缓存。`cache_creation_input_tokens` 会计入较高的写入费,但只会发生这一次。
**第一轮 API 请求 Payload:**
```json
{
"model": "claude-3-5-sonnet-20241022",
"system": "[80,000 Token 的项目规范与系统提示...]",
"messages": [
{
"role": "user",
"content": "找出 `userService` 中的所有 Bug"
}
]
}
```
**第一轮 API 返回的 usage 统计:**
```json
{
"usage": {
"input_tokens": 80120,
"output_tokens": 880,
"cache_creation_input_tokens": 80120,
"cache_read_input_tokens": 0
}
}
```
* `cache_creation_input_tokens: 80120`——首次输入,将 80,120 Token 写入服务端缓存。
* `cache_read_input_tokens: 0`——首次请求,没有可命中的历史缓存。
---
### 📌 第二轮对话:多轮历史成为前缀,100% 缓存命中!
到了第二轮,Agent 工具会将**第一轮的对话内容与 AI 回答**当做历史前缀拼装进去。前缀变成了 `[System] + [User 第一轮提问] + [Assistant 第一轮回答]`。由于这个前缀与第一轮计算结束后的状态**完全一致**,它在第二轮请求中将被完全命中!
**第二轮 API 请求 Payload(由工具自动拼接历史记录):**
```json
{
"model": "claude-3-5-sonnet-20241022",
"system": "[80,000 Token 的项目规范与系统提示...]",
"messages": [
{
"role": "user",
"content": "找出 `userService` 中的所有 Bug"
},
{
"role": "assistant",
"content": "[880 Token 的 AI 回答:发现了 3 个 Bug,分别是...]"
},
{
"role": "user",
"content": "把刚才发现的第 2 个 Bug 修复掉"
}
]
}
```
**第二轮 API 返回的 usage 统计:**
```json
{
"usage": {
"input_tokens": 81040,
"output_tokens": 620,
"cache_creation_input_tokens": 0,
"cache_read_input_tokens": 81000
}
}
```
* `cache_read_input_tokens: 81000`——100% 命中了第一轮建立的缓存!前缀中的 81,000 Token 完全复用显存里的 KV Cache,只对新增的 40 Token 指令进行了全新计算。
* `cache_creation_input_tokens: 0`——无需再写入新缓存,成本最低。
### ⚠️ 警惕多轮对话中的"缓存雪崩"
虽然多轮对话能自动滑行累积缓存,但在以下情况中,缓存会**瞬间彻底归零(Cache Miss)**,导致重新产生 80,000+ Token 的昂贵算力开销:
* **回溯编辑**:如果你修改了第一轮提问中的一个错别字,整个消息数组的 Hash 前缀彻底改变,后面的所有缓存直接失效。
* **多会话切换**:在 IDE 里从一个繁重的 Chat A 切换到 Chat B。因为两个 Chat 的对话前缀不同,服务器需要重新对 B 的上下文进行 Prefill 计算。
* **静默超时(TTL 限制)**:如果你中途离开工位超过 5~10 分钟。由于服务器显存中的 KV Cache 已经因为 TTL 超时释放,下次发送时必须从头重新全量计算。
---
## 7. 缓存命中的底层原理:KV Cache 机制
要理解缓存命中的原理,我们需要回到 Transformer 模型的推理机理。
### A. KV Cache(键值缓存)机制
在 Transformer 的多头注意力机制中,模型需要计算输入文本中每一个 Token 与其他所有 Token 之间的相关性。这个过程分两个阶段:
* **Prefill 阶段(输入预填充)**:模型读取你的所有输入(如 100K 提示词),把这些 Token 转化为隐藏层的向量表示,并计算出用于注意力机制的 **Key(键)** 和 **Value(值)** 矩阵。这个阶段涉及极其繁重的矩阵乘法,是导致首字延迟卡顿的根源。
* **Decoding 阶段(文本输出)**:模型开始一个字一个字地吐出回复。每生成一个新的 Token,它只需要为这个新 Token 计算新的 Key 和 Value,并与之前已经算好的历史 Token 的 KV 矩阵进行 Attention 计算。
因此,在服务器显存中保存的历史 Token 的 KV 矩阵,就被称为 **KV Cache**。
### B. 前缀一致性匹配(Prefix Matching)
如果一个会话是连续的(或者是项目里稳定的 `AGENT.md`),它的输入前缀在多次请求中是**完全一致**的。当你的第二轮请求发送到服务端时,服务端的缓存路由会计算你的输入文本的 Hash 值。如果发现前 N 个 Token 与显存中缓存的某次历史计算前缀完全一致,就会:
* **免去重新计算**:服务端直接加载这段前缀已经计算好的 KV 矩阵(KV Cache)。
* **免去重新推理**:直接跳过这 80K Token 的 Prefill 阶段,直接进入对新输入(你刚打的那句话)的计算。
这就是 Prompt Caching 的本质——**直接复用显存中的 KV 矩阵,免去了大段重复输入的 Prefill 前向传播计算**。
### C. 缓存的分块与生存期(TTL)
服务商通常会采用**分块对齐(Chunk Alignment)**机制(例如以每 1024 或 2048 个 Token 为一个缓存块)。只有当前缀长度超过阈值(如 Anthropic 为 1024 Token,DeepSeek 为 1024 Token)且对齐到块边界时才会被缓存。缓存会在显存中保留几分钟到几十分钟不等(滑动窗口 TTL),只要你在这个时间段内持续提问,缓存就会一直被热激活。
---
## 8. 缓存命中的巨大红利:速度与成本的双重飞跃
理解并利用好 Prompt Caching,可以为开发带来颠覆性的提升:
### 速度红利:消除等待,即时响应
在传统模式下,如果上下文里塞了 80K Token 的代码,即使大模型生成速度非常快,它在计算这 80K 输入时(Prefill)也要耗费 5~15 秒。这会导致 AI 在开始吐字前产生严重的卡顿。如果**完全命中缓存**,Prefill 的耗时会被直接**缩短 90% 以上**。AI 几乎会在你按下回车键的 0.5 秒内,立刻开始飞速打字,开发心流完全不会被打断。
### 价格红利:成本缩减至 1/10
各个大模型服务商对命中缓存的 Token 提供了极大的折扣:
* **Anthropic Claude 3.5 Sonnet**:写入/未命中缓存的输入 Token 为 \$3.00/MTok,而命中缓存的 Token 仅为 **\$0.30/MTok**(直接打 1 折!)。
* **DeepSeek-V3**:未命中缓存的输入 Token 约为 \$0.14/MTok,命中缓存的 Token 仅为 **\$0.014/MTok**。
在大型项目频繁交互中,90% 以上的输入都会被缓存命中,这会使你的整体 AI 使用成本呈现断崖式下跌。
---
## 9. 如何在日常开发中最大化缓存命中率?
要在日常 Vibe Coding 中把缓存吃满,我们需要遵循以下几条**上下文排列黄金铁律**:
### 铁律一:前缀必须保持绝对静止(把变动内容放最后)
缓存匹配必须是从 Prompt 的**第 1 个字符开始连续匹配**的。只要你在 Prompt 的开头改动了一个字,那么整个缓存就会全部失效,这被称为**"雪崩效应"**。
因此,你的上下文结构必须严格按照以下顺序排列:
```text
┌────────────────────────────────────────────────┐
│ 1. 绝对不变的系统指令 / System Prompt │ ───► 永远放在最开头 (最易缓存)
├────────────────────────────────────────────────┤
│ 2. 项目级稳定规则 (AGENT.md / CLAUDE.md) │ ───► 紧随其后 (稳定缓存)
├────────────────────────────────────────────────┤
│ 3. 历史聊天记录 (Session History) │ ───► 按对话顺序追加
├────────────────────────────────────────────────┤
│ 4. 当前光标选中的代码 / 临时读取的文件 │ ───► 动态变化
├────────────────────────────────────────────────┤
│ 5. 用户当次输入的 Request / 指令 │ ───► 每次都在变 (放最后,不影响前文缓存)
└────────────────────────────────────────────────┘
```
> **反面教材**:每次提问时都把"当前时间:2026-08-19 17:08"或者动态生成的随机数塞在 Prompt 的最开头。这会导致每次请求的第一行都不同,从而导致后面的几十万 Token 缓存全部失效!
### 铁律二:避免在长会话中随意修改历史消息
在很多 IDE(如 Cursor)中,你可以选择编辑之前说过的某句话。注意:一旦你修改了第 3 轮对话的内容,第 3 轮之后的所有 KV 矩阵都会因前缀失效而全部作废,大模型必须重新计算之后的所有对话。尽量采用追加提问的方式,而不是频繁回溯修改历史。
### 铁律三:大文件按需引入,避免"闪烁式投喂"
如果你在这一轮提问里 `@` 了一个 20KB 的文件,下一轮提问又把它取消掉,第三轮再把它加回来。这种操作会导致服务端的缓存被频繁覆写和刷新,无法形成稳定的热缓存。对于频繁用到的核心文件,尽量在当前子任务 Session 内保持持续挂载。
---
## 总结
在 Vibe Coding 时代,**优秀的开发者不仅是代码审查员,更是一名出色的上下文架构师**。
通过编写精简的 `AGENT.md` 约束 AI、建立 `.claudeignore` 屏蔽无用噪声、适时开启干净会话,并严格遵循"前缀静止"的原则排列上下文,你就能在享受 Prompt Caching 带来的 **90% 资费降幅** 的同时,获得**近乎零延迟**的高速编码响应。
这种对上下文和底层算力特性的精细掌控力,正是决定企业级 AI 开发质效分水岭的关键竞争力。
返回 PPT:CLAUDE.md & AGENTS.md 最佳实践 ↗