Calendar 第 3 天

混合搜索和通用查询API

了解如何结合密集和稀疏向量搜索方法,构建强大的混合搜索管道,以满足不同的用户需求。


你将学到什么

  • 了解何时使用密集向量与稀疏向量
  • 使用Qdrant的通用查询API构建混合搜索管道
  • 应用倒数排名融合(RRF)来合并结果
  • 设计多阶段检索和重排策略

挑战:不同的用户,不同的搜索需求

现实情况是,您的用户分布在不同光谱上:从精确的关键词搜索者到模糊的自然语言描述者,强制采用单一搜索方法意味着让部分受众感到失望。混合搜索让您能够满足所有用户的需求,而不是为了适应不同用户类型而牺牲搜索质量。

密集向量:语义理解

密集向量由经过训练的神经网络编码器生成,这些编码器将含义与输入关联起来。无论您是搜索elevator还是lift,您的意图都是到达另一个楼层,即使单词不同。甚至扶梯或楼梯,也不应该离得太远,因为它们服务于相同的目的!

有些模型还可以通过以下语言找到您需要的内容:

  • 西班牙语:ascensor
  • 波兰语:winda
  • 德语:der Aufzug

或任何其他语言,因为这些模型可能被训练成同时支持多种语言。

稀疏向量:精确匹配

另一方面,稀疏向量更适用于精确匹配至关重要的情况。它们通常被描述为基于关键词或词汇的搜索。

想象一下,您知道要查找项目的标识符。例如,您拥有一款特定的智能手机型号,因此在购买配件时,您不希望看到所有不同设备的充电器,而只希望看到您能使用的那些。在这种情况下,词汇搜索将比密集向量搜索更适合!

不幸的是,现实从来没有那么简单。您的一些用户可能是领域专家,使用与您相同的术语,因此仅实施基于关键词的搜索对他们来说完全没问题。但是,您不想忽略那些无法用适当术语表达意图的受众。您的一些用户可能倾向于对他们想查找的内容进行模糊的、类似自然语言的描述,在这种情况下,密集向量能更好地捕捉到这一点。

想象一个法律搜索系统

  • 律师通常会知道他们想查找的法律条文的段落、节或甚至一个点
  • 其他人宁愿描述他们遇到的一个非常具体的案例
  • 混合案例:也许有人知道特定的法案,但不知道段落?

您无需只选择一种方法或构建单独的管道来服务两种类型的用户!混合搜索可能就是您正在寻找的解决方案!

混合搜索没有单一的定义,但总的来说,当一个特定的管道结合了至少两种不同的搜索方法时,我们就可以称之为混合搜索。

它可能是密集向量和稀疏向量的链式组合,其中您:

  1. 使用密集向量搜索预取一些候选结果,然后使用稀疏向量重排它们
  2. 或者反过来:使用稀疏向量进行检索,然后使用密集向量进行重排

因此,我们有检索器重排器

复杂的多阶段管道

构建混合搜索的选项是无限的。在某些情况下,人们会创建真正复杂的多阶段搜索管道,其中包括:

  • 用于初始检索的更快方法
  • 更慢但更精确的模型对这些预检索的候选结果进行重排

Qdrant的通用查询API允许您在一次调用中构建此类复杂的管道。

通用查询API

通用查询API在Qdrant 1.10中引入。它将所有其他API整合到一个方法中,最重要的是,它使构建混合搜索管道变得更加容易。

模式1:密集检索 → 稀疏重排

假设我们已经创建了一个包含两个命名向量的集合,最简单的密集检索和稀疏重排链可能看起来像这样:

from qdrant_client import QdrantClient, models

client = QdrantClient(...)
client.query_points(
    collection_name="my_collection",
    prefetch=[
        models.Prefetch(
            query=models.Document(
                text=query,
                model="sentence-transformers/all-MiniLM-L6-v2",
            ),
            using="dense",
            limit=20,
        ),
    ],
    query=models.Document(
        text=query,
        model="Qdrant/bm25",
    ),
    using="sparse",
    limit=20,
)

在这个例子中,我们:

  1. 通过密集向量搜索预取了20个结果
  2. 然后使用稀疏向量根据稀疏相关性分数定义的顺序重新排列这些结果

这意味着稀疏方法并非用于集合中的所有点,而仅用于这20个候选点中的一小部分。

模式2:稀疏检索 → 密集重排

如果我们交换两种向量的使用方式,那么我们将得到一个不同的管道,即稀疏检索和密集重排。

from qdrant_client import QdrantClient, models

client = QdrantClient(...)
client.query_points(
    collection_name="my_collection",
    prefetch=[
        models.Prefetch(
            query=models.Document(
                text=query,
                model="Qdrant/bm25",
            ),
            using="sparse",
            limit=20,
        ),
    ],
    query=models.Document(
        text=query,
        model="sentence-transformers/all-MiniLM-L6-v2",
    ),
    using="dense",
    limit=20,
)

顺序使用多种搜索方法并非总是最佳方法。考虑我们之前的例子,我们或者:

  1. 使用密集向量检索文档,然后使用稀疏方法重排,或者
  2. 使用稀疏方法检索,然后使用密集方法重排

这两种方法会产生完全不同的结果。初始检索方法实际上比重排步骤更关键,因为重排只能处理检索器已经选择的文档。

