Qdrant
什么是Qdrant?
Qdrant是一个开源的向量相似度搜索引擎,它提供了一个生产就绪的服务,通过便捷的API来存储、搜索和管理带有额外有效载荷的向量。
简单来说,Qdrant可以帮助我们:
存储高维向量数据
快速进行相似度搜索
管理带有元数据的向量
支持多种距离度量方式
为什么需要向量数据库?
在人工智能和机器学习领域,我们经常需要处理高维向量数据,比如:
arduino
文本嵌入(Text Embeddings)
图像特征(Image Features)
音频特征(Audio Features)
用户行为向量(User Behavior Vectors)
传统的关系型数据库在处理这类数据时效率较低,而向量数据库专门为此类场景优化,提供了:
高效的向量存储
快速的相似度搜索
灵活的元数据管理
可扩展的架构
Qdrant的架构
以下是Qdrant的主要组件:
1.集合(Collection)
markdown
定义:一个逻辑容器,用于存储具有相同特性的向量数据
特点:
包含多个带标识的向量点(每个点可附加额外数据)
同一集合中的所有向量必须维度相同
使用单一相似度度量标准进行检索
扩展功能:支持"命名向量",允许单个数据点包含多个不同维度的向量(各向量可使用不同度量标准)
2.距离度量(Distance Metric)
markdown
作用:量化向量之间的相似程度
重要说明:
必须在创建集合时选定(后续不可更改)
选择依据:取决于生成向量的神经网络特性
常见类型:余弦相似度、欧氏距离等
3.数据点(Point)
markdown
基本组成:
核心:高维向量(数据的主要表示)
可选:唯一ID + 附加数据(有效载荷)
地位:Qdrant系统中最基础的操作单元
ID:每个向量的唯一标识符(类似数据库主键)
向量:将复杂数据(如图片、音频等)转换为数学表示的高维数组
有效载荷:采用JSON格式的附加元数据,用于存储与向量相关的业务信息
4.存储方案
markdown
内存存储(In-memory):
特点:所有数据常驻RAM
优势:极致性能(磁盘仅用于持久化备份)
内存映射存储(Memmap):
特点:建立磁盘文件与内存地址的映射关系
优势:平衡性能与内存占用
5.客户端支持
提供多种编程语言接口(如Python、Java等)
功能:通过这些接口实现与Qdrant服务端的交互
Qdrant的核心概念
1. 集合(Collection):
集合是Qdrant中的基本组织单位,类似于传统数据库中的表。每个集合包含:
向量维度
距离度量方式
配置参数
集合的主要特点:
可以定义多个向量字段
支持自定义距离度量
可以设置分片和复制策略
支持动态扩容
2. 点(Points): 点是Qdrant中的基本数据单位,包含:
ID:唯一标识符
向量:高维数据表示
有效载荷:附加的元数据
点的特点:
支持多个向量字段
可以存储丰富的元数据
支持批量操作
支持原子更新
3. 向量(Vectors): 向量是Qdrant中存储的核心数据,特点包括:
高维浮点数数组
支持多种距离度量
支持稀疏向量
支持量化存储
4. 有效载荷(Payload): 有效载荷是与向量关联的元数据,支持:
javascript
JSON格式存储
多种数据类型
索引和过滤
动态更新
5. 搜索(Search): Qdrant提供多种搜索方式:
相似度搜索
混合查询
过滤搜索
范围搜索
6. 探索(Explore): 探索功能包括:
数据可视化
聚类分析
推荐系统
数据统计
7. 混合查询(Hybrid Queries): 混合查询支持:
多阶段查询
多条件组合
权重调整
结果融合
8. 过滤(Filtering): 过滤功能支持:
条件过滤
范围过滤
逻辑运算
嵌套过滤
9. 优化器(Optimizer): 优化器功能包括:
索引优化
存储优化
查询优化
性能调优
10. 存储(Storage): 存储系统特点:
内存存储
磁盘存储
混合存储
数据压缩
11. 索引(Indexing): 索引类型包括:
向量索引
有效载荷索引
稀疏向量索引
过滤索引
12. 快照(Snapshots): 快照功能支持:
数据备份
数据恢复
版本控制
数据迁移
13. 距离度量: Qdrant支持多种距离度量方式:
余弦相似度(Cosine Similarity)
欧氏距离(Euclidean Distance)
点积(Dot Product)
曼哈顿距离(Manhattan Distance)
安装与配置
1. 使用Docker安装
bash
# 拉取Qdrant镜像
docker pull qdrant/qdrant
# 运行Qdrant服务
docker run -p 6333:6333 -p 6334:6334 \
-v $(pwd)/qdrant_storage:/qdrant/storage \
qdrant/qdrant
Qdrant启动后可通过以下信息来连接操作
python
REST API:localhost:6333
Web 用户界面:localhost:6333/dashboard
GRPC API:localhost:6334
浏览器访问https://localhost:6333/dashboard
即可访问Web用户界面,它提供了一个对向量数据库的管理界面。
2. Python客户端安装
Qdrant支持以下SDK来创建客户端
python
Python、JavaScript/Typescript、Rus、Go、.NET、java
这里使用Python操作,执行以下命令安装Qdrant的Python版SDK
bash
pip install qdrant-client
3.文本数据向量化
这里借助OpenAI官方工具库,实现文本数据调用嵌入模型进行向量化操作,并提供2个实现获取文本向量数据的函数方法
python
import openai
# 初始化客户端
openai.api_key = "sk-CwH3lQk0Cxxxxxx91e213d0bC5B13b"
openai.base_url = "http://xxx.com/v1/"
def get_text_embedding(text, model="m3e-large"):
"""
获取文本的嵌入向量
:param text: 要嵌入的文本
:param model: 使用的嵌入模型名称
:return: 嵌入向量(numpy数组)
"""
embedding_response = openai.embeddings.create(input=text, model=model)
return embedding_response.data[0].embedding
def get_texts_embedding(texts, model="m3e-large"):
"""
获取文本的嵌入向量
:param text: 要嵌入的文本(可以是字符串或字符串列表)
:param model: 使用的嵌入模型名称
:return: 嵌入向量列表(每个元素对应一个输入文本的向量)
"""
embedding_response = openai.embeddings.create(input=texts, model=model)
return [item.embedding for item in embedding_response.data]
if __name__ == "__main__":
text = ["熊猫是中国的国宝"]
embedding = get_text_embedding(text)
if embedding is not None:
print(f"嵌入模型输出向量维度: {len(embedding)}")
print(embedding[:10])
客户端
创建客户端连接
创建客户端连接是用于建立与Qdrant服务器的连接,是后续所有操作的基础
bash
from qdrant_client import QdrantClient
from qdrant_client.http import models
def create_qdrant_client(host="localhost", port=6333):
"""
创建Qdrant客户端连接
参数说明:
host: IP地址或域名
port: Qdrant服务端口
返回:
QdrantClient实例
"""
try:
client = QdrantClient(host, port=port)
print(f"成功连接到Qdrant服务: {host}:{port}")
return client
except Exception as e:
print(f"连接Qdrant服务失败: {str(e)}")
raise
关闭客户端连接
当不需要Qdrant客户端连接后,需要及时进行连接释放。
python
def close_qdrant_client(client):
"""
关闭Qdrant客户端连接
参数说明:
client: QdrantClient
- Qdrant客户端实例
- 必须是通过create_qdrant_client创建的实例
"""
try:
client.close()
print("Qdrant客户端连接已关闭")
except Exception as e:
print(f"关闭Qdrant客户端连接失败: {str(e)}")
集合操作
创建集合
创建基础的文本向量集合是用于存储文本向量数据,以支持相似度搜索
python
def create_collection(client, collection_name="text_collection", vector_size=1536):
"""
创建文本向量集合
client: QdrantClient
- Qdrant客户端实例
- 必须是通过create_qdrant_client创建的实例
collection_name: 集合名称 必须是唯一
"""
try:
result = client.create_collection(
collection_name=collection_name,
vectors_config=models.VectorParams(
size=vector_size, # 向量维度 根据使用的模型确定
distance=models.Distance.COSINE # 距离度量方式: 余弦相似度
)
)
print(f"集合 {collection_name} 创建成功")
return result
except Exception as e:
print(f"创建集合 {collection_name} 失败: {str(e)}")
raise
if __name__ == '__main__':
client = create_qdrant_client()
create_collection(client, collection_name="text_collection")
当代码执行成功后,可以在web管理控制台查看创建的集合
删除集合
python
client.delete_collection(collection_name="text_collection")
检查集合是否存在
python
client.collection_exists(collection_name="text_collection")
创建多向量集合
QDrant允许每个集合存储多个向量。但是为了区分一条记录中的向量,它们应该在创建集合时定义一个唯一的名称。此模式下,每个命名向量都有其距离和大小
多向量集合支持存储和搜索多种类型的向量数据
python
def create_multi_vector_collection(client, collection_name, vector_configs):
"""
创建多向量集合
参数说明:
vector_configs: dict
- 向量配置字典
- 每个向量字段的配置
- 示例: {
"text_vector": models.VectorParams(
size=384,
distance=models.Distance.COSINE
),
"image_vector": models.VectorParams(
size=512,
distance=models.Distance.EUCLID
)
}
"""
try:
result = client.create_collection(
collection_name=collection_name,
vectors_config=vector_configs
)
print(f"多向量集合 {collection_name} 创建成功")
return result
except Exception as e:
print(f"创建多向量集合失败: {str(e)}")
raise
if __name__ == '__main__':
client = create_qdrant_client()
# 创建多向量集合
vector_configs = {
"text_vector": models.VectorParams(
size=384,
distance=models.Distance.COSINE
),
"image_vector": models.VectorParams(
size=512,
distance=models.Distance.EUCLID
)
}
create_multi_vector_collection(client, "multi_vector_collection", vector_configs)
向量数据类型
从 Qdrant v1.9.0 开始,Qdrant 支持存储和处理 uint8 类型的向量数据。这对于一些嵌入提供商提供的预量化格式的嵌入特别有用,比如 Cohere 的 int8 和二进制嵌入。
uint8 嵌入:
这是一种将向量数据存储为无符号 8 位整数的格式。相比于传统的浮点数格式,这种格式可以显著减少存储空间和提高处理速度。
预量化格式:
一些嵌入提供商会在生成嵌入时就对数据进行量化处理,以减少数据的大小和提高处理效率。Qdrant 支持直接存储这些量化后的数据。 二进制量化: 这是一种将向量数据进一步压缩为二进制格式的技术,进一步减少存储需求。
python
client.create_collection(
collection_name="{collection_name}",
vectors_config=models.VectorParams(
size=1024,
distance=models.Distance.COSINE,
datatype=models.Datatype.UINT8,
),
)
集合别名
在生产环境中,使用集合别名可以实现无缝切换不同版本的向量数据。当需要升级到新版本的神经网络时,直接停止服务并重建集合可能会导致服务中断。通过使用别名,您可以在后台构建一个新的集合,然后将别名从旧集合切换到新集合。这样,所有对集合的查询都可以继续使用别名进行,而不需要更改查询逻辑。由于别名的更改是原子操作,切换过程中不会影响任何并发请求,从而实现平滑过渡。
python
if __name__ == "__main__":
client = create_qdrant_client()
# 创建别名
client.update_collection_aliases(
change_aliases_operations=[
models.CreateAliasOperation(
create_alias=models.CreateAlias(
collection_name="example_collection", alias_name="production_collection"
)
)
]
)
# 删除集合别名
client.update_collection_aliases(
change_aliases_operations=[
models.DeleteAliasOperation(
delete_alias=models.DeleteAlias(alias_name="production_collection")
),
]
)
# 删除与新建集合别名
client.update_collection_aliases(
change_aliases_operations=[
# 删除现有的别名 "production_collection"
models.DeleteAliasOperation(
delete_alias=models.DeleteAlias(alias_name="production_collection")
),
# 创建一个新的别名 "production_collection" 指向 "example_collection"
models.CreateAliasOperation(
create_alias=models.CreateAlias(
collection_name="example_collection", # 新的集合名称
alias_name="production_collection" # 别名名称
)
),
]
)
# 列出集合别名
client.get_collection_aliases(collection_name="collection_name")
# 列出所有集合别名
client.get_aliases()
# 列出所有集合
client.get_collections()
点Payload操作
插入单个向量数据点
插入单个向量数据点是指用于向集合中添加单个向量数据及其元数据
python
def insert_single_point(client, collection_name, point_id, vector, payload):
"""
插入单个点数据
参数说明:
client: QdrantClient
- Qdrant客户端实例
- 必须是通过create_qdrant_client创建的实例
collection_name: 集合名称 必须是已存在的集合
point_id: 点的唯一标识符 可以是整数或字符串
vector: 向量数据 维度必须与集合定义一致
payload: 元数据 可以包含任意JSON可序列化的数据
"""
try:
point = models.PointStruct(
id=point_id,
vector=vector,
payload=payload
)
result = client.upsert(
collection_name=collection_name,
points=[point]
)
print(f"点数据 {point_id} 插入成功")
return result
except Exception as e:
print(f"插入点数据失败: {str(e)}")
raise
if __name__ == '__main__':
client = create_qdrant_client()
# 1. 插入文本数据
embedding = get_text_embedding(["人工智能正在改变世界"])
result1 = insert_single_point(client, collection_name="text_collection", point_id=1, vector=embedding,
payload={"text": "人工智能正在改变世界", "category": "科技", "date": "2024-01-01"})
# 2. 插入带标签的数据
embedding = get_text_embedding(["机器学习是AI的核心技术"])
result2 = insert_single_point(client, collection_name="text_collection", point_id=2, vector=embedding,
payload={"text": "机器学习是AI的核心技术", "tags": ["AI", "ML", "深度学习"], "author": "李白"})
# 3. 插入带评分的数据
embedding = get_text_embedding(["自然语言处理的最新进展"])
result3 = insert_single_point(client, collection_name="text_collection", point_id=3, vector=embedding,
payload={"text": "自然语言处理的最新进展", "score": 0.95, "source": "学术论文"})
插入代码执行成功后,同样可以在Web界面的某集合中查看存储数据情况
批量插入单个向量数据点
为了优化性能,Qdrant支持批量加载点。可以通过一次API调用将多个点加载到服务中。批量加载功能可以最大限度地减少创建网络连接的开销。
Qdrant API 支持两种创建批次的方式:基于记录和基于列。在内部,这两种方式并无区别,只是为了方便交互。
基于记录:
python
def batch_insert_points(client, collection_name, points_data):
"""
批量插入点数据
points_data: list[dict]
- 点数据列表
- 每个元素包含:
- id: 点ID
- vector: 向量数据
- payload: 元数据
"""
try:
points = [
models.PointStruct(
id=data["id"],
vector=data["vector"],
payload=data["payload"]
)
for data in points_data
]
result = client.upsert(
collection_name=collection_name,
points=points
)
print(f"成功批量插入 {len(points)} 个点数据")
return result
except Exception as e:
print(f"批量插入失败: {str(e)}")
raise
if __name__ == '__main__':
client = create_qdrant_client()
points_data = [{"id": 4, "vector": get_text_embedding(["人工智能已经来临,正在改变世界"]),
"payload": {"text": "人工智能已经来临,正在改变世界", "category": "科技"}},
{"id": 5, "vector": get_text_embedding(["机器学习是人工智能的核心技术"]),
"payload": {"text": "机器学习是人工智能的核心技术", "category": "技术"}}]
result = batch_insert_points(client, "text_collection", points_data)
基于列:
python
client.upsert(
collection_name="{collection_name}",
points=models.Batch(
ids=[1, 2, 3],
payloads=[
{"color": "red"},
{"color": "green"},
{"color": "blue"},
],
vectors=[
[0.9, 0.1, 0.1],
[0.1, 0.9, 0.1],
[0.1, 0.1, 0.9],
],
),
)
多向量插入数据点
向多向量集合插入包含多个向量字段的数据点
python
def insert_multi_vector_point(client, collection_name, point_id, vectors, payload):
"""
向多向量集合插入数据点
参数:
- client: Qdrant客户端实例
- collection_name: 集合名称
- point_id: 数据点ID
- vectors: 向量字典,键为向量字段名
- payload: 元数据
"""
try:
point = models.PointStruct(
id=point_id,
vector=vectors,
payload=payload
)
result = client.upsert(
collection_name=collection_name,
points=[point]
)
print(f"多向量数据点 {point_id} 插入成功")
return result
except Exception as e:
print(f"插入多向量数据点失败: {str(e)}")
raise
if __name__ == '__main__':
client = create_qdrant_client()
embedding = get_text_embedding(["人工智能正在改变世界"])
# 插入数据
point = {
"id": 1,
"vectors": {
"text_vector": embedding[:384], # 384维向量
"image_vector": embedding[:512] # 512维向量
},
"payload": {
"title": "人工智能",
"category": "科技",
"tags": ["AI", "ML"]
}
}
insert_multi_vector_point(client, "multi_vector_collection", point["id"], point["vectors"], point["payload"])
批量插入多向量数据点
python
def batch_insert_multi_vector_points(client, collection_name, points_data):
"""
批量插入多向量数据点
参数:
- client: Qdrant客户端实例
- collection_name: 集合名称
- points_data: 批量数据列表
"""
try:
points = [
models.PointStruct(
id=data["id"],
vector=data["vectors"],
payload=data["payload"]
)
for data in points_data
]
result = client.upsert(
collection_name=collection_name,
points=points
)
print(f"成功批量插入 {len(points)} 个多向量数据点")
return result
except Exception as e:
print(f"批量插入多向量数据点失败: {str(e)}")
raise
if __name__ == '__main__':
client = create_qdrant_client()
embedding = get_text_embedding(["人工智能正在改变世界"])
# 插入数据
points_data = [{"id": 3, "vectors": {"text_vector": embedding[:384], "image_vector": embedding[:512]},
"payload": {"title": "文章1", "category": "科技"}},
{"id": 4, "vectors": {"text_vector": embedding[:384], "image_vector": embedding[:512]},
"payload": {"title": "文章2", "category": "教育"}}]
batch_insert_multi_vector_points(client, "multi_vector_collection", points_data)
更新向量
对于点,可以修改其向量,只需要更新给定点上的指定向量,未指定的向量保持不变。
python
client.update_vectors(
collection_name="{collection_name}",
points=[
models.PointVectors(
id=1,
vector={
"image": [0.1, 0.2, 0.3, 0.4],
},
),
models.PointVectors(
id=2,
vector={
"text": [0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2],
},
),
],
)
删除向量
对于点,可以删除其向量,只需要从给定点中删除指定的向量。其他向量保持不变。点永远不会被删除。
python
client.delete_vectors(
collection_name="{collection_name}",
points=[0, 3, 100],
vectors=["text", "image"],
)
删除点
根据点的ID来精确地删除集合中的特定点
python
client.delete(
collection_name="{collection_name}",
points_selector=models.PointIdsList(
points=[0, 3, 100],
),
)
使用过滤器来删除点
python
client.delete(
collection_name="{collection_name}",
points_selector=models.FilterSelector(
filter=models.Filter(
must=[
models.FieldCondition(
key="color",
match=models.MatchValue(value="red"),
),
],
)
),
)
有效载荷Payload操作
概述
Qdrant的一大重要特性是能够存储与向量相关的附加信息,附加信息就是指有效载荷Payload。
有效载荷Payload是指一个使用JSON表示的信息。
有效载荷的示例:
bash
{
"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
}
]
}
类型
Qdrant还允许基于特定类型的值进行搜索,在搜索过程中作为附加过滤器实现。在过滤过程中,Qdrant会检查符合过滤条件类型的值,如果存储的值类型不符合过滤条件,则视为不满足。
例如:
如果对字符串数据应用范围条件,则会得到空输出。
如果对数组应用过滤器时,只要数组中至少有一个值满足条件,过滤器就会成功
类型 | 描述 | 示例代码 |
---|---|---|
integer | 64位整数,范围从-9223372036854775808到9223372036854775807 | "count": 10 "sizes": [35, 36, 38] |
float | 64位浮点数 | "price": 11.99 "ratings": [9.1, 9.2, 9.4] |
bool | 布尔值,true或false | "is_delivered": true "responses": [false, false, true, false] |
keyword | 字符串值,用于精确匹配 | "name": "Alice" "friends": ["bob", "eva", "jack"] |
geo | 地理坐标,包含经度(lon)和纬度(lat) | "location": {"lon": 52.5200, "lat": 13.4050} "cities": [{"lon": 51.5072, "lat": 0.1276}, {"lon": 40.7128, "lat": 74.0060}] |
datetime | 日期时间,RFC 3339格式(v1.8.0+) | "created_at": "2023-02-08T10:49:00Z" "updated_at": ["2023-02-08T13:52:00Z", "2023-02-21T21:23:00Z"] |
uuid | UUID类型(v1.11.0+),与keyword功能相同但内部存储更高效 | "uuid": "550e8400-e29b-41d4-a716-446655440000" "uuids": ["550e8400-e29b-41d4-a716-446655440000", "550e8400-e29b-41d4-a716-446655440001"] |
text | 文本类型,支持全文检索和向量搜索 | "description": "This is a long text description..." |
null | 空值类型 | "optional_field": null |
创建带有效载荷的点
bash
client.upsert(
collection_name="{collection_name}",
points=[
models.PointStruct(
id=1,
vector=[0.05, 0.61, 0.76, 0.74],
payload={
"city": "Berlin",
"price": 1.99,
},
)
),
],
)
设置有效载荷
可以使用set_payload方法来更新有效载荷,此方法会更新特定字段,同时保持其他字段不变,它仅在某个点上设置给定的有效载荷值。
bash
client.set_payload(
collection_name="{collection_name}",
payload={
"property1": "string",
"property2": "string",
},
points=[0, 3, 10],
)
另一种方法是使用过滤器,无需知道要修改的点的ID。
bash
client.set_payload(
collection_name="{collection_name}",
payload={
"property1": "string",
"property2": "string",
},
points=models.Filter(
must=[
models.FieldCondition(
key="color",
match=models.MatchValue(value="red"),
),
],
),
)
覆盖有效载荷
可以使用overwrite_payload方法来更新有效载荷,它会用给定的有效载荷完全替换任何现有的有效载荷。
bash
client.overwrite_payload(
collection_name="{collection_name}",
payload={
"property1": "string",
"property2": "string",
},
points=[0, 3, 10],
)
清除有效载荷
可以使用clear_payload方法更新有效载荷,此方法从指定点删除所有有效载荷键
bash
client.clear_payload(
collection_name="{collection_name}",
points_selector=[0, 3, 100],
)
删除特定载荷键
可以使用delete_payload方法从点中删除特定的有效载荷键。
bash
client.delete_payload(
collection_name="{collection_name}",
keys=["color", "price"],
points=[0, 3, 100],
)
使用过滤器从点中删除有效载荷键。
bash
client.delete_payload(
collection_name="{collection_name}",
keys=["color", "price"],
points=models.Filter(
must=[
models.FieldCondition(
key="color",
match=models.MatchValue(value="red"),
),
],
),
)
有效载荷索引
Qdrant允许通过指定字段的名称和类型来为有效载荷字段创建索引。索引字段也会影响向量索引。
bash
client.create_payload_index(
collection_name="{collection_name}",
field_name="name_of_the_field_to_index",
field_schema="keyword",
)
搜索操作
向量相似度搜索
向量相似度搜索就是查找与查询向量最相似的数据点
python
def search_similar_vectors(client, collection_name, query_vector, limit=1):
"""
搜索相似向量
参数说明:
query_vector: 查询向量 维度必须与集合定义一致
limit: 返回结果数量 控制返回的最相似向量的数量
返回:
搜索结果列表,每个结果包含:
- id: 点ID
- version:
- score: 相似度分数
- payload: 元数据
"""
try:
results = client.search(
collection_name=collection_name,
query_vector=query_vector,
limit=limit
)
print(f"搜索完成,找到 {len(results)} 个结果")
return results
except Exception as e:
print(f"搜索失败: {str(e)}")
raise
if __name__ == '__main__':
client = create_qdrant_client()
embedding = get_text_embedding(["AI与智能技术的最新进展"])
results = search_similar_vectors(client, collection_name="text_collection", query_vector=embedding, limit=2)
print(results)
python
搜索完成,找到 2 个结果
[ScoredPoint(id=1, version=2, score=0.64689106, payload={'text': '人工智能正在改变世界', 'category': '科技', 'date': '2024-01-01'}, vector=None, shard_key=None, order_value=None), ScoredPoint(id=2, version=3, score=0.56421924, payload={'text': '机器学习是AI的核心技术', 'tags': ['AI', 'ML', '深度学习'], 'author': '李白'}, vector=None, shard_key=None, order_value=None)]
基于ID搜索
基于ID搜索就是通过数据点ID进行搜索查找
python
client.query_points(
collection_name="{collection_name}",
query="43cf51e2-8777-4f52-bc74-c2cbde0c8b04", # <--- point id
)
分页
允许跳过搜索的第一个结果并仅返回从某个指定偏移量开始的结果
bash
from qdrant_client import QdrantClient
client = QdrantClient(url="http://localhost:6333")
client.query_points(
collection_name="{collection_name}",
query=[0.2, 0.1, 0.9, 0.7],
with_vectors=True,
with_payload=True,
limit=10,
offset=100,
)
分组
可以按特定字段对结果进行分组。当对同一项目有多个点数据,并且希望避免结果中出现重复项时,此功能非常有用。
应用场景:将一个大型文档分成多个块,并且想要按文档进行搜索或推荐,则可以按文档ID对结果进行分组。
bash
client.query_points_groups(
collection_name="{collection_name}",
query=[1.1],
group_by="document_id", # 按document_id字段分组
limit=4,
group_size=2, # 每组最多2个点数据 各组按最高点的得分排序。每个组内的点也按最高点的得分排序。
)
带过滤的搜索
在向量相似度搜索的基础上增加元数据过滤条件
python
def search_with_filter(client, collection_name, query_vector, filter_conditions, limit=2):
"""
带过滤条件的搜索
参数说明:
filter_conditions: list[dict]
- 过滤条件列表
- 每个条件包含:
- key: 字段名
- value: 匹配值
- 示例: [ {"key": "category", "value": "科技"}]
"""
try:
filter = models.Filter(
must=[
models.FieldCondition(
key=condition["key"],
match=models.MatchValue(value=condition["value"])
)
for condition in filter_conditions
]
)
results = client.search(
collection_name=collection_name,
query_vector=query_vector,
query_filter=filter,
limit=limit
)
print(f"带过滤搜索完成,找到 {len(results)} 个结果")
return results
except Exception as e:
print(f"带过滤搜索失败: {str(e)}")
raise
if __name__ == '__main__':
client = create_qdrant_client()
# 1. 按类别过滤
filter_conditions = [{"key": "category", "value": "科技"}]
result1 = search_with_filter(client, collection_name="text_collection", query_vector=get_text_embedding(['世界']),
filter_conditions=filter_conditions, limit=2)
print(f"result1 ==> {result1}")
# 2. 按日期和类别过滤
filter_conditions = [{"key": "category", "value": "科技"}, {"key": "date", "value": "2024-01-01"}]
result2 = search_with_filter(client, collection_name="text_collection", query_vector=get_text_embedding(['世界']),
filter_conditions=filter_conditions, limit=2)
print(f"result2 ==> {result2}")
最新版本的Qdrant使用以下搜索API进行搜索
python
from qdrant_client import QdrantClient, models
client = QdrantClient(url="http://localhost:6333")
client.query_points(
collection_name="{collection_name}",
query=[0.2, 0.1, 0.9, 0.7],
query_filter=models.Filter(
must=[
models.FieldCondition(
key="city",
match=models.MatchValue(
value="London",
),
)
]
),
search_params=models.SearchParams(hnsw_ef=128, exact=False),
limit=3,
)
多条件搜索
支持must和should条件的复杂搜索场景
python
def search_with_multiple_conditions(client, collection_name, query_vector, must_conditions, should_conditions, limit=2):
"""
多条件搜索
参数说明:
must_conditions: list[dict]
- 必须满足的条件列表
- 每个条件包含:
- key: 字段名
- value: 匹配值
- 示例: [{"key": "category", "value": "科技"}]
should_conditions: list[dict]
- 应该满足的条件列表
- 每个条件包含:
- key: 字段名
- values: 匹配值列表
- 示例: [ {"key": "tags", "values": ["AI", "ML"]}]
"""
try:
filter = models.Filter(
must=[
models.FieldCondition(
key=condition["key"],
match=models.MatchValue(value=condition["value"])
)
for condition in must_conditions
],
should=[
models.FieldCondition(
key=condition["key"],
match=models.MatchAny(any=condition["values"])
)
for condition in should_conditions
]
)
results = client.search(
collection_name=collection_name,
query_vector=query_vector,
query_filter=filter,
limit=limit
)
print(f"多条件搜索完成,找到 {len(results)} 个结果")
return results
except Exception as e:
print(f"多条件搜索失败: {str(e)}")
raise
if __name__ == '__main__':
client = create_qdrant_client()
# 插入数据
points = [
models.PointStruct(id=6, vector=get_text_embedding(["机器学习与人工智能"]),
payload={"category": "科技", "tags": ["AI", "ML"]}
),
models.PointStruct(id=7, vector=get_text_embedding(["Python编程语言是学习人工智能的基础"]),
payload={"category": "科技", "tags": ["编程", "Python"]})
]
client.upsert(collection_name="text_collection", points=points)
must_conditions = [{"key": "category", "value": "科技"}]
should_conditions = [{"key": "tags", "values": ["AI", "ML"]}]
results = search_with_multiple_conditions(
client,
collection_name="text_collection",
query_vector=get_text_embedding(["人工智能"]),
must_conditions=must_conditions,
should_conditions=should_conditions,
limit=2
)
print(results)
python
多条件搜索完成,找到 1 个结果
[ScoredPoint(id=6, version=6, score=0.7632638, payload={'category': '科技', 'tags': ['AI', 'ML']}, vector=None, shard_key=None, order_value=None)]
多向量搜索
在指定向量字段上进行相似度搜索
python
def search_multi_vector(client, collection_name, vector_name, query_vector, limit=2):
"""
多向量搜索
参数说明:
vector_name: str
- 向量字段名称
- 必须是集合中定义的向量字段
- 示例: "text_vector"
query_vector: list[float] 查询向量 维度必须与指定向量字段一致
"""
try:
results = client.search(
collection_name=collection_name,
query_vector=(vector_name, query_vector),
limit=limit
)
print(f"多向量搜索完成,找到 {len(results)} 个结果")
return results
except Exception as e:
print(f"多向量搜索失败: {str(e)}")
raise
if __name__ == '__main__':
client = create_qdrant_client()
embedding = get_text_embedding(["人工智能正在改变世界"])
# 1. 搜索文本向量
result1 = search_multi_vector(
client,
collection_name="multi_vector_collection",
vector_name="text_vector",
query_vector=embedding[:384],
limit=3
)
print(f"result1 ==> {result1}")
# 2. 搜索图像向量
result2 = search_multi_vector(
client,
collection_name="multi_vector_collection",
vector_name="image_vector",
query_vector=embedding[:512],
limit=2
)
print(f"result2 ==> {result2}")
python
多向量搜索完成,找到 2 个结果
result1 ==> [ScoredPoint(id=4, version=1, score=1.0, payload={'title': '文章2', 'category': '教育'}, vector=None, shard_key=None, order_value=None), ScoredPoint(id=1, version=1, score=1.0, payload={'title': '文章1', 'category': '科技'}, vector=None, shard_key=None, order_value=None)]
多向量搜索完成,找到 2 个结果
result2 ==> [ScoredPoint(id=4, version=1, score=0.0, payload={'title': '文章2', 'category': '教育'}, vector=None, shard_key=None, order_value=None), ScoredPoint(id=1, version=1, score=0.0, payload={'title': '文章1', 'category': '科技'}, vector=None, shard_key=None, order_value=None)]
性能优化
集合配置优化
集合配置优化可以调整集合的索引和存储参数以提升性能
python
def optimize_collection(client, collection_name, optimizers_config):
"""
优化集合配置
参数说明:
optimizers_config: models.OptimizersConfig
- 优化器配置
- 控制索引和存储优化
"""
try:
result = client.update_collection(
collection_name=collection_name,
optimizers_config=optimizers_config
)
print(f"集合 {collection_name} 优化成功")
return result
except Exception as e:
print(f"集合优化失败: {str(e)}")
raise
if __name__ == '__main__':
client = create_qdrant_client()
# 优化器配置
optimizers_config = models.OptimizersConfigDiff(
indexing_threshold=0, # 立即建立索引(0表示不等待,有数据就建索引)
max_optimization_threads=1, # 只用1个CPU核心做优化(增加可加快优化但占用更多资源)
flush_interval_sec=5, # 每5秒自动保存一次数据到磁盘
memmap_threshold=20000, # 当数据超过2万条时,将部分数据存到磁盘以节省内存
default_segment_number=0, # 自动决定分成几个数据段(设为0让系统自动优化)
max_segment_size=None, # 每个数据段最大尺寸(不限制大小,None表示自动管理)
deleted_threshold=0.2, # 当20%的数据被标记删除时,自动清理这些数据
vacuum_min_vector_number=1000 # 只有当集合中有超过1000条数据时,才会执行清理操作
)
optimize_collection(client, "text_collection", optimizers_config)
索引优化
python
if __name__ == '__main__':
client = create_qdrant_client()
# 创建一个高性能向量搜索集合,包含向量配置、优化器配置和HNSW索引配置
client.create_collection(
collection_name="optimized", # 集合名称,需唯一
# 向量配置
vectors_config=models.VectorParams(
size=100, # 向量维度大小,需与嵌入模型输出维度一致
distance=models.Distance.COSINE # 距离度量方式,COSINE表示余弦相似度
),
# 优化器配置
optimizers_config=models.OptimizersConfigDiff(
indexing_threshold=0, # 索引阈值,0表示立即索引
memmap_threshold=20000, # 内存映射阈值,超过此数量的向量会使用内存映射
max_optimization_threads=4, # 最大优化线程数
deleted_threshold=0.2, # 删除向量比例阈值,超过此值会触发清理
vacuum_min_vector_number=1000, # 最小向量数,低于此值不会触发清理
default_segment_number=0, # 默认段数量,0表示自动
flush_interval_sec=5 # 刷新间隔(秒)
),
# HNSW索引配置(高效近似最近邻搜索)
hnsw_config=models.HnswConfigDiff(
m=16, # 每个节点的最大连接数(推荐16-64)
ef_construct=100, # 构建时的动态候选列表大小(推荐100-400)
full_scan_threshold=10000 # 全扫描阈值,向量数低于此值不使用索引
)
)
查询优化
python
def search_with_approximation(client, collection_name, query_vector, limit=3):
"""
使用近似搜索查询向量
参数说明:
:param client: QdrantClient实例
:param collection_name: 集合名称
:param query_vector: 用于搜索的向量数据 维度必须与集合定义一致
:param limit: 控制返回的最相似向量的数量
返回:搜索结果列表
"""
try:
search_result = client.search(
collection_name=collection_name,
query_vector=query_vector,
limit=limit,
search_params=models.SearchParams(
hnsw_ef=128, # 控制搜索精度
# 值越大越精确但速度越慢
# 建议范围: 32-256
exact=False, # 使用近似搜索
# False: 使用近似搜索(更快)
# True: 使用精确搜索(更慢但更准确)
quantization=models.QuantizationSearchParams(
ignore=False, # 是否忽略量化
# False: 使用量化 内存有限
# True: 忽略量化 内存充足
rescore=True # 是否重新计算分数
# True: 重新计算更准确 精度要求高
# False: 使用量化分数 速度要求高
)
)
)
print(f"近似搜索成功,返回{len(search_result)}条结果")
return search_result
except Exception as e:
print(f"近似搜索失败: {str(e)}")
raise
if __name__ == '__main__':
# 创建Qdrant客户端连接
client = create_qdrant_client()
# 获取查询文本的嵌入向量
# 使用"人工智能"作为查询文本
embedding = get_texts_embedding(["人工智能"])[0]
# 执行近似搜索
# 在"text_collection"集合中搜索
# 使用默认的limit=3参数
search_with_approximation(client, "text_collection", embedding)
存储优化
python
def configure_collection_storage(client, collection_name):
"""
配置更新集合的存储参数
"""
try:
# 更新集合配置
result = client.update_collection(
collection_name=collection_name,
optimizers_config=models.OptimizersConfigDiff(
indexing_threshold=0, # 立即建立索引(0表示不等待,有数据就建索引)
max_optimization_threads=1, # 只用1个CPU核心做优化(增加可加快优化但占用更多资源)
flush_interval_sec=5, # 每5秒自动保存一次数据到磁盘
memmap_threshold=20000, # 当数据超过2万条时,将部分数据存到磁盘以节省内存
default_segment_number=0, # 自动决定分成几个数据段(设为0让系统自动优化)
max_segment_size=None, # 每个数据段最大尺寸(不限制大小,None表示自动管理)
deleted_threshold=0.2, # 当20%的数据被标记删除时,自动清理这些数据
vacuum_min_vector_number=1000 # 只有当集合中有超过1000条数据时,才会执行清理操作
)
)
print(f"集合 {collection_name} 存储配置更新成功")
return result
except Exception as e:
print(f"更新存储配置失败: {str(e)}")
raise
if __name__ == '__main__':
# 创建客户端连接
client = create_qdrant_client()
result = configure_collection_storage(client, "text_collection")
# 关闭客户端连接
client.close()
使用案例
借助上述部分功能函数,实现一个简单的从文本向量化、集合创建、批量插入数据到执行相似度搜索的流程,基于这个流程就可以构建文本相似度搜索应用。
python
def process_text_search(client, collection_name, texts, vectors, query_vector, limit=2):
"""
处理文本搜索
参数说明:
texts: 未进行向量化的文本数据
vectors: 文本数据对应向量数据
query_vector: 查询文本对应向量数据
limit: 控制返回的最相似文本的数量
"""
try:
# 插入数据
points_data = [
{
"id": i,
"vector": vectors[i],
"payload": {"text": texts[i]}
}
for i in range(len(texts))
]
batch_insert_points(client, collection_name, points_data)
# 搜索
results = search_similar_vectors(client, collection_name, query_vector, limit)
return results
except Exception as e:
print(f"处理文本搜索失败: {str(e)}")
raise
if __name__ == '__main__':
client = create_qdrant_client()
# 获取存储数据的向量数据
texts = [
"人工智能正在改变世界",
"机器学习是AI的核心技术",
"深度学习在计算机视觉中的应用",
"自然语言处理的最新进展"
]
vectors = get_texts_embedding(texts)
# 查询向量数据
query_vector = get_texts_embedding(["AI技术发展"])[0]
# 创建集合
create_collection(client, "text_search", len(query_vector))
# 处理文本与搜索
results = process_text_search(
client,
"text_search",
texts,
vectors,
query_vector,
limit=2
)
print(results)
python
[ScoredPoint(id=0, version=0, score=0.59974897, payload={'text': '人工智能正在改变世界'}, vector=None, shard_key=None, order_value=None), ScoredPoint(id=1, version=0, score=0.5409482, payload={'text': '机器学习是AI的核心技术'}, vector=None, shard_key=None, order_value=None)]