AI Agent Context 压缩策略选型详解

作者:袖梨 2026-07-21

Context 压缩有哪些主流策略?
它们各自的成本与保真度怎么算?
面对具体场景,到底该怎么选?

上一篇 AI Agent Context 工程我们铺开了 4 层 context 的全貌(输入 / 运行时 / 压缩 / 长期记忆),里面只给了一张策略对比表就翻篇了。本篇专门把「压缩层」拆开讲——6 种主流策略的可运行代码、各自的代价,以及一张决策树。

img_6a5f38983cbf430.webp

一、压缩策略选错,问题代价是任务崩盘

先说一个反直觉的事实:压缩策略选错,不是 token 多花一点的问题,是任务完成率掉 30% 的问题

NirDiamant 在项目里跑了一组对照实验(基于 LoCoMo benchmark),同一段 50 轮长对话,硬截断会把"用户在第 12 轮提到的忌口信息"直接砍掉,下游 Agent 推荐餐厅时直接踩雷;摘要压缩在 5 轮一次时任务完成率最高(92%),但 1 轮一摘要时反而掉到 71%——因为每次摘要都丢掉一些次要细节,最后细节全没了。

压缩率高的策略不一定保真度高——这是新手最容易踩的坑。压缩的本质是"用信息熵换 token 预算",不同策略在不同熵分布上表现天差地别。

二、6 大核心概念 + 引用块定义(含酒店类比)

Context 压缩策略:在 LLM 调用前对 prompt 做有损/无损瘦身的技术族,由 6 大主流方法组成。

  • 硬截断 (Truncation):直接砍掉超出 N token 的最早内容,最便宜也最暴力
  • 滑动窗口 (Sliding Window):保留最近 K 轮 + K-1 轮重叠,平衡遗忘与开销
  • 摘要压缩 (Summarization):LLM 把历史生成摘要替换原文,保真度最高但要 LLM 调用
  • 关键提取 (Extraction):用规则/小模型抽实体/数字/决策,只留骨架
  • 神经压缩 (LLMLingua):用小分类模型逐 token 判重要性再裁剪,微软 2023 提出的
  • 外部化 (Offloading):大块内容写盘/向量库,只在 prompt 里留引用/检索片段

类比时间。我把这 6 种策略映射成"酒店前台给客人办入住":

  • 硬截断 = 行李员只看最上面 5 件行李,剩下的直接塞仓库
  • 滑动窗口 = 只看你最近 3 次出差带了什么,第 4 次就忘掉
  • 摘要压缩 = VIP 客户经理帮你写一段"客户画像",下次凭画像接待
  • 关键提取 = 入住登记表只填"姓名/身份证/房型",其他信息都不要
  • 神经压缩 = 前台配了个 AI 助理,看一眼行李就知道哪些是"重要物品"
  • 外部化 = 行李太多?前台告诉你"行李柜 031 号,凭取件码随时取"

选型的关键不是"哪个最好",是"哪个最匹配你的行李特征"——你出差带的是书(长文本)还是杂物(多模态),前台给你的方案完全不同。

三、代码实现:6 种策略逐个上手

下面 6 段代码都可以独立运行,逻辑都从 Agent_Memory_Techniques 和 mem0ai/mem0 提炼出来,可直接 copy 到你的项目里

3.1 硬截断(最便宜)

# 硬截断:保留 System + 最近 N 条消息
from langchain_core.messages import SystemMessage, BaseMessage
def hard_truncate(messages: list[BaseMessage], max_messages: int = 20) -> list[BaseMessage]:
    """保留 SystemMessage 不动 + 最近 max_messages 条"""
    system = [m for m in messages if isinstance(m, SystemMessage)]
    others = [m for m in messages if not isinstance(m, SystemMessage)]
    return system + others[-max_messages:]

适用:闲聊机器人、简单客服问答。禁用:用户偏好、关键决策类对话。

3.2 滑动窗口(任务流友好)

# 滑动窗口:保留最近 K 轮(user+assistant 算一轮)
def sliding_window(messages: list[BaseMessage], window_size: int = 10) -> list[BaseMessage]:
    """以"轮"为单位保留,user+assistant 算 1 轮"""
    system = [m for m in messages if isinstance(m, SystemMessage)]
    others = [m for m in messages if not isinstance(m, SystemMessage)]
    # 按 2 条消息一轮倒推
    kept = others[-(window_size * 2):] if len(others) > window_size * 2 else others
    return system + kept

