Deep Dive | services/compact/ 3960 行上下文压缩系统拆解¶
重要性:⭐⭐⭐⭐(LLM 长会话的命脉 —— 没有压缩就没法跑长任务) 真实位置:
src/services/compact/(12 文件 / ~3960 行) 角色:在 token 接近上限时,摘要历史消息以腾出空间 关联:topics/context-compaction.md、phase-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¶
关键 —— microCompact 不开 LLM 调用。
否则每次工具完成都贵 0.1 秒 + 0.001 美元。
15.2 "压缩分 4 状态" 的渐进警告¶
渐进 —— 给用户反应时间,不是突然压缩。
15.3 "压缩保留结构" 的 messages 重组¶
4 段结构 —— LLM 能"看到"压缩发生过。
15.4 "session memory" 的跨会话学习¶
跨会话 —— Claude Code "能记住"。
15.5 "Group 评分" 是算法核心¶
简单规则 —— 但准确。
15.6 "DCE 门控" 隔离实验性策略¶
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. 阅读清单¶
- ✅ 完整通读
src/services/compact/(12 文件 / ~3960 行) - ✅ 读 topics/context-compaction.md
- ✅ 读 phase-06-agent-loop.md § 6.6
- 📌 读
src/services/api/claude.ts:2898-3213压缩相关 - 📌 读
src/services/api/promptCacheBreakDetection.tscache 失效检测
19. 练习任务¶
- 数 7 种压缩策略的入口函数 —— 应该 7+
- 画 7 种策略的"决策树" —— 什么情况下用哪种
- 设计你自己的简化版压缩 —— 50 行
- 思考:如果让你加一种"智能压缩"(用小模型做摘要),你怎么加?