Skip to main content

没有压缩会怎样?记忆丢失的代价

长期记忆 = 持续学习 = 智能积累


问题场景

假设:Agent 有工具、有循环、有上下文、有 System Prompt。

最后一个问题:如果对话太长,超出上下文窗口,会怎样?


上下文窗口的限制

什么是上下文窗口?

上下文窗口:LLM 一次能"看到"的最大 token 数量。

主流模型的限制

  • GPT-4:128K tokens(约 10 万字)
  • Claude 3.5:200K tokens(约 15 万字)
  • GPT-4o:128K tokens

看起来很大?

实际上:

  • 一个中等复杂的对话:50K tokens
  • 一个长对话(3 小时):200K tokens
  • 一个项目的完整上下文:300K+ tokens

很容易超出限制。

超出后会怎样?

两种处理方式

方式 1:截断(Truncate)

保留最近的 200K tokens
丢弃早期的内容

问题

  • ❌ 遗忘早期决策
  • ❌ 前后矛盾
  • ❌ 重复讨论

方式 2:拒绝(Reject)

错误:上下文窗口已满
请开始新对话

问题

  • ❌ 对话被迫中断
  • ❌ 丢失所有上下文
  • ❌ 用户体验差

真实案例 1:长对话遗忘

场景

一个复杂的重构任务,对话 3 小时。

没有压缩

第 1 小时(50K tokens):

你:重构 query.ts,我们决定:
1. 提取 10 个函数
2. 使用 async/await
3. 不用 Promise.then

Agent:好的,我会遵守这些决策
[开始重构...]

第 2 小时(100K tokens):

你:继续重构其他文件

Agent:好的
[继续重构...]

第 3 小时(200K tokens,接近上限):

你:重构 api.ts

Agent:好的
[开始重构 api.ts]
[使用 Promise.then] ← 忘记了第 1 小时的决策

问题

  • ❌ 忘记了"不用 Promise.then"
  • ❌ 前后不一致
  • ❌ 需要返工

第 3.5 小时(超出 200K tokens):

错误:上下文窗口已满
请开始新对话

结果

  • ❌ 对话被迫中断
  • ❌ 丢失所有上下文
  • ❌ 无法继续

有了压缩

第 1 小时(50K tokens):

你:重构 query.ts,我们决定:
1. 提取 10 个函数
2. 使用 async/await
3. 不用 Promise.then

Agent:好的
[开始重构...]

第 2 小时(100K tokens):

[继续重构...]

第 2.5 小时(150K tokens,达到 75% 阈值):

[触发自动压缩]
[压缩前 150K tokens → 压缩后 40K tokens]

压缩内容:
- 保留:关键决策(不用 Promise.then)
- 保留:重构进度(已完成 query.ts)
- 丢弃:中间过程(具体的修改细节)

第 3 小时(80K tokens):

你:重构 api.ts

Agent:好的,我会使用 async/await,不用 Promise.then
[正确重构]

第 10 小时(还能继续):

[多次压缩,保留关键信息]
[对话可以持续几天]

结果

  • ✅ 记住关键决策
  • ✅ 前后一致
  • ✅ 对话无限长

真实案例 2:成本爆炸

场景

长对话,每次调用都发送完整历史。

没有压缩

第 1 次调用(10K tokens):

发送:10K tokens
成本:10K × $0.003 / 1K = $0.03

第 10 次调用(100K tokens):

发送:100K tokens(完整历史)
成本:100K × $0.003 / 1K = $0.30

第 50 次调用(200K tokens):

发送:200K tokens(完整历史)
成本:200K × $0.003 / 1K = $0.60

第 100 次调用(200K tokens):

发送:200K tokens(完整历史)
成本:200K × $0.003 / 1K = $0.60

总成本

100 次调用 × 平均 150K tokens × $0.003 / 1K = $45

问题

  • ❌ 成本随对话长度线性增长
  • ❌ 长对话用不起
  • ❌ 大部分 tokens 是"重复内容"

有了压缩

第 1-20 次调用(未压缩):

平均 50K tokens
成本:20 × 50K × $0.003 / 1K = $3

第 21 次调用(触发压缩):

压缩:150K → 40K tokens
成本:$0.12(压缩成本)

