在RAG系统中,即使使用了混合搜索,检索出的top-K文档中仍可能存在不够相关的内容。重排序(Reranking)作为"最后一道防线",能显著提升最终输入LLM的内容质量,从而改善答案准确性。
为什么需要重排序
向量检索的局限
向量检索使用Bi-Encoder架构:
- 查询→向量
- 文档→向量
- 计算相似度(余弦/点积)
优势:快速,可预先计算文档向量并索引。
局限:查询和文档独立编码,无法捕捉细粒度的交互关系。
示例:
- 查询:"Python API如何调用?"
- 文档A:"Python SDK安装指南"(包含Python但不相关)
- 文档B:"API调用示例代码"(高度相关)
向量检索可能给文档A更高分(因为"Python"词频高),而文档B更相关但得分低。
重排序的作用
重排序使用Cross-Encoder架构:
- 查询和文档一起输入模型
- 模型直接输出相关性得分
优势:能理解查询-文档之间的深层交互。
流程:
1. 检索:返回100个候选文档(快但粗糙)
2. 重排序:精细评估top-100,重新排序
3. 输出:top-5高质量文档给LLM
重排序模型对比
1. Cohere Rerank
特点:
- 商业API,易用
- 支持多语言
- 高准确率
使用:
import cohere
co = cohere.Client("YOUR_API_KEY")
query = "如何优化LLM成本?"
documents = [
"GPT-4定价是$0.03/1K tokens",
"使用更小的模型如GPT-3.5可以降低成本",
"今天天气很好" # 不相关文档
]
# 重排序
results = co.rerank(
query=query,
documents=documents,
top_n=2,
model="rerank-multilingual-v2.0"
)
for result in results:
print(f"文档{result.index}: 得分{result.relevance_score:.3f}")
# 文档1: 得分0.987
# 文档0: 得分0.654
# 文档2被过滤(得分低)
定价:$2/1000次搜索
2. Sentence-Transformers Cross-Encoder
特点:
- 开源免费
- 可本地部署
- 多个预训练模型
使用:
from sentence_transformers import CrossEncoder
model = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
query = "如何优化LLM成本?"
documents = [
"GPT-4定价是$0.03/1K tokens",
"使用更小的模型如GPT-3.5可以降低成本",
"今天天气很好"
]
# 计算相关性得分
scores = model.predict([(query, doc) for doc in documents])
# 排序
ranked_docs = sorted(zip(documents, scores), key=lambda x: x[1], reverse=True)
for doc, score in ranked_docs:
print(f"{score:.3f}: {doc}")
# 0.987: 使用更小的模型...
# 0.654: GPT-4定价...
# 0.021: 今天天气很好
3. BGE Reranker(中文优化)
from FlagEmbedding import FlagReranker
reranker = FlagReranker('BAAI/bge-reranker-large', use_fp16=True)
scores = reranker.compute_score([[query, doc] for doc in documents])
模型对比
| 模型 | 准确率 | 速度 | 成本 | 多语言 |
|---|---|---|---|---|
| Cohere Rerank | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | 付费 | ✅ |
| MS-MARCO Cross-Encoder | ⭐⭐⭐⭐ | ⭐⭐⭐ | 免费 | ❌(英文) |
| BGE Reranker | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | 免费 | ✅ |
完整的RAG + Reranking实现
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CohereRerank
from langchain.vectorstores import Pinecone
class AdvancedRAG:
def __init__(self):
# 向量检索器
self.vectorstore = Pinecone.from_existing_index("my-index")
self.base_retriever = self.vectorstore.as_retriever(search_kwargs={"k": 20})
# 重排序器
self.reranker = CohereRerank(
model="rerank-multilingual-v2.0",
top_n=5
)
# 组合检索器
self.retriever = ContextualCompressionRetriever(
base_compressor=self.reranker,
base_retriever=self.base_retriever
)
def query(self, question):
# 检索 + 重排序
docs = self.retriever.get_relevant_documents(question)
# 生成答案
context = "\n\n".join([doc.page_content for doc in docs])
answer = llm.generate(f"基于以下内容回答:\n{context}\n\n问题:{question}")
return {
"answer": answer,
"sources": [doc.metadata for doc in docs]
}
重排序策略
1. 两阶段重排序
对于超大规模文档库,使用两阶段重排序:
def two_stage_rerank(query, all_docs):
"""
阶段1:快速重排序(粗排)
阶段2:精细重排序(精排)
"""
# 阶段1:轻量级模型,从1000个候选中筛选出100个
stage1_model = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-2-v2') # 小模型,快
stage1_scores = stage1_model.predict([(query, doc) for doc in all_docs])
top_100 = sorted(zip(all_docs, stage1_scores), key=lambda x: x[1], reverse=True)[:100]
# 阶段2:重量级模型,从100个中筛选出5个
stage2_model = CrossEncoder('cross-encoder/ms-marco-TinyBERT-L-6') # 大模型,准
stage2_docs = [doc for doc, _ in top_100]
stage2_scores = stage2_model.predict([(query, doc) for doc in stage2_docs])
final_top_5 = sorted(zip(stage2_docs, stage2_scores), key=lambda x: x[1], reverse=True)[:5]
return [doc for doc, _ in final_top_5]
2. 多样性重排序
避免返回的文档过于相似(信息冗余):
def diversity_rerank(docs, scores, lambda_param=0.5):
"""
MMR(Maximal Marginal Relevance)算法
平衡相关性和多样性
"""
selected = []
remaining = list(zip(docs, scores))
while remaining and len(selected) < 5:
if not selected:
# 第一个文档:选择最相关的
selected.append(remaining.pop(0))
else:
# 后续文档:平衡相关性和与已选文档的差异性
mmr_scores = []
for doc, relevance in remaining:
# 计算与已选文档的最大相似度
max_sim = max(
compute_similarity(doc, sel_doc)
for sel_doc, _ in selected
)
# MMR得分 = 相关性 - λ * 相似度
mmr = lambda_param * relevance - (1 - lambda_param) * max_sim
mmr_scores.append(mmr)
# 选择MMR得分最高的
best_idx = mmr_scores.index(max(mmr_scores))
selected.append(remaining.pop(best_idx))
return [doc for doc, _ in selected]
3. 领域自适应
在特定领域数据上微调重排序模型:
from sentence_transformers import CrossEncoder, InputExample
from torch.utils.data import DataLoader
# 准备训练数据
train_samples = [
InputExample(texts=['查询1', '相关文档'], label=1.0),
InputExample(texts=['查询1', '不相关文档'], label=0.0),
# ...
]
# 加载预训练模型
model = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
# 微调
train_dataloader = DataLoader(train_samples, shuffle=True, batch_size=16)
model.fit(
train_dataloader=train_dataloader,
epochs=3,
warmup_steps=100
)
# 保存微调后的模型
model.save('./my-domain-reranker')
性能优化
1. 批处理
def batch_rerank(query, docs, batch_size=32):
"""批量处理,提升吞吐量"""
all_scores = []
for i in range(0, len(docs), batch_size):
batch = docs[i:i+batch_size]
scores = reranker.predict([(query, doc) for doc in batch])
all_scores.extend(scores)
return all_scores
2. 缓存
from functools import lru_cache
@lru_cache(maxsize=1000)
def cached_rerank(query, doc_tuple):
"""缓存重排序结果"""
return reranker.predict([(query, doc) for doc in doc_tuple])
3. 异步处理
import asyncio
async def async_rerank(query, docs):
"""异步重排序,减少等待时间"""
loop = asyncio.get_event_loop()
scores = await loop.run_in_executor(
None,
reranker.predict,
[(query, doc) for doc in docs]
)
return scores
效果评估
测试数据集:1000个查询,每个查询检索top-20文档
| 配置 | 准确率@5 | 延迟 |
|---|---|---|
| 仅向量检索 | 68% | 50ms |
| 向量+混合搜索 | 82% | 70ms |
| 向量+混合搜索+重排序 | 91% | 150ms |
结论:重排序虽增加延迟,但准确率提升显著(+9个百分点)。
何时使用重排序
适合场景:
- 对准确率要求极高(法律、医疗、金融)
- 检索候选文档多(>20个)
- 可接受适度的延迟增加
不适合场景:
- 实时性要求极高(<100ms)
- 计算资源有限
- 初次检索已足够准确
实战案例
某法律文书检索系统:
Before:仅向量检索
- 查询:"劳动合同纠纷判决"
- 返回文档包含离婚、刑事等无关案例
- 律师需手动筛选,效率低
After:向量检索 + Cohere Rerank
- 重排序过滤掉无关案例
- top-5全部为劳动合同相关
- 律师工作效率提升3倍
总结
重排序是RAG系统提升准确率的"最后一道防线":
- 检索阶段:快速召回候选(向量/混合检索)
- 重排序阶段:精细评估,重新排序
- 生成阶段:高质量输入→高质量输出
通过合理使用重排序,RAG准确率可提升10-20个百分点。对于高价值应用,这一投入完全值得。
相关资源
RAG优化:
检索技术:
应用实践:
SearchCans提供高性价比的Bing搜索API和Reader API服务,专为AI Agent和开发者打造。立即体验 →