第十四篇:Context Management 上下文管理 —— 200K Token 窗口的利用艺术

发布时间:2026/7/8 1:11:38
第十四篇:Context Management 上下文管理 —— 200K Token 窗口的利用艺术 第十四篇Context Management 上下文管理 —— 200K Token 窗口的利用艺术系列文章 第14篇/共100篇· 2026年7月 · ⏱️ 阅读时间约 15 分钟源码位置src/services/compact/、src/query/tokenBudget.ts、src/utils/context.ts一、引言为什么需要 Context ManagementClaude Code 的上下文窗口是200K tokens约 15 万英文词或 6 万中文字。听起来很大但实际用起来你才会发现有多容易撑满一次git status输出 ≈ 800 tokens读完一个大型文件的 Read 工具调用 ≈ 5K tokens三次对话轮次用户助手工具结果≈ 12K tokens工具调用结果编译输出、测试日志、API 响应≈ 数十 K tokens图片附件一张 ≈ 数百 tokenmetadata但 base64 嵌入可能有 1K用户粘了 100 行代码的终端输出 ≈ 20K tokens在长会话中——代码调试、大项目重构、多文件工作流——200K 窗口几轮就能耗尽而不自知。更糟的是一旦上下文满了会怎样最新的 API 调用会得到prompt_too_long错误引发「压缩-重试」的连锁反应用户感受到的就是超时、出错、卡死。这就是 Context Management上下文管理存在的理由。它不是「要不要」的问题而是「怎么管才能不让用户感觉到被管」的问题。Claude Code 的上下文管理是一整套体系┌──────────────────────────────────────────────────────────────────┐ │ Context Management │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ ① 阈值感测 ② 自动压缩Auto Compact │ │ ┌──────────────────┐ ┌────────────────────────────────────┐ │ │ │ tokenCount │ │ stripImages → microcompact → │ │ │ │ getEffectiveSize │ │ compact (API summary) → rebuild │ │ │ │ buffer 安全区 │ │ PromptTooLong 重试 │ │ │ └──────────────────┘ └────────────────────────────────────┘ │ │ │ │ ③ Token Budget ④ /context 命令 │ │ ┌──────────────────────┐ ┌────────────────────────────────┐ │ │ │ 续写检测 │ │ 可视化各分区 token 占用 │ │ │ │ 递减收益判定 │ │ 系统提示/用户消息/工具结果 │ │ │ │ 停止或续写决策 │ │ 手动触发压缩 │ │ │ └──────────────────────┘ └────────────────────────────────┘ │ │ │ │ ⑤ Snip / Collapse实验 ⑥ 上下文窗口升级检测 │ │ ┌──────────────────────┐ ┌──────────────────────────────┐ │ │ │ 片段压缩 / 折叠 │ │ model → context window │ │ │ │ feature gate 控制 │ │ 自动选择更大窗口模型 │ │ │ └──────────────────────┘ └──────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────────────────────┘下面从代码层面拆解每一层。二、核心常量默认 200K但可以不同Claude Code 的上下文窗口默认是 200K但这不是写死的// src/utils/context.tsexportconstMODEL_CONTEXT_WINDOW_DEFAULT200_000exportfunctiongetContextWindowForModel(model:string,betas:string[]|undefined,):number{// 1) 优先查 API 的 model capsconstcapgetModelCap(model)if(cap?.max_input_tokens){if(cap.max_input_tokensMODEL_CONTEXT_WINDOW_DEFAULT){// 某些 model 超过 200K如 Opus 更新后returncap.max_input_tokens}returnMath.min(cap.max_input_tokens,MODEL_CONTEXT_WINDOW_DEFAULT)}// 2) 环境变量覆盖if(process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW){// 调试 / 企业管控时可设constparsedparseInt(envVar,10)if(!isNaN(parsed)parsed0)returnparsed}// 3) 兜底returnMODEL_CONTEXT_WINDOW_DEFAULT}关键设计点模型感知不同模型有不同的窗口Sonnet 200K、Opus 可能更大getContextWindowForModel读取model caps来动态计算。环境变量覆盖企业可通过CLAUDE_CODE_AUTO_COMPACT_WINDOW限制窗口——对安全敏感场景很有用。Beta 兼容SDK 模式的 beta 标志也影响窗口大小。三、Auto Compact自动压缩无声无息3.1 阈值感测autoCompact.ts中的getEffectiveContextWindowSize()确定真正的可用空间exportfunctiongetEffectiveContextWindowSize(model:string):number{constreservedTokensForSummaryMath.min(getMaxOutputTokensForModel(model),MAX_OUTPUT_TOKENS_FOR_SUMMARY,// 20_000)letcontextWindowgetContextWindowForModel(model,getSdkBetas())constautoCompactWindowprocess.env.CLAUDE_CODE_AUTO_COMPACT_WINDOWif(autoCompactWindow){constparsedparseInt(autoCompactWindow,10)if(!isNaN(parsed)parsed0){contextWindowMath.min(contextWindow,parsed)}}returncontextWindow-reservedTokensForSummary}然后在它之上设置多重缓冲区exportconstAUTOCOMPACT_BUFFER_TOKENS13_000// 触发自动压缩的缓冲exportconstWARNING_THRESHOLD_BUFFER_TOKENS20_000// 告警阈值exportconstERROR_THRESHOLD_BUFFER_TOKENS20_000// 错误阈值exportconstMANUAL_COMPACT_BUFFER_TOKENS3_000// 手动压缩的缓冲exportfunctiongetAutoCompactThreshold(model:string):number{consteffectiveContextWindowgetEffectiveContextWindowSize(model)returneffectiveContextWindow-AUTOCOMPACT_BUFFER_TOKENS} 计算示例模型为 Sonnet200K 窗口保留 20K 给输出。有效窗口 180K。自动压缩阈值 180K - 13K 167K。也就是说上下文达到 167K tokens 时自动压缩就开始了——而不是等到彻底满。3.2 自动压缩执行链autoCompactIfNeeded()的流程简化版// 概念模型 —— 实际在 autoCompact.ts 中实现asyncfunctionautoCompactIfNeeded(messages,context,...){// 1) 检查是否已达到压缩阈值if(currentTokensthreshold){return{messages,compacted:false}// 还不够阈值跳过}// 2) 压缩前的准备工作// 调用 PreCompact hooks → 触发 compact progress UI → 设置 requesting 状态// 3) 跳过无意义的压缩如上下文还太短if(messages.lengthMIN_MESSAGES_FOR_COMPACT){return{messages,compacted:false}}// 4) 执行压缩constresultawaitcompactConversation(messages,...)// 5) 重建消息列表边界标记 摘要 保留消息 附件 hook 结果constnewMessagesbuildPostCompactMessages(result)// 6) 后处理清理、通知缓存、发送遥测awaitrunPostCompactCleanup(...)return{messages:newMessages,compacted:true}}3.3 断路器机制Claude Code 还实现了「断路器」——连续失败 3 次后不再尝试自动压缩constMAX_CONSECUTIVE_AUTOCOMPACT_FAILURES3设计思考为什么需要断路器数据表明单次会话中有多达 1279 次连续失败的自动压缩每天浪费约 250K API 调用。这意味着某些情况下上下文确实已经「不可恢复地超限」了比如用户的单条消息就撑爆窗口继续尝试压缩只是烧钱。四、compactConversation核心压缩引擎核心函数compactConversation()约 1300 行位于src/services/compact/compact.ts是整个上下文管理的执行主体。4.1 压缩前剥离图片图片对生成摘要没有帮助但会占用大量输入 token甚至让压缩请求也触发prompt_too_longexportfunctionstripImagesFromMessages(messages:Message[]):Message[]{returnmessages.map(message{if(message.type!user)returnmessageconstcontentmessage.message.contentif(!Array.isArray(content))returnmessage// 替换所有 image block 为文本标记 [image]// document block → [document]// tool_result 中嵌套的图片同样处理constnewContentcontent.flatMap(block{if(block.typeimage)return[{type:text,text:[image]}]if(block.typedocument)return[{type:text,text:[document]}]if(block.typetool_resultArray.isArray(block.content)){// 递归处理 tool_result 内的图片}returnblock})return{...message,message:{...message.message,content:newContent}}})}4.2 压缩请求压缩本身是通过 API 调用另一个模型来完成的——用 Sonnet 总结对话内容// 核心发送「请总结此对话」请求给模型constcompactPromptgetCompactPrompt(customInstructions)constsummaryRequestcreateUserMessage({content:compactPrompt})// 流式获得摘要constsummaryResponseawaitstreamCompactSummary({messages:messagesToSummarize,// 已剥离图片的完整对话summaryRequest,// 压缩指令 promptappState,context,cacheSafeParams,})// 提取摘要文本constsummarygetAssistantMessageText(summaryResponse)压缩 prompt 包含以下要素CRITICAL: Respond with TEXT ONLY. Do NOT call any tools. 在 analysis 中按时间线分析对话识别 - 用户的明确请求和意图 - 关键决策、技术概念和代码模式 - 具体细节文件名、代码片段、函数签名 在 summary 中提供结构化摘要4.3 压缩后的消息结构压缩后重新组织消息使用buildPostCompactMessages()exportfunctionbuildPostCompactMessages(result:CompactionResult):Message[]{return[result.boundaryMarker,// ← 边界标记: 标示以下内容是压缩摘要...result.summaryMessages,// ← 摘要消息: 模型生成的对话总结...(result.messagesToKeep??[]),// ← 保留消息: 最近几轮未压缩的消息...result.attachments,// ← 附件: 文件内容、技能等...result.hookResults,// ← Hook 结果: 第三方扩展注入]}元素含义Token 影响boundaryMarker告诉模型「下面这段是摘要」的系统边界标记很小几十 tokensummaryMessages压缩摘要用户消息格式模型继续对话的依据约 5K-20KmessagesToKeep最近几轮未压缩的原始消息可变attachments重新附加的关键文件内容POST_COMPACT_FILE_BUDGET50K上限hookResults插件/企业的压缩后回调结果可变4.4 Prompt Too LongPTL重试压缩请求本身也可能触发prompt_too_long这时候compactConversation会自动重试逐轮截断最早的消息consttruncatedptlAttemptsMAX_PTL_RETRIES?truncateHeadForPTLRetry(messagesToSummarize,ptlAttempts):nullif(truncated){messagesToSummarizetruncatedcontinue// 重试压缩请求}else{// 截断到最少的消息也超限 → 返回错误thrownewError(ERROR_MESSAGE_PROMPT_TOO_LONG)}五、Token Budget续写还是停止当模型用完一次生成的 token 限额时需要决定再给它一轮生成机会续写还是到此为止核心在src/query/tokenBudget.tsexportfunctioncheckTokenBudget(tracker:BudgetTracker,// 当前轮的预算追踪器agentId:string|undefined,// Agent 任务的特殊处理budget:number|null,// 本轮允许的 token 上限globalTurnTokens:number,// 本轮实际消耗的 token):TokenBudgetDecision{// Agent 模式不限制 tokenif(agentId||budgetnull||budget0){return{action:stop,completionEvent:null}}constpctMath.round((token/budget)*100)// 实际占比constdeltaSinceLastChecktokens-tracker.lastGlobalTurnTokens// 递减收益判定如果连续 3 次续写且每次增加不到 500 token// 说明越写越少了——这是模型卡在重复输出的信号constisDiminishingtracker.continuationCount3deltaSinceLastCheckDIMINISHING_THRESHOLD// 500// 占比超过 90% 才继续if(pctCOMPLETION_THRESHOLD){return{action:stop,completionEvent:null}}return{action:continue,nudgeMessage:getBudgetContinuationMessage(),// 继续...continuationCount:tracker.continuationCount1,pct,turnTokens:tokens,budget,}}关键设计点90% 阈值只有实际 token 消耗超过预算的 90% 才考虑续写——防止微小的超限就续写浪费时间。递减收益检测连续 3 次续写 每次增量 500 token → 判定为「递减收益」停止续写。这防止了模型在「继续写」的循环中浪费 token。Agent 豁免Agent 子任务不参与续写决策他们的 token 由父任务统一管理。六、/context 命令用户视角的上下文可视化src/commands/context/context-noninteractive.ts中实现的collectContextData()是/context命令的数据采集核心/context 显示当前上下文的使用情况 系统提示: 45,210 token (22.6%) ├─ 基本系统提示: 30,000 ├─ CLAUDE.md 记忆: 8,210 ├─ git状态: 2,500 ├─ 工具定义: 4,500 └─ 技能: 0 ━ 对话消息: 122,432 token (61.2%) ├─ 用户消息: 45,100 (5条) ├─ 助手消息: 62,332 (6条) ├─ 工具结果: 14,000 (12条) └─ 附件: 1,000 (2个) ━ 已压缩消息: 55,220 (压缩为一条摘要, 12,400 token) ━ 总计: 167,642 / 200,000 (83.8%) 自动压缩在 167,000 token 时触发差 1,358 token数据采集含一个重要的预转换步骤exportasyncfunctioncollectContextData(context){letapiViewgetMessagesAfterCompactBoundary(messages)// 如果启用了上下文折叠CONTEXT_COLLAPSE先折叠if(feature(CONTEXT_COLLAPSE)){apiViewprojectView(apiView)}// 对消息执行微压缩后再统计const{messages:compactedMessages}awaitmicrocompactMessages(apiView)// 获取 AppState 中的各分区信息constappStategetAppState()// ...统计各分区 token 数}这意味着/context看到的数据是经过微压缩后的跟模型实际收到的消息尽可能一致。七、微压缩Microcompact在完整压缩之前Claude Code 还做「微压缩」——轻量级、无需调用 API 的压缩步骤// src/services/compact/microCompact.ts// 微压缩在不调用模型的前提下// 1) 压缩连续的多段系统提示// 2) 合并相邻的用户消息// 3) 移除冗余的边界标记// 4) 移除与最终工具调用无关的历史附件// 目的是减少「噪声 token」而不损失信息微压缩不生成摘要只是结构性地删除冗余。它发生在每一次 API 调用之前以及/context数据采集时。八、实验性功能上下文折叠Context Collapse与 Snip 压缩8.1 Context Collapsesrc/services/contextCollapse/目录下的实验性功能当前状态// operations.ts当前是 stub桩函数exportfunctionsummarizeContextCollapseState(){returnnull}exportfunctiongetContextCollapsePreview(){return[]}上下文折叠的概念是为「子任务完成」的场景设计的——当 Agent 的子工具调用序列完成时折叠为一段简洁摘要类似函数返回值的优化。目前受CONTEXT_COLLAPSEfeature gate 控制尚未正式上线。8.2 Snip Compact类似地snipCompact.ts也是 stubexportfunctionsnipCompactIfNeededT(messages,_options?){return{messages,changed:false}}它的理念是「片段级」压缩——不等整轮对话在每次工具调用结果返回后立即做片段压缩但目前尚未实现。九、CompactionResult压缩后的最终数据结构exportinterfaceCompactionResult{boundaryMarker:SystemMessage// 压缩边界标记summaryMessages:UserMessage[]// 模型生成的摘要作为用户消息attachments:AttachmentMessage[]// 重新附加的关键文件hookResults:HookResultMessage[]// 第三方 hook 结果messagesToKeep?:Message[]// 最近的未压缩消息userDisplayMessage?:string// 向用户显示的压缩通知preCompactTokenCount?:number// 压缩前 token 数postCompactTokenCount?:number// 压缩后预估的 token 数truePostCompactTokenCount?:number// 压缩后实际 token 数compactionUsage?:TokenUsage// 压缩 API 调用本身的 token 消耗}重要设计决策为什么摘要以用户消息形式放置因为摘要需要「推入」模型的上下文。如果放在system消息里它可能被system_reminder之类的内容覆盖。作为user消息它会被模型正常读取和处理。Claude Code 用一个boundaryMarker系统消息来告诉模型「下面是一段对话摘要不是新的用户输入」。十、总结组件位置核心职责窗口计算utils/context.ts按模型返回 200K / Opus 更大窗口阈值感测services/compact/autoCompact.ts计算有效窗口、安全缓冲区、自动压缩占比自动压缩services/compact/autoCompact.ts检查 ifNeeded → 调用 compact核心压缩services/compact/compact.tsstripImages → 流式摘要 → PTL 重试 → buildPostCompact微压缩services/compact/microCompact.ts结构性去冗余零 API 成本Token 预算query/tokenBudget.ts续写 / 停止决策递减收益判定上下文命令commands/context/可视化上下文占用支持手动触发压缩实验性contextCollapse/、snipCompact.ts子任务折叠、片段压缩stubClaude Code 的 Context Management 不仅仅是一个「满了就压缩」的简单逻辑。它是一套多层次、递进式、断路器保护的窗口管理体系每轮对话结束时微压缩先做一次轻量清理达到 167K/200K 阈值时自动压缩触发 API 摘要摘要后保留最近的原始消息 关键附件压缩本身超限时自动截断重试连续失败 3 次断路器熔断避免浪费/context命令让用户随时查看压缩前后状态这是一套让「200K 窗口永远够用」的保障系统。十一、下一篇预告第15篇我们将深入Auto Compact 自动压缩机制——分析压缩触发条件、对话摘要策略、以及 Haiku / Sonnet 模型在压缩中的角色。敬请期待Claude Code 源码解析系列源码泄露事件深度解析开发环境搭建指南CLI入口与命令系统AI对话引擎全解析工具系统深度解析CLI命令行解析核心Handler处理器链QueryEngine查询引擎query.ts API对话层callModel.ts API调用层AnthropicClient API客户端MessageBuilder 消息构建器Permission Model 权限模型Context Management 上下文管理← 本文Auto Compact 自动压缩待续如果这篇文章对你有帮助欢迎点赞、收藏、关注❓有问题欢迎在评论区讨论#ClaudeCode #源码分析 #ContextManagement #上下文管理 #200K #Token预算 #AutoCompact #TypeScript #AI编程