万字长文!解读claude code的上下文压缩,结合源码深度分析
源码我是从https://github.com/alex000kim/claude-code/tree/main/src下载的
上下文压缩的实现位置如下
先来说说为什么需要上下文压缩。 核心原因很简单:所有大语言模型都有最大上下文 Token 限制(Context Window)。如果用户在同一个会话里长时间调试、执行大量命令、读取冗余内容,对话总 Token 很容易超限,最终触发Prompt Too Long错误。
这个目录下的代码正是为解决这类问题而设计:通过智能判断,对历史消息进行压缩或局部截断,让 AI 能在有限的上下文窗口内持续工作,同时尽可能不丢失关键信息。
接下来我们先看上下文压缩的触发位置。 我个人倾向于:只要对输出内容做了精简处理,都可以算作广义上的压缩;如果范围再放宽一些,这类操作更适合统称为上下文处理。 因此在后续讲解中,我会把相关逻辑拆成两部分来讲:上下文处理与上下文压缩,大家可以自行判断哪些属于压缩、哪些属于更通用的上下文管理。
上下文什么样子呢,大概是这个样子:
let state: State = { messages: params.messages, // 当前对话所有消息 toolUseContext: params.toolUseContext, // 工具调用上下文 maxOutputTokensOverride: params.maxOutputTokensOverride, // 输出Token覆盖配置 autoCompactTracking: undefined, // 自动压缩追踪状态 stopHookActive: undefined, // 停止钩子状态 maxOutputTokensRecoveryCount: 0, // Token恢复重试次数 hasAttemptedReactiveCompact: false, // 是否尝试过响应式压缩 turnCount: 1, // 当前对话轮次 pendingToolUseSummary: undefined, // 待处理工具调用摘要 transition: undefined // 循环过渡状态(用于测试/恢复)}哪里会调用这个上下文的处理
上下文相关处理逻辑的入口,位于src/query.ts文件的核心循环函数queryLoop内部。本节我们重点关注主循环中对上下文的各类处理流程,暂时不纠结这些操作到底算不算 “压缩”,也不提前给它们贴标签。 上下文处理的具体调度顺序如下,与源码执行逻辑完全一致:所有操作均围绕上下文内容管理展开,具体包含哪些行为、是否属于压缩,大家可以结合后续细节自行判断。
1. 触发源头:queryLoop持续循环
在 Claude 处理用户请求的每一轮对话(Loop)开始时,它都会在发送 API 请求之前,通过一系列“拦截器”来检查和压缩上下文状态。
2. 核心调用链条 (按执行顺序)
如上图所示:
在src/query.ts的while(true)循环中,你可以看到以下关键入口逐个依次执行:
- 入口 A:工具输出结果预算硬性裁剪 (applyToolResultBudget)
- 代码位置:
src/query.ts第 379 行左右。 - 函数:
await applyToolResultBudget(messagesForQuery, ...) - 触发条件:无条件执行。遍历发给 API 之前的消息体。
- 作用:对每一次单条的消息实行强制拦截与预算管理。由于某些工具没有配置上限或者其输出可能极其庞大,这步会在进入真正的高级压缩处理池前,确保工具运行产生的原始文字负荷不会直接溢出单条上限。
- 入口 B:局部信息修剪模块 (snipCompactIfNeeded)
- 代码位置:
src/query.ts第 403 行左右。 - 函数:
snipModule!.snipCompactIfNeeded(messagesForQuery) - 触发条件:处于
feature('HISTORY_SNIP')特性开启状态下。 - 作用:根据特定规则去把大块不再紧密关联的局部对话进行“硬切除”(Snip),并在本次切除完毕后算出“释放了多少 Token(
snipTokensFreed)”,留给后续判断步骤使用。
注意:这里虽然是函数,但是我们没有找到对应的ts文件,也就是或这个源码还是有遗漏的。
# 各位可以看看能不能找到snipCompact这个文件/* eslint-disable @typescript-eslint/no-require-imports */const snipModule = feature('HISTORY_SNIP') ? (require('./services/compact/snipCompact.js') as typeof import('./services/compact/snipCompact.js')) : null- 入口 C:微压缩清理 (Micro-compaction)
- 代码位置:
src/query.ts第 414 行左右。 - 函数:
await deps.microcompact(...) - 触发条件:无条件执行(但在其内部会自动根据“冷热缓存”与“时间阈值”进行旁路决策)。
- 作用:在发起主请求前,清理不再需要的历史大尺寸工具执行结果记录。如果判断上下文由于闲置过久已经变成了“冷缓存”,会直接清空历史 grep/cat 等工具打印结果的内容区以释放空间;若是“热缓存”则会排队等候向 API 发布“清空旧节点”的指令从而保全其它缓存命中率。
- 入口 D:局部上下文折叠 (contextCollapse)
- 代码位置:
src/query.ts第 441 行左右。 - 函数:
await contextCollapse.applyCollapsesIfNeeded(...) - 触发条件:当系统带有
CONTEXT_COLLAPSE标识并且上下文折叠功能被启用。 - 作用:该模块会在真正的“暴力合并压缩(AutoCompact)”发生之前抢跑。它检查历史记录中可以被折叠、归档合并的工作流,并将其转化为本地短文本节点。通过它的提前抢答和瘦身,系统上下文会大量释放,从而极有可能避免接下来的暴力合并。
- 入口 E:全局红线自动压缩 (autoCompact)
- 代码位置:
src/query.ts第 454 行左右。 - 函数:
await deps.autocompact(...) - 触发条件:通过估算目前的令牌消耗量(并在评估时聪明地减掉刚刚第 B 步里
snipTokensFreed省下的令牌)。如果发现将要超过API 安全上限 - Buffer预留空间,则强制触发。 - 作用:全局防爆兜底机制。一旦触发,它在内部会首选把对话写入长期记忆流中(Session Memory 模式进行保存)。若是走投无路,就会正式冻结界面并新建子任务给原生 Claude 下发指令:“彻底读一遍上面这几十轮对话,给出一段核心 Summary”,并把所有老的几十轮消息无情抛弃,换成一句总结,实现重新续航。
上面是5个小模块对吧,这里我们分开来说,首先说的是2个处理模块的一些细节。
先说下2处理模块的介绍和细节
处理模块我认为有两个,分别是applyToolResultBudget和contextCollapse
1.applyToolResultBudget(单次工具输出预算管理)
该机制的核心作用是:大内容本地保存 + 原文替换为短摘要,避免单个工具返回超长内容(如读取超大文件、爬取超长网页)把大模型的上下文撑满。
- 代码位置:实现逻辑在
src/utils/toolResultStorage.ts - 核心调用:由
applyToolResultBudget内部调用算法enforceToolResultBudget - 触发时机:在每轮对话发送给大模型 API 前,自动检查
messages列表 - 控制阈值:由
MAX_TOOL_RESULTS_PER_MESSAGE_CHARS决定上限,也可通过动态参数tengu_hawthorn_window覆盖
工作流程
1.采集待处理工具结果
从消息里把所有工具返回内容抽出来,按消息分组。 例:消息1里有cat package.json、grep两个工具结果,消息2里有curl一个工具结果
2.读取配置与过滤规则
拿到单条消息最大长度限制(比如 50KB),并跳过某些工具(比如 Read 工具不处理)。 例:限制 50KB,跳过Read工具,其他都要检查。
3.按历史状态拆分内容
根据之前处理记录,把每条消息的结果分成三类: mustReapply:之前已经被截断过的 frozen:之前处理过但没超限,正常保留 fresh:本轮新出现的,还没检查过 例:消息1里的grep是之前截过的 → mustReapply,cat package.json是新的 → fresh
4.重新应用历史替换
之前截过的内容,直接用上次一模一样的短文本,保证缓存不炸。 例:grep上次截成了预览文本,这次直接再用一遍,一字不差。
5.对新内容算总大小
把本轮新内容 + 历史没超限的内容加起来,看超不超 50KB。 例:历史冻结 10KB + 新的cat结果 60KB = 70KB 50KB → 超限
6.筛选超长内容去落盘
超限就挑最新、最大的工具结果处理。 例:选中这个 60KB 的cat结果,准备存本地。
7.安全标记已处理ID
不需要替换的先标记“已见过”; 要存盘的等存完再标记,防止状态错乱。 例:grep直接标记已处理;cat先不标记,等存好文件再说。
8.落盘 + 生成短预览
把大内容存到本地文件,生成一段短预览。 例:60KB 内容存到session123/tool-results/abc.txt,替换成:<persisted-output>太大了,存到xxx,预览前2000字...</persisted-output>
9.替换并返回结果
把原消息里的大文本换成短预览,返回新消息列表。例:最终发给模型的是短文本,原大内容留在本地。
2.contextCollapse(局部动作链条折叠)
该机制的本质是:对局部对话做高压缩归档。 它比applyToolToolResultBudget覆盖范围更广,又比全局强制总结autoCompact更精细、更温和。
- 代码位置:
src/services/contextCollapse/index.js(按需加载) - 核心调用:
contextCollapse.applyCollapsesIfNeeded(...),内部依赖projectView()及恢复逻辑 - 开关控制:由特性开关
feature('CONTEXT_COLLAPSE')启用
但是,兄弟们,没有找到这个代码位置啊,你们找到的话对我说下
# query.ts中的19行const contextCollapse = feature('CONTEXT_COLLAPSE') ? (require('./services/contextCollapse/index.js') as typeof import('./services/contextCollapse/index.js')) : null/* eslint-enable @typescript-eslint/no-require-imports */下面的工作流程就看看得了,不保熟…
工作流程
1.优先优化,避免全局压缩: 该函数会在破坏性最强的全局总结autoCompact之前执行,目标是:只要折叠后能把 Token 降下来,就不触发全局强总结。
2.不修改原始消息: 不会直接删除或改动state.messages里的真实历史,而是维护一套: - 折叠中心(Collapse store) - 提交日志(Commit log) 实现“无损折叠”。
3.自动识别可折叠的动作链: 自动扫描历史对话,找到冗长、重复、只为完成一个小目标的对话段(例如连续5次调试只为改一个拼写错误)。
4.生成局部摘要,只发送精简版给模型: 把一长串消息“折叠”成一句精简摘要,只将摘要发给大模型。
这种折叠方式让对话具备层级化、可伸缩的能力:保留核心逻辑,隐藏冗余细节。
各个压缩模块的文件介绍和整体流程
我们在src/services/compact下面,真compact就是压缩的意思,可以看到其主要的实现方式,compact 文件夹是 Claude Code 中负责管理对话上下文与消耗(Token limits)最核心的模块,即“上下文压缩(Compaction)”系统,总共有apiMicrocompact.ts、 autoCompact.ts、 compact.ts、 compactWarningHook.ts、 compactWarningState.ts、 grouping.ts、 microCompact.ts、 postCompactCleanup.ts、 prompt.ts、 sessionMemoryCompact.ts、 timeBasedMCConfig.ts这个几个文件。但是注意哈,我们只能看到这几个文件,其实从主循环里面来看的话应该还是有其它文件的。
注意,这里我说的是核心的压缩模块哈,是压缩,不是处理哈。其中各个核心模块的作用与原理如下
各个核心模块的作用与原理
1. 核心压缩引擎
compact.ts:执行全局对话“压缩”的主文件。当需要压缩时,它会取出当前聊天记录的最老部分,调用 LLM 对其进行高质量“总结(Summarize)”生成一份概要记录,并舍弃原有的庞大对话节点。同时它还负责重新插入重要的状态,如当时用过的 Tool、计划(Plan)附件以及仍旧打开的文件状态。
prompt.ts:里面定义了多套精心设计的系统提示词(Prompt)。这些 Prompt 专门用来教导 LLM “如何给自己做压缩总结”。例如,里面有极其严格的指示,让 LLM 做总结时切勿调用任何工具(NO_TOOLS_PREAMBLE),而且必须关注用户过去的根本意图、已修复的 Bug、以及代码架构等等。
2. 自动化触发机制(Proactive Compacting)
autoCompact.ts:自动化压缩守护进程。它会根据当前使用的模型,精确计算其允许的 Token 上限,并留出缓冲区(Buffer)。每次工具执行或者回合结束时它都会计算评估,一旦触及警告阈值(Auto Compact Threshold),它就会在后台自动触发压缩。用户感受到的就是“对话一直在继续,不需要我自己去开新会话”。
timeBasedMCConfig.ts:基于时间的评估配置方案。
3. 微压缩与策略缓存(Micro Compaction)
microCompact.ts/apiMicrocompact.ts:微压缩策略(Micro Compaction)。不同于把所有对话生成总结,微压缩主要是把繁重的工具返回结果(Tool Result)清理掉。比如你用grep搜出了几百行结果或猫了一个大文件,这其实很快就会失去时效性并且占据极大 Token,微压缩就会针对这种特定的 Tool Result 截掉其内容(替换成[Old tool result content cleared])。
sessionMemoryCompact.ts:该文件实现了另一种维度的压缩逻辑。也就是将一些旧状态压缩进入一种更加“长期的记忆(Session Memory)”片段中进行存储,而不是单纯作为聊天流水账。
4. 状态预警与维护杂项
compactWarningHook.ts/compactWarningState.ts:用来处理“Token 报警”时的状态管理,比如决定什么时候该给用户弹警告,告诉用户当前上下文正在变得很重,马上要进行压缩了。
postCompactCleanup.ts:它主要承担“后事处理”。当完成一次完整的 Context 压缩后,它负责清空某些过时的缓存树(比如之前残留的旧消息 ID)、重置一些上下文环境,从而保证压缩后的会话纯净且不发生内部 ID 错换或报错。
grouping.ts:协助将庞大的消息队列依据 API 的回合(Round)规则进行分类和聚合,以防压缩时割裂了 “User -> Assistant” 这个成对出现的消息结构导致请求格式报错。
这几个模块在 Claude Code 里的执行调度顺序是有明确优先级的,它们主要卡在“向大模型发起请求前”的执行 Loop 中。整个拦截和清理机制的先后顺序可以总结为一个漏斗:先剔除冗余片段 -> 算账报警 -> 尝试智能记忆归档 -> 没办法了才做暴力总结。
好了,说完了每个ts的功能,我们再看下整理的流程,将先后顺序拉齐,流程的入口是:autocompact.ts文件,我们在query.ts中就可以看出来:
// src/query.ts 中的 queryLoop 函数片段// ... 步骤 1: 预算裁剪 (applyToolResultBudget)// ... 步骤 2: 历史片段剪枝 (snipCompactIfNeeded - HISTORY_SNIP 特性)// ... 步骤 3: 微清理 (microcompact - 清理旧的工具输出)queryCheckpoint('query_autocompact_start')// 核心调用点:这里通过依赖注入调用了 services/compact/autoCompact.ts 中的 autoCompactIfNeededconst { compactionResult, consecutiveFailures } = await deps.autocompact( messagesForQuery,)queryCheckpoint('query_autocompact_end')if (compactionResult) { // 如果触发了压缩,则在此处应用压缩结果并更新消息序列 const postCompactMessages = buildPostCompactMessages(compactionResult) // ... messagesForQuery = postCompactMessages}所有的其它ts文件主要是通过通过query.ts里面的loop串起来
自动压缩详细解释
焦于autocompact.ts文件,看它里面的流程,如下:
代码在services/compact/autoCompact.ts-Line241的autoCompactIfNeeded
主要逻辑包含以下几个关键判定点:
1.安全守门员阶段 (Guard Clauses) 流程图最左侧展示了两个前置判定:
- 环境禁用:首先读取环境变量,如果用户显式禁用了压缩(
DISABLE_COMPACT),后续所有逻辑都不会跑,直接返回。 - 熔断保护 (Circuit Breaker):检查最近连续失败的次数。如果已经连续失败 3 次,系统会认为当前上下文状态可能存在“不可救药”的问题(例如某条消息过于巨大导致总结接口始终崩溃),此时会主动停止尝试,防止无谓的 API 消耗。
2.压力评估阶段 (Should Compact?)
- 调用
shouldAutoCompact函数进行计算。 - 逻辑:先排除掉各种不能触发自动压缩的场景,比如递归调用、上下文折叠已生效、仅响应式压缩模式等,避免冲突或死锁;然后计算当前消息的实际 Token 数量,判断是否超出模型设定的压缩阈值,最后返回是否需要执行全局自动压缩。
3.第一优先级:会话记忆压缩 (SM Compact - 策略1) 这是图中第一个核心分支:
- 做啥:把长的会话记忆文本压缩成短摘要,同时保留关键信息,并标记被截断的部分。
- 输入:这个部分输入是
message、session_memory,不压message - 做法:记录压缩前 token 数(用于统计)、 创建边界标记(标记从哪里开始压缩,记录使用了哪些工具)、 截断过长的 session memory(防止占满 token 预算)、 生成摘要内容(如果被截断,会提示完整内容路径)、 包装成特殊消息(不显示给用户,只作为上下文给 AI)、 返回压缩结果(包含摘要、保留的消息、token 统计等)。举个例子:假设对话已经进行很久:原始对话:5000 tokens、保留最近 10 条消息:1000 tokens、压缩之前的 4000 tokens → 生成 500 tokens 的摘要、最终上下文:摘要(500) + 保留消息(1000) = 1500 tokens。代码上是通过自己定义的规则来实现的。具体在
utils/messages.ts-Line460 - 代码:
services/compact/sessionMemoryCompact.ts-Line514
4.兜底策略:总结压缩 (Full Compact - 策略2) 如果策略 1 失败(通常是因为后台提取器还没来得及生成一致的总结),系统会进入这个兜底环节:
- 原理:调用专门的总结 Prompt 让 LLM 对当前历史进行重写。
- 逻辑(图中 Simplified):1.暂停 UI 和加载 Hooks (
executePreCompactHooks):界面会进入“Compacting…”等待状态,暂停处理你的新需求。2.构建 Prompt (使用prompt.ts):获取你先前的会话记录,并给 Claude 添加极度严厉的指令参数(类似要求必须以包含<summary>、<analysis>的格式去浓缩整段历史,且绝对禁止在此总结期间使用任何工具)。3.触发隐式 AI 请求:发送一次幕后请求拿回浓缩结论(Summary String)。如果这一步连发要求总结的请求都报 prompt-too-long 的错(即CC-1180问题),系统甚至会用truncateHeadForPTLRetry无脑切掉头部的几个轮回硬试。4.截断与状态重建:拿到生成的短文本摘要后,用它替换掉旧对话序列的最开头,插入边界标志SystemCompactBoundaryMessage。然后系统会将你在压缩前正开着的文件、计划列表(Plan)等重要附件重新挂载回去,免得大模型失忆。5.完成后,一切恢复,带着瘦身后的上下文去回答你的初始提问。 - 输入:message,压缩的是这个,不是记忆哦
- 代码:
services/compact/autoCompact.ts-Line387的compactConversation
5.状态平滑复位与收尾 (Post-Process) 图的最右侧展示了压缩成功后的必要动作:
- ID 重置:因为压缩会改变消息的 UUID,所以必须重置
lastSummarizedMessageId,否则下一轮循环会找不到锚点。 - 钩子执行:调用
runPostCompactCleanup,触发如PROMPT_CACHE_BREAK_DETECTION(通知缓存检测系统:Token 下降是正常的压缩行为,不是缓存断档)。 - 计数归零:如果压缩成功,会将“连续失败计次”重置为 0,解除熔断状态。
轻量级微清理 (Micro-Compacting)详细解释
代码位于services/compact/microCompact.ts-Line253的microcompactMessages函数。
在每次向外发送消息之前,会先走microCompact.ts或者原生的apiMicrocompact.ts。系统不想一上来就动大手术,所以先看看能不能把没用的“胖子资源”删掉。
1.基于时间的判断 (evaluateTimeBasedTrigger):如果距离上一次调用时间已经过去很久(代表缓存早已失效、变冷),直接本地扫一遍对话,把之前老旧工具的输出结果(比如好几个长长的被猫(cat)出来的文件文本或是 grep 结果)替换为缩写提示[Old tool result content cleared],从而立刻腾出空间。
2.基于热缓存编辑 (cachedMicrocompactPath):就算对话很频密,如果累计调用了太多的 Shell、FileRead,它也会打个标签并排队(Pending Cache Edits),去告诉大模型 API “这几个之前的结果你从上下文里删掉”。
3.调用apiMicrocompact.ts构建原生 API 请求策略:正如你打开的代码,如果没有采用客户端清理,就会去配置 Anthropic API 的原生清空策略(ContextEditStrategy),比如让后端大模型自动clear_thinking(剔除旧的思维链内容) 或者clear_tool_uses。
Snip裁剪
这个流程我放到处理里面去了,在这里不展示了
Context Collapse
这个流程我放到处理里面去了,在这里不展示了
所以整体上是 Snip -> Microcompact -> Context Collapse -> AutoCompact。其中Snip和Context Collapse没有源码,只能猜怎么做的了。
总结
基于源码分析,Claude Code 的上下文压缩机制按以下顺序执行:
- 五个入口依次触发:
applyToolResultBudget(工具输出裁剪)→snipCompactIfNeeded(硬切除)→microcompact(清理旧工具输出)→contextCollapse(局部折叠)→autoCompact(全局压缩) - 两种核心压缩策略:优先使用会话记忆压缩(本地规则压缩
sessionMemory,不调 API),失败后降级为LLM 总结压缩(调用 AI 生成对话摘要,消耗 token) - 辅助清理机制:
applyToolResultBudget将超大工具输出存本地文件并替换为短摘要;microcompact按时间或缓存状态清理旧工具结果 - 安全保护:包含熔断机制(连续失败 3 次后停止尝试)和环境变量禁用开关
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋
📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~
