mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
2263 words
6 minutes
RAG 基础(二):用 Qdrant 保存和检索向量
2026-08-08

RAG 基础(二):用 Qdrant 保存和检索向量#

上一篇介绍了 bge-m3:它能把商城规则和用户问题转换成 1024 维语义向量。

接下来还有两个问题:

几十万组向量放在哪里?
如何快速找到与问题向量最相似的几组?

这正是 Qdrant 的工作。

1. 为什么普通数据库查询不够#

MySQL 很擅长精确条件查询:

SELECT * FROM product WHERE id = 100;

也能做关键词匹配:

SELECT * FROM knowledge WHERE content LIKE '%退货%';

但用户可能问“收到商品后不想要了怎么办”,文档写的是“签收后可以申请退货”。两边缺少完全相同的关键词,却具有相近语义。

向量数据库查询的是:

哪些文档向量与问题向量最接近?

Qdrant 就是面向这种相似度检索设计的数据库。

2. Qdrant 的四个核心概念#

Collection#

Collection 类似关系数据库中的表,是一组可以共同检索的 Point。

创建 Collection 时需要定义向量配置:

{
"vectors": {
"size": 1024,
"distance": "Cosine"
}
}

同一向量空间里的 Point 必须遵循相同维度与距离配置。

Point#

Point 是 Qdrant 的基本记录,由 ID、Vector 和可选 Payload 组成:

{
"id": "稳定的片段 ID",
"vector": [0.021, -0.034, 0.008],
"payload": {
"text": "商品签收后七天内可以申请退货",
"title": "退换货政策",
"source": "return-policy.md"
}
}

Vector#

Vector 是 Embedding 模型生成的数字表示,用于计算相似度。Qdrant 支持稠密向量、稀疏向量和多向量,AgentShop 第一版使用 bge-m3 的稠密向量。

Payload#

Payload 是附加在 Point 上的 JSON 元数据,可以保存原文、标题、来源、租户、分类和版本等信息。

Vector 负责“找得像不像”,Payload 负责“这条知识具体是什么,以及能否在当前条件下使用”。

3. Qdrant 如何找到近邻#

数据量小时,可以逐个计算问题向量和所有文档向量的相似度,这叫 Full Scan。

数据量增大后,逐个比较成本太高。Qdrant 的稠密向量索引使用 HNSW:它构造多层图结构,让查询从稀疏的上层开始快速接近目标区域,再逐层寻找更近的节点。

它属于近似最近邻检索:用少量召回精度交换显著的查询速度。

常见参数包括:

  • m:图中每个节点连接边的数量,越大通常召回更好,但占用更多内存;
  • ef_construct:构建索引时考虑的邻居数量;
  • ef:查询时的搜索范围,越大通常越准确但越慢。

第一版项目不需要急着调这些参数。先建立评测集,确认出现召回或性能瓶颈后再优化。

4. Cosine、Dot 与 Euclid 怎么选#

距离算法必须与 Embedding 模型和向量处理方式匹配。

  • Cosine:比较方向,常用于文本语义向量;
  • Dot:计算点积,归一化向量下与 Cosine 关系紧密;
  • Euclid:比较欧氏空间中的直线距离。

AgentShop 使用:

size = 1024
distance = Cosine

不要在同一个 Collection 中混用不同模型生成的向量,也不要在不重新评测的情况下随意更改距离算法。

5. 用 Docker 持久化运行 Qdrant#

最小 Docker Compose 配置:

services:
qdrant:
image: qdrant/qdrant
ports:
- "6333:6333"
- "6334:6334"
volumes:
- qdrant_storage:/qdrant/storage
volumes:
qdrant_storage:

其中:

  • 6333 通常用于 HTTP API 和 Web UI;
  • 6334 通常用于 gRPC;
  • /qdrant/storage 挂载 Volume,容器重建后数据仍然存在。

这与 InMemoryEmbeddingStore 的根本区别是:应用重启不需要重新计算全部向量,多实例也可以共享同一套知识库。

6. 在 AgentShop 中创建 Collection#

项目配置:

mall:
ai:
qdrant:
host: ${QDRANT_HOST:localhost}
grpc-port: ${QDRANT_GRPC_PORT:6334}
http-port: ${QDRANT_HTTP_PORT:6333}
collection-name: ${QDRANT_COLLECTION:mall_knowledge_bge_m3_v1}

Collection 名称中包含模型和版本信息:

mall_knowledge_bge_m3_v1

这比直接叫 knowledge 更容易迁移。当更换模型、切分策略或维度时,可以创建 v2,完成全量同步和评测后再切换。

项目启动时会检查 Collection:

Integer existingDimension = getVectorDimension(collectionName);
if (existingDimension == null) {
createCollection(collectionName, vectorDimension);
return;
}
if (!existingDimension.equals(vectorDimension)) {
throw new KnowledgeCollectionDimensionMismatchException(...);
}

如果期望 1024 维,而已有 Collection 是 768 维,系统直接报错,不让错误向量悄悄进入数据库。

7. LangChain4j 如何连接 Qdrant#

当前项目使用 QdrantEmbeddingStore

@Bean
public EmbeddingStore<TextSegment> knowledgeEmbeddingStore(
MallAiQdrantProperties properties) {
return QdrantEmbeddingStore.builder()
.host(properties.getHost())
.port(properties.getGrpcPort())
.useTls(properties.isUseTls())
.collectionName(properties.getCollectionName())
.build();
}

