跳转至

Deep Dive | services/compact/ 3960 行上下文压缩系统拆解

重要性:⭐⭐⭐⭐(LLM 长会话的命脉 —— 没有压缩就没法跑长任务) 真实位置src/services/compact/12 文件 / ~3960 行角色:在 token 接近上限时,摘要历史消息以腾出空间 关联topics/context-compaction.mdphase-06-agent-loop.md § 6.6


1. 12 个文件全景

src/services/compact/ (12 文件 / ~3960 行)
├── compact.ts                    1705 行  ⭐ 主压缩逻辑(最大)
├── sessionMemoryCompact.ts        630 行  ⭐ Session memory 压缩(实验性)
├── microCompact.ts                530 行  ⭐ 轻量压缩(只压工具结果)
├── prompt.ts                      374 行  压缩 prompt 模板
├── autoCompact.ts                 351 行  ⭐ 自动压缩(token 接近上限)
├── apiMicrocompact.ts             153 行  API 级 microCompact
├── postCompactCleanup.ts           77 行  压缩后清理
├── grouping.ts                     63 行  消息分组算法
├── timeBasedMCConfig.ts             43 行  时间维度的 microCompact 配置
├── compactWarningState.ts          18 行  警告 store
└── compactWarningHook.ts           16 行  警告 React hook

结构清晰 —— 主压缩 + 多种辅助压缩 + UI 警告。


2. compact.ts 1705 行主压缩

2.1 推测的导出

// compact.ts 推测
export async function compact(
  messages: Message[],
  context: CompactContext,
): Promise<CompactResult>

export type CompactResult = {
  newMessages: Message[]  // 压缩后的 messages
  removedCount: number     // 删了多少
  summaryTokens: number   // 摘要用了多少 token
}

2.2 1705 行的"巨型函数"剖析

为什么这么大?推测拆分:

async function compact(messages, context) {
  // 1. 预处理(~100 行)
  //    - 过滤 synthetic
  //    - 合并连续 user messages
  //    - 处理 attachments

  // 2. 决策(~200 行)
  //    - 选压缩策略(auto / manual / reactive)
  //    - 计算要压缩的 messages
  //    - 计算要保留的 messages

  // 3. 分组(~150 行)
  //    - 按时间 / 主题 / 工具结果分组

  // 4. 摘要(~400 行)
  //    - 调 LLM(不同 prompt)
  //    - 验证摘要
  //    - 错误恢复

  // 5. 重组(~300 行)
  //    - 构造 compact_boundary message
  //    - 拼回新 messages
  //    - 保留 system / 最近 N 条

  // 6. 后处理(~200 行)
  //    - 清理
  //    - 持久化
  //    - 通知 UI

  // 7. 错误处理(~150 行)
  //    - 各种边界 case
  //    - 重试
  //    - fallback
}

1705 行的真相 —— 7 阶段,每阶段 100-400 行

2.3 关键设计

// 推测的"压缩后 messages 结构"
[
  // 1. 原始 system prompt(保留)
  { role: 'system', content: systemPrompt },

  // 2. compact_boundary message(标记压缩发生)
  { 
    role: 'system', 
    content: '[Conversation was compacted at turn 50. Summary follows.]' 
  },

  // 3. 摘要(替代中间消息)
  { 
    role: 'assistant', 
    content: '<compacted_summary>用户问了 X,我做了 Y...' 
  },

  // 4. 最近 N 条消息(保留)
  ...recentMessages,
]

4 段结构 —— system + boundary + summary + recent。

2.4 4 种压缩策略

// 推测
enum CompactStrategy {
  AUTO,             // 累计 token 接近上限
  MANUAL,           // 用户 /compact 命令
  REACTIVE,         // 动态判断(Ant-only)
  CONTEXT_COLLAPSE,  // 高级压缩(Ant-only)
}

4 策略 = 4 种触发条件 + 4 种实现。


3. sessionMemoryCompact.ts 630 行 Session Memory 压缩

3.1 角色

// 推测
/**
 * EXPERIMENT: Session memory compaction
 * 
 * 把"重要信息"提取到长期记忆(~/.claude/memories/),
 * 而不是丢弃。下次会话开始时自动注入。
 */

与"普通压缩"不同: - 普通压缩 = 丢弃(保留摘要) - session memory = 迁移(保留信息,搬到长期)

3.2 推测的 3 步骤

async function sessionMemoryCompact(messages) {
  // 1. 提取"重要"信息
  //    - 用户偏好
  //    - 项目约定
  //    - 重要决策
  //    - 代码风格
  const important = await extractImportantInfo(messages)

  // 2. 写入长期记忆
  await writeToMemory(important)

  // 3. 构造精简 messages
  return compactMessages(messages, {
    keepRecent: 5,
    removeOld: true,
    injectMemory: true,
  })
}