适用:固定长度的多步任务(数据分析、报表生成)。禁用:长程依赖任务(用户 12 轮前提到的信息要用到第 30 轮)。

3.3 摘要压缩(保真度最高)

# 摘要压缩:Anchored Iterative Summarization(Anthropic 推荐结构)
from langchain_openai import ChatOpenAI
from langchain_core.messages import SystemMessage, RemoveMessage
SUMMARY_PROMPT = """请将以下对话压缩成结构化摘要,每个 section 一行,不要超过 250 字:
- INTENT: 用户核心目标
- DECISIONS: 已做出的关键决定
- FACTS: 重要的实体/数字/事实
- OPEN: 未解决的问题
对话:
{messages}
"""
def summarize_compress(messages: list[BaseMessage], threshold: int = 12) -> dict:
    if len(messages) <= threshold:
        return {"messages": messages, "summary": ""}
    # 保留 System + 最近 2 轮,其余送 LLM 摘要
    system = [m for m in messages if isinstance(m, SystemMessage)]
    to_compress = [m for m in messages if not isinstance(m, SystemMessage)][:-2]
    recent = [m for m in messages if not isinstance(m, SystemMessage)][-2:]
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
    text_blob = "n".join(f"{m.type}: {m.content[:300]}" for m in to_compress)
    summary = llm.invoke(SUMMARY_PROMPT.format(messages=text_blob)).content
    # 显式删除被压缩的消息
    return {
        "messages": system + recent + [SystemMessage(content=f"历史摘要:{summary}")]
                              + [RemoveMessage(id=m.id) for m in to_compress if hasattr(m, "id")],
        "summary": summary,
    }

适用:长对话、连续多任务。成本:每次压缩 1 次 LLM 调用,建议用 gpt-4o-miniclaude-haiku-4-5,省 80% 成本。

3.4 关键提取(PII/合规场景必选)

# 关键提取:只保留实体/数字/决策的骨架
import re
from typing import TypedDict
class ExtractedFacts(TypedDict):
    entities: list[str]      # 人名/地名/产品名
    numbers: list[str]       # 金额/日期/百分比
    decisions: list[str]     # 用户做出的决定
def extract_key_facts(messages: list[BaseMessage]) -> ExtractedFacts:
    """规则版:正则抽实体/数字/LLM 抽决策"""
    text = "n".join(m.content for m in messages if hasattr(m, "content"))
    # 实体:简单正则(生产换 GLiNER / 小模型)
    entities = list(set(re.findall(r"[A-Z][a-zA-Z]{2,}", text)))[:20]
    # 数字:金额/日期
    numbers = re.findall(r"d{1,3}(?:,d{3})*(?:.d+)?|d{4}-d{2}-d{2}", text)[:20]
    # 决策:用小模型判断
    llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
    decisions_prompt = (
        f"从下面对话中提取用户做出的关键决定,每条一行,无决定返回'NONE':n{text[:2000]}"
    )
    decisions_text = llm.invoke(decisions_prompt).content
    decisions = [d.strip() for d in decisions_text.split("n") if d.strip() and d.strip() != "NONE"]
    return {"entities": entities, "numbers": numbers, "decisions": decisions}
# 用法:把 facts 拼到 system prompt 里
def build_prompt_with_facts(messages, facts: ExtractedFacts) -> str:
    fact_str = (
        f"已知实体:{', '.join(facts['entities'][:10])}n"
        f"关键数字:{', '.join(facts['numbers'][:10])}n"
        f"用户决定:{'; '.join(facts['decisions'][:5])}"
    )
    return fact_str

适用:合规要求高(PII 不能进 LLM)、长文本检索任务。优势:提取后从 10K token 压到 ~200 token 几乎是无损的。

3.5 神经压缩(LLMLingua-2)

# 神经压缩:Microsoft LLMLingua-2,逐 token 分类
# pip install llmlingua
from llmlingua import PromptCompressor
def neural_compress(prompt: str, target_ratio: float = 0.4) -> str:
    """target=0.4 表示压缩到原文 40%"""
    compressor = PromptCompressor(
        model_name="microsoft/llmlingua-2-bert-base-multilingual-cased-meetingbank",
        device_map="cpu",  # GPU 改 "cuda"
    )
    result = compressor.compress_prompt(
        prompt,
        rate=target_ratio,
        force_context_tokens=["IMPORTANT", "DECISION", "USER_PREF"],
    )
    return result["compressed_prompt"]
