RAG上下文压缩:一个让Token消耗直接砍半的骚操作
上个月一个RAG项目上线,跑了一周一看账单傻眼了。
调用量没多多少,Token消耗翻了三倍。仔细一查,好家伙——每次调用都往上下文里塞了几千字的检索结果,其中一多半跟用户问的问题没关系。
信息是检索回来了,但大模型得读完啊。读就费钱。
然后我研究了一圈,发现这事有个专门的领域叫"上下文压缩"(Context Compression)。折腾下来,效果最好的方案能把Token消耗砍掉一半以上。
这篇文章聊聊我踩的坑和经验。
问题到底出在哪
做RAG的人都会遇到一个矛盾:
你要检索得全面,就得召回尽可能多的相关文档。但你让模型读的上下文越多:
- 钱烧得更快——很多大模型按Token计价
- 推理变慢——长上下文推理速度明显下降
- 质量反而下降——"lost in the middle"效应,输入太长模型反而找不到重点
我那个项目,RAG pipeline是这样的:
用户提问 → 向量检索 → Top-10文档 → 全塞给LLMTop-10文档加一起,少说两三千字。用户问的问题可能就一句话,结果上下文中90%都是参考材料。这里面大量信息对回答毫无帮助。
说白了就是:检索好的东西一股脑全喂给模型,不挑食。
我试过的几种压缩方案
方案1:摘要型压缩(推荐)
原理很简单:把检索到的多篇文档,先让模型"读一遍,总结一下",把浓缩版扔给下游的问答模型。
这样做的好处是去掉了噪音。比如检索到3篇文档,每篇讲不同维度,摘要工具能把三个维度的核心信息提取出来,合并成一个300字的摘要。
我用的实践代码:
defcompress_context(documents,query):"""把多篇文档压缩成精简摘要"""texts=[doc.page_contentfordocindocuments]combined="\n---\n".join(texts)prompt=f"""以下是检索到的参考材料,请根据用户问题,提取关键信息,用200字以内输出。 用户问题:{query}参考材料:{combined}关键信息摘要:"""# 用一个便宜的小模型做摘要compressed=call_cheap_llm(prompt,max_tokens=300)returncompressed注意我用的是便宜的模型做摘要。比如千问Turbo或者DeepSeek Lite就行,不需要上GPT-5.5。成本降下来了。
方案2:LLMlingua(效果猛,但复杂)
微软出的一个方案,思路非常直接——直接从检索结果里删掉不重要的词和句子。
说白了就是"智能删减"。模型判断哪些内容最相关,把无关的副词、修饰语、甚至是整个句子删掉。
我试过在中文场景下用LLMlingua,效果还不错。一份3000字的文档能压缩到1000-1200字,信息损失控制在10%以内。
但缺点也很明显:
- 需要单独部署一个小模型做压缩
- 延迟增加了200-400ms
- 有时候会误删关键数据(比如数字被当做不重要的词删掉了)
方案3:Rerank + Top-K截断
这个其实不算"压缩",只是"精挑细选"。
检索阶段拿Top-20的文档,然后过一个Rerank模型重新排序,最后只取Top-3喂给模型。
我现在的项目用的就是这套。效果比直接用向量检索引擎的结果好得多。
defretrieve_and_rerank(query,top_k=20,final_k=3):# 向量检索拿Top-20raw_results=vector_store.similarity_search(query,k=top_k)# Rerank重新打分reranked=reranker.rerank(query,raw_results)# 只保留Top-3final=reranked[:final_k]returnfinalRerank模型我用的是BGE-Reranker-V2,中文效果不错,而且模型不大,本地跑没问题。
三种方案对比
我实际对比了一下:
| 方案 | Token节省 | 质量影响 | 延迟增加 | 部署成本 |
|---|---|---|---|---|
| 摘要压缩 | 50%-70% | 轻微下降 | 300-500ms | 低(用小模型) |
| LLMlingua | 40%-60% | 约10%信息损失 | 200-400ms | 中(需单独部署) |
| Rerank截断 | 30%-50% | 质量可能提升 | 50-100ms | 低(模型不大) |
我的推荐:
- 对质量要求极高:Rerank截断 + 摘要压缩,组合使用
- 对成本敏感:摘要压缩就够了,便宜见效快
- 对延迟敏感:只做Rerank截断
我现在的方案
折腾了一个多月,最后我的pipeline长这样:
用户提问 ↓ 向量检索 (Top-20) ↓ Rerank重排序 (取Top-5) ↓ 动态截断 (根据问题长度动态调整) ↓ 轻量摘要合并 (用DeepSeek Lite) ↓ 送入主模型Token消耗对比之前:从平均3800 Token/次降到1500 Token/次。省了一半不止。
更关键的是回答质量反而提升了。之前上下文塞太多无关信息,模型经常答非所问。现在干净多了。
关于上下文压缩的一句话真相
研究了一圈,我最大的感触是:
上下文压缩不是在"压缩",而是在"聚焦"。
你真正想做的不是减少Token数量,而是让模型看到真正有用的信息。一个300字的金句,比3000字的废话有用一百倍。
很多RAG做不好,问题真的不在检索上。而在于你捡回来了一大堆金子,结果全掺了沙子一起喂给模型了。
先洗干净,再上菜。
几个实战经验
中文场景下别直接套英文方案。LLMlingua有中文版,但效果不如英文版。我现在中文场景主要靠摘要压缩+Rerank。
压缩率不是越高越好。我之前试过把5000字压到200字,结果模型回答质量明显下降。找到一个平衡点很重要,我一般控制在30%-50%的压缩率。
动态调整压缩策略。如果用户问的是事实性问题(“某某API的参数是什么”),少压缩甚至不压缩,保留原文档。如果是开放性回答(“总结一下”),大胆压缩。
监控你的压缩效果。我在pipeline里加了个简单评估:每次压缩后,让模型对压缩结果和原结果分别做回答,对比质量。说实话,自动化评估还挺难做的,目前我还在手动抽样检查。
写在最后
RAG上线容易,优化难。上下文压缩这件事,花两天就能搞定一个初步方案,但调优可能花两周。
但话说回来,这是RAG优化中性价比最高的项目之一——改几行代码就能把Token成本砍半,这个ROI我觉得很值。
如果你的RAG项目还没做上下文压缩,不如今天就试试。从Rerank截断开始,效果立竿见影。
有什么问题欢迎评论区交流。
参考资料:
- LLMlingua: 压缩长上下文
- BGE-Reranker 模型
- Lost in the Middle: 长上下文的位置偏差