3 步 —— 提取 + 写 + 精简。

3.3 关键设计

  • 不丢信息 —— 搬到 memory 而非丢弃
  • 下次注入 —— 新会话开始时从 memory 恢复
  • "跨会话学习" —— Claude Code "能记住"

4. microCompact.ts 530 行轻量压缩

4.1 角色

只压缩工具结果(不动对话流)。

// 推测
function shouldMicroCompact(toolResult: ToolResult): boolean {
  const size = estimateTokens(toolResult.content)
  return size > MICRO_COMPACT_THRESHOLD  // 推测 2000 tokens
}

function microCompactToolResult(result: ToolResult): ToolResult {
  // 保留关键信息:
  // 1. exit code / 错误
  // 2. 头尾各 200 字符
  // 3. 元数据
  // 4. 折叠其他内容
}

4.2 4 种实现(推测)

// 1. microCompact(轻量,heuristic)
function microCompactToolResult(result): ToolResult {
  return truncateAndFold(result, { head: 200, tail: 200 })
}

// 2. apiMicrocompact(API 级,server 端)
// 在 src/services/compact/apiMicrocompact.ts:153 行
// 推测:调 Anthropic API 的 microCompact 参数

// 3. reactiveCompact(动态判断,Ant-only DCE)
// 在 query.ts 推测的 feature('REACTIVE_COMPACT') 分支

// 4. contextCollapse(高级,Ant-only DCE)
// 在 query.ts 推测的 feature('CONTEXT_COLLAPSE') 分支

4 种 microCompact —— 各有适用场景。

4.3 关键设计

  • 每次工具调用都跑(高频、轻量)
  • 不调 LLM(heuristic + 字符串截断)
  • 不影响对话流(只动 tool_result)

5. prompt.ts 374 行压缩 prompt

// 推测
export const COMPACT_PROMPT = `
You are a context compactor. Summarize the following conversation
in a way that preserves:
- User's goals and requirements
- Key decisions and their rationale
- Important file paths and code patterns
- Unresolved issues

Output format:
- Section 1: Goals
- Section 2: Decisions
- Section 3: Code Changes
- Section 4: Open Questions
`

export const MICRO_COMPACT_PROMPT = `
You are summarizing a tool result. Preserve:
- Exit code
- Error messages
- First/last 200 chars of stdout
- File paths
`

~374 行 —— 不同压缩策略用不同 prompt。


6. autoCompact.ts 351 行自动压缩

6.1 函数签名

// 推测
export function isAutoCompactEnabled(): boolean
export function calculateTokenWarningState(
  totalTokens: number,
  maxTokens: number,
): 'ok' | 'warning' | 'critical' | 'auto_compact'

export async function runAutoCompact(
  messages: Message[],
  context: CompactContext,
): Promise<Message[]>

6.2 4 状态警告

// 推测
function calculateTokenWarningState(total, max) {
  const ratio = total / max
  if (ratio < 0.7) return 'ok'         // 健康
  if (ratio < 0.85) return 'warning'    // 警告
  if (ratio < 0.95) return 'critical'   // 严重
  return 'auto_compact'                  // 触发压缩
}

4 状态: - ok (< 70%) - warning (70-85%) - critical (85-95%) - auto_compact (>= 95%)

6.3 触发流程

// 推测
async function runAutoCompact(messages, ctx) {
  // 1. 检查是否启用
  if (!isAutoCompactEnabled()) {
    return messages  // 不压缩
  }

  // 2. 计算 token
  const tokens = estimateTokens(messages)
  const max = getMaxContextTokens()

  // 3. 检查状态
  const state = calculateTokenWarningState(tokens, max)
  if (state !== 'auto_compact') {
    return messages
  }

  // 4. 调主压缩
  return compact(messages, ctx)
}

4 步 —— 检查 + 计算 + 判断 + 压缩。


7. apiMicrocompact.ts 153 行 API 级 microCompact

7.1 角色

Anthropic API 自带的 microCompact(vs Claude Code 自研的 microCompact)。

// 推测
export function applyApiMicrocompact(
  params: MessageStreamParams,
): MessageStreamParams {
  // 1. 找到最大的 tool_result
  // 2. 加 cache_control: { type: 'ephemeral' }
  // 3. 让 API 服务端做 microCompact
  return params
}

API 级 —— Claude Code 不用自己实现,让 server 端压缩

7.2 与 Claude Code 自研 microCompact 的对比