# 用法
raw_prompt = "用户在前 20 轮的对话内容..." * 50
compressed = neural_compress(raw_prompt, target_ratio=0.3)
print(f"原 {len(raw_prompt)} → 压 {len(compressed)} chars")

适用:超长 prompt(>50K)、RAG 多文档合并。优势:压缩率 60-80% 时仍保留关键信息,比纯摘要快 10 倍。:首次下载 ~500MB 模型。

3.6 外部化(RAG-offload)

# 外部化:大块内容写向量库,prompt 里只留检索片段
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import FAISS
from langchain_core.documents import Document
import uuid
class ContextOffloader:
    """把长内容外置,prompt 只引用 ref_id"""
    def __init__(self):
        self.embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
        self.store = FAISS.from_texts(["__init__"], self.embeddings)
        self.ref_map: dict[str, str] = {"__init__": "__init__"}
    def offload(self, content: str, metadata: dict | None = None) -> str:
        """返回 ref_id,content 写进向量库"""
        ref_id = f"ctx_{uuid.uuid4().hex[:8]}"
        self.store.add_texts(
            [content],
            metadatas=[{**({"ref_id": ref_id}), **(metadata or {})}],
        )
        self.ref_map[ref_id] = content
        return ref_id
    def retrieve(self, ref_id: str, query: str, k: int = 3) -> str:
        """按 query 检索回最相关片段(不是原文)"""
        if ref_id not in self.ref_map:
            return ""
        full = self.ref_map[ref_id]
        # 把引用内容加进去检索
        tmp_store = FAISS.from_texts([full], self.embeddings)
        docs = tmp_store.similarity_search(query, k=k)
        return "n".join(d.page_content for d in docs)
# 用法
offloader = ContextOffloader()
ref = offloader.offload("(此处是 10K 字的会议记录...)")
# 后续调用 LLM 时 prompt 里只写:[REF:ref_id] 查"用户决策"
# LLM 触发工具调用时再 retrieve()

适用:工具返回超长(PDF 解析、日志文件)、文档处理任务。优势:上下文永远不爆,引用解耦。

四、4 个常见坑:选对了策略也不一定赢

坑 1:摘要触发时机太晚

现象:等 prompt_too_long 报错才压缩
根因:触发后状态已污染,回滚困难,且此时调用 LLM 已经失败
解法:60% 窗口占用就主动压缩,80% 强制压缩(与上篇一致,Anthropic Claude Code 内部建议)

坑 2:神经压缩把关键决策一起砍了

现象:LLMLingua 压完后,user 说"以后别再给我推 5G 套餐"这条决定没了
根因:默认按"通用重要性"裁剪,没保护业务关键词
解法:传 force_context_tokens=["禁止", "决定", "不", "以后"] 等业务关键词,强制保留

坑 3:摘要破坏了"原子消息组"

现象:第 8 轮的 tool_call 还在,对应的 tool_result 被砍了
根因:按"条数"截断,没考虑消息组完整性
解法:必须以 MessageGroup 为单位(tool_call + tool_result 配对),要么都留要么都删

坑 4:外部化的"假检索"

现象:把内容 offload 后,retrieve 没用 query 匹配,原文一字不差全塞回来
根因:把"外置"当成了"备份",没有真正做语义检索
解法:retrieve 必须接用户当前 query 做 similarity_search,否则外部化就是脱裤子放屁

五、生产选型决策树

面对 6 种策略怎么选?给你一个 4 维度决策框架

  1. 内容特征:是结构化(JSON/日志)还是非结构化(自然语言)?
  2. 延迟预算:单次响应必须 <1s 还是有 3-5s 余量?
  3. 成本上限:每千次调用预算 1 美元还是 10 美元?
  4. 保真度要求:错了能不能容忍(闲聊)还是错了要命(医疗/法律)?

按这 4 维打分,能淘汰掉 80% 的不适用方案:

