Contextual Retrieval:给每个文本块补回它丢掉的上下文
要让 AI 模型在特定场景里有用,它常常需要背景知识:客服机器人得懂它服务的那家公司,法律分析机器人得知道海量过往判例。
开发者通常用 RAG(Retrieval-Augmented Generation,检索增强生成) 来给模型补知识:从知识库里检索相关信息、拼到用户的 prompt 上,显著增强回答。问题是——传统 RAG 在编码信息时会把上下文剥掉,结果系统经常检索不到那条本该相关的信息。
这篇介绍一种大幅改进 RAG 检索环节的方法,叫 Contextual Retrieval(上下文检索),由两个子技术组成:Contextual Embeddings 和 Contextual BM25。它能把检索失败数降低 49%,再叠加 reranking(重排)能降到 67%。
先说一句:有时直接用长 prompt 就够了
最简单的方案往往最好。如果你的知识库小于 20 万 token(约 500 页),直接把整个知识库塞进 prompt 就行,根本不用 RAG。
我们前阵子发布的 prompt caching(提示缓存) 让这条路又快又省:常用 prompt 可以在多次 API 调用间缓存,延迟降一半以上、成本最多省 90%。
不过知识库一旦变大,你就需要更可扩展的方案——这才是 Contextual Retrieval 登场的地方。
RAG 速成:扩展到更大的知识库
对装不进 context window 的大知识库,RAG 的常规预处理是:
- 把知识库(文档「语料」)切成小块(chunk),通常不超过几百 token;
- 用 embedding 模型把每块转成向量嵌入(编码语义);
- 把这些嵌入存进向量数据库,支持按语义相似度检索。
运行时,用户的查询拿去向量库里找语义最相近的块,再把最相关的块拼进 prompt。
embedding 擅长抓语义关系,但会漏掉关键的精确匹配。这时一个更老的技术能帮忙:BM25——一个用词法匹配找精确词/短语的排序函数,对含唯一标识符或技术术语的查询特别有效。
例子:用户查「Error code TS-999」。embedding 模型可能找到一堆讲「错误码」的泛泛内容,却漏掉「TS-999」这个精确串;BM25 会盯着这个具体字符串去找对的文档。
把两者结合(用 rank fusion 合并去重),传统 RAG 能在「精确词匹配」和「广义语义理解」之间取得平衡。但它有个大软肋:经常把上下文给毁了。
传统 RAG 的「上下文困境」
文档被切成小块利于检索,但单个块缺乏足够上下文时就出问题。
设想你的知识库里嵌了一堆财报(比如美国 SEC 文件),有人问:「ACME 公司 2023 年 Q2 的营收增长是多少?」
一个相关块可能写着:
「公司营收较上一季度增长了 3%。」
但这块自己根本说不清指的是哪家公司、哪个时段——既难被检索到,也难被有效利用。
Contextual Retrieval 怎么解
办法是:在嵌入前、建 BM25 索引前,给每个块前面拼上一段针对该块的解释性上下文。回到 SEC 的例子:
| 内容 | |
|---|---|
| 原始块 | 公司营收较上一季度增长了 3%。 |
| 加上下文后 | 这块出自 ACME 公司 2023 年 Q2 业绩的 SEC 文件;上一季度营收为 3.14 亿美元。公司营收较上一季度增长了 3%。 |
注:过去也有人提过别的上下文增强法(给块加通用文档摘要、假设性文档嵌入、基于摘要的索引等),我们都试过,收益很有限。本文这套和它们不同。
怎么实现
手动给成千上万、甚至上百万个块写注解显然不现实——所以我们交给 Claude。我们写了一段 prompt,让模型用「整篇文档的语境」为每个块生成简洁的、针对该块的上下文。用的是 Claude 3 Haiku,大意是:把整篇文档和待定位的那个块都给它,让它只输出一段简短的、用于改善检索的定位说明,别的不要。
生成的上下文通常 50~100 token,拼到块前面,再去嵌入、建 BM25 索引。
⭐ 用 Prompt Caching 把成本压下来
Contextual Retrieval 之所以能在 Claude 上低成本做到,靠的正是前面说的 prompt caching:你不用为每个块都重新传一遍参考文档,只需把文档载入缓存一次,后续引用缓存内容即可。按 800 token/块、8k token/文档、50 token 指令、100 token/块上下文算,生成上下文化块的一次性成本约为每百万文档 token 1.02 美元。
效果
我们在多个领域(代码库、小说、ArXiv 论文、科学论文)、多种 embedding 模型和检索策略上做了实验,用 1 − recall@20 作指标(衡量「相关文档没能进前 20 块」的比例):
| 配置 | top-20 检索失败率 | 相对降幅 |
|---|---|---|
| 基线 | 5.7% | — |
| Contextual Embeddings | 3.7% | −35% |
| Contextual Embeddings + Contextual BM25 | 2.9% | −49% |
| 再叠加 Reranking | 1.9% | −67% |
实现时要注意几点
- 切块策略:块大小、边界、重叠都会影响检索表现。
- embedding 模型:所有模型都受益,但程度不同。我们发现 Gemini 和 Voyage 的嵌入特别有效。
- 定制上下文 prompt:通用 prompt 已经不错,针对你领域定制(比如加术语表)可能更好。
- 块的数量:块越多越可能包含相关信息,但太多也会干扰模型——我们试了 5、10、20,20 最优,但值得在你的场景上自测。
- 永远跑 eval。
再加一步:Reranking(重排)
大知识库的初次检索常常返回几百个相关度参差的块。Reranking 是常用的过滤技术:只把最相关的块交给模型,既提升回答质量,又因模型处理的信息变少而降本降延迟。步骤是:
- 先初检索拿到一批候选块(我们取前 150);
- 把这批块连同用户查询喂给 reranking 模型;
- reranker 给每块按相关性打分,选出 top-K(我们取前 20);
- 把 top-K 拼进 prompt 生成最终结果。
我们用 Cohere 的 reranker 测试,跨多个领域,Reranked Contextual Embedding + Contextual BM25 把 top-20 失败率降低了 67%(5.7% → 1.9%)。
⚠️ 注意 reranking 会在运行时多加一步、带来少量延迟。「重排更多块换更好效果」vs「重排更少换更低延迟成本」之间有内在权衡,建议在你的场景上调出平衡点。
总结
我们跑了大量测试,综合结论:
- Embeddings + BM25 优于单用 embeddings;
- 测过的里面 Voyage 和 Gemini 的嵌入最好;
- 给模型 top-20 比 top-10、top-5 更有效;
- 给块加上下文,能大幅提升检索准确率;
- Reranking 优于不 reranking;
- ⭐ 这些收益会叠加:上下文嵌入 + 上下文 BM25 + 重排 + top-20,一起上,效果最大化。
我们鼓励所有和知识库打交道的开发者,用我们的 cookbook 去试这些方法,解锁新的性能水平。