维度 apiMicrocompact microCompact.ts
实现 server 端 client 端
触发 API 请求时 工具完成后
性能 0 开销 微小
控制 API 提供 完全可控

两者共存 —— 互补。


8. grouping.ts 63 行分组算法

// 推测
export function groupMessagesForCompaction(
  messages: Message[],
): CompactionGroup[]

type CompactionGroup = {
  messages: Message[]
  priority: number  // 0 = 不压, 10 = 必压
  tokens: number
}

8.1 推测的算法

function groupMessagesForCompaction(messages) {
  // 1. 按时间窗口分组(每 10 条一组)
  // 2. 评分
  //    - 用户相关 → priority 0
  //    - 最近 5 轮 → priority 0
  //    - 工具结果密集 → priority 10
  //    - 系统消息 → priority 0
  // 3. 返回分组
}

63 行 = 紧凑的算法

8.2 关键启发式

  • user-related → 永压
  • 最近 N 轮 → 永压
  • 工具结果 → 优先压
  • 系统消息 → 永压

9. postCompactCleanup.ts 77 行压缩后清理

// 推测
export async function postCompactCleanup(
  context: AppState,
  compactedCount: number,
): Promise<void> {
  // 1. 清 file state cache
  context.fileStateCache.clearExpired()

  // 2. 清 tool result cache
  context.toolResultCache.purgeExpired()

  // 3. 清理 orphan attachments
  await cleanupOrphanAttachments()

  // 4. 记录 metrics
  recordCompactMetrics(compactedCount)
}

77 行的 cleanup —— 4 类资源清理。


10. prompt.ts 374 行(详解)

10.1 推测的 5 个 prompt 模板

// 1. 主压缩 prompt
export const COMPACT_PROMPT = `...`  // 完整摘要

// 2. 工具结果压缩 prompt
export const TOOL_RESULT_COMPACT_PROMPT = `...`

// 3. 决策压缩 prompt
export const DECISIONS_COMPACT_PROMPT = `...`

// 4. Open questions 压缩 prompt
export const OPEN_QUESTIONS_PROMPT = `...`

// 5. Session memory 提取 prompt
export const SESSION_MEMORY_EXTRACT_PROMPT = `...`

5 个不同 prompt —— 不同压缩策略用不同 prompt。

10.2 关键设计

  • 结构化输出 —— 强制 LLM 按 section 输出
  • 校验 —— prompt 输出要可解析
  • fallback —— LLM 失败时用简单截断

11. timeBasedMCConfig.ts 43 行时间配置

// 推测
export function getTimeBasedMCConfig(): {
  // microCompact 触发时间阈值
  triggerAfterMs: number
  // microCompact 强制时间阈值
  forceCompactAfterMs: number
}

时间维度的压缩配置 —— 长 idle 后强制压缩。


12. compactWarningState.ts 18 行 + compactWarningHook.ts 16 行 UI 警告

12.1 警告 store

// compactWarningState.ts 推测
import { createStore } from '../../state/store.js'

export type CompactWarning = {
  level: 'warning' | 'critical' | 'auto_compact'
  percentage: number
  message: string
}

export const compactWarningStore = createStore<CompactWarning | null>(null)

18 行的 store —— 警告状态。

12.2 React hook

// compactWarningHook.ts 推测
import { useSyncExternalStore } from 'react'
import { compactWarningStore } from './compactWarningState.js'

export function useCompactWarning(): CompactWarning | null {
  return useSyncExternalStore(
    compactWarningStore.subscribe,
    () => compactWarningStore.getState(),
  )
}

16 行的 hook —— UI 显示用。

12.3 UI 集成(推测)

// 在 REPL.tsx 中
const warning = useCompactWarning()

if (warning?.level === 'auto_compact') {
  return <AutoCompactWarningBanner percentage={warning.percentage} />
}

UI 显示 —— token 接近上限时弹横幅。


13. 整体压缩策略矩阵

策略 触发 范围 LLM 调用 文件
microCompact 工具结果超大 单个 tool_result ❌ heuristic microCompact.ts
apiMicrocompact 每次 API 请求 服务端处理 apiMicrocompact.ts
autoCompact token >= 95% 整段对话 ✅ 主 LLM autoCompact.ts + compact.ts
reactiveCompact 动态判断 智能选段 ✅ 主 LLM compact.ts (DCE)
contextCollapse 高级压缩 智能选段 ✅ 主 LLM compact.ts (DCE)
sessionMemoryCompact session 结束 提取到 memory ✅ 摘要 LLM sessionMemoryCompact.ts
manual (/compact) 用户命令 整段对话 ✅ 主 LLM compact.ts

7 种策略 —— 覆盖所有场景。


14. 压缩的"双向"流

