前言 #
基于向量数据库的 RAG 是最基础的 RAG 形式,在实际生产中被广泛应用。正如其名,向量数据库主要存储的元素是一个个的向量,通过向量运算,我们能够实现更加精准地相关性搜索,这个功能是一些应用场景地基石。
向量数据库的供应商有很多:Weaviate、Qdrant、Milvus、Chroma……等等。但大体上都差不多,详细的主流向量数据库对比分析报告见:向量数据库比较报告:12款主流向量数据库详细对比
这里就只关注 Qdrant 这一个供应商。所有内容均参考 Qdrant官方文档 (2025-03)
并且这篇文章较少涉及具体的代码语法,重点关注功能和原理,帮助开发者更加快速地设计产品工作流程,而不是被繁琐的语法限制了想象力和工作效率。
基础数据类型 #
Qdrant 为了更好地处理向量数据定义了一些抽象数据类型,理解这些类型是灵活运用的基础。
数据点·Point #
数据点是向量数据库的核心数据类型,所有的操作都围绕着数据点进行。
一个非常纯净的数据点只包含其向量,但是一般情况下数据点都会被附加上一些标签用来提供向量数据之外的更多的信息。这些标签被称为负载·Payload 👇
因此:数据点=向量+负载标签
// 这是一个简单的数据点
{
"id": 129,
"vector": [0.1, 0.2, 0.3, 0.4],
"payload": {"color": "red"},
}
Qdrant 的数据点能够配置多种不同类型的向量:密集向量,稀疏向量、多重向量和命名向量
向量类型 | 描述 |
---|---|
密集向量 | 就是一般意义上的向量;大部分嵌入模型生成的向量都是这个类型 |
稀疏向量 | 长度非固定,只有少量非零元素;一般用于精确的词元匹配和协同过滤 |
多重向量 | 多个密集向量构成的矩阵,不同数据点之间密集向量的维度必须相同,但向量的个数不比相等;用于存储同一个目标的不同向量描述 |
命名向量 | 上述三种类型的向量的混合,允许不同类型的向量存储在同一个数据点内,这些不同类型的向量构成的集合被抽象为所谓的命名向量 |
然后就是数据库基本的增删改查操作,不必赘述。
负载·Payload #
被附加在向量上的额外元数据信息,使用 JSON 语法描述并存储,下面是一个例子
{
"name": "jacket",
"colors": ["red", "blue"],
"count": 10,
"price": 11.99,
"locations": [
{
"lon": 52.5200,
"lat": 13.4050
}
],
"reviews": [
{
"user": "alice",
"score": 4
},
{
"user": "bob",
"score": 5
}
]
}
既然都存进数据库里面了,那么这些数据肯定是有作用的:向量数据库的搜索机制主要是语义近似匹配,通过这些标签你就能够在这个基础上添加更多的逻辑过滤条件
关于过滤的更多信息见:过滤·Filtering
集合·Collection #
Collection 简而言之就是一组数据点的集合。在这个数据类型的层面上你能够定义:
- 数据点之间的相似度算法
- 向量的维度
- 优化器配置
- HNSW 算法配置:需要调整的参数是
ef
,用来确定算法访问邻接结点的个数,数值越大查询越准确,速度越慢 - WAL 配置
- 量化配置
常见操作 #
这是向量数据库使用过程中的最基础也是最重要的一些操作。
搜索·Search #
搜索在向量数据库的语境下主要指的是相似度搜索,其重要理论前提就是:在现实世界中相似度更高的对象在向量空间中更接近
而其中"更接近"一词则暗含了一种衡量相似度的算法,Qdrant 中支持了一些最流行的相似度算法:
- 点积相似度
- 余弦相似度
- 欧氏距离
- 曼哈顿距离
为了更良好的用户体验,Qdrant 提供了一套完整的程序接口,让用户能够方便地调用:Search API - Qdrant
概括下来,你能够调用功能大概有这些:
- 基本操作:输入一个向量,在数据库中进行相似度匹配;如果你的数据点存储了"命名向量",那么你需要指定用哪个特定的向量去进行相似度匹配
- 调控搜索算法:可以控制是否使用精确搜索,如果启用,那么相似度匹配会在每一个数据点上执行,耗时更长(还有更多的参数可调,但是一般不用)
- 结果过滤:在实际执行搜索之前,按照负载标签进行过滤,减小搜索范围;实际执行搜索之后,使用相似度评分阈值来过滤
- 结果数量:参数
limit
控制搜索出来的结果的数量,也就是相似度评分最高的limit
个 - 批量搜索:一次性输入一组向量进行搜索
- 搜索分组:能够对搜索结果按照某些标签进行分组,参数
group_size
可以设置每组的结果个数 - 搜索规划:依据可选索引、过滤条件的复杂性和总共的数据点的数量,以启发式的方法选择一个合适的搜索方式(提高性能🤔)
group_size
和 limit
被同时设置,那么此时 limit
参数表示分组的数量
另外,在 Qdrant 中,稀疏向量和密集向量的搜索有一些关键的不同,对比结果如下:
对比项 | 稀疏向量 | 密集向量 |
---|---|---|
相似度算法 | 默认使用点积相似度,不用指明 | 你可以指定受支持的算法 |
搜索方式 | 只能精确搜索 | 可以使用 HNSW 算法进行模糊搜索 |
搜索结果 | 只返回含有共同非零项的向量 | 返回你设置的 limit 个向量 |
探索·Explore #
探索操作直观来说就是更加灵活的搜索操作:能够依靠相似度进行搜索的同时,也能够依靠区分度来查询
推荐·Recommendation #
“推荐"允许你同时提供一个正面的向量和反面的向量来进行搜索,下面是官方给出的例子:
import { QdrantClient } from "@qdrant/js-client-rest";
const client = new QdrantClient({ host: "localhost", port: 6333 });
client.query("{collection_name}", {
query: {
recommend: {
positive: [100, 231],
negative: [718, [0.2, 0.3, 0.4, 0.5]],
strategy: "average_vector"
}
},
filter: {
must: [
{
key: "city",
match: {
value: "London",
},
},
],
},
limit: 3
});
其中 strategy
参数用于调控搜索算法,下面是具体的算法说明:
-
平均算法:对正例、反例分别进行平均,得到的两个向量再进行加权平均,产生最终用于搜索的向量
-
最佳评分算法:每一个待搜索的数据点都会分别和正例负例中的所有数据点进行匹配得出评分,然后分别选出最高评分,然后按照如下计算方法得出这个待搜索的数据点的最终评分
let score = if best_positive_score > best_negative_score {
best_positive_score
} else {
-(best_negative_score * best_negative_score)
};
- 只考虑负例算法:使用最佳评分算法👆同时不提供正例,你就会得到一个反向评分算法,能够找到最不相关的数据点
发现·Discovery #
“发现"操作的核心思路就是样本空间的分割。你需要提供一系列的正负向量对,每一个向量对都会将样本空间划分为正区域和负区域,最终将会搜索得出处于正区域更多或者负区域更少的数据点。
和推荐·Recommendation 类似,但是这里你需要将正向量和负向量组合成一对来输入。
通过发现操作,Qdrant 能够处理一下两种新的搜索需求:
-
发现型搜索:给定一个搜索目标向量,同时提供一系列正负向量对作为上下文约束条件;
-
区域划分搜索:是发现型搜索👆的特例,在不提供目标向量的情况下,数据库会直接使用正负向量对进行区域划分,并最后返回处在正区域中最多的数据点
距离矩阵·Distance-Matrix #
这个操作很类似于批量搜索。批量搜索的流程是:用户输入一批向量数据点,然后在数据集合中逐个搜索这些向量数据点的相似向量。而距离矩阵的流程是:系统随机选取一些向量数据点构成一个样本子集,然后对每一个向量数据点都在这个样本子集里面去搜索相似向量。
例如,系统随机选取 sample=100
个数据点,构成一个含有 100 个数据点的样本子集,然后设置每个数据点搜索出最相似的 limit=10
个数据点,那么最后返回的距离矩阵将会是一个
\(100\times 10\) 的矩阵,每一行代表着其中一个数据点的最相似的 10 个数据点。
这个操作一般用于数据可视化或者数据降维。
过滤·Filtering #
官方英文指南:A Complete Guide to Filtering in Vector Search;这份指南中的前面部分直观地解释了 Qdrant 内部是如何执行过滤操作的,了解之后更有助于设计一个高效的体系。
指南中只是简要列举了一些功能,更完整详细的功能文档见:Filtering
过滤条件 #
这里的过滤条件专指单个过滤条件,是过滤操作的基本单元。下面是具体的类型列表:
类型 | 功能 |
---|---|
Match | 过滤条件是一个具体值,属性值必须和过滤条件完全相等 |
Match Any | 过滤条件是一组选项,属性值在过滤条件中存在即可 |
Match Except | 过滤条件是一组选项,属性值不在过滤条件中存在即可 |
Range | 过滤条件是一个范围,属性值需要在范围之中 |
Values count | 属性值是一个数组,依据数组中元素数量来过滤 |
Is Empty | 以属性值是否存在为依据来过滤 |
以上就是最基本的过滤类型,针对不同的负载类型,其语法上会有一定差异:
- JSON负载:一个数据点的负载可以是一个 JSON 对象,这个 JSON 对象中的任意字段都可以参与过滤;具体语法见官方网站:Nested key
- 日期范围:类似于普通的数值范围,日期范围也支持过滤
- 地理过滤:地理位置也支持过滤
- 命名向量:命名向量中含有多个不同维度的向量,可以根据特定的向量是否存在来过滤,例如可以过滤出"含有图片嵌入向量"的命名向量
这些基本的过滤条件可以通过下面的方法进行嵌套,形成复杂的过滤条件👇
逻辑关键字 #
类似于 SQL 中的关键字 AND
OR
NOT
,在 Qdrant 中使用 must
should
must_not
来表达类似的逻辑。通过逻辑关键字,能够构建一个复杂的过滤器。
must
:当所有列出的过滤条件都被满足的时候才会返回真should
:当所有列出的过滤条件中有一个被满足就会返回真must_not
:当所有列出的条件都不被满足的时候返回真