128K 上下文也会爆?看 Claude Code 怎么用一条命令「瘦身」对话

一句话总结

  • /compact 是一个本地命令 (type: local),不是 prompt 注入------它直接在本地执行压缩逻辑,然后用一段 summary 替换整个对话历史
  • 压缩分三层:Session Memory Compaction(增量摘要)→ Reactive Compaction(响应式)→ Traditional Compaction(全量压缩)
  • 核心 prompt 要求 LLM 输出 9 个维度的结构化摘要,用 <analysis> + <summary> 两段式输出确保质量
  • 自动触发机制 :当 token 用量超过 contextWindow - 13000 时自动压缩,无需手动干预
  • 压缩后会恢复关键文件内容(最多 5 个文件,每个最多 5000 token),确保 AI 不丢失工作上下文

源码定位

bash 复制代码
src/commands/compact/index.ts       ← 命令注册入口(15行)
src/commands/compact/compact.ts     ← 命令执行逻辑(287行)
src/services/compact/compact.ts     ← 核心压缩服务(60KB,1700+行)
src/services/compact/prompt.ts      ← 压缩 prompt 设计(374行)
src/services/compact/autoCompact.ts ← 自动触发逻辑
src/services/compact/microCompact.ts ← 微压缩(预处理)

命令类型local

这是和 /init(type: prompt)的关键区别。/compact 不会把 prompt 注入对话让 LLM 自行发挥------它有完整的本地执行逻辑,自己调用 API 做摘要,然后用摘要替换历史消息。

typescript 复制代码
const compact = {
  type: 'local',           // ← 本地执行,不是 prompt 注入
  name: 'compact',
  description: 'Clear conversation history but keep a summary in context.',
  isEnabled: () => !isEnvTruthy(process.env.DISABLE_COMPACT),
  argumentHint: '<optional custom summarization instructions>',
  load: () => import('./compact.js'),
} satisfies Command

执行流程:三层压缩策略

当你输入 /compact 或系统自动触发时,执行顺序如下:

复制代码
┌─────────────────────────────────────────────────────────┐
│  1. Session Memory Compaction(增量式,优先尝试)         │
│     ↓ 失败或不适用                                       │
│  2. Reactive Compaction(响应式,仅内部启用)             │
│     ↓ 功能未开启                                         │
│  3. Traditional Compaction(全量压缩,兜底方案)          │
│     3.1 MicroCompact 预处理                              │
│     3.2 compactConversation 全量摘要                     │
└─────────────────────────────────────────────────────────┘

源码中的分层逻辑:

typescript 复制代码
export const call: LocalCommandCall = async (args, context) => {
  let { messages } = context
  messages = getMessagesAfterCompactBoundary(messages)  // 跳过已压缩的部分

  const customInstructions = args.trim()

  // 第一层:Session Memory Compaction
  if (!customInstructions) {
    const sessionMemoryResult = await trySessionMemoryCompaction(messages, context.agentId)
    if (sessionMemoryResult) {
      // 成功!清理缓存、标记状态、返回
      markPostCompaction()
      suppressCompactWarning()
      return { type: 'compact', compactionResult: sessionMemoryResult }
    }
  }

  // 第二层:Reactive Compaction(ant-only feature)
  if (reactiveCompact?.isReactiveOnlyMode()) {
    return await compactViaReactive(messages, context, customInstructions, reactiveCompact)
  }

  // 第三层:Traditional Compaction
  const microcompactResult = await microcompactMessages(messages, context)  // 预处理
  const result = await compactConversation(microcompactResult.messages, context, ...)
  return { type: 'compact', compactionResult: result }
}

核心源码解读

1. 自动触发机制(autoCompact.ts)

Claude Code 不会等你手动输入 /compact------它有一套精密的自动触发逻辑:

typescript 复制代码
// 核心阈值常量
export const AUTOCOMPACT_BUFFER_TOKENS = 13_000
export const WARNING_THRESHOLD_BUFFER_TOKENS = 20_000
export const MANUAL_COMPACT_BUFFER_TOKENS = 3_000

export function getAutoCompactThreshold(model: string): number {
  const effectiveContextWindow = getEffectiveContextWindowSize(model)
  return effectiveContextWindow - AUTOCOMPACT_BUFFER_TOKENS
}

