没有压缩会怎样?记忆丢失的代价
长期记忆 = 持续学习 = 智能积累
问题场景
假设: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 // 保留最近的消息
]
}
关键点:
- 触发条件:75% 阈值
- 压缩算法:调用 LLM 总结
- 断路器:最多失败 3 次
- 保留策略:关键决策 + 最近消息
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 条 | 无限 | ∞ |
| 最大 tokens | 200K | 无限 | ∞ |
成本
| 场景 | 无压缩 | 有压缩 | 节省 |
|---|---|---|---|
| 100 次调用 | $45 | $18 | 60% |
| 500 次调用 | $225 | $60 | 73% |
| 1000 次调用 | $450 | $100 | 78% |
用户体验
| 维度 | 无压缩 | 有压缩 |
|---|---|---|
| 对话长度 | 受限(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%?
关键要点
- 压缩 = 长期记忆 = 持续学习
- 没有压缩的对话最多 2 小时
- 有压缩的对话无限长
- 成本节省 60-78%
- 断路器防止无限重试
- 真实案例:3272 次压缩失败
- 触发阈值:75%
记住:没有长期记忆,就没有持续学习;没有持续学习,就没有智能积累。
字数:约 4200 字
阅读时间:约 11 分钟