当你已经不满足于单机原型,真正的差异往往出现在过滤表达和检索组合能力上
从“能搜相似向量”到“能服务真实业务”
到了生产前夜,团队关心的问题会迅速变化。大家不再只问“能不能搜到相似内容”,而开始追问“能不能只搜当前租户的数据”“能不能限定最近三个月的文档”“能不能同时兼顾关键词命中和语义相似”“能不能在已有服务里稳定扩展”。这时,向量数据库之间的差异才真正显现出来。
Qdrant 与 Weaviate 都属于这个阶段里非常常见的选择。两者都支持向量存储、近似检索、元数据过滤与服务化部署,但产品重心并不完全一样。简化地说,QdrantQdrantQdrant is a vector database known for strong payload filtering, practical APIs, and a focused retrieval-oriented design. It is often favored when teams want explicit control over vectors, filters, and hybrid retrieval behavior. 更偏向“检索内核与过滤能力做得扎实、接口清楚”;WeaviateWeaviateWeaviate is a vector database with a broader platform flavor. It combines vector search with schema management, modules, and retrieval features that can fit teams wanting a more integrated application-facing stack. 则更有“平台型产品”的气质,围绕 schema、模块化扩展与应用接口做了更多封装。
这并不意味着谁更先进,而是说明它们在团队协作方式和项目约束上各有适配面。
Qdrant 的强项:payload 过滤清晰而实用
在真实业务里,单纯按相似度返回结果往往是不够的。你几乎一定会带上业务条件,例如租户 ID、文档类型、语言、时间范围、是否公开、是否已审核。Qdrant 把这部分能力做得非常明确,它把结构化字段称为payloadpayload在 Qdrant 里,payload 是附着在向量点上的结构化元数据。检索时既可以先用 payload 做过滤,再做向量搜索,也可以把过滤与相似度检索组合到同一次请求里。 ,并提供较完整的条件表达。
这使得 Qdrant 很适合那些“过滤不是边缘需求,而是核心前提”的场景。例如企业知识库,租户隔离和权限过滤必须在召回阶段就生效;再例如电商或内容平台,用户可见范围、发布时间和标签状态都直接影响检索结果。此时,向量相似度只是条件之一,不是全部。
Qdrant 的另一个优势,是接口相对直接。集合、点、payload、filter 这些概念都比较贴近检索工程师的思维,系统行为通常也容易预期。对于已经清楚自己在做什么的团队,这种明确性很重要。
Weaviate 的特点:更完整的应用层抽象
Weaviate 除了提供向量检索本身,还更强调上层对象模型与模块化扩展。它常常以类和属性组织数据,提供较强的 schema 管理感受,并能配合不同模块处理向量化、生成、关键词搜索等能力。这让它对一部分偏应用开发、希望快速组合完整能力的团队很有吸引力。
尤其当你希望数据库不仅存储向量,还承担一部分应用入口职责时,Weaviate 的产品风格会显得更顺手。它对混合检索hybrid searchHybrid search combines lexical matching and vector similarity in one retrieval pipeline. It is valuable when exact terms, names, or identifiers matter alongside semantic similarity. 的强调也比较鲜明。对于企业文档、商品检索、知识问答这类场景,关键词与语义往往都重要。仅靠向量相似度,专有名词、编号和罕见实体可能不稳定;仅靠关键词,又难以处理改写和意图相近表达。混合检索正是为了同时利用两种信号。
混合检索为什么越来越重要
向量检索解决的是“语义接近”,但业务里常常还存在另一类强信号:关键词精确命中。比如用户问“错误码 E11000 是什么”,如果系统完全依赖向量相似度,未必能把包含这个错误码的文档排到最前;反过来,如果只依赖关键词,又可能错过用自然语言解释该错误的说明文档。
因此,混合检索已经从“加分项”逐渐变成许多系统的默认配置。通常做法是把稀疏信号与稠密信号结合,再通过一定权重融合排序。Qdrant 和 Weaviate 都支持类似能力,只是入口设计与生态集成方式不同。你真正要考虑的,是自己的数据更偏哪一类信号,以及团队更习惯怎样配置和调试这套组合。
选 Qdrant 还是 Weaviate,关键看团队与问题
如果你的团队目标明确,已经知道自己需要怎样的 embedding、怎样的过滤字段、怎样的混合检索策略,而且希望对底层检索行为保持较强掌控,那么 Qdrant 往往是很稳妥的选择。它的优势在于能力聚焦、接口直接、过滤表现突出。
如果你的团队更希望获得一个“向量检索 + schema + 模块能力”一体化程度较高的平台,或者你更倾向于在产品层快速组合对象模型、向量化和搜索能力,那么 Weaviate 可能更顺手。它更像一个应用导向的平台,而不仅是检索引擎外壳。
这背后其实对应着两种工程风格。一种风格偏“检索系统思维”,强调索引、过滤、召回链路的可控性;另一种风格偏“应用平台思维”,强调通过更高层抽象缩短开发路径。两种风格都合理,关键是与你的团队能力结构是否匹配。
一个常见误区:把向量数据库选型完全等同于索引算法选型
Qdrant 完整实战:从启动到带过滤的混合检索
理论说够了,上代码。先用 Docker 起一个 Qdrant:
docker run -p 6333:6333 -p 6334:6334 \
-v $(pwd)/qdrant_storage:/qdrant/storage \
qdrant/qdrant
Python 客户端:
pip install qdrant-client sentence-transformers
1. 创建 collection(一次性 schema 定义)
from qdrant_client import QdrantClient
from qdrant_client.http import models
client = QdrantClient(url="http://localhost:6333")
# 创建 collection(相当于关系库的"表")
client.create_collection(
collection_name="kb",
vectors_config=models.VectorParams(
size=512, # 向量维度(要跟 embedding 模型对得上)
distance=models.Distance.COSINE, # 距离度量:COSINE / DOT / EUCLID
),
# HNSW 参数 —— 上一篇讲过
hnsw_config=models.HnswConfigDiff(
m=16,
ef_construct=200,
),
# 可选:开启 quantization 进一步省内存
quantization_config=models.ScalarQuantization(
scalar=models.ScalarQuantizationConfig(
type=models.ScalarType.INT8,
quantile=0.99,
always_ram=True, # 量化后的小向量常驻 RAM
),
),
)
2. 写入数据(向量 + payload)
from sentence_transformers import SentenceTransformer
from uuid import uuid4
model = SentenceTransformer("BAAI/bge-small-zh-v1.5")
# 一批文档
docs = [
{"text": "Python 异常 TypeError 通常因为对错误类型做操作",
"tenant_id": "team-a", "category": "python", "date": "2026-05-01"},
{"text": "JavaScript 中 undefined 和 null 的区别",
"tenant_id": "team-a", "category": "frontend", "date": "2026-05-10"},
{"text": "PostgreSQL 索引选型:B-tree、GIN、GiST 何时用",
"tenant_id": "team-b", "category": "database", "date": "2026-04-15"},
{"text": "错误码 E11000 在 MongoDB 里表示重复主键冲突",
"tenant_id": "team-b", "category": "database", "date": "2026-05-20"},
]
# 把文本批量编码成向量
texts = [d["text"] for d in docs]
vectors = model.encode(texts, normalize_embeddings=True).tolist()
# 批量插入
client.upsert(
collection_name="kb",
points=[
models.PointStruct(
id=str(uuid4()),
vector=vec,
payload=d, # payload 就是结构化元数据
)
for vec, d in zip(vectors, docs)
],
)
print(client.count(collection_name="kb")) # CountResult(count=4)
3. 纯向量检索
q = "怎么解决重复主键的报错"
q_vec = model.encode([q], normalize_embeddings=True)[0].tolist()
results = client.search(
collection_name="kb",
query_vector=q_vec,
limit=3,
)
for r in results:
print(f"score={r.score:.3f} {r.payload['text']}")
4. 向量检索 + payload 过滤(这是 Qdrant 的强项)
# 只在 team-b 这个租户里搜,且只要 database 分类的文档
results = client.search(
collection_name="kb",
query_vector=q_vec,
limit=3,
query_filter=models.Filter(
must=[
models.FieldCondition(
key="tenant_id",
match=models.MatchValue(value="team-b"),
),
models.FieldCondition(
key="category",
match=models.MatchValue(value="database"),
),
],
),
)
更复杂的过滤——日期范围 + 排除某些标签 + 多分类 OR:
results = client.search(
collection_name="kb",
query_vector=q_vec,
limit=10,
query_filter=models.Filter(
must=[
models.FieldCondition(
key="date",
range=models.DatetimeRange(
gte="2026-05-01T00:00:00",
lte="2026-05-31T23:59:59",
),
),
],
should=[ # OR 关系
models.FieldCondition(key="category", match=models.MatchValue(value="database")),
models.FieldCondition(key="category", match=models.MatchValue(value="backend")),
],
must_not=[
models.FieldCondition(key="status", match=models.MatchValue(value="draft")),
],
),
)
Qdrant 过滤的关键设计:它的过滤直接嵌入到 HNSW 搜索过程里——遍历图时如果某个节点不满足 filter 就跳过,不是先全召回再后置过滤。这让过滤后召回率和延迟都更稳定,特别是过滤条件很苛刻(命中率 < 1%)时优势明显。
5. 给 payload 字段加索引(生产必做)
client.create_payload_index(
collection_name="kb",
field_name="tenant_id",
field_schema=models.PayloadSchemaType.KEYWORD,
)
client.create_payload_index(
collection_name="kb",
field_name="category",
field_schema=models.PayloadSchemaType.KEYWORD,
)
client.create_payload_index(
collection_name="kb",
field_name="date",
field_schema=models.PayloadSchemaType.DATETIME,
)
经常被过滤的字段一定要加索引,否则大数据集上过滤会变慢。
6. 混合检索:稠密 + 稀疏(Qdrant 1.10+)
from qdrant_client.http import models
# 创建支持混合检索的 collection
client.create_collection(
collection_name="kb_hybrid",
vectors_config={
"dense": models.VectorParams(size=512, distance=models.Distance.COSINE),
},
sparse_vectors_config={
"sparse": models.SparseVectorParams(), # BM25-style 稀疏向量
},
)
# 写入时同时提供稠密 + 稀疏
# 稀疏向量可以是 BM25 提取出的 token weight,例如用 fastembed:
# from fastembed import SparseTextEmbedding
# sparse_model = SparseTextEmbedding("Qdrant/bm25")
# 混合检索 + RRF 融合
results = client.query_points(
collection_name="kb_hybrid",
prefetch=[
models.Prefetch(query=q_dense, using="dense", limit=20),
models.Prefetch(query=q_sparse, using="sparse", limit=20),
],
query=models.FusionQuery(fusion=models.Fusion.RRF), # Reciprocal Rank Fusion
limit=10,
)
RRF(Reciprocal Rank Fusion)是混合检索最常用的融合算法:score = Σ 1/(60 + rank_i),简单稳定,不需要调超参。
Weaviate 完整实战:用 schema + 模块化思维
Weaviate 跟 Qdrant 的最大区别:更强调 schema 和模块。你定义类(Class),属性(Property),然后 Weaviate 把 vectorizer(向量化器)/ generative(生成器)/ reranker 等能力当成可插拔模块。
启动:
docker run -p 8080:8080 -p 50051:50051 \
-e QUERY_DEFAULTS_LIMIT=25 \
-e AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED=true \
-e PERSISTENCE_DATA_PATH=/var/lib/weaviate \
-e DEFAULT_VECTORIZER_MODULE=none \
-e ENABLE_MODULES=text2vec-openai,generative-openai \
-e CLUSTER_HOSTNAME=node1 \
cr.weaviate.io/semitechnologies/weaviate:1.27.0
Python 客户端:
pip install weaviate-client
1. 定义 schema(Class)
import weaviate
from weaviate.classes.config import Configure, Property, DataType
client = weaviate.connect_to_local()
client.collections.create(
name="Article",
properties=[
Property(name="title", data_type=DataType.TEXT),
Property(name="content", data_type=DataType.TEXT),
Property(name="tenant_id", data_type=DataType.TEXT),
Property(name="category", data_type=DataType.TEXT),
Property(name="published_at", data_type=DataType.DATE),
],
# 不让 Weaviate 自己 vectorize,我们外部提供向量
vectorizer_config=Configure.Vectorizer.none(),
# HNSW 参数
vector_index_config=Configure.VectorIndex.hnsw(
ef_construction=200,
max_connections=16,
distance_metric=weaviate.classes.config.VectorDistances.COSINE,
),
)
2. 写入数据(带向量)
articles = client.collections.get("Article")
with articles.batch.dynamic() as batch:
for d in docs:
batch.add_object(
properties={
"title": d["text"][:30],
"content": d["text"],
"tenant_id": d["tenant_id"],
"category": d["category"],
"published_at": d["date"] + "T00:00:00Z",
},
vector=model.encode(d["text"], normalize_embeddings=True).tolist(),
)
3. 向量检索 + 过滤
from weaviate.classes.query import Filter
q_vec = model.encode("怎么解决重复主键的报错", normalize_embeddings=True).tolist()
results = articles.query.near_vector(
near_vector=q_vec,
limit=5,
filters=(
Filter.by_property("tenant_id").equal("team-b") &
Filter.by_property("category").equal("database")
),
return_metadata=weaviate.classes.query.MetadataQuery(distance=True),
)
for o in results.objects:
print(f"distance={o.metadata.distance:.3f} {o.properties['content']}")
4. Weaviate 的原生混合检索(一行 API)
results = articles.query.hybrid(
query="重复主键报错怎么解决",
vector=q_vec,
alpha=0.5, # 0 = 纯关键词(BM25),1 = 纯向量,0.5 = 一半一半
limit=5,
)
alpha 参数控制稠密 vs 稀疏的权重。这是 Weaviate 比 Qdrant 接口更简洁的地方——一个参数就能完成混合检索。Qdrant 要自己组装 prefetch + RRF。
5. Weaviate 的 generative 模块(边检索边生成)
# 如果配置了 generative-openai 模块
results = articles.generate.near_vector(
near_vector=q_vec,
limit=3,
# grouped_task: 把检索到的 3 条作为 context,让 LLM 回答用户问题
grouped_task="根据上面的文档,回答:如何修复 MongoDB 的重复主键报错?",
)
print(results.generated)
这就是 Weaviate 的"平台型"风格——把 RAG 中的"检索 + 生成"压成一次 API 调用。Qdrant 不提供这个,要自己在应用层用 OpenAI SDK 拼。
Qdrant 和 Weaviate 关键差异对比
| 维度 | Qdrant | Weaviate |
|---|---|---|
| 核心理念 | 检索引擎,控制力强 | 平台,封装多,开发快 |
| 向量化 | 外部提供 | 外部提供,或用内置 module |
| 混合检索 | 自己组 prefetch + RRF | 一行 hybrid(alpha=0.5) |
| 过滤性能 | HNSW 内嵌过滤,苛刻条件下也快 | 后置过滤为主,复杂条件稍慢 |
| schema 严格度 | 灵活(payload 任意 JSON) | 严格(Class + Property) |
| 生成集成 | 无,自己拼 | 有 generative 模块 |
| 典型客户 | 工程团队,明确知道自己要什么 | 应用团队,要快速出活 |
| 写入吞吐 | 较高 | 中等 |
| 运维复杂度 | 低(单二进制 + 数据目录) | 中等(模块管理稍多) |
什么时候应该优先考虑过滤能力
有些业务把过滤放在很后面,结果上线前才发现系统逻辑不成立。比如多租户知识库,如果不能先限制在当前租户内,召回出来的内容再相似也不能用;比如内部文档系统,如果检索阶段不处理访问权限,后面再裁掉结果,会让真正应该返回的相关文档数量不够。此时,过滤不是优化项,而是正确性的一部分。
在这种场景下,Qdrant 往往更容易成为优先候选,因为它的 payload 过滤设计长期围绕这类需求展开。Weaviate 也能做过滤,但团队通常会更关注其整体平台能力是否正好契合项目节奏。
什么时候应该优先考虑混合检索
如果你的语料里充满术语、产品型号、错误码、人名、药名、法规编号或字段名,那么纯向量检索通常不够稳。因为这些对象经常要求字面命中,而不是仅靠语义接近。此时,混合检索的重要性会迅速上升。
这类场景里,Weaviate 往往能吸引那些想快速把多种信号整合进一个应用接口的团队;而 Qdrant 则更适合已经有明确检索流水线设计,想把稀疏与稠密组合逻辑掌握在自己手里的团队。差异不在“能不能做”,而在“默认工作方式是否顺手”。
本篇要点
- 进入真实业务后,向量数据库的差异往往首先体现在过滤与混合检索能力上。
- Qdrant 的优势在于 payload 过滤清晰、接口直接,适合对检索行为有明确掌控需求的团队。
- Weaviate 更有平台化特征,适合希望结合 schema、模块与应用层抽象一起使用的团队。
- 混合检索越来越重要,因为很多场景同时需要关键词精确命中和语义相似召回。
- 选型不应只盯索引算法,还要综合考虑数据建模、权限、接口习惯和团队工程风格。
下一篇
如果说 Qdrant 和 Weaviate 代表的是更成熟的服务化向量产品,那么下一篇将进一步走向重型生产架构:Milvus 如何把向量检索做成分布式系统,以及 Collection、Partition 与 Helm 部署分别承担什么角色。
参考资料
版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。
(采用 CC BY-NC-SA 4.0 许可协议进行授权)
本文标题:Qdrant与Weaviate
本文链接:https://www.sshipanoo.com/blog/ai/vector-db/06-Qdrant与Weaviate/
本文最后一次更新为 天前,文章中的某些内容可能已过时!