混合搜索 21 分钟阅读

混合搜索:结合关键词与向量检索,提升RAG系统准确率的秘诀

单纯的向量检索或关键词搜索各有局限。混合搜索结合两者优势,大幅提升RAG系统的检索准确率。本文详解混合搜索的原理、实现方法和最佳实践。

8,014 字

在构建RAG系统时,检索质量直接决定了最终答案的准确性。纯向量检索擅长语义理解但可能忽略精确匹配,纯关键词搜索快速准确但无法理解语义。混合搜索(Hybrid Search)结合两者优势,是提升RAG性能的关键技术。

为什么需要混合搜索

向量检索的局限

优势:理解语义,即使查询词与文档用词不同,也能匹配。

查询:"如何降低AI成本?"
匹配文档:"LLM费用优化策略"(语义相关)

局限

  1. 精确词匹配失效:查询"GPT-4 API定价",可能匹配到GPT-3.5的文档
  2. 专业术语问题:查询"SERP API",可能匹配到通用的"搜索API"
  3. 数字和日期不敏感:查询"2024年数据",可能返回2023年的文档

关键词搜索的局限

优势:精确匹配,快速。

局限

  1. 同义词问题:查询"便宜",无法匹配"性价比高"、"实惠"
  2. 语序敏感:"AI如何改变医疗" vs "医疗如何被AI改变"
  3. 缺乏语义理解:查询"苹果",无法区分是水果还是公司

混合搜索的优势

结合两者:

  • 向量检索:覆盖语义相似的文档
  • 关键词搜索:确保精确匹配的文档排前面
  • 融合排序:综合两种得分

效果提升:实践中,混合搜索比单一方法准确率提升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篇)

总结

混合搜索不是简单地"两种检索一起用",而是:

  1. 理解各自优势:向量语义,关键词精确
  2. 智能融合:RRF、加权、动态调整
  3. 持续优化:查询扩展、元数据过滤、重排序

在现代RAG系统中,混合搜索已成为标准配置。掌握它,是构建高质量AI应用的必备技能。


相关资源

RAG技术

检索技术

实战应用

SearchCans提供高性价比的Bing搜索API和Reader API服务,专为AI Agent和开发者打造。立即体验 →

标签:

混合搜索 RAG优化 检索技术 向量检索

准备好用 SearchCans 构建你的 AI 应用了吗?

立即体验我们的 SERP API 和 Reader API。每千次调用仅需 ¥0.56 起,无需信用卡即可免费试用。