// 有效窗口 = 总窗口 - 预留输出token(最多20000)
export function getEffectiveContextWindowSize(model: string): number {
  const reservedTokensForSummary = Math.min(
    getMaxOutputTokensForModel(model),
    20_000,  // MAX_OUTPUT_TOKENS_FOR_SUMMARY
  )
  return getContextWindowForModel(model) - reservedTokensForSummary
}

以 Claude Sonnet(200K 上下文)为例

  • 有效窗口 = 200,000 - 20,000 = 180,000 token
  • 自动压缩阈值 = 180,000 - 13,000 = 167,000 token
  • 也就是说,当对话消耗约 83% 的上下文时自动触发

熔断机制:连续失败 3 次后停止自动重试,防止无限循环浪费 API 调用:

typescript 复制代码
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3
// BQ 2026-03-10: 1,279 sessions had 50+ consecutive failures
// wasting ~250K API calls/day globally.

2. 压缩 Prompt 设计(prompt.ts)

这是整个命令最精华的部分------如何让 LLM 在一次调用中产出高质量摘要。

两段式输出策略

typescript 复制代码
const NO_TOOLS_PREAMBLE = `CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.
- Tool calls will be REJECTED and will waste your only turn --- you will fail the task.
- Your entire response must be plain text: an <analysis> block followed by a <summary> block.`

<analysis> 是一个草稿区 ------让 LLM 先梳理思路,然后才写正式摘要。最终 formatCompactSummary() 会把 <analysis> 整段删掉,只保留 <summary> 中的内容:

typescript 复制代码
export function formatCompactSummary(summary: string): string {
  let formattedSummary = summary
  // 删除分析部分------它是提升质量的"思维链",不进入最终上下文
  formattedSummary = formattedSummary.replace(/<analysis>[\s\S]*?<\/analysis>/, '')
  // 格式化 summary 标签
  const summaryMatch = formattedSummary.match(/<summary>([\s\S]*?)<\/summary>/)
  if (summaryMatch) {
    formattedSummary = formattedSummary.replace(
      /<summary>[\s\S]*?<\/summary>/, `Summary:\n${summaryMatch[1].trim()}`
    )
  }
  return formattedSummary.trim()
}

9 维度结构化摘要模板

markdown 复制代码
1. Primary Request and Intent   ← 用户的原始意图
2. Key Technical Concepts       ← 涉及的技术栈
3. Files and Code Sections      ← 文件和代码片段(要求完整代码)
4. Errors and fixes             ← 踩过的坑
5. Problem Solving              ← 解决过程
6. All user messages            ← 所有用户消息(防止丢失反馈)
7. Pending Tasks                ← 待办事项
8. Current Work                 ← 当前正在做什么(最重要)
9. Optional Next Step           ← 接下来该做什么

第 9 条的特殊约束

vbnet 复制代码
IMPORTANT: ensure that this step is DIRECTLY in line with the user's most recent
explicit requests. Do not start on tangential requests or really old requests
that were already completed without confirming with the user first.

防止 AI 压缩后"跑偏"去做已经完成的老任务------这是从真实用户反馈中总结的 bug。

3. MicroCompact:压缩前的预处理

在全量压缩之前,先做一轮"微压缩"------去除对摘要没用的噪音:

typescript 复制代码
export function stripImagesFromMessages(messages: Message[]): Message[] {
  return messages.map(message => {
    if (message.type !== 'user') return message
    // 图片替换为 [image] 标记
    // 文档替换为 [document] 标记
    // 嵌套在 tool_result 中的图片也会被处理
  })
}

图片对摘要毫无价值,但占大量 token。微压缩就是把这些"噪音"先去掉,让摘要 API 调用本身不会触发 prompt-too-long 错误。

4. 压缩后的文件恢复

压缩后,AI 会丢失所有它之前读过的文件内容。为了不让 AI"失忆",压缩后会自动重新注入关键文件:

typescript 复制代码
export const POST_COMPACT_MAX_FILES_TO_RESTORE = 5
export const POST_COMPACT_TOKEN_BUDGET = 50_000
export const POST_COMPACT_MAX_TOKENS_PER_FILE = 5_000

