想象你即将出发去远方旅行,但背包只有45升——你从纪念品店抱回一大堆东西:朋友强力推荐的漂亮石头、重复买了三张的明信片、永远用不上的大帽子。如果全塞进去,背包不仅拉不上拉链,到了酒店你还得把整包倒出来才能找到护照。RAG系统面临的正是这个困境:知识库检索回来的文档太多太长,而大模型的“上下文窗口”就像那个45升的背包,空间有限、按字收费。所谓“上下文压缩”,就是一门打包的艺术——扔掉没用的、合并重复的、折叠冗长的,让有限的空间装下最有价值的信息。本文从必要性、压缩方式、重排配合、开源工具、比例平衡到质量评估,逐步拆解这门手艺。

岗位关联

对AI从业者而言,掌握上下文压缩等于直接手里握住“降本增效”的开关:它是RAG应用上线后性价比最高、收益最直观的优化方向之一,也是区分普通RAG工程师与资深RAG工程师的关键能力。

目录
  1. 可视化演示
  2. 压缩必要性
  3. 压缩方式
  4. 重排配合
  5. 开源工具
  6. 比例平衡
  7. 质量评估
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示RAG上下文压缩管道流程、去重消除冗余策略、重排模型协同筛选机制,实时显示Token压缩比和片段数量。

本节配有交互动效(进入视口后自动加载,约 3KB)。

压缩管道流程 检索返回20个候选片段 去重消除冗余后剩14个 压缩+重排精选3个送入LLM

压缩必要性

想象一下,旅行背包只有45升,而你从纪念品店抱回了一堆东西:朋友推荐的石头、重复的明信片、占地方的大帽子。全塞进去,背包不仅拉不上拉链,到了目的地你还得把整包翻个底朝天才能找到护照。RAG系统的处境如出一辙——一次检索往往返回5到20篇文档,加起来动辄几千个token,而大模型的上下文窗口有限,每多喂一个token就要多付一份钱。

把超量内容一股脑塞给大模型,代价有三层:第一层是真金白银的token费用,检索结果越长,单次调用的成本越高;第二层是“迷路效应”,研究发现模型对长上下文中间位置的内容关注度明显下降,关键信息被淹没在冗长文本里;第三层是噪音干扰,检索结果中与问题无关的段落会像旅行箱里的大帽子一样挤压空间,诱导模型给出偏离事实的答案。这三层代价叠加,让“给背包减负”成为RAG上线前必须解决的第一件事。

我们可以用一个简单的量——信息密度——来理解这件事。信息密度指单位长度的文本承载的有用信息量,密度越低,说明“水分”越多,越需要压缩。

出发前必须学会做“减负清单”:先认清背包为什么塞不下——不是包太小,而是你没筛选就什么都往里装。RAG应用的上下文压缩,正是从认清“信息密度太低”这一天开始的。

ρ=IL\rho = \frac{I}{L}
信息密度ρ等于文本中有效信息量I除以总长度L,它告诉我们一段文本里有多少“干货”,密度越低越值得压缩。
  1. 信息量 I文本中真正对回答有帮助的有效信息点数量,比如关键实体、结论性陈述。
    II
  2. 文本长度 L文本的token数或字符数,是大模型计费的基本单位。
    LL
  3. 信息密度 ρ用信息量除以长度得到的比值,数值越高代表文本越“干”,越低代表越需要压缩。
    ρ=IL\rho = \frac{I}{L}

压缩方式

收拾背包有三招:用真空袋把羽绒服压扁(摘要)、只挑百搭的基础款T恤(提取)、把同款袜子合并成一双(去重)。上下文压缩的手段与之巧妙对应,掌握了这三招,就等于拿到了打包的入场券。

第一招是提取式压缩,它不改变原文,直接从检索结果里挑出最高价值的句子,像只带百搭款衣服一样,原汁原味地保留关键信息。第二招是摘要式压缩,让语言模型把几段话浓缩成一小段全新文字,像真空袋一样把体积压到最小。第三招是去重合并,用相似度算法判断多个片段是否在说同一件事,重复的内容只保留一份,就像同款袜子只带一双。此外还有关键词过滤作为辅助手段,按领域关键词做一次粗筛,快速排除明显无关的内容。

