所有文章

LLM 上下文五层压缩机制详解

做 Agent 项目时,对话持续进行,token 会不断累积,迟早超出模型的 context window。这篇文章整理一套五层上下文压缩机制,从轻到重依次触发,核心思路是"能少压就少压,实在不行再大压"。

做 Agent 项目时,对话持续进行,token 会不断累积,迟早超出模型的 context window。这篇文章整理一套五层上下文压缩机制,从轻到重依次触发,核心思路是"能少压就少压,实在不行再大压"。

整体逻辑

对话持续进行 → Token 接近上限 → 触发压缩 → 压缩成功继续 → 压缩失败升级到下一层

五层从左到右,压缩力度递增,成本也递增:

Tool Result Replace → Snip Compact → Micro Compact → Context Collapse → Auto Compact
      极低成本              低               低              高              最高

第一层:Tool Result Replace

做什么: 把工具调用的返回结果替换掉,只保留占位符。

# 原来
tool_result = "这是一段很长的日志输出,有几千行..."

# 替换后
tool_result = "[已压缩,原始结果已清除]"

工具返回了大量原始数据(日志、代码、搜索结果),但后续对话其实不需要完整内容,只需要知道"调用过这个工具、拿到了结果"。

成本: 极低,只是字符串替换,不需要调用 LLM。


第二层:Snip Compact

做什么: 精准裁剪,找到对话里最占空间的片段,把它们缩短,但保留上下文结构。

比如一个很长的 tool_result 块,只保留前几行 + 末尾摘要,中间截断:

[原始内容前100行]
... [中间 2000 行已截断] ...
[原始内容最后20行]

适用场景: 有几个特别大的消息拖累了整体 token 数,其他消息都正常。

成本: 低,基于规则裁剪,不需要 LLM。


第三层:Micro Compact

做什么: 清理掉已经没用的工具调用记录。

比如 cache_readcache_write 这类中间过程的工具调用,任务完成后这些记录对后续对话没有价值,直接标记为 [tool cleared] 删掉。

# 删除前
[tool_use: cache_write, params: {...}, result: "ok"]
[tool_use: cache_read, params: {...}, result: "...大量内容..."]

# 删除后
[tool cleared]
[tool cleared]

适用场景: 对话里有大量中间步骤的工具调用,但最终结果已经体现在后续消息里了。

成本: 低,规则删除,不需要 LLM。


第四层:Context Collapse

做什么: 不再保留原始消息,把整段历史对话喂给 LLM,让它生成一个 summary,用 summary 替代原始历史。

def context_collapse(history):
    prompt = f"""
请将以下对话历史压缩为简洁摘要,保留:
- 用户的核心目标和需求
- 已完成的重要操作
- 关键决策和结论
- 当前任务状态

对话历史:
{format_history(history)}
"""
    summary = llm.call(prompt)
    
    # 用摘要替代原始历史
    return [{"role": "system", "content": f"[历史摘要]\n{summary}"}]
原来:[20轮完整对话,8000 token]
压缩后:[1条 summary 消息,500 token]

适用场景: 前三层都压不下去,token 还是超限。

成本: 高,需要调用一次 LLM 生成摘要,有延迟和费用。

注意: 这一层只压 Model-Facing 部分(发给模型的消息),不压 Raw Messages(原始记录),保留审计能力。


第五层:Auto Compact

做什么: 完全重建上下文。生成一个新的 compact prompt 作为全新对话的起点,原始历史全部丢弃。

新的起点包含三部分:

compact_prompt = {
    "boundary": "当前任务边界和约束",      # 告诉模型它在做什么
    "summary": "历史摘要",                 # 之前发生了什么
    "assistant_context": "必要的角色设定"  # 模型应该以什么状态继续
}

内部还分两档:

  • Session Memory Compact(轻量): 用已有的 session memory 替代,成本低,但信息可能不完整,适合对话内容不太重要的场景
  • Full Compact(兜底): 完整重建,成本最高,但保证能继续运行,是最后的兜底手段

适用场景: 对话极长,前四层都无法把 token 压到窗口以内。


机制设计要点

容量精准计算

不用固定阈值,而是动态计算可用窗口:

def get_effective_context_window():
    total = model.context_window          # 模型总窗口
    reserved_output = max_output_tokens   # 预留给输出的空间
    reserved_system = system_prompt_size  # 系统 prompt 占用
    return total - reserved_output - reserved_system

缓存优先策略

压缩时优先保留 cache_read/cache_write 相关内容,避免缓存失效导致重复计算费用。

结构完整性保障

压缩后保证 thinking/tool_use 配对完整,不出现孤立的工具调用(没有对应 tool_result 的 tool_use 会导致 API 报错)。

失败兜底

每层失败都有明确的升级路径:

第1层失败 → 升级到第2层
第2层失败 → 升级到第3层
...
第5层失败 → 报错,人工介入

五层对比

层级方式成本信息损失触发条件
Tool Result Replace替换工具结果为占位符极低工具结果过大
Snip Compact裁剪大消息中间部分单条消息过大
Micro Compact删除无用工具调用记录极低中间步骤堆积
Context CollapseLLM 生成历史摘要前三层不够用
Auto Compact完全重建上下文最高最后兜底

实际项目怎么选

大多数 Agent 项目不需要实现全部五层,按场景选:

短会话 Agent(每次对话独立,如运维告警): 滑动窗口就够,不需要压缩机制。

中等长度对话(如代码助手、问答): 实现第1层(Tool Result Replace)+ 第4层(Context Collapse)基本够用,覆盖了 80% 的场景。

超长会话(如长期任务执行、个人助手): 需要完整五层,或者引入向量库做长期记忆。

面试被问到"你的 Agent 怎么处理长对话",不要只说"我用了摘要压缩",说清楚在哪一层触发、为什么不需要更重的层,才能体现你对整个方案空间的理解。