入库时,LangChain4j 将 EmbeddingTextSegment 一起写入 Qdrant。查询时再把命中的 Point 恢复为包含文本与 Metadata 的 Segment。

需要注意:LangChain4j 帮我们封装了客户端调用,但 Collection 维度、数据版本、稳定 ID、删除旧片段和同步状态仍然是应用自身的责任。

8. 一次检索发生了什么#

用户问题:

商品签收后怎么申请退货?

完整过程:

问题文本
↓ bge-m3
1024 维 queryVector
↓ Qdrant
在 mall_knowledge_bge_m3_v1 中检索
↓ Cosine + HNSW
返回 TopK Point
Text + title + source
作为参考上下文交给 DeepSeek

Qdrant 不理解“售后业务规则”,也不生成自然语言答案。它只根据向量与过滤条件找到最符合要求的 Point。

9. Payload 不只是用来显示来源#

一个知识 Point 可以包含:

{
"knowledgeKey": "return-policy",
"title": "退换货政策",
"source": "return-policy.md",
"chunkIndex": 2,
"contentHash": "...",
"status": "PUBLISHED"
}

Payload 有两个作用:

  1. 命中后把标题、原文和来源交给生成模型;
  2. 检索前按租户、状态、语言、权限或分类过滤。

当过滤字段使用频繁时,可以为它创建 Payload Index。官方文档建议只给实际用于过滤的字段建立索引,因为索引也会占用内存和磁盘资源。

对于多租户知识库,租户过滤不能只写在 Prompt 里,而应当成为 Qdrant 查询条件,避免不属于当前租户的内容先被召回。

10. 为什么还需要 MySQL#

既然 Qdrant 已经保存向量和 Payload,为什么 AgentShop 还用 MySQL 保存同步状态?

因为两者解决的问题不同:

Qdrant:相似度检索
MySQL:文档管理、版本、哈希、同步状态和失败记录

只依赖 Qdrant 很难直接回答:

  • 哪份 Markdown 最后一次何时同步?
  • 这次是新增、更新还是跳过?
  • 哪个文档同步失败?
  • 新版本有多少个 Chunk?
  • 内容未变化时是否需要重新 Embedding?

因此持久化 RAG 常常需要“业务数据库 + 向量数据库”,而不是二选一。

11. 稳定 Point ID 与增量同步#

知识同步不能每次都生成随机 Point ID,否则重复执行会不断产生重复片段。

AgentShop 使用这些字段生成稳定标识:

collectionName
+ knowledgeKey
+ contentHash
+ chunkIndex
→ stablePointId

文档内容不变时可以直接跳过;内容变化时先处理旧版本,再写入新版本。这样 Qdrant 中保存的是可解释、可替换的一组知识片段。

12. TopK 和 minScore 解决不同问题#

典型查询有两个限制:

  • maxResults / TopK:最多返回几条;
  • minScore:结果至少要多相关。

只设 TopK 的问题是:即使没有相关知识,Qdrant 仍然可以返回“最不差”的几条。

只设 minScore 的问题是:高分片段很多时,会给生成模型过多重复上下文。

因此通常同时使用:

先过滤低于 minScore 的结果
再保留前 TopK 条

阈值必须通过业务评测确定。分数不是概率,0.70 不代表答案有 70% 的正确率。

13. 生产环境还要考虑什么#

备份与恢复#

Docker Volume 只是持久化,不等于备份。仍然需要 Snapshot、异地保存和恢复演练。

认证与网络#

不要把 Qdrant 管理端口直接暴露到公网。生产环境应配置私有网络、API Key 或 TLS,并限制调用来源。

监控#

至少监控:

  • Collection 中 Point 数量;
  • 查询延迟和失败率;
  • 同步成功、跳过和失败数量;
  • 磁盘使用量;
  • Embedding 维度不匹配;
  • 无结果和低相关结果比例。

数据删除#

知识下线时必须同步删除对应 Point,不能只修改 MySQL 状态。否则旧规则仍可能被向量检索召回。

14. 常见误区#

Qdrant 会调用大模型生成回答#

不会。它保存和检索向量,回答由生成模型完成。

有了 Qdrant 就不需要保存原文#

错误。向量用于定位,原文才是交给 DeepSeek 的证据。

Collection 可以混存任意维度#

错误。向量配置必须匹配,换模型通常应新建版本化 Collection。

使用 Docker Volume 就等于有备份#

错误。Volume 解决容器重建后的持久化,不能替代备份与恢复方案。

相似度最高的结果一定正确#

错误。它只是候选中最相似的内容,还需要阈值、过滤、评测和生成阶段约束。

15. 本文总结#

Qdrant 在 RAG 中承担的是持久化向量检索:

bge-m3 生成 Vector
Qdrant Point = Vector + Payload
Cosine / HNSW 找到相似 Point
原文和来源交给 DeepSeek

最重要的四点是:

  1. Collection 固定向量维度和距离算法;
  2. Vector 用于相似度,Payload 保存原文和过滤字段;
  3. Qdrant 负责检索,MySQL 仍负责文档版本和同步管理;
  4. 持久化、备份、权限和删除是四件不同的事。

掌握 bge-m3 和 Qdrant 后,再看商城 Agent 的 RAG 入库与查询链路,就不再只是一串框架配置。

参考资料#

Share

If this article helped you, please share it with others!

RAG 基础(二):用 Qdrant 保存和检索向量
https://mizuki.mysqil.com/posts/qdrant-vector-database-basics/
Author
梦幻晨风
Published at
2026-08-08
License
CC BY-NC-SA 4.0

Some information may be outdated

Table of Contents