例如,如果有一个完美的文档匹配只能通过稀疏检索识别,但您首先使用了密集检索,那么您将永远找不到该文档,因为它不会被包含在传递给重排器的候选文档中。

有办法结合这两种信号!让我们讨论融合!

融合:结合搜索信号

每种检索方法都会为它选择的每个文档生成一个数值分数。这些分数在不同方法之间不能直接比较,因此没有明确的方法来混合候选结果。

评分问题

  • 您的密集检索可能会产生从未超过1的余弦相似度
  • BM25分数实际上是无界的
  • 您如何判断0.9的余弦相似度是否优于10.7的BM25分数?

融合算法旨在解决这个问题。倒数排名融合(Reciprocal Rank Fusion)是最常用的技术之一,所以让我们来看看它。

倒数排名融合 (RRF)

每种方法返回的原始分数主要用于按相关性对文档进行排名,因此它们定义了每个结果的优劣顺序。RRF不考虑分数,只考虑由分数定义的排名,并根据每种单独方法返回的排名计算自己的分数。

RRF 公式

$$\text{RRF_score}(d) = \sum_{i=1}^{n} \frac{1}{k + \text{rank}_i(d)}$$

其中

  • d 是文档
  • k 是一个常数(通常为60)
  • n 是检索方法的数量
  • rank_i(d) 是文档 d 在第 i 种检索方法中的排名

参数 k 的值可以帮助您增加或减少排名较低文档的影响。参数值越小,排名靠前结果的影响越大。

RRF 示例:逐步演示

让我们假设我们从这两种方法中获得了这些结果

密集搜索结果

  1. D1 (分数: 0.95)
  2. D2 (分数: 0.89)
  3. D3 (分数: 0.85)
  4. D4 (分数: 0.82)

稀疏搜索结果

  1. D5 (分数: 15.2)
  2. D3 (分数: 12.8)
  3. D1 (分数: 10.1)
  4. D2 (分数: 8.5)

现在我们来计算RRF分数(使用 k=60)

文档密集排名密集RRF稀疏排名稀疏RRF总RRF最终排名
D111/(60+1) = 0.016431/(60+3) = 0.01590.03232
D221/(60+2) = 0.016141/(60+4) = 0.01560.03173
D331/(60+3) = 0.015921/(60+2) = 0.01610.03201
D441/(60+4) = 0.0156-00.01565
D5-011/(60+1) = 0.01640.01644

注意:D3成为了赢家,尽管它不是任何单个方法的最佳匹配!然而,两种方法都将其排名很高,因此它可能同时捕捉了词汇和语义含义。

在Qdrant中实现RRF

如果您想在Qdrant中实现这个过程,那么您不必自己计算RRF分数。它已经内置了!

这是一个API调用,您需要执行此调用才能从密集和稀疏搜索中检索20个文档,然后根据倒数排名融合获取前10个结果

from qdrant_client import QdrantClient, models

client = QdrantClient(...)
client.query_points(
    collection_name="my_collection",
    prefetch=[
        models.Prefetch(
            query=models.Document(
                text=query,
                model="sentence-transformers/all-MiniLM-L6-v2",
            ),
            using="dense",
            limit=20,
        ),
        models.Prefetch(
            query=models.Document(
                text=query,
                model="Qdrant/bm25",
            ),
            using="sparse",
            limit=20,
        ),
    ],
    query=models.FusionQuery(fusion=models.Fusion.RRF),
    limit=10,
)

关键点

  • 我们从密集和稀疏方法中预取了20个结果
  • FusionQuery 应用 RRF 来合并它们
  • 我们根据融合分数获得最终的前10个结果

显然,您可以嵌套多个预取操作,并在任何级别应用融合。

超越融合:高级重排

融合并非构建混合搜索的唯一方式。有时,您拥有一组较弱的检索器和一个强大的重排器,它可能是:

  • 一个不同的神经网络模型,例如交叉编码器
  • 一个更复杂的密集检索器,实际上不适用于初始检索
  • 多向量表示,例如ColBERT
  • 特定于您项目的自定义业务规则

混合搜索可能非常复杂。重排是用于我们应用于某些中间搜索操作结果的所有方法的通用术语,而融合是解决这个问题的一种方式。

关键要点

你学到了什么

  • 密集向量擅长语义理解和多语言搜索
  • 稀疏向量非常适合精确匹配和基于关键词的检索
  • 顺序检索 + 重排存在局限性(第一阶段决定了可用内容)
  • RRF等融合算法能有效结合多种方法的结果
  • Qdrant的通用查询API让构建复杂的混合管道变得简单

何时使用混合搜索

  • 您的用户有不同的搜索行为(专家用户 vs. 普通用户)
  • 您需要语义理解和精确匹配两者
  • 单一方法搜索难以处理某些查询类型
  • 您希望提高整体搜索质量和用户满意度

后续步骤和资源

在下一课中,我们将使用Qdrant的通用查询API和真实数据构建一个混合搜索管道。既然您知道了如何操作,是时候亲自动手进行实际实现了!

其他资源

准备好亲自动手实践了吗?在下一个演示中,您将实现一个真实的混合搜索,测试不同的策略,并衡量其对搜索质量的影响!