# 9. 上下文压缩机制

# 9.1 原理

压缩目标不是“尽可能短”,而是在固定预算下最大化未来任务成功率。应保留:用户目标、硬约束、当前计划、已修改内容、关键工具证据、失败教训、未完成事项和恢复指针。

常见策略:

  • Truncation:对单个巨大 ToolResult 做 head/tail/结构化裁剪。
  • Micro-compaction:折叠旧的重复工具输出、搜索结果、进度消息。
  • Semantic compaction:模型生成历史摘要并替换较老消息。
  • State lifting:从自由文本提取 plan、file set、todo 等结构化状态。
  • 保存到 Context 外部(Externalization):把大内容写入文件或对象存储,Context 只保留摘要、URI 和哈希。

不能破坏 API 不变量:Assistant 的每个 tool_use_id 必须有对应 User tool_result;不能从一组并行 ToolCall 中间切断;系统/开发者约束要重新注入而非只依赖摘要。

# 9.2 Claude Code 源码

  • calculateMessagesToKeepIndex 按最小/最大 Token 预算寻找保留后缀,并用 adjustIndexToPreserveAPIInvariants 调整切点:sessionMemoryCompact.ts:232sessionMemoryCompact.ts:317
  • 传统 compact 生成 summary,保留选定后缀,再恢复 Session Hook、近期文件、Plan、Skill、Plan Mode 和后台 Agent 状态:compact.ts:957compact.ts:1402compact.ts:1471compact.ts:1567
  • 压缩 Agent 被禁止调用 Tool,只允许输出文本摘要,减少副作用面:compact.ts:1127

# 9.3 Codex 源码

Codex 先从历史收集用户消息,再构建替代历史;对用户消息设总 Token 上限,必要时截断;随后以 replace_compacted_history 原子替换模型历史并持久化 compaction checkpoint:compact.rs:346compact.rs:368compact.rs:616

在 Turn 内,如果模型还需要根据工具结果继续推理且 Token 达到阈值,系统会在 Turn 执行中压缩历史(mid-turn compact),然后继续同一个 Turn,而不是直接失败:turn.rs:422

# 9.4 伪代码

function compact(history, budget):                                    // 在给定 Token 预算内生成可继续执行的替代历史
    safeBoundary = findBoundary(history, rules = [                    // 按 API 不变量寻找可安全切分的历史边界
        doNotSplitToolUseAndResult,                                   // 禁止拆开工具调用与对应工具结果
        doNotSplitParallelToolBatch,                                  // 禁止从一组并行调用的中间位置切断
        preserveLatestUserGoal])                                      // 确保最新用户目标保留在未压缩或明确摘要区域
    oldPrefix = history.before(safeBoundary)                          // 提取将被语义摘要替代的较旧历史
    recentSuffix = history.from(safeBoundary)                         // 保留包含近期细节的原始历史后缀
    summary = summaryModel.generate({                                 // 要求摘要模型按结构保留未来执行所需信息
        goal, constraints, decisions, failures,                      // 保存目标、硬约束、关键决策和失败教训
        fileChanges, pendingTasks, evidenceHandles                    // 保存文件变更、未完成任务和证据句柄
    }, oldPrefix)                                                     // 只对安全边界前的旧历史执行摘要
    replacement = [systemContext, summary, recentSuffix]              // 用系统上下文、摘要和近期后缀重建模型历史
    assert tokenEstimate(replacement) <= budget                       // 防止压缩结果本身仍超过目标窗口预算
    persistCompactionCheckpoint(oldHistoryHash, summary, replacement) // 保存原历史哈希和替换内容,支持审计与恢复
    return replacement                                                // 返回满足预算且保持任务连续性的上下文

# 9.5 面试题

问:如何评测压缩质量?

不能只看压缩率。要在真实长任务上比较压缩前后的任务成功率、下一步动作一致性、关键事实召回率、重复工具调用率、恢复成功率、Token 与延迟成本;还要检查摘要是否符合事实、是否保留信息来源。

# 9.6 原理深化:压缩是一笔可回滚的历史替换事务

压缩不是调用摘要模型后覆盖数组,而是一笔事务:先冻结待压缩窗口和世界状态,选择不破坏 ToolUse/ToolResult 的边界,生成摘要,重建强制上下文和近期后缀,验证 Token 与协议不变量,最后原子替换 History 并记录 checkpoint。摘要失败时保留旧 History;替换成功后才推进 window id,避免恢复时读到“旧窗口已删除、新窗口尚未完整写入”的中间态。

Codex 为 Turn 执行中压缩(mid-turn)与 Turn 开始前压缩(pre-turn)定义了不同的初始 Context 插入策略,因为模型训练所期待的“最后一条消息”位置不同;替代历史写入后还会重算 Token,并记录压缩前后的有效输入量、摘要和缓存读写量等指标:compact.rs:57compact.rs:344compact.rs:368compact.rs:448。这说明“摘要文本正确”还不够,插入位置、窗口编号和 Token 统计更新也属于压缩逻辑。

触发判断也应使用预测预算,而不只看当前 Token:预计下一步输入 = 当前有效输入 + 待注入的世界状态变化 + 用户输入 + 工具 Schema 变化。如果已超限才压缩,连压缩请求本身都可能无法发送。失败恢复分三层:先截断单个过大的 ToolResult,再折叠重复日志、进度和旧工具结果(Micro-compaction),最后生成语义摘要;生成摘要后仍超限时,应逐步移除价值较低的旧用户消息,而不是无限递归调用摘要模型。

最后更新: 2026/8/5 22:05:21