跳转至

Mini Context Compaction

从 services/compact 12 个文件(~5000 行)提炼到 ~150 行 的滑动窗口 + 摘要压缩。 3 策略 + 5 场景:drop-middle / keep-first-last / summarize-middle + 阈值对比。

文件

mini-context-compaction/
├── src/
│   ├── compaction.ts  Compactor + 3 策略 + mock summarizer(~120 行)
│   └── cli.ts         5 场景
└── README.md

npm install && npm start

真实代码对照

Demo 真实文件 简化
estimateTokens 真实用 tiktoken char/4 简化
dropMiddle compact/window.ts:200 简化
keepFirstLast 同上 简化
summarizeMiddle compact/summarize.ts:100 (~800 行) mock LLM
触发器 compact/compact.ts:30 (~300 行) 单阈值

核心 4 件套

1️⃣ Token 估算 char/4

function estimateTokens(messages) {
  return Math.ceil(messages.reduce((sum, m) => sum + m.content.length, 0) / 4);
}
真实代码用 tiktoken + Claude tokenizer 精确。char/4 是 Claude 文档给出的近似比例。对触发判断足够

2️⃣ 3 策略按需选

策略 优点 缺点
drop-middle 零开销、简单 丢上下文信息
keep-first-last 系统 prompt + 最近消息保留 完全丢中间
summarize-middle 保留语义 调 LLM 慢 + 贵

真实 CC 默认用 summarize-middle(保留语义对长对话关键)。

3️⃣ Mock Summarizer = 1 行

const mockSummarizer = async (messages) => `[Summary of ${messages.length} messages]`;
真实 CC 调 LLM(opus / sonnet)做摘要。本 demo 用占位符展示接口形态。

4️⃣ 阈值触发 = O(1) 检查

shouldCompact(messages) {
  return estimateTokens(messages) > this.config.maxTokens;
}
每次 setState 后调一次(或每 N 次)。O(1) 计算 + O(N) 压缩。

5 场景输出

📌 Test 1: small conversation (no trigger)
  triggered? false
📌 Test 2: large conversation + drop-middle
  reduction: 615 → 100 (16%)
📌 Test 3: large conversation + keep-first-last
  reduction: 545 → 82 (15%)
📌 Test 4: large conversation + summarize-middle
  summary: "[Summary of 34 messages, 463 tokens compressed]"
📌 Test 5: threshold comparison
  maxTokens=2000: triggered=false
  maxTokens=200:  triggered=true (14%)

进阶练习

  1. 接真实 LLM summarizer:用 fetch 调 Claude API
  2. 加 sliding window 优化:不要 hard cut,而是按 token 滑
  3. 加 importance scoring:基于关键词/工具调用标记"重要消息"不丢
  4. 加 hierarchical compaction:多轮压缩,每轮总结上一轮
  5. 加 compaction history:记录每次压缩前的快照(可回滚)
  6. 接真实 tokenizer:用 @anthropic-ai/tokenizer 精确算 token

相关阅读