第 22-100 次调用(压缩后):

平均 60K tokens(包含压缩后的历史)
成本:79 × 60K × $0.003 / 1K = $14.22

总成本

$3 + $0.12 + $14.22 = $17.34

节省

$45 - $17.34 = $27.66(节省 61%)

真实案例 3:3272 次压缩失败

背景

这是 Claude Code 代码中的真实注释:

// src/utils/compaction/autoCompact.ts
// 某个会话的压缩失败了 3272 次
// 如果没有断路器,会无限重试,成本爆炸

问题

场景:压缩 API 调用失败(网络问题、API 限流等)

没有断路器

async function compact() {
while (true) {
try {
await callCompactionAPI()
break
} catch (error) {
// 失败了,继续重试
continue // 无限重试
}
}
}

如果一直失败

第 1 次:失败
第 2 次:失败
第 3 次:失败
...
第 3272 次:失败
...(无限重试)

后果

  • 💰 成本爆炸:3272 次 × $0.01 = $32.72
  • ⏱️ 时间浪费:3272 次 × 2 秒 = 1.8 小时
  • 😱 用户体验:卡死

解决方案:断路器

// src/utils/compaction/autoCompact.ts
let compactionFailures = 0
const MAX_FAILURES = 3

async function autoCompact() {
if (!shouldCompact()) return

try {
await compact()
compactionFailures = 0 // 成功,重置
} catch (error) {
compactionFailures++

// 断路器:连续失败 3 次 → 熔断
if (compactionFailures >= MAX_FAILURES) {
throw new Error(
"Compaction circuit breaker triggered. " +
"Failed 3 times in a row."
)
}
}
}

效果

  • ✅ 最多失败 3 次
  • ✅ 成本可控:$0.03
  • ✅ 用户体验:提示错误,而不是卡死

为什么需要压缩?

原因 1:上下文窗口有限

限制

  • Claude 3.5:200K tokens
  • GPT-4:128K tokens

实际使用

  • 中等对话:50K tokens
  • 长对话:200K tokens
  • 复杂项目:300K+ tokens

很容易超出。

原因 2:成本随长度增长

没有压缩

成本 = 调用次数 × 平均 tokens × 单价
= 100 × 150K × $0.003 / 1K
= $45

有了压缩

成本 = 100 × 60K × $0.003 / 1K
= $18

节省 60%。

原因 3:长期记忆 = 持续学习

没有压缩

  • 对话最多 2-3 小时
  • 超出后被迫中断
  • 无法持续学习

有了压缩

  • 对话可以持续几天
  • 保留关键信息
  • 持续积累知识

长期记忆 = 智能积累。


压缩的实现

Claude Code 的实现

// src/utils/compaction/autoCompact.ts
export async function autoCompact() {
// 1. 检查是否需要压缩
if (!shouldCompact()) return

// 2. 调用压缩
try {
await compact()
compactionFailures = 0
} catch (error) {
compactionFailures++

// 3. 断路器
if (compactionFailures >= 3) {
throw new Error("Compaction circuit breaker triggered")
}
}
}

function shouldCompact(): boolean {
const currentTokens = estimateTokens(messages)
const maxTokens = 200000

// 75% 阈值
return currentTokens / maxTokens > 0.75
}

async function compact() {
// 调用 LLM 压缩历史
const summary = await callAPI({
system: "Summarize the conversation. Keep key decisions and conclusions.",
messages: oldMessages
})

// 替换历史
messages = [
{ role: "user", content: summary },
...recentMessages // 保留最近的消息
]
}

关键点

  1. 触发条件:75% 阈值
  2. 压缩算法:调用 LLM 总结
  3. 断路器:最多失败 3 次
  4. 保留策略:关键决策 + 最近消息

Codex 的实现

// codex-rs/core/src/compact.rs (16,172 行)
pub async fn compact(&mut self) -> Result<()> {
// 1. 检查阈值
if self.token_count < self.max_tokens * 0.75 {
return Ok(());
}

// 2. 调用压缩 API
let summary = self.call_compaction_api().await?;

// 3. 替换历史
self.messages = vec![
Message::user(summary),
// 保留最近的消息
self.messages.iter()
.rev()
.take(10)
.cloned()
.collect()
];

// 4. 更新 token 计数
self.token_count = self.estimate_tokens(&self.messages);

Ok(())
}

