语义搜索对SKU、错误码等精确匹配失效,需混合BM25/keyword搜索,不能靠更大embedding模型解决。
大多数 RAG 项目从向量搜索起步:把文档 embed、存进去、按相似度检索。演示环境里跑得通。然后用户搜索一个精确的东西——商品编码、错误号、具体名字——系统就漏掉了,因为向量搜索按语义排序,而不是按字面 token。这篇文章要聊的就是这种失败,以及截至 2026 年 6 月已有的检索架构修复方案。
纯向量搜索隐藏的失败
Embedding 把文本变成空间里的一个点,相近的点意味着语义相似。这对于"找关于退换货政策的文档"恰好是你想要的,而对于"找 SKU-4471"恰好不是。模型会把"SKU-4471"编码成与那些看起来、读起来相近的其他编码在空间上接近的东西,所以用户输入的那个精确编码可能刚好落在结果之外。错误码、零件号、工单 ID、缩写词、稀有人名专有名词同理。
这种失败在测试阶段是隐藏的。语义查询能工作,演示看起来完成了,然后用户输入他们期望匹配的精确内容,系统没找到,他们就对整个系统失去了信任。在对话中问题会加剧,用户按字面措辞并期望字面匹配。换成更大或更好的 embedding 模型解决不了这个问题,因为问题不在 embedding 质量,而在于精确匹配和语义相似是完全不同的任务。
检索阶梯,按序攀登
只攀到评估告诉你需要的高度。每上一级代价都更高。
混合搜索。BM25(关键词)和向量查询同时跑,用 rank fusion 合并结果。找回精确 token 同时不丢失语义召回。对大多数项目而言这是最大的单次提升。
重排序。Cross-encoder 拿查询对顶部候选文档重新打分并重新排序。通常是继混合搜索之后质量最大的跃升。
查询和元数据工作。查询扩展、元数据过滤、更优的分块策略。当正确的 chunk 存在但没有被检索出来时有用。
结构感知检索。实体感知检索或图层(GraphRAG),适用于跨多个段落分布答案的问题,没有单个 chunk 能充当总结。
混合搜索跑一个词法查询(BM25 或全文)和一个向量查询,然后合并两个结果集,通常用 Reciprocal Rank Fusion(倒数排名融合)。词法搜索精准命中精确 term 和稀有 token;向量搜索精准命中同义改写和语义匹配。合并后找回任一单独方法会漏掉的内容。这是大多数项目应该优先攀上的那一级。
Reciprocal Rank Fusion 值得完整看一下,因为它比听起来简单。它忽略每个系统给出的原始分数(反正也无法比较),只使用排名位置:
def reciprocal_rank_fusion(result_lists, k=60):
"""Merge ranked ID lists. k dampens the weight of top positions."""
scores = {}
for results in result_lists:
for rank, doc_id in enumerate(results):
scores[doc_id] = scores.get(doc_id, 0) + 1 / (k + rank + 1)
return sorted(scores, key=scores.get, reverse=True)
# bm25_hits and vector_hits are ranked lists of document IDs
merged = reciprocal_rank_fusion([bm25_hits, vector_hits])
这就是全部算法。某个文档在任一系统中排名靠前就会上升;在两个系统中都排名靠前就上升更多。因为它从不拿 BM25 分数和 cosine similarity 做比较,所以完全避免了分数归一化问题。
混合搜索有两种实现方式。原生支持混合的向量数据库在一个查询里处理两种信号:Weaviate 和 Qdrant 使用基于 BM25 的 sparse 向量加 dense 向量,Pinecone 则将 dense 向量与自己的 sparse 模型配对。或者用一个从一开始围绕混合搜索构建的搜索引擎:Typesense 和 Meilisearch 结合全文和向量搜索并支持容错搜索,Azure AI Search 在一个托管服务里提供全文、向量和混合搜索。正确选择取决于你是否已经在跑向量数据库,还是想要搜索引擎的操作体验(分面搜索、容错、地理搜索)配合检索。
混合搜索扩大了召回率的网,但合并后的排名是近似的。重排序器修正排序:cross-encoder 把查询和每个候选一起读取并给真实相关性打分,然后重新排序。这通常是混合搜索之后最高杠杆的质量变化,是在召回率已经不错但精确率拖后腿时要用的杠杆。
重排序器以托管 API 的形式与 embedding 并行提供。Jina AI(2025 年 10 月起属于 Elastic)在其 embedding 和 reader 旁边提供多语言重排序器;其重排序权重在 Hugging Face 上采用 CC-BY-NC 许可,商业使用需通过 API。Voyage AI(现属于 MongoDB)提供带有免费层级的重排序模型,专注于检索质量。Cohere 提供 Rerank 端点。需要注意的成本点:只对顶部候选(通常 50 到 100 个)做重排序,而不是整个结果集,因为 cross-encoder 每个文档的代价比向量查询贵得多。
如果正确的 chunk 存在但没有被检索出来,杠杆就移到了上游。查询扩展生成入向查询的变体(同义词、展开的缩写、替代表达),并行搜索后再合并,适用于用户输入简短或模糊查询的情况。元数据过滤在检索前缩小搜索空间(按日期、来源、语言或 country 之类的字段),既提升精确率也降低延迟。而分块策略决定了一个相关段落是否能作为一个单元被检索。这些在混合搜索和重排序到位之后才值得调优,而不是之前。
有些问题根本无法通过给 chunk 排序来回答。"谁是谁"或"X 和 Y 是什么关系"——答案横跨多个段落,没有任何单个 chunk 充当总结——这是检索结构问题。实体感知方法为每个实体建立档案或摘要;GraphRAG 风格的方法构建实体和关系的图,在 chunk 检索之前或同时遍历它。LlamaIndex 和 Haystack 这类 RAG 框架为这些模式提供了构建块。这一级是真实的工作量,只有在评估表明排序不再是瓶颈时才值得攀。
上面的顺序是默认值,不是处方。知道实际上需要哪一级的办法是先建一个小评估集:几十条真实查询,加上应该回答它们的文档,然后把失败分到桶里——检索到了错误文档、检索到了正确文档但答案没命中它、查询太模糊。每个桶指向不同的级。不做测量就加功能只是在把失败搬来搬去。关于生成侧(捕获引用了正确 chunk 但得出错误结论的答案),请参阅配套文章:在 RAGAS 之外评估 RAG 质量。
为什么我的 RAG 系统漏掉 SKU 或错误码这样的精确匹配?
向量搜索按语义相似度排序,而不是按字面 token 匹配。"SKU-4471" 或"error E-1042" 的 embedding 会落在意义上看相似的其他编码附近,所以用户输入的精确编码可能掉出顶部结果之外。Embedding 长于语义而短于字面标识符。修复方法是混合搜索:同时跑一个关键词(BM25)查询和向量查询,然后合并结果,这样精确 token 被精确匹配,同时保留语义召回。
RAG 中的混合搜索是什么?
混合搜索结合词法搜索(BM25 或全文)和向量搜索,通常用 Reciprocal Rank Fusion 合并两个结果集。词法搜索捕获精确 term、标识符和稀有词;向量搜索捕获同义改写和语义匹配。两者一起找回任一方法单独会漏掉的案例。混合搜索是生产级 RAG 系统在遇到真实查询后的常见默认配置。
用了混合搜索还需要重排序器吗?
通常需要。混合搜索扩大了召回的候选集,但合并列表的顶部不一定按真实相关性排序。重排序器(cross-encoder 模型)拿查询对顶部候选重新打分并重新排序,这通常带来混合搜索之后最大的精确率提升。它增加延迟和成本,所以只重排序小候选集(例如前 50 到 100 个),而不是整个索引。
什么时候该超越混合搜索和重排序?
当评估表明失败不再关于排序时。如果无论用什么方法都检索不到正确的 chunk,问题在上游:分块、元数据过滤,或缺失的结构。关于实体、关系和身份的问题通常需要实体感知检索或图层级(GraphRAG),而不是更好的 embedding。先建评估集,这样你改的是实际失败的那一层。
小型 RAG 项目值得上混合搜索吗?
取决于文档。如果语料包含用户会按字面搜索的标识符、产品名、编码或稀有技术术语,混合搜索很快就有回报。如果内容是散文,意义占主导、精确 token 很少重要,纯向量搜索可能就够了。实际检验方法是拿你实际期望的查询建一个小评估集,然后在上面比较纯向量和混合搜索的效果。
原文首发于 Infrabase.ai,作者在那里维护着一个经过人工验证的 AI 基础设施工具目录。