衡量压缩做得好不好,最直观的指标是压缩率。压缩率描述“节省了多少空间”,数值越高代表背包越轻。

这三招可以灵活组合,就像打包时先折叠衣服再分类摆放再塞进缝隙。实际项目中,通常先用关键词过滤做粗筛,再用提取或摘要做精压缩,最后用去重清理重复内容,层层递进地把背包空间用到极致。

Cr=1−ScSoC_r = 1 - \frac{S_c}{S_o}
压缩率等于1减去压缩后大小与原始大小的比值,0.5意味着体积节省了一半。压缩率是后续所有权衡讨论的基础标尺。
  1. 原始大小 S_o压缩前检索结果的总token数或字符数,相当于打包前的行李体积。
    SoS_o
  2. 压缩后大小 S_c经过压缩处理后保留的token数或字符数,相当于真空压缩后的行李体积。
    ScS_c
  3. 压缩率 C_r1减去两者之比,0表示没压缩,0.6表示省掉了六成体积,越接近1压缩越狠。
    Cr=1−ScSoC_r = 1 - \frac{S_c}{S_o}

重排配合

一个人打包容易犯糊涂,分不清哪件衣服才是这次旅行的必需品。聪明的做法是请一位熟悉行程的朋友帮你看一遍行李——他在每件物品上贴标签:“必带”、“可带可不带”、“别带”。重排模型(Rerank)正是这位朋友,它帮你判断每个检索片段到底有多值得进背包。

Rerank的核心原理是对检索阶段返回的候选片段做二次打分:先由粗排(如BM25或向量检索)快速召回一批候选,再由重排模型逐一评估片段与用户问题的相关度,给出精细分数。相比粗排,重排模型特别是交叉编码器(Cross-Encoder)能让问题和片段在每个token层面对齐交互,打分更准确,相当于朋友不是只看标签,而是把每件衣服都拿起来仔细掂量。

拿到分数后,通常用两步筛选:先取Top-K,只保留分数最高的前K个片段;再设一个最低分数线(阈值),低于阈值的片段即使排在前K位也照样丢弃。这个筛选逻辑用数学语言表达就是:

经朋友把关之后,背包里的每样东西都对得上前方的路。重排和压缩配合的价值正在于此:重排解决“哪些值得留”,压缩解决“留下的怎么装”,两道工序一前一后,行李才能又准又轻。

1[score(si)≥τ]\mathbb{1}[\text{score}(s_i) \geq \tau]
这是一个保留指示函数:当第i个片段的重排得分大于等于阈值τ时函数值为1(保留),否则为0(丢弃)。重排加阈值,等于把“朋友的建议”变成了一道严格的安检门。
  1. 片段 s_i检索返回的第i个文本片段,相当于背包里的一件物品。
    sis_i
  2. 重排得分 score(s_i)重排模型给出的相关度评分,数值越大代表与问题越相关,相当于朋友贴的“必带”标签。
    score(si)\text{score}(s_i)
  3. 阈值 τ预先设定的最低保留分,低于这个分数的一律不放进背包。
    τ\tau
  4. 指示函数 1方括号内条件成立取1、不成立取0,数学上把“留/不留”变成了一步干净的判断。
    1[ ⋅ ]\mathbb{1}[\,\cdot\,]

开源工具

自己从零缝一个背包太费劲,市面上已经有成熟的“收纳神器”了。上下文压缩领域同样如此,开源生态提供了开箱即用的工具和框架,你只需要学会挑选和组装。

其中最有名的是两套框架。LlamaIndex提供了Node Postprocessor(节点后处理)机制,允许你在检索之后、送进大模型之前插入处理步骤,比如相似度压缩、关键词过滤、去重合并,都能以插件形式挂上去。LangChain则提供了ContextualCompressionRetriever(上下文压缩检索器),它把“检索”和“压缩”组合成一条流水线:先召回文档,再对每个文档独立执行压缩器逻辑,最后只返回精简后的内容。