[AppState 变化]
[onChange 钩子]
[每条消息 → microCompact 候选?]
  ├─ 是 → 立即 microCompact
  └─ 否 → 累积到 messages
[每 N 秒检查 token 状态]
  ├─ ok → 不动
  ├─ warning → UI 警告
  ├─ critical → 准备压缩
  └─ auto_compact → 触发主压缩
[主压缩: 调 LLM 摘要]
[postCompactCleanup 清缓存]
[新 messages 替换]

多层防御 —— microCompact 高频防 + autoCompact 兜底。


15. 关键设计

15.1 "压缩不调主 LLM" 的 microCompact

function microCompactToolResult(result) {
  // 不调 LLM
  // 用 heuristic
  return truncate(result, 200)
}

关键 —— microCompact 不开 LLM 调用
否则每次工具完成都贵 0.1 秒 + 0.001 美元

15.2 "压缩分 4 状态" 的渐进警告

'ok'  'warning'  'critical'  'auto_compact'

渐进 —— 给用户反应时间不是突然压缩

15.3 "压缩保留结构" 的 messages 重组

[
  system,           // 保留
  compact_boundary,  // 标记
  summary,          // 替代
  recent,           // 保留
]

4 段结构 —— LLM 能"看到"压缩发生过。

15.4 "session memory" 的跨会话学习

// 把重要信息搬到 ~/.claude/memories/
// 下次会话开始时从 memory 恢复

跨会话 —— Claude Code "能记住"。

15.5 "Group 评分" 是算法核心

const priority = isUserRelated(messages) ? 0 : 10

简单规则 —— 但准确

15.6 "DCE 门控" 隔离实验性策略

const reactiveCompact = feature('REACTIVE_COMPACT')
  ? require('./reactiveCompact.js') : null

Ant 内部实验 —— 外部用户不可见。


16. 实战:写一个简化版压缩系统

// 简化版(~50 行)
function simpleCompact(messages: Message[], maxTokens: number): Message[] {
  const totalTokens = estimateTokens(messages)
  if (totalTokens < maxTokens * 0.7) return messages

  // 保留最近 5 条
  const recent = messages.slice(-5)
  const old = messages.slice(0, -5)

  // 简单截断(不调 LLM)
  const truncated = old.map(m => ({
    ...m,
    content: m.content.slice(0, 200) + '... [truncated]',
  }))

  // 加 compact_boundary
  return [
    messages[0],  // system
    { role: 'system', content: '[Conversation was compacted]' },
    ...truncated,
    ...recent,
  ]
}

对比 Claude Code: - 简化版不调 LLM 摘要 - 简化版没有分策略 - 简化版没有 session memory - Claude Code 多了 100+ 边界处理


17. 关键洞察

17.1 "压缩 = LLM 应用的命门"

没有压缩: - 长会话跑 30 分钟就报错 - token 成本爆炸 - LLM 迷失在长上下文

有了压缩: - 跑 3 小时无问题 - 成本可控 - 性能稳定

17.2 "多层压缩"是性能 vs 质量 trade-off

层级 频率 成本 质量
microCompact 0 低(heuristic)
autoCompact 高(LLM 摘要)
sessionMemory 高(跨会话)

频率 ↑ = 成本 ↓ = 质量 ↓

17.3 "4 状态警告" 是用户体验

不是"突然压缩",是渐进提示: - warning → 用户可见 - critical → 用户操作 - auto_compact → 自动执行

17.4 "Group 评分"是压缩的灵魂

不是"压最老的",是"压评分最低的"。

评分函数 = 项目核心算法

17.5 "DCE" 让 4 种压缩共存

公开版只用 1-2 种(microCompact、autoCompact)。
Ant 内部 4 种(+ reactiveCompact、contextCollapse)。

DCE 是"产品矩阵"管理

17.6 "session memory" 是 LLM 的"长期记忆"

不是"丢信息",是"搬家"。

让 Claude Code "能记住"是产品差异化


18. 阅读清单

  1. ✅ 完整通读 src/services/compact/(12 文件 / ~3960 行)
  2. ✅ 读 topics/context-compaction.md
  3. ✅ 读 phase-06-agent-loop.md § 6.6
  4. 📌 读 src/services/api/claude.ts:2898-3213 压缩相关
  5. 📌 读 src/services/api/promptCacheBreakDetection.ts cache 失效检测

19. 练习任务

  1. 数 7 种压缩策略的入口函数 —— 应该 7+
  2. 画 7 种策略的"决策树" —— 什么情况下用哪种
  3. 设计你自己的简化版压缩 —— 50 行
  4. 思考:如果让你加一种"智能压缩"(用小模型做摘要),你怎么加?