在构建RAG系统时,检索质量直接决定了最终答案的准确性。纯向量检索擅长语义理解但可能忽略精确匹配,纯关键词搜索快速准确但无法理解语义。混合搜索(Hybrid Search)结合两者优势,是提升RAG性能的关键技术。
为什么需要混合搜索
向量检索的局限
优势:理解语义,即使查询词与文档用词不同,也能匹配。
查询:"如何降低AI成本?"
匹配文档:"LLM费用优化策略"(语义相关)
局限:
- 精确词匹配失效:查询"GPT-4 API定价",可能匹配到GPT-3.5的文档
- 专业术语问题:查询"SERP API",可能匹配到通用的"搜索API"
- 数字和日期不敏感:查询"2024年数据",可能返回2023年的文档
关键词搜索的局限
优势:精确匹配,快速。
局限:
- 同义词问题:查询"便宜",无法匹配"性价比高"、"实惠"
- 语序敏感:"AI如何改变医疗" vs "医疗如何被AI改变"
- 缺乏语义理解:查询"苹果",无法区分是水果还是公司
混合搜索的优势
结合两者:
- 向量检索:覆盖语义相似的文档
- 关键词搜索:确保精确匹配的文档排前面
- 融合排序:综合两种得分
效果提升:实践中,混合搜索比单一方法准确率提升15-30%。
混合搜索的实现方法
方法1:RRF(Reciprocal Rank Fusion)
最常用的融合算法,简单有效。
原理:
RRF_score = 1 / (k + rank_vector) + 1 / (k + rank_keyword)
其中k是常数(通常为60),rank是排名位置。
实现:
def reciprocal_rank_fusion(vector_results, keyword_results, k=60):
"""
RRF融合两个检索结果
Args:
vector_results: [(doc_id, score), ...]
keyword_results: [(doc_id, score), ...]
k: RRF常数
"""
scores = {}
# 向量检索得分
for rank, (doc_id, _) in enumerate(vector_results):
scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
# 关键词检索得分
for rank, (doc_id, _) in enumerate(keyword_results):
scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
# 排序
final_ranking = sorted(scores.items(), key=lambda x: x[1], reverse=True)
return final_ranking
方法2:加权融合
根据查询类型动态调整权重。
def weighted_fusion(vector_results, keyword_results, alpha=0.5):
"""
加权融合
Args:
alpha: 向量检索权重(0-1),关键词权重为1-alpha
"""
scores = {}
# 归一化得分
max_vector_score = max(score for _, score in vector_results) if vector_results else 1
max_keyword_score = max(score for _, score in keyword_results) if keyword_results else 1
for doc_id, score in vector_results:
scores[doc_id] = alpha * (score / max_vector_score)
for doc_id, score in keyword_results:
scores[doc_id] = scores.get(doc_id, 0) + (1 - alpha) * (score / max_keyword_score)
return sorted(scores.items(), key=lambda x: x[1], reverse=True)
动态权重调整:
def smart_alpha(query: str) -> float:
"""
根据查询特征动态调整权重
"""
# 查询包含专业术语或产品名 → 提高关键词权重
if any(term in query for term in ["API", "GPT-4", "SERP"]):
return 0.3 # 向量30%,关键词70%
# 查询是自然语言问题 → 提高向量权重
if query.endswith("?") or query.startswith("如何"):
return 0.7 # 向量70%,关键词30%
# 默认平衡
return 0.5
方法3:使用Weaviate的原生混合搜索
import weaviate
client = weaviate.Client("http://localhost:8080")
# 混合搜索:alpha=0.5表示向量和关键词各50%
results = client.query.get(
"Article",
["title", "content"]
).with_hybrid(
query="AI成本优化",
alpha=0.5 # 0=纯关键词,1=纯向量,0.5=混合
).with_limit(10).do()
完整的RAG混合搜索实现
from sentence_transformers import SentenceTransformer
from rank_bm25 import BM25Okapi
import numpy as np
class HybridRAG:
def __init__(self, documents):
self.documents = documents
# 向量检索
self.embedding_model = SentenceTransformer('all-MiniLM-L6-v2')
self.doc_embeddings = self.embedding_model.encode([doc["content"] for doc in documents])
# 关键词检索(BM25)
tokenized_docs = [doc["content"].split() for doc in documents]
self.bm25 = BM25Okapi(tokenized_docs)
def vector_search(self, query, top_k=10):
"""向量检索"""
query_embedding = self.embedding_model.encode([query])[0]
# 计算余弦相似度
similarities = np.dot(self.doc_embeddings, query_embedding) / (
np.linalg.norm(self.doc_embeddings, axis=1) * np.linalg.norm(query_embedding)
)
# 排序
top_indices = np.argsort(similarities)[::-1][:top_k]
return [(idx, similarities[idx]) for idx in top_indices]
def keyword_search(self, query, top_k=10):
"""关键词检索(BM25)"""
tokenized_query = query.split()
scores = self.bm25.get_scores(tokenized_query)
top_indices = np.argsort(scores)[::-1][:top_k]
return [(idx, scores[idx]) for idx in top_indices]
def hybrid_search(self, query, top_k=5, method="rrf"):
"""混合搜索"""
# 两种检索
vector_results = self.vector_search(query, top_k=20)
keyword_results = self.keyword_search(query, top_k=20)
# 融合
if method == "rrf":
final_results = reciprocal_rank_fusion(vector_results, keyword_results)
elif method == "weighted":
alpha = smart_alpha(query)
final_results = weighted_fusion(vector_results, keyword_results, alpha)
# 返回top_k结果
top_doc_ids = [doc_id for doc_id, _ in final_results[:top_k]]
return [self.documents[i] for i in top_doc_ids]
# 使用
documents = [
{"id": 1, "content": "GPT-4 API的定价是$0.03/1K tokens..."},
{"id": 2, "content": "如何降低LLM成本:使用更便宜的模型..."},
# ...
]
rag = HybridRAG(documents)
# 查询
query = "GPT-4价格"
results = rag.hybrid_search(query, top_k=3, method="rrf")
for doc in results:
print(doc["content"][:100])
高级技巧
1. 查询扩展
在检索前扩展查询,提升召回率:
def expand_query(original_query):
"""使用LLM扩展查询"""
prompt = f"""
原始查询:{original_query}
生成3个语义相似但措辞不同的查询变体:
1.
2.
3.
"""
expanded = llm.generate(prompt)
return [original_query] + expanded.split("\n")[1:4]
# 使用
queries = expand_query("如何优化AI成本")
# ['如何优化AI成本', '降低人工智能费用的方法', 'AI应用省钱策略', 'LLM成本控制技巧']
# 对每个查询进行检索,合并结果
all_results = []
for q in queries:
all_results.extend(rag.hybrid_search(q, top_k=5))
# 去重并排序
final_results = deduplicate_and_rerank(all_results)
2. 动态Top-K
根据查询复杂度调整检索数量:
def dynamic_top_k(query):
"""简单问题少检索,复杂问题多检索"""
if len(query.split()) <= 5: # 简单查询
return 3
elif len(query.split()) <= 15: # 中等
return 5
else: # 复杂查询
return 10
3. 元数据过滤
结合结构化过滤,缩小搜索范围:
def filtered_hybrid_search(query, filters):
"""
混合搜索 + 元数据过滤
filters = {
"category": "技术",
"date": {"$gte": "2025-01-01"}
}
"""
# 先过滤
filtered_docs = [
doc for doc in documents
if doc.get("category") == filters.get("category")
and doc.get("date") >= filters.get("date", {}).get("$gte", "")
]
# 在过滤后的文档中混合搜索
rag_filtered = HybridRAG(filtered_docs)
return rag_filtered.hybrid_search(query)
性能对比
测试数据集:10,000篇技术文档,100个查询
| 方法 | 准确率@5 | 召回率@5 | 检索延迟 |
|---|---|---|---|
| 纯向量检索 | 68% | 72% | 50ms |
| 纯关键词(BM25) | 62% | 65% | 20ms |
| 混合搜索(RRF) | 82% | 85% | 70ms |
| 混合搜索(加权) | 80% | 83% | 70ms |
结论:混合搜索在略微增加延迟的情况下,显著提升准确率和召回率。
实战案例
某技术文档搜索系统:
Before:纯向量检索
- 查询"GPT-4 API pricing" → 返回"GPT-3.5定价"(语义相似但不准确)
- 查询"Python SDK安装" → 返回"JavaScript SDK"(关键词不匹配)
After:混合搜索
- 关键词确保"GPT-4"精确匹配
- 向量理解"pricing"="定价"="费用"
- 准确率从65%提升至84%
何时使用混合搜索
适合场景:
- 用户查询多样(有精确查询,也有模糊问题)
- 文档包含专业术语、产品名、代码
- 对检索准确率要求高
不适合场景:
- 纯语义检索即可满足需求
- 计算资源极度有限(混合搜索更耗资源)
- 文档库极小(<1000篇)
总结
混合搜索不是简单地"两种检索一起用",而是:
- 理解各自优势:向量语义,关键词精确
- 智能融合:RRF、加权、动态调整
- 持续优化:查询扩展、元数据过滤、重排序
在现代RAG系统中,混合搜索已成为标准配置。掌握它,是构建高质量AI应用的必备技能。
相关资源
RAG技术:
检索技术:
实战应用:
SearchCans提供高性价比的Bing搜索API和Reader API服务,专为AI Agent和开发者打造。立即体验 →