场景内容延迟成本保真推荐策略
闲聊机器人非结构化<1s极低硬截断
数据分析助手半结构化3s滑动窗口 + 关键提取
写作/创作非结构化5s摘要压缩(5 轮一次)
合规咨询结构化3s极高关键提取 + 摘要
长文档 RAG非结构化5s神经压缩 + 外部化
工具调用密集半结构化1s滑动窗口 + 外部化

核心原则单一策略永远不够。生产项目都是"截断/滑窗打头阵 → 摘要兜底 → 必要时外部化"分层组合。Claude Code 的 5 层压缩(microcompact → autocompact → context collapse → 工具结果截断 → 子代&理隔离)就是这个套路。

六、6 大策略 + 决策总表

策略核心思路成本保真度延迟适用场景不适用
硬截断砍最早 N 条00闲聊/简单问答关键决策类
滑动窗口保留最近 K 轮00固定长度任务流长程依赖
摘要压缩LLM 生成摘要1-3s长对话/多任务极高频调用
关键提取抽实体/数字/决定低-中0.5-1s合规/检索自由对话
神经压缩小模型逐 token 评分1-2s超长 prompt强实时
外部化内容外置+检索中-高0.5-1s工具结果超长短上下文

决策树简化版

你的上下文有多长?
├─ < 4K  → 不用压缩(裸跑)
├─ 4K-32K → 滑动窗口(最稳)
├─ 32K-100K → 摘要压缩(5 轮一次)
├─ 100K-500K → 神经压缩(LLMLingua)
└─ > 500K → 外部化 + RAG 检索
你的内容能丢吗?
├─ 闲聊可丢 → 硬截断 / 滑窗
├─ 决策不能丢 → 关键提取 + 摘要
└─ 合规不能错 → 关键提取 + 外部化

七、面试官还会追问什么?

追问 1:神经压缩和摘要压缩的本质区别?
→ 摘要压缩是"生成式"(LLM 重新组织语言),神经压缩是"判别式"(小模型给每个 token 打分)。摘要保留语义完整,神经压缩保留关键 token。1M token 场景优先神经压缩(快),100K 以内优先摘要(准)。

追问 2:为什么不直接用 1M 窗口模型,省得压缩?
→ 三个问题:(1) Context Rot——Chroma 2025 实验 GPT-4o 准确率从 98% 掉到 64%;(2) 成本线性增长,输入 1M token 单次调用比 32K 贵 30 倍;(3) 延迟,1M token 推理首 token 延迟 5-10s。大窗口不是银弹,分层管理才是

追问 3:怎么评估压缩策略好不好?
→ 看三个指标:(1) 任务完成率(最终对不对),(2) token 压缩率(省了多少),(3) 关键信息保留率(重要决策是否还在)。

追问 4:长期记忆和压缩的边界?
→ 长期记忆是"跨会话"且"用户画像/历史决策"类信息,存向量库;压缩是"单会话内"的瘦身,存当前 prompt。一个跨会话的事实(比如用户忌口)应该走长期记忆,不应该每次会话都从历史里压缩出来

追问 5:实时性要求极高(<100ms)时怎么压缩?
→ 放弃所有 LLM 调用类策略,只用硬截断 + 滑动窗口 + 关键提取(规则版)。规则版提取(正则/小分类器)能在 10ms 内完成 10K 文本的实体抽取。

八、总结:压缩策略是"分层 + 组合",不是"选一个"

Context 压缩不是"挑一个最好的策略"问题,是"按场景组合分层"问题:

  • 默认起手式:滑动窗口 + 关键提取(绝大多数项目够用)
  • 长对话必加:摘要压缩(5 轮一次,不要每轮都压)
  • 超长 prompt:神经压缩(LLMLingua-2 是事实标准)
  • 工具结果超长:外部化(写向量库 + 检索回注)
  • 合规场景:关键提取 + 长期记忆,不要让 PII 走 LLM

读完这篇你应该能:

  • 说出 6 种主流策略的代码实现(不是只背概念)
  • 用 4 维度决策框架给你的项目选型
  • 避开 4 个常见坑(时机、关键词、原子组、假检索)

如果这篇文章让你对压缩选型少一些纠结,欢迎点赞 + 在看

最后一个问题留给你:你的项目现在 prompt 平均有多长?有没有跑过 token 占用率统计?哪一段最值得压缩

相关文章

精彩推荐