最多恢复 5 个文件,总预算 50K token,单个文件最多 5K token。


Prompt 拆解:为什么用两段式?

设计决策 原因
<analysis> 先行 相当于 Chain-of-Thought,让 LLM 先"想清楚"再输出正式结果
<analysis> 最终被删除 不浪费宝贵的压缩后上下文空间
NO_TOOLS_PREAMBLE 放在最前面 Sonnet 4.6 上模型有 2.79% 概率忽略后置的"禁止工具"指令
要求"include direct quotes" 防止摘要在多次压缩后产生语义漂移
9 个固定维度 结构化输出比自由发挥的摘要更稳定、更可靠

设计亮点:三个可迁移的 Pattern

1. 分层降级策略

复制代码
Session Memory → Reactive → Traditional

不是一种压缩方案打天下,而是按成本和效果分层:Session Memory 最便宜(增量式),Traditional 最贵(全量重新摘要)。优先尝试便宜的,失败再 fallback。

可迁移:任何需要"降低延迟但保证成功"的 AI 场景都可以用这种分层 fallback 模式。

2. 草稿-正稿两段式输出

让 LLM 先在 <analysis> 里自由发挥(提升质量),然后只取 <summary> 部分(控制格式)。这是一个既要质量又要结构化的通用解法。

可迁移:任何需要 LLM 输出结构化内容的场景(报告生成、数据提取),都可以用"先思考后输出"的两段式 prompt。

3. 熔断器模式(Circuit Breaker)

typescript 复制代码
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3

连续失败 3 次就停止重试。这不是 AI 特有的------这是微服务架构中经典的熔断器模式,用在 AI Agent 中同样有效。

可迁移:任何自动化 AI 流程都需要熔断机制,防止错误状态下无限消耗资源。


实战建议

自定义压缩指令

bash 复制代码
# 压缩时强调保留代码变更
/compact focus on typescript code changes and test output

# 压缩时保留错误信息
/compact remember all error messages and stack traces verbatim

环境变量调优

bash 复制代码
# 调低自动压缩阈值(更早触发,适合复杂任务)
export CLAUDE_AUTOCOMPACT_PCT_OVERRIDE=60

# 自定义窗口大小
export CLAUDE_CODE_AUTO_COMPACT_WINDOW=100000

# 禁用自动压缩(只保留手动)
export DISABLE_AUTO_COMPACT=1

什么时候该手动 /compact

  • 切换任务方向时(前面的上下文已不相关)
  • 出现 AI "幻觉"引用旧文件内容时
  • 看到 "Context left until auto-compact" 警告时

下篇预告

第 3 篇:AI 写的 commit message 为什么比你写的好?/commit 源码揭秘

/commit 是整个系列里最"极简"的命令------92 行代码、一段 prompt 就搞定了 Conventional Commits。我们看看 Anthropic 如何用 !git diff`` 语法预执行 shell 命令,让 AI 拿到完整上下文后再写 commit message。


本文基于 Claude Code CLI 源码(TypeScript,反编译自 source map)分析。源码路径:src/commands/compact/ + src/services/compact/。如有更新,以最新版本为准。

相关推荐
码农小旋风3 小时前
Claude Code 基础用法大全:对话、分析、修改、测试、Git 和工作流
人工智能·git·chatgpt·claude
写点啥呢4 小时前
GLM HUD用量实时显示
claude·token·hud·glm
小闹54919 小时前
Claude Code 给自己接了一部飞书,从此不用守在工位等它
后端·claude
武子康21 小时前
调查研究-176 taste-skill:AI 编程时代,前端开发最缺的不是代码,而是品味
人工智能·openai·claude
147API1 天前
Fable 5访问暂停后,模型接入层不能再只写死一个模型名
大数据·人工智能·api·claude
乘风gg1 天前
前端死到第几轮了?得物前端部门解散有感!
前端·ai编程·claude
老程序猿1 天前
PDLC 1.1:v1.0 在产物形状上犯的两个错误
ai编程·claude
302wanger1 天前
/init 的设计哲学——为什么 AI 的项目记忆只需要写「它猜不到的」
claude