高级检索策略
问题
如何提升 RAG 的检索质量?有哪些高级检索策略?
答案
Naive RAG 最大的问题是检索质量不够。高级检索策略从 Query 处理、检索方式、结果优化三个维度提升。
一、Query 处理
1. Query 改写(Query Rewriting)
用户的原始问题往往不适合直接检索:
# 原始 query: "它怎么用"(太模糊)
# 改写后: "React useEffect Hook 的使用方法和最佳实践"
from openai import OpenAI
client = OpenAI()
def rewrite_query(original_query: str, chat_history: list) -> str:
"""基于对话历史改写 query"""
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "将用户问题改写为适合搜索的独立查询,保留对话上下文。只输出改写后的查询。"},
*chat_history,
{"role": "user", "content": original_query}
]
)
return response.choices[0].message.content
2. HyDE(Hypothetical Document Embedding)
核心思想:先让 LLM 生成一个假设性答案,用答案的 Embedding 去检索(而非用问题去检索)。
def hyde_retrieval(query: str, vectorstore, llm):
# 1. 生成假设性答案
hypothetical_answer = llm.invoke(
f"请详细回答以下问题(即使不确定也请尽量回答):\n{query}"
)
# 2. 用假设性答案做检索(答案和文档更相似)
docs = vectorstore.similarity_search(hypothetical_answer.content, k=5)
return docs
为什么 HyDE 有效?
用户的问题("什么是 MVCC?")和文档("MVCC 通过版本链实现多版本并发控制...")的文体差异大。假设性答案和文档的文体更接近,Embedding 匹配度更高。
3. 多查询检索(Multi-Query)
将一个问题扩展为多个角度的子查询,分别检索后合并去重:
def multi_query_retrieval(query: str, llm, vectorstore, num_queries: int = 3):
# 1. 生成多个角度的子查询
response = llm.invoke(
f"将以下问题从{num_queries}个不同角度改写,每行一个:\n{query}"
)
sub_queries = response.content.strip().split("\n")
# 2. 分别检索
all_docs = []
for sub_q in sub_queries:
docs = vectorstore.similarity_search(sub_q, k=5)
all_docs.extend(docs)
# 3. 去重(按内容哈希)
unique_docs = list({doc.page_content: doc for doc in all_docs}.values())
return unique_docs
二、混合检索(Hybrid Search)
将语义检索和关键词检索结合,互补优势:
| 检索方式 | 优势 | 劣势 |
|---|---|---|
| 语义检索(Embedding) | 理解同义词、语义 | 专有名词、ID 难匹配 |
| 关键词检索(BM25) | 精确匹配、专有名词 | 不理解语义 |
| 混合检索 | 两者互补 | 需要融合权重 |
from langchain.retrievers import EnsembleRetriever
from langchain_community.retrievers import BM25Retriever
# 关键词检索器
bm25_retriever = BM25Retriever.from_documents(documents)
bm25_retriever.k = 5
# 语义检索器
embedding_retriever = vectorstore.as_retriever(search_kwargs={"k": 5})
# 混合检索(权重各 50%)
hybrid_retriever = EnsembleRetriever(
retrievers=[bm25_retriever, embedding_retriever],
weights=[0.5, 0.5]
)
RRF(Reciprocal Rank Fusion)
最常用的混合排序算法,不依赖具体分数,只依赖排名:
其中 是文档 在检索器 中的排名, 通常取 60。
三、元数据过滤
在向量检索之前或之后加上结构化过滤:
# Pinecone 示例:先过滤再搜索
results = index.query(
vector=query_embedding,
top_k=10,
filter={
"department": {"$eq": "engineering"},
"date": {"$gte": "2024-01-01"}
}
)
四、检索策略对比
| 策略 | 复杂度 | 效果提升 | 适用场景 |
|---|---|---|---|
| Query 改写 | 低 | 中 | 对话场景 |
| HyDE | 中 | 中-高 | 问答差异大 |
| 多查询 | 中 | 中 | 复杂问题 |
| 混合检索 | 中 | 高 | 通用推荐 |
| 元数据过滤 | 低 | 视场景 | 有结构化属性 |
常见面试问题
Q1: 混合检索中 BM25 和语义检索的权重如何确定?
答案:
- 经验值:通用场景通常 0.5:0.5 或 0.4:0.6(偏向语义)
- RRF:不需要设权重,只看排名,鲁棒性更强
- 学习排序:用标注数据训练权重(如果有评测数据集)
Q2: 什么时候不该用 HyDE?
答案:
- 事实性查询("公司2024年营收是多少"):LLM 生成的假设答案可能包含错误数字,误导检索
- 关键词敏感查询("BUG-12345 状态"):需要精确匹配,语义检索反而有害
- HyDE 最适合解释性、概念性问题