除了框架,还有一些值得关注的压缩算法:sentence-window(句子窗口)把文本切成句子粒度,只保留与问题最相关的句子及其邻近上下文;auto-merging(自动合并)先把文档拆成小节点,再根据相关性自动合并相邻节点,减少冗余的同时保留结构。选型时主要看三件事——延迟是否可接受、与现有技术栈是否兼容、压缩逻辑能否灵活定制。请记住:工具只是锦上添花,理解原理才知道什么时候该用哪一个,就像登山前要分清“防水背包”和“日常双肩包”适用场景完全不同。

比例平衡

背包塞太满,走在路上拉链崩开;塞太少,到了目的地发现必需品没带。压缩比例的拿捏是同样的哲学——不存在“越瘦越好”或“越全越好”,只存在“刚好够用且不超重”的平衡点。

压缩率与信息保留之间存在天然的拉扯:压缩率越高,token越省,但信息丢失的风险越大;压缩率越低,信息越完整,但成本和噪音又反弹回来。这种权衡关系可以用一个最优化目标来刻画——在质量收益和成本开销之间找到最优解。

实际工程中很少死板地求同一个压缩率。更常见的做法是动态调整:问题简单时压缩率高一些,问题复杂时需要保留更多细节;检索结果很长时加大压缩力度,检索结果本就精炼时少压甚至不压。好比登山时路况好就多背点物资,路况险就轻装上阵——没有绝对正确的比例,只有最适合当前场景的比例。

Copt=arg⁡max⁡c(αR(c)−βCcost(c))C_{opt} = \arg\max_{c} \left( \alpha R(c) - \beta C_{\text{cost}}(c) \right)
最优压缩率就是让“质量收益R乘以权重α”减去“成本开销C乘以权重β”之后数值最大的那个压缩比例。α和β是你的主观偏好:如果更在意回答质量就调大α,如果更在乎省钱就调大β。
  1. 候选压缩率 c在0到1之间尝试的每个压缩比例,相当于每次调整背包的打包力度。
    cc
  2. 质量收益 R(c)采用压缩率c后的信息保留程度,可以理解为回答质量的保持率。
    R(c)R(c)
  3. 成本开销 C_cost(c)压缩后剩余token的部署费用,以及与压缩本身相关的计算开销。
    Ccost(c)C_{\text{cost}}(c)
  4. 权重参数 α 和 β它们决定你更看重质量还是更看重成本,调整这两个系数就能得到不同取向的压缩策略。
    α,β\alpha, \beta
  5. 取最大值的比率在所有候选c里挑选使括号内表达式最大的那个,就是当前场景下的最优压缩率。
    arg⁡max⁡c( ⋅ )\arg\max_{c}(\, \cdot \,)

质量评估

旅行结束回到家的第一件事是什么?复盘——护照还在吗?照片拍了吗?下次同类旅行哪些该带、哪些不该带?上下文压缩同样需要这样的“复盘机制”,否则你永远不知道自己扔掉的是“垃圾”还是“宝贝”。

压缩后质量评估主要看三个维度。一是忠实度(Faithfulness),检查压缩后生成的答案是否仍然忠于检索到的原始事实,有没有因为丢细节而出现幻觉;二是答案相关性(Answer Relevance),看回答是否仍然命中用户的真实问题;三是覆盖度(ROUGE等指标),衡量压缩结果在多大程度上保留了原文的关键内容。这些指标共同构成一把尺子,量出“压缩后的回答”和“理想回答”之间的距离。

要判断压缩是否导致退化,最直接的办法是对比检测:用同一组测试问题分别跑压缩前和压缩后两条管线,把两边的输出质量得分拿来对比。退化度的计算如下:

除了离线指标,工程上还常用A/B测试做线上验证:随机把流量分给压缩版和原版,观察真实用户对回答的点赞/踩率。定期抽取一批案例做人工标注,则是兜底的安全网。没有评估就没有优化——评估是这一切“减负”决策的坐标,也是让你下一次打包时越来越有把握的旅行笔记。

D=1−FcFbD = 1 - \frac{F_c}{F_b}
退化度D等于1减去“压缩后得分与基线得分的比值”。D越接近0代表质量损失越小,D为正值说明压缩造成了退化,为负值反而说明压缩提升了质量。
  1. 基线得分 F_b未做压缩时,原版管线的生成质量评估得分,它是退化检测的“参考标准”。
    FbF_b
  2. 压缩后得分 F_c采用上下文压缩后,管线输出的质量评估得分。
    FcF_c
  3. 退化度 D用1减去两者比值得到退化程度,0表示无损,正数表示有损,负值表示压缩后反而更好。
    D=1−FcFbD = 1 - \frac{F_c}{F_b}

