Qdrant数据写进去了却搜不到
最近在做的系统中集成RAG,需要将商品知识库、物流知识库写入Qdrant,实现RAG检索。
技术栈:
Spring Boot
LangChain4j
Qdrant
OpenAI Embedding
整体流程非常简单:
文档
↓
Chunk切分
↓
Embedding
↓
Qdrant存储
↓
向量检索
↓
LLM生成
结果测试时,出现了一个非常奇怪的问题。
问题现象
数据写入完全正常。
Qdrant后台也能看到数据:
{
"id": 1,
"vector": [...]
}
插入日志:
INFO Insert 5000 points success
但是查询始终为空:
List<ScoredPoint> result = client.search(...)
返回:
[]
完全搜不到数据。
第一轮排查
怀疑Embedding有问题。
打印向量长度:
Embedding embedding =
embeddingModel.embed(text).content();
System.out.println(
embedding.vector().length
);
结果:
1536
正常。
再查看Qdrant Collection:
curl localhost:6333/collections/docs
返回:
{
"vectors": {
"size": 1536
}
}
维度一致。
排除Embedding问题。
第二轮排查
怀疑相似度阈值过高。
代码:
SearchParams.newBuilder()
.setHnswEf(128)
.build();
搜索:
scoreThreshold=0.8
改成:
scoreThreshold=0.3
还是查不到。
第三轮排查
开始怀疑数据没写进去。
查看point数量:
curl localhost:6333/collections/docs
结果:
{
"points_count": 5000
}
确实存在。
甚至通过ID查询:
retrieve(1)
也能返回数据。
说明:
数据存在
向量存在
Collection存在
但就是搜不到。
真正原因
最后查看官方文档才发现问题。
创建Collection时:
VectorParams.newBuilder()
.setSize(1536)
.setDistance(Distance.Cosine)
后来项目切换Embedding模型:
bge-large-zh
向量维度变成:
1024
但是Collection没有重建。
此时出现:
Qdrant Collection: 1536维
Embedding结果: 1024维
写入时因为封装层没有显式报错。
查询时:
1536 vs 1024
向量根本不在同一个空间。
自然检索不到结果。
为什么会这样?
这里涉及Qdrant底层原理。
Qdrant本质上是:
向量ID
↓
向量数组
↓
HNSW索引
例如:
[0.12,0.33,0.55...]
HNSW建立的是固定维度空间。
比如:
1536维空间
所有向量必须:
长度完全一致
否则无法计算:
Cosine Similarity
Euclidean Distance
Dot Product
因为:
A=[1,2,3]
B=[1,2]
数学上根本无法求距离。
所以Qdrant要求:
Collection创建后
Vector Size不可变
深入源码
Qdrant创建Collection时:
message VectorParams {
uint64 size = 1;
Distance distance = 2;
}
对应:
CreateCollection.newBuilder()
.setCollectionName("docs")
.setVectorsConfig(...)
最终保存到:
Collection Config
后续所有Point都会校验:
vector.length == size
一旦不一致:
Wrong input vector dimension
或者某些SDK直接吞掉异常。
如何避免
方案一:启动时校验
项目启动时读取Collection信息:
CollectionInfo info =
client.getCollection("docs");
检查:
info.getConfig()
.getParams()
.getVectors()
.getSize();
与Embedding模型配置对比:
1536 == embeddingDimension
不一致直接启动失败。
方案二:Collection版本化
不要复用Collection。
例如:
knowledge_v1
knowledge_v2
knowledge_v3
Embedding模型升级:
text-embedding-3-small
↓
bge-large-zh
直接创建:
knowledge_v4
重新构建索引。
方案三:记录Embedding模型
Payload中增加:
{
"model":"bge-large-zh"
}
检索异常时快速定位:
到底是哪批数据生成的Embedding
后续引出的Qdrant原理
这个问题解决后,又顺带理解了几个以前没注意的知识点:
Collection为什么不能随便改维度
因为HNSW索引是基于固定维度建立的。
修改维度意味着:
整个索引失效
必须重建。
为什么Embedding模型不能随便切换
因为不同模型生成的是不同向量空间:
OpenAI
↓
1536维
BGE
↓
1024维
Jina
↓
768维
即使维度相同:
语义空间也不同
老数据和新数据混存会导致召回效果急剧下降。
为什么RAG要做全量重建
很多团队升级Embedding后发现:
检索命中率下降
根本原因往往不是Qdrant。
而是:
旧Embedding
+
新Embedding
混在同一个Collection里。
这个案例在真实项目里非常常见,而且很有迷惑性,因为:
数据能写进去
Collection也存在
没有明显报错
查询结果却永远为空
最后定位下来并不是Spring问题,也不是LangChain4j问题,而是Qdrant最核心的设计原则——一个Collection只能对应一个固定维度、固定语义空间的Embedding模型