Codex 还支持远程压缩

// codex-rs/core/src/compact_remote.rs (10,942 行)
pub async fn compact_remote(&mut self) -> Result<()> {
// 发送到远程服务器压缩
// 适用于大型对话
}

压缩的三个关键要素

1. 触发条件

何时触发压缩?

Claude Code

// 75% 阈值
if (currentTokens / maxTokens > 0.75) {
await compact()
}

为什么是 75%?

  • 太早(50%):频繁压缩,成本高
  • 太晚(90%):可能来不及压缩
  • 75%:平衡点

2. 压缩算法

如何压缩?

调用 LLM 总结

const summary = await callAPI({
system: `
Summarize the conversation.
Keep:
- Key decisions
- Important conclusions
- Project context
Discard:
- Intermediate steps
- Debug details
- Temporary discussions
`,
messages: oldMessages
})

压缩比

  • 输入:150K tokens
  • 输出:30-50K tokens
  • 压缩比:3-5 倍

3. 断路器

如何防止压缩失败?

let compactionFailures = 0

try {
await compact()
compactionFailures = 0 // 成功,重置
} catch (error) {
compactionFailures++

if (compactionFailures >= 3) {
throw new Error("Circuit breaker triggered")
}
}

为什么需要断路器?

  • 防止无限重试
  • 防止成本爆炸
  • 提供明确的错误提示

真实数据对比

对话长度

场景无压缩有压缩差距
最大对话时长2 小时无限
最大消息数~100 条无限
最大 tokens200K无限

成本

场景无压缩有压缩节省
100 次调用$45$1860%
500 次调用$225$6073%
1000 次调用$450$10078%

用户体验

维度无压缩有压缩
对话长度受限(2 小时)无限
记住早期决策
前后一致
成本
体验受限自由

关键洞察

1. 压缩 = 长期记忆

没有压缩

  • 对话最多 2 小时
  • 超出后"失忆"
  • 无法持续学习

有了压缩

  • 对话无限长
  • 保留关键信息
  • 持续积累知识

长期记忆 = 智能积累。

2. 断路器同样重要

没有断路器

  • 可能无限重试
  • 成本爆炸(3272 次 = $32.72)
  • 用户体验差

有了断路器

  • 最多失败 3 次
  • 成本可控($0.03)
  • 明确的错误提示

3. 压缩 = 成本优化

没有压缩

  • 每次发送完整历史
  • 成本随长度增长
  • 长对话用不起

有了压缩

  • 只发送压缩后的历史
  • 成本降低 60-78%
  • 长对话也用得起

失败案例总结

现在我们看完了 5 个失败案例:

缺少的机制后果效率差距
没有工具只能"建议",不能"执行"10-20 倍
没有循环只能"一次性",不能"优化"4.75 倍
没有上下文只能"通用",不能"专业"10 倍
没有 System Prompt行为不可控,不可靠1.9 倍
没有压缩对话受限,成本高

总差距

10 × 4.75 × 10 × 1.9 = 900+ 倍

这就是为什么 Agent 比 ChatGPT 强大 100+ 倍。


下一部分预告

现在你通过"失败案例"深刻理解了五大机制的必要性。

下一部分(设计决策分析)会深入分析:

这些设计是怎么来的?背后的证据是什么?

包括:

  • 为什么需要断路器?(3272 次失败的教训)
  • 为什么工具要分"并发安全"和"独占"?
  • 为什么 System Prompt 要分段缓存?
  • 为什么需要 5 种权限模式?
  • 为什么压缩触发阈值是 75%?

关键要点

  1. 压缩 = 长期记忆 = 持续学习
  2. 没有压缩的对话最多 2 小时
  3. 有压缩的对话无限长
  4. 成本节省 60-78%
  5. 断路器防止无限重试
  6. 真实案例:3272 次压缩失败
  7. 触发阈值:75%

记住:没有长期记忆,就没有持续学习;没有持续学习,就没有智能积累。


字数:约 4200 字
阅读时间:约 11 分钟