Claude Code 源码分析(四):上下文窗口管理 —— 长对话场景下的 Token 预算与自动压缩

张开发
2026/5/20 22:22:13 15 分钟阅读
Claude Code 源码分析(四):上下文窗口管理 —— 长对话场景下的 Token 预算与自动压缩
本系列文章基于 Claude Code 2.1.88 版本的 TypeScript 源码进行分析。源码版权归 Anthropic 所有本文仅用于技术研究。引言上下文窗口管理是 AI 应用工程中最核心的挑战之一。Claude Code 作为一个长时间运行的终端助手单次会话可能持续数小时、涉及数百次工具调用上下文 token 消耗极易超出模型限制。src/services/compact/目录下的 11 个文件实现了一套完整的上下文预算管理方案涵盖自动压缩触发、多策略压缩、熔断保护、token 估算等环节。涉及的核心源码文件src/services/compact/autoCompact.ts—— 自动压缩触发与熔断src/services/compact/compact.ts—— 压缩策略实现src/services/compact/microCompact.ts—— 缓存级微压缩src/services/compact/sessionMemoryCompact.ts—— 会话记忆压缩src/services/compact/prompt.ts—— 压缩 prompt 模板src/services/tokenEstimation.ts—— 多策略 token 估算src/cost-tracker.ts—— 成本追踪一、自动压缩触发机制1.1 阈值计算系统根据模型的上下文窗口大小动态计算压缩阈值。计算过程分为两步第一步计算有效上下文窗口大小扣除输出预留constMAX_OUTPUT_TOKENS_FOR_SUMMARY20_000exportfunctiongetEffectiveContextWindowSize(model:string):number{constreservedTokensForSummaryMath.min(getMaxOutputTokensForModel(model),MAX_OUTPUT_TOKENS_FOR_SUMMARY)letcontextWindowgetContextWindowForModel(model,getSdkBetas())// 支持环境变量覆盖用于测试constautoCompactWindowprocess.env.CLAUDE_CODE_AUTO_COMPACT_WINDOWif(autoCompactWindow){contextWindowMath.min(contextWindow,parseInt(autoCompactWindow,10))}returncontextWindow-reservedTokensForSummary}20,000 token 的预留基于生产数据——源码注释指出p99.99 of compact summary output being 17,387 tokens。第二步在有效窗口基础上扣除缓冲区exportconstAUTOCOMPACT_BUFFER_TOKENS13_000exportfunctiongetAutoCompactThreshold(model:string):number{returngetEffectiveContextWindowSize(model)-AUTOCOMPACT_BUFFER_TOKENS}1.2 多级警告系统设置了四个递进的警告级别exportconstWARNING_THRESHOLD_BUFFER_TOKENS20_000exportconstERROR_THRESHOLD_BUFFER_TOKENS20_000exportconstMANUAL_COMPACT_BUFFER_TOKENS3_000exportfunctioncalculateTokenWarningState(tokenUsage,model){return{percentLeft,// 剩余百分比isAboveWarningThreshold,// 距上限 20K tokensisAboveErrorThreshold,// 距上限 20K tokensisAboveAutoCompactThreshold,// 触发自动压缩isAtBlockingLimit,// 距上限 3K tokens阻止新请求}}当 token 用量达到阻塞阈值距上限仅 3,000 tokens时系统会阻止新的请求强制用户手动压缩。1.3 递归保护自动压缩本身也是一次 API 调用因此需要防止递归触发exportasyncfunctionshouldAutoCompact(messages,model,querySource){// 会话记忆提取和压缩本身不触发自动压缩if(querySourcesession_memory||querySourcecompact){returnfalse}// 上下文折叠代理不触发自动压缩if(feature(CONTEXT_COLLAPSE)){if(querySourcemarble_origami)returnfalse}}源码注释解释了marble_origami上下文折叠代理被排除的原因如果它的上下文膨胀触发了自动压缩runPostCompactCleanup会调用resetContextCollapse()而这会销毁主线程的已提交日志因为是模块级共享状态。二、熔断机制这是整个压缩系统中最具工程价值的设计之一。constMAX_CONSECUTIVE_AUTOCOMPACT_FAILURES3exportasyncfunctionautoCompactIfNeeded(messages,toolUseContext,...){// 熔断器连续失败 N 次后停止重试if(tracking?.consecutiveFailuresMAX_CONSECUTIVE_AUTOCOMPACT_FAILURES){return{wasCompacted:false}}try{constcompactionResultawaitcompactConversation(...)return{wasCompacted:true,consecutiveFailures:0}// 成功则重置}catch(error){constnextFailures(tracking?.consecutiveFailures??0)1if(nextFailuresMAX_CONSECUTIVE_AUTOCOMPACT_FAILURES){logForDebugging(autocompact: circuit breaker tripped after${nextFailures}consecutive failures — skipping future attempts this session)}return{wasCompacted:false,consecutiveFailures:nextFailures}}}源码注释中记录了这一设计的数据驱动背景// BQ 2026-03-10: 1,279 sessions had 50 consecutive failures // (up to 3,272) in a single session, wasting ~250K API calls/day globally.在引入熔断器之前当上下文不可恢复地超出限制时系统会在每个 turn 都尝试压缩最多一个会话内连续失败 3,272 次全局每天浪费约 25 万次 API 调用。熔断器将最大重试次数限制为 3 次。三、多策略压缩3.1 压缩优先级autoCompactIfNeeded中的压缩策略有明确的优先级// 优先尝试会话记忆压缩constsessionMemoryResultawaittrySessionMemoryCompaction(messages,toolUseContext.agentId,threshold)if(sessionMemoryResult){// 会话记忆压缩成功跳过传统压缩return{wasCompacted:true,compactionResult:sessionMemoryResult}}// 回退到传统对话压缩constcompactionResultawaitcompactConversation(messages,...)会话记忆压缩优先于传统压缩因为它基于记忆提取而非简单截断能保留更多有价值的上下文信息。3.2 传统压缩的预处理compact.ts中的传统压缩在调用 API 之前会进行多步预处理// 剥离消息中的图片内容functionstripImagesFromMessages(messages:Message[]):Message[]{...}// 清理重复注入的附件functionstripReinjectedAttachments(messages:Message[]):Message[]{...}// 针对 prompt-too-long 错误的紧急头部截断functiontruncateHeadForPTLRetry(messages:Message[]):Message[]{...}3.3 微压缩microCompact.ts实现了缓存级别的细粒度压缩维护了 pending 和 pinned 两个编辑队列functionconsumePendingCacheEdits():CacheEdit[]{...}functiongetPinnedCacheEdits():PinnedCacheEdits[]{...}functionpinCacheEdits(edits:CacheEdit[]):void{...}微压缩在 API 请求层面操作通过编辑缓存中的内容来减少 token 消耗粒度比对话级压缩更细。3.4 按轮次分组grouping.ts将消息按 API 轮次分组确保压缩时不会在一个轮次的中间截断functiongroupMessagesByApiRound(messages:Message[]):Message[][]{...}四、Token 估算tokenEstimation.ts提供了多层次的 token 计数策略在精度和性能之间做了梯度权衡。4.1 API 精确计数asyncfunctioncountTokensWithAPI(messages,model,systemPrompt):Promisenumber{...}调用 Claude API 的 token counting 端点精度最高但有网络延迟。4.2 Haiku 回退asyncfunctioncountTokensViaHaikuFallback(messages,systemPrompt):Promisenumber{...}当主模型不可用时使用 Haiku 模型估算。Haiku 的 tokenizer 与主模型相近但调用成本更低。4.3 粗略估算functionroughTokenCountEstimation(text:string):number{// 默认 4 bytes per token}functionbytesPerTokenForFileType(fileExtension:string):number{// 不同文件类型的 bytes-per-token 比率不同// 代码文件通常比自然语言文本有更高的 bytes-per-token}粗略估算按字节数计算并根据文件类型调整比率。这是最快的估算方式用于不需要精确计数的场景。4.4 Bedrock 计数asyncfunctioncountTokensWithBedrock({messages,model}):Promisenumber{...}针对 AWS Bedrock 部署的专用计数端点。五、成本追踪cost-tracker.ts实现了按模型的 token 用量和费用追踪。5.1 核心功能functionaddToTotalModelUsage(model,inputTokens,outputTokens){...}functionaddToTotalSessionCost(cost){...}functionformatModelUsage():string{...}// 格式化各模型用量functionformatTotalCost():string{...}// 格式化总费用functionformatCost(cost,maxDecimalPlaces):string{...}5.2 会话恢复functiongetStoredSessionCosts(sessionId):StoredCosts|null{...}functionrestoreCostStateForSession(sessionId):boolean{...}functionsaveCurrentSessionCosts(fpsMetrics?):void{...}成本状态支持跨会话持久化和恢复确保用户在恢复会话时能看到累计的费用统计。六、Prompt Cache 感知压缩系统与 prompt cache 机制紧密集成。每次压缩后都会通知 cache 检测系统if(feature(PROMPT_CACHE_BREAK_DETECTION)){notifyCompaction(querySource??compact,toolUseContext.agentId)}源码注释解释了这一设计的必要性// Reset cache read baseline so the post-compact drop isnt flagged // as a break. BQ 2026-03-01: missing this made 20% of // tengu_prompt_cache_break events false positives压缩会导致 prompt cache 命中率下降如果不重置基线cache 监控系统会将这种正常下降误报为 cache 中断。在引入这一修复之前20% 的 cache 中断告警是误报。七、与其他系统的协同7.1 与上下文折叠的互斥当上下文折叠Context Collapse功能启用时自动压缩会被禁用if(feature(CONTEXT_COLLAPSE)){const{isContextCollapseEnabled}require(../contextCollapse/index.js)if(isContextCollapseEnabled())returnfalse}源码注释详细解释了原因上下文折叠在 90% 时提交、95% 时阻塞而自动压缩在约 93% 时触发。两者会竞争自动压缩通常先触发会销毁折叠系统正在保存的细粒度上下文。7.2 与会话记忆的协同压缩完成后会重置会话记忆的状态// Reset lastSummarizedMessageId since compaction replaces all messages// and the old message UUID will no longer existsetLastSummarizedMessageId(undefined)压缩会替换消息列表旧的消息 UUID 不再存在因此记忆系统的上次总结位置标记需要重置。八、总结Claude Code 的上下文窗口管理系统展示了一套经过生产验证的方案。其核心设计决策可以归纳为其一数据驱动的参数选择。20,000 token 的输出预留基于 p99.99 统计熔断阈值 3 次基于全局 API 调用浪费的量化分析。这些参数不是拍脑袋决定的而是来自生产环境的持续监控。其二多策略梯度降级。从会话记忆压缩到传统压缩从 API 精确计数到粗略估算系统在每个环节都提供了降级方案确保在各种条件下都能正常工作。其三系统间的协同设计。压缩系统与 prompt cache、上下文折叠、会话记忆等系统之间有明确的协同规则和互斥关系避免了多个系统同时操作上下文导致的冲突。其四熔断保护防止级联故障。当压缩持续失败时熔断器及时止损避免了无效的 API 调用浪费。这种防御性编程在 AI 应用中尤为重要因为 API 调用既有延迟又有成本。对于正在构建长对话 AI 应用的团队这套系统最值得借鉴的是熔断机制的设计——在 AI 应用中知道何时停止重试与知道何时触发操作同样重要。

更多文章