AI 应用场景

智能客服历史工单检索压缩

阶段:检索后处理 / 上下文优化

解决的问题:智能客服系统每轮对话都要从历史工单库检索相似案例,一次返回十几条工单摘要,token消耗巨大且大量内容是重复的退换货说明。传统做法是直接限制检索数量,但这样会漏掉真正有效的处理方案。通过重排模型对工单按相关度打分后再做去重合并,既保留了最有效的处理经验,又把token成本砍掉近一半。

具体案例:某电商平台客服助手接入上下文压缩后,历史工单的注入量从平均3500 token降到1800 token,单轮费用下降48%,而问题解决率反而提升了3%,原因是模型不再被重复和无关工单干扰。

企业知识库多文档问答去重

阶段:检索增强生成 / 知识库优化

解决的问题:大型企业的知识库中,同一制度条款往往散落在多个版本的文档里,检索时反复返回相似内容,浪费token且容易造成答案前后矛盾。采用“去重合并 + 摘要式压缩”组合拳,把多个来源的重复条款合并为一段权威性摘要,再配合阈值过滤去掉低相关段落,让回答稳定且精炼。

具体案例:某法律咨询平台的知识库中,同一合同条款在数百份历史合同里重复出现,传统的RAG每条都返回导致回答冗长。加入上下文压缩后,系统自动识别重复段落并只保留最新版本的条款描述,答案长度缩短60%,引用来源也更清晰可信。

长文档研报摘要压缩

阶段:上下文构建 / 提示词优化

解决的问题:投研或法律场景常需分析上百页的PDF,直接把全文塞进模型既不现实又昂贵。通过摘要式压缩把每个章节浓缩成一段结构化要点,再按问题相关性筛选章节,让模型在有限的上下文窗口中看到最核心的信息。

具体案例:某证券公司用RAG分析年度研报,原始PDF共120页约15万token,先按章节做摘要式压缩至1.5万token,再针对具体问题重排筛选最相关的3-5段,单次分析成本从十几元降到不足两元,关键数据点无遗漏。

Python 演示

python73 行
import re class ContextCompressor:    def __init__(self, compress_ratio=0.5, sim_threshold=0.75):        self.compress_ratio = compress_ratio        self.sim_threshold = sim_threshold     def sentence_split(self, text):        parts = re.split(r"([。!?.!?])", text)        sentences = []        for i in range(0, len(parts) - 1, 2):            s = (parts[i] + parts[i + 1]).strip()            if s:                sentences.append(s)        if parts and parts[-1].strip():            sentences.append(parts[-1].strip())        return sentences     def jaccard_sim(self, s1, s2):        a, b = set(s1), set(s2)        if not a or not b:            return 0.0        union = a | b        if not union:            return 0.0        return len(a & b) / len(union)     def keyword_score(self, sent, keywords):        s = sent.lower()        return sum(1 for k in keywords if k.lower() in s)     def compress(self, text, keywords, max_len=None):        sentences = self.sentence_split(text)        if not sentences:            return "", []        n = len(sentences)        scored = []        for i, sent in enumerate(sentences):            kw = self.keyword_score(sent, keywords)            pos = max(0.0, 1.0 - i / n)            score = 1.5 * kw + pos            scored.append((score, i, sent))        keep_n = max(1, int(n * self.compress_ratio))        selected = sorted(scored, key=lambda x: -x[0])[:keep_n]        selected.sort(key=lambda x: x[1])        deduped, chosen = [], []        for _, _, sent in selected:            if any(self.jaccard_sim(sent, c) > self.sim_threshold for c in chosen):                continue            deduped.append(sent)            chosen.append(sent)        result = "".join(deduped)        if max_len and len(result) > max_len:            result = result[:max_len]        return result, deduped if __name__ == "__main__":    doc = ("RAG检索增强生成是一种结合检索与生成的技术。"           "它先从知识库中检索相关文档。"           "然后将检索结果注入提示词。"           "大模型基于注入的上下文生成答案。"           "检索结果往往包含大量冗余信息。"           "冗余信息会增加token消耗。"           "冗余信息也会干扰模型生成的准确性。"           "上下文压缩可以保留精华去除噪音。"           "这是一种低成本高收益的优化技巧。")    c = ContextCompressor(compress_ratio=0.5, sim_threshold=0.7)    out, kept = c.compress(doc, keywords=["检索", "压缩", "冗余"])    print("原始长度:", len(doc))    print("压缩后长度:", len(out))    print("压缩率: {:.2%}".format(1 - len(out) / len(doc)))    print("保留句子数:", len(kept))    print("压缩结果:", out)
目的:实现一个轻量级上下文压缩器,用“关键词打分 + 位置加权 + Jaccard去重”三步完成压缩,模拟真实RAG链路中的去重合并环节。
  1. class ContextCompressor定义一个压缩器类,把压缩比例和相似度阈值作为可调参数,相当于给背包设定“打包规格”。
  2. re.split(r"([。!?.!?])", text)按中英文句号按句切分文本,切好的句子就是待挑选的“行李物品”。
  3. self.jaccard_sim(s1, s2)用交集除以并集计算两个句子的相似度,值越大表示越可能在说同一件事。
  4. score = 1.5 * kw + pos综合打分公式:命中关键词的句子权重更高,位置靠前的句子也有保底加分。
  5. selected = sorted(scored, key=lambda x: -x[0])[:keep_n]按分数从高到低排序后截取前K句,相当于重排模型的Top-K筛选。
  6. if any(...)若新句子与已选句子相似度超过阈值就跳过,这是“同款袜子只带一双”的去重逻辑。

