提示压缩与上下文裁剪:给长上下文瘦身
你已经能写出把任务说清楚的提示,但一旦把产品文档、历史对话、检索到的资料整段塞进上下文,提示就会迅速膨胀到几千甚至上万个 token。上下文越长,账单越贵,模型的注意力也越容易被无关信息稀释。本文讲清为什么长提示需要压缩,梳理四类可落地的裁剪方法(提示摘要压缩、关键句抽取、语义检索裁剪、自动压缩),说明它如何与 prompt-caching 叠加省钱,并附一段可直接运行的抽取式压缩最小示例。
为什么需要压缩
把长文本直接堆进提示,代价体现在两个层面。
token 成本随长度线性上升
主流大模型按 token 计费,输入与输出分别计价,输入往往占大头。上下文越长,每次请求的输入 token 越多,成本就越高。更关键的是,很多场景(客服、RAG、Agent)会把相同或相似的背景资料反复发给模型,长提示会被重复计费成千上万次。以 GPT-4 为例,它使用 cl100k_base 编码(tiktoken 官方对照表确认 gpt-4、gpt-3.5-turbo 均归在 cl100k_base 之下)【已核验】,中文约每 1 到 2 个字折成一个 token,几页文档轻松突破数千 token。把背景从五千 token 压到一千,单次请求成本直接降八成,乘以调用量就是可观的节省。
长上下文的噪声与注意力稀释
成本之外,更隐蔽的问题是质量。研究反复指出,当无关内容占据上下文,模型对真正重要指令的遵从度会下降,也就是「中间遗忘」(lost in the middle)现象:排在长上下文中部的关键约束,模型容易读不到或读偏。冗余信息还会引入噪声,让模型基于不相干内容产生幻觉。压缩不是单纯删字,而是把信噪比提上去,让有限的注意力预算花在刀刃上。
一句话概括:压缩同时解决「贵」和「准」两件事。
四类压缩方法
下面四类方法从简单到自动,难度与保真度递增,可单独用也可组合。
提示摘要压缩
最直观的做法:把长背景先交给一个模型(可用便宜的小模型)压成摘要,再把摘要而非原文放进正式提示。例如把一百页产品文档摘要成三百字要点,用户问题只针对要点回答。优点是实现零门槛,缺点是摘要会引入信息损失,且额外消耗一次调用。适合背景相对稳定、可离线预处理的场景。
关键句抽取
不做改写,只做筛选:把长文本分句,按与当前任务的相关度打分,保留高分句、丢掉低分句。它属于抽取式压缩,不改动原文措辞,保真度高于摘要。打分可以很简单——命中查询词越多、越靠前的句得分越高——也可以用语义相似度。下文代码即演示此法。
语义检索裁剪
这是 RAG 思路在提示内的落地:不把整库资料塞进提示,而是先用向量检索挑出与问题最相关的若干片段,再只把命中片段拼接进提示。它的本质是「按需取用」,上下文长度被检索命中数严格控制。配合重排(rerank)能进一步提升命中质量。适合资料库庞大、单次问题只涉及其中一小部分的场景。
自动压缩:LLMLingua 思路概述【已核验】
前述方法多靠规则或外部模型。LLMLingua(Jiang 等,arXiv:2310.05736,EMNLP 2023)提出端到端的自动压缩:用一个小语言模型(如 GPT-2 small)为每个 token 估算信息量,按预算删掉「不重要」的 token,再用预算控制器(budget controller)保证高压缩比下语义完整,用迭代式 token 级压缩建模被压缩内容间的依赖,并通过指令微调做分布对齐。论文在 GSM8K、BBH、ShareGPT、Arxiv 等数据集上实现最高 20 倍压缩且性能损失很小。其后续工作 LLMLingua-2(Pan 等,arXiv:2403.12968,Findings of ACL 2024)把压缩建模为 token 分类任务,用数据蒸馏从大模型学习压缩目标,并以 XLM-RoBERTa-large、mBERT 等小编码器实现 2 倍到 5 倍压缩、比前代快 3 倍到 6 倍。这类方法保真度与自动化程度最高,代价是需要引入对应依赖与一次压缩推理。
四类方法定位不同,可用一张表区分:
| 方法 | 核心动作 | 保真度 | 适合场景 |
|---|---|---|---|
| 提示摘要压缩 | 先摘要再喂入 | 中 | 背景稳定、可离线预处理 |
| 关键句抽取 | 按相关度筛句 | 高 | 需保留原文措辞 |
| 语义检索裁剪 | 向量检索按需取片段 | 高 | 资料库庞大、问题局部 |
| 自动压缩(LLMLingua) | 模型估信息量删 token | 高 | 追求自动与高压缩比 |
与 prompt-caching 的配合
压缩与缓存是天然搭档。Prompt caching 的机制是复用精确匹配的提示前缀:OpenAI 对长度达到 1,024 token 及以上的提示默认开启缓存,命中后缓存读取的 token 按更低费率计费(文档中 GPT-5.6 及之后模型缓存输入为未缓存输入的 0.1 倍)【已核验】。注意它要求前缀「逐 token 完全一致」才能命中。
关键洞察:先压缩、再缓存,比直接缓存原始长提示更省。原因有二。其一,缓存按 token 量计费,压缩后的前缀本身更短,写入与读取都更便宜。其二,压缩让前缀更精简稳定,配合把易变部分(用户问题)放在尾部、把稳定部分(系统指令、背景知识)放在头部,能最大化缓存命中率。实践建议:把系统提示与压缩后的背景固定在提示最前,把每次不同的用户问题放在最后,这样前缀不变、缓存稳定命中,叠加压缩收益翻倍。
若你的背景来自 RAG 检索,注意检索结果每次不同会破坏前缀一致性;可改为先压缩再固定一段「知识摘要」前缀,或对检索片段做确定性排序后再拼接,以保住缓存命中。
实战:用 Python 做抽取式压缩
下面是一段最小可运行的抽取式压缩示例:把长文本分句,按与查询的相关度打分,保留高分句,并用 tiktoken 统计压缩前后的 token 数。它不依赖重型框架,可直接复制到项目里改。
import re
import tiktoken
def split_sentences(text):
# 用句号、问号、感叹号做简单分句,并保留句末标点
parts = re.split(r"(?<=[。!?])", text)
return [p.strip() for p in parts if p.strip()]
def get_query_terms(query):
# 中文查询取二元词组作为匹配单元,英文则按空格切分
if re.search(r"[\u4e00-\u9fff]", query):
return [query[i:i + 2] for i in range(len(query) - 1)]
return [t for t in query.split() if t]
def score_sentence(sentence, terms, index, total):
# 命中查询词越多分越高,靠前的句子给予轻微位置加权
hits = sum(1 for term in terms if term in sentence)
position_bias = 1.0 - (index / max(total, 1)) * 0.3
return hits * 2.0 + position_bias
def compress_prompt(text, query, keep_ratio=0.5):
# 抽取式压缩主流程:分句、打分、按得分取前 keep_ratio 部分
sentences = split_sentences(text)
terms = get_query_terms(query)
scored = []
for i, s in enumerate(sentences):
scored.append((score_sentence(s, terms, i, len(sentences)), s))
scored.sort(key=lambda x: x[0], reverse=True)
keep_n = max(1, int(len(scored) * keep_ratio))
kept = [item[1] for item in scored[:keep_n]]
return "".join(kept)
def count_tokens(text, model="gpt-4"):
# 按模型对应的 tiktoken 编码统计 token 数(GPT-4 使用 cl100k_base)
enc = tiktoken.encoding_for_model(model)
return len(enc.encode(text))
if __name__ == "__main__":
# 一段示例长上下文:只有部分句子与用户问题相关
long_context = (
"我们的产品是一款面向开发者的提示管理工具。它支持版本管理与团队协作。"
"最近用户反馈导入大文档后响应变慢。我们怀疑是提示过长导致推理成本上升。"
"团队决定先统计 token 用量再决定优化方案。下一季度计划接入缓存以降本。"
)
query = "提示过长导致成本上升"
compressed = compress_prompt(long_context, query, keep_ratio=0.5)
before = count_tokens(long_context)
after = count_tokens(compressed)
print("压缩前 token:", before)
print("压缩后 token:", after)
print("压缩率:", round(after / before, 2))
print("压缩结果:", compressed)
运行后,与查询相关的句子(如「我们怀疑是提示过长导致推理成本上升。」)会被保留,无关句子被丢弃,token 数明显下降。要更稳,可把 score_sentence 换成句向量与查询向量的余弦相似度;要更省,可把 keep_ratio 调更低,或在压缩前先跑一次摘要。
小结
- 长提示同时带来 token 成本上升与注意力稀释(中间遗忘、噪声引入幻觉),压缩一举两得。
- 四类方法:提示摘要压缩(改写、门槛低)、关键句抽取(保真、按相关度筛句)、语义检索裁剪(RAG 式按需取片段)、自动压缩(LLMLingua 思路,模型估信息量删 token)。
- LLMLingua(arXiv:2310.05736)实现最高 20 倍压缩且损失小;LLMLingua-2(arXiv:2403.12968)以 token 分类建模把速度再提 3 倍到 6 倍。
- 压缩与 prompt-caching 叠加更省:先压缩缩短前缀,再把稳定部分放头部、易变部分放尾部以保缓存命中。
- 抽取式压缩可用极简代码落地,按关键句得分保留相关句并统计 token 节省。
参考与延伸阅读
- Jiang H. 等. LLMLingua: Compressing Prompts for Accelerated Inference of Large Language Models(arXiv:2310.05736,EMNLP 2023)【已核验】. https://arxiv.org/abs/2310.05736
- Pan Z. 等. LLMLingua-2: Data Distillation for Efficient and Faithful Task-Agnostic Prompt Compression(arXiv:2403.12968,Findings of ACL 2024)【已核验】. https://arxiv.org/abs/2403.12968
- OpenAI. Prompt caching(提示缓存:1,024 token 阈值、缓存读取 0.1 倍费率、需前缀精确一致)【已核验】. https://platform.openai.com/docs/guides/prompt-caching
- OpenAI Cookbook. How to count tokens with Tiktoken(GPT-4 / GPT-3.5-turbo 使用
cl100k_base编码)【已核验】. https://cookbook.openai.com/examples/how_to_count_tokens_with_tiktoken - Anthropic. Prompt caching(另一种缓存实现,适合长系统提示与长上下文复用)【待核实】. https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
- Liu N. F. 等. Lost in the Middle: How Language Models Use Long Contexts(长上下文注意力位置偏差的研究,作为注意力稀释的理论依据)【待核实】. https://arxiv.org/abs/2307.03172