上下文压缩
将上下文精简到真正重要的内容。
上下文压缩 是 CoddyKit 上的免费 AI Prompt Engineering 课时。 这是第 3 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 AI Prompt Engineering 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 AI Prompt Engineering 课程共包含 4 节课。
为什么要压缩上下文
检索到的片段往往包含噪声:一个相关片段可能大部分都是模板化内容,只有一个句子承载关键信息。上下文压缩会在文本到达生成器之前,将检索到的内容裁剪为真正能够回答查询的部分。
这些收益会叠加:降低词元成本、减少延迟、减少干扰内容,并通过缩小模型需要处理的上下文来缓解中间遗忘问题。
def compress(query, chunks):
# Goal: keep only spans that bear on the query,
# dropping boilerplate, navigation, and off-topic sentences.
return [extract_relevant(query, c) for c in chunks]extractive 与抽象式压缩
extractive压缩会选择与查询相关的原文片段(句子、段落),保留精确措辞和来源。抽象式压缩则会改写或总结内容,能够实现更高的压缩率,但可能造成信息丢失并引入错误。
对于带引用的事实型 RAG,请优先使用 extractive,以便让各项陈述都能追溯到来源;只有在能够验证忠实度时,才使用抽象式压缩。
def extractive(query, chunk):
sents = split_sentences(chunk.text)
scored = [(s, relevance(query, s)) for s in sents]
kept = [s for s, r in scored if r > TAU]
return ' '.join(kept) or top1(scored) # never return empty句子级过滤
这是一种轻量且稳健的技术:使用小型交叉编码器或嵌入相似度,针对查询为每个片段中的每个句子评分,然后删除低分句子。这样可以保留高价值句子,同时丢弃填充内容。
请为每个片段保留最低限度的上下文,以免删掉为事实提供含义的周围句子。
def sentence_filter(query, chunk, keep_ratio=0.4):
sents = split_sentences(chunk.text)
scores = embed_sim_batch(query, sents)
n_keep = max(1, int(len(sents) * keep_ratio))
idx = sorted(range(len(sents)), key=lambda i: -scores[i])[:n_keep]
return ' '.join(sents[i] for i in sorted(idx)) # keep original order基于 LLM 的上下文压缩
LLM 可以逐个片段进行压缩:提示它只提取段落中与查询相关的部分;如果没有任何相关内容,则返回裁剪后的原文片段,或者返回空标记。
这是 LangChain ContextualCompressionRetriever 模式。它比嵌入过滤器更准确,但每个片段都会增加一次 LLM 调用,因此请将它保留给高风险流程,或采用批处理。
def llm_compress(query, chunk):
prompt = (
'Extract ONLY sentences from the passage relevant to the query. '
'If none are relevant, output NONE. Do not paraphrase.\n'
'Query: ' + query + '\nPassage: ' + chunk.text
)
out = llm(prompt, temperature=0).strip()
return None if out == 'NONE' else out词元级裁剪(LLMLingua)
LLMLingua 等方法会在词元级别进行压缩,使用小型模型估计词元的信息量,并删除信息量低的词元。它们可以在保留大模型所需信息的同时,实现很高的压缩率。
代价是压缩后的文本对人类来说可读性较差,因此它适合仅供机器使用的上下文,不适合面向用户展示。
def token_prune(context, target_ratio=0.3):
# small LM estimates per-token information (perplexity-based);
# drop the least informative tokens down to target_ratio length
scores = small_lm_token_importance(context)
return keep_top_fraction(context, scores, target_ratio)查询感知式与查询无关式
查询感知式压缩会保留与本次查询相关的内容,从而生成最紧凑的上下文,但必须针对每个请求运行。查询无关式压缩(预先计算的片段摘要)在请求时成本更低,但无法针对具体问题进行聚焦。
一种混合方案是离线存储凝缩后的片段版本,再在线应用轻量的查询感知式裁剪。
# Offline: precompute a dense summary per chunk (query-agnostic)
chunk.summary = summarize(chunk.text)
# Online: query-aware trim over summaries (cheap) then verify on full
ctx = [sentence_filter(query, Chunk(c.summary)) for c in top_chunks]防止信息丢失
过于激进的压缩可能会删掉唯一重要的从句。请通过召回检查进行防护:验证压缩后的上下文仍然能够推出答案;如果压缩器返回的内容少得可疑,则保留未压缩的片段作为后备方案。
如果重排序器将某个片段评为高度相关,绝不能让压缩将其变为空;这说明压缩器发生了故障,而不是该片段不相关。
def safe_compress(query, chunk):
out = llm_compress(query, chunk)
if out is None and chunk.rerank_score > 0.7:
return chunk.text # trust re-ranker over compressor
return out or chunk.text保留来源可追溯性
压缩必须保持来源归属完整。请为每个保留的片段标注其片段 ID 和文档 ID,以便生成器引用。如果压缩时删除了元数据,就会失去可验证性,也无法检测幻觉。
请将 ID 作为结构化字段贯穿 pipeline 传递,绝不要将其作为可能被压缩删掉的自由文本。
def compress_with_ids(query, chunks):
out = []
for c in chunks:
span = safe_compress(query, c)
out.append({'id': c.id, 'doc': c.meta['doc_id'], 'text': span})
return out # generator cites id; provenance preserved压缩与词元预算
压缩让您可以为了提高召回率而检索更多候选项,同时保持最终提示词较小。请为上下文窗口设置词元预算,并采用贪心策略装入价值最高的压缩片段,直到达到预算上限。
这样可以将检索规模与生成所消耗的资源解耦,这是提高效率的关键手段。
def pack(spans, budget_tokens, tok):
spans = sorted(spans, key=lambda s: -s['score'])
used, packed = 0, []
for s in spans:
t = tok(s['text'])
if used + t > budget_tokens:
continue
packed.append(s); used += t
return packed衡量压缩质量
请跟踪三个数值:压缩比(节省的词元数)、answer 准确率(质量是否保持)以及忠实度(压缩器是否避免改动事实)。目标是在不改变 answer 准确率的前提下,实现最高的压缩比。
请遍历不同的压缩强度,选择满足成本目标且不损失质量的帕累托点。
def eval_compression(eval_set, levels):
return {
lvl: {
'ratio': mean_ratio(eval_set, lvl),
'acc': answer_accuracy(eval_set, lvl),
'faith': faithfulness(eval_set, lvl),
} for lvl in levels
}压缩感知型 pipeline
完整流程如下:广泛检索,重排序,对每个保留下来的片段进行压缩(采用 extractive 或基于 LLM 的方法)并保留来源信息,防止过度裁剪,按照词元预算装入内容,然后生成并附带引用。
压缩层让高召回检索变得经济可行,并使生成器专注于真正重要的内容。
def pipeline(query):
top = rerank(query, hybrid_retrieve(query, 50))[:12]
spans = compress_with_ids(query, top)
spans = [s for s in spans if s['text']]
packed = pack(spans, budget_tokens=2000, tok=count_tokens)
return generate_with_citations(query, packed)快速检查
请选择适用于事实型、带引用的 RAG 系统的正确压缩方法。
回顾
关键要点:
- 压缩会将检索到的片段裁剪为与查询相关的内容,从而降低成本和延迟,并减少干扰内容。
- 对于带引用的事实型 RAG,请优先使用 extractive(原文、可追溯)而不是抽象式压缩;词元级裁剪适合仅供机器使用的上下文。
- 查询感知式压缩最为紧凑,但需要针对每个请求运行;请结合离线摘要来提高效率。
- 防止信息丢失,并保留片段和文档的来源信息以支持引用。
- 利用压缩实现广泛检索,同时保持提示词较小;在不损失 answer 准确率的前提下,将压缩比调到最高。
常见问题解答
「上下文压缩」课时是免费的吗?
是的 — 「上下文压缩」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 AI Prompt Engineering 课程的其余内容,请升级到 CoddyKit PRO。 AI Prompt Engineering 课程共包含 4 节课。
「上下文压缩」这节课中我会学到什么?
将上下文精简到真正重要的内容。 你通过在浏览器中直接运行的动手代码来练习 AI Prompt Engineering,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 AI Prompt Engineering 需要有经验吗?
无需任何先前经验。CoddyKit 上的 AI Prompt Engineering 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 3 节课,共 4 节。
「上下文压缩」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 AI Prompt Engineering 课中编写并运行代码吗?
能。每节 AI Prompt Engineering 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。