常见误区

误区“压缩率调到90%以上,token省到极致,一定是最优方案。反正大模型能力强,给点关键信息它也能自己推理。”

正解压缩率过高会把关键细节一并压掉,导致回答空洞、幻觉增多。实践表明60%-70%的压缩率通常性价比最高,就像背包塞到极限,省了空间却把护照压丢了——省下的钱还不够弥补回答质量下降带来的损失。

为何常见:token费用的下降是立竿见影的,而回答质量的隐性损失不容易被直观感知,所以人们容易高估高压缩率的收益。

误区“既然检索结果太长,那我在检索数据库时就少返回一些文档不就行了?直接限制检索数量就等于压缩了。”

正解检索前限制数量是“盲减”,可能把分散但高价值的片段丢掉。正确做法是先召回尽量全面的候选,再由重排和压缩在检索后精细筛选。就像出发前不确定哪些物品有用,等朋友帮你打包时再决定取舍更稳妥。

为何常见:控制检索返回数量操作最简单直观,看起来和压缩目的一致,容易被误认为同一件事。

误区“重排模型已经按相关度排好序了,直接取前几个片段就行,没必要再做压缩这一步。”

正解重排解决的是“哪些更相关”,不解决“重复”和“冗余”。排序靠前的片段依然可能互相重复,依然可能有一大段铺垫句子。重排与压缩是互补的两道工序:先排序后精简。相当于朋友帮你标好了“必带”,你仍然需要把一件件衣服叠好才能装进背包。

为何常见:重排和压缩都在减少“不相关内容”,功能边界看起来模糊,容易让人误以为存在其一即可。

误区“压缩本质上是丢信息,丢了信息回答质量一定会下降,所以干脆别压缩了。”

正解好的压缩是有损但“去噪”的过程。去掉的是噪音和冗余,留下的信息信噪比更高,模型注意力更集中,回答质量往往不降反升。关键是控制比例并配合质量评估,而不是因噎废食。

为何常见:人对“有损”二字有天然抵触,忽略了噪音对模型同样是一种干扰,去噪本身就在提升信息质量。

面试题

Q1. 上下文压缩的核心理念是什么?

单选题 · easy

  • A.压缩词汇数量越多越好
  • B.保留核心信息去除噪音← 正确答案
  • C.删除文本中较长句子
  • D.只保留文本开头内容

答案:B。上下文压缩的核心是在有限上下文窗口里保留最有价值的信息,同时丢掉噪音和冗余,B正确。A只追求数量不看质量,C和D都是简单粗暴的截断策略,无法保证保留关键信息。

Q2. 下列哪种压缩方式会生成全新文本?

单选题 · easy

  • A.提取式压缩
  • B.摘要式压缩← 正确答案
  • C.去重合并压缩
  • D.关键词过滤

答案:B。摘要式压缩让大模型把原文浓缩成一段全新文字,会生成原文中没有的新表述。提取式是原句摘抄,去重和关键词过滤都不产生新文本,所以选B。

Q3. 重排模型在上下文压缩中的作用是什么?

单选题 · mid

  • A.直接删除低分片段
  • B.将文档翻译成他语
  • C.按相关度给片段打分← 正确答案
  • D.替代大模型生成答案

答案:C。重排模型的核心职责是对检索片段与问题的相关度做精细打分,C正确。它给出分数但不直接删除(由后续阈值决定),A错误;B和D与重排完全无关。

Q4. 压缩率极高时可能出现什么问题?

单选题 · mid

  • A.信息丢失使得答案变差← 正确答案
  • B.token成本反而不断增加
  • C.检索速度明显提升更快
  • D.模型生成变得更加稳定

答案:A。压缩率越高,被丢弃的文本越多,关键细节越容易丢失,导致答案空洞甚至幻觉增加,A正确。token成本随压缩率升高而降低而非升高,B错误;C和D不是极端压缩的典型后果。

Q5. Jaccard相似度衡量什么?

单选题 · mid

  • A.两个文本的长度差异
  • B.交集与并集的大小比值← 正确答案
  • C.两个句子出现的频率
  • D.单词在语料库的权重

答案:B。Jaccard相似度计算两个集合交集元素数与并集元素数的比值,B正确。它不关心长度差、频率或权重,A、C、D都是对其他相似度或统计概念的错误描述。

Q6. 以下哪些属于常用的上下文压缩手段?

多选题 · mid

  • A.提取式压缩← 正确答案
  • B.摘要式压缩← 正确答案
  • C.去重合并压缩← 正确答案
  • D.修改模型参数

答案:ABC。提取式、摘要式、去重合并是三种主流压缩手段,A、B、C正确。修改模型参数属于模型微调范畴,与上下文压缩是两条技术路线,D错误。

Q7. 关于压缩比例与回答质量的关系,哪种说法正确?

单选题 · hard

  • A.压缩比例越高质量线性上升
  • B.压缩比例与质量完全无关
  • C.存在最佳压缩比需权衡取舍← 正确答案
  • D.压缩比例越低效果一定越好

答案:C。压缩比例与质量之间存在倒U形关系:过低压不住噪音,过高丢失关键信息,中间存在最佳区间,C正确。质量不会随比例线性上升,A错误;二者强相关而非无关,B错误;比例低未必好,还要考虑成本和噪音,D错误。

Q8. 如何评估上下文压缩后质量是否退化?

多选题 · hard

  • A.对比压缩前后生成结果质量← 正确答案
  • B.做A/B测试用指标验证← 正确答案
  • C.人工抽查压缩的效果← 正确答案
  • D.直接删除所有评估环节

答案:ABC。对比压缩前后质量、做A/B测试、人工抽查都是有效的评估手段,A、B、C正确。删除评估环节会让压缩退化无法被发现,D错误。

Q9. LlamaIndex实现上下文压缩的主要机制是什么?

单选题 · easy

  • A.后处理节点对文档再次加工← 正确答案
  • B.直接修改大模型权重
  • C.增加检索文档的数量
  • D.把文本文档重新分词

答案:A。LlamaIndex通过Node Postprocessor后处理节点在检索后对文档做二次加工,实现压缩和过滤,A正确。修改模型权重是微调的事,增加检索数量与压缩相反,重新分词只是文本预处理,B、C、D均错误。

Q10. 压缩上下文中降低token成本的方式有哪些?

多选题 · hard

  • A.用提取式保留关键句← 正确答案
  • B.对重复段落做去重← 正确答案
  • C.用摘要式压缩长文← 正确答案
  • D.增加检索返回数量

答案:ABC。提取式保关键句、去重合并重复段、摘要式压缩长文都能直接减少喂给模型的token量,A、B、C正确。增加检索返回数量只会让上下文更长、成本更高,D错误。