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 = 1024distance = 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:
@Beanpublic EmbeddingStore<TextSegment> knowledgeEmbeddingStore( MallAiQdrantProperties properties) { return QdrantEmbeddingStore.builder() .host(properties.getHost()) .port(properties.getGrpcPort()) .useTls(properties.isUseTls()) .collectionName(properties.getCollectionName()) .build();}入库时,LangChain4j 将 Embedding 和 TextSegment 一起写入 Qdrant。查询时再把命中的 Point 恢复为包含文本与 Metadata 的 Segment。
需要注意:LangChain4j 帮我们封装了客户端调用,但 Collection 维度、数据版本、稳定 ID、删除旧片段和同步状态仍然是应用自身的责任。
8. 一次检索发生了什么
用户问题:
商品签收后怎么申请退货?完整过程:
问题文本 ↓ bge-m31024 维 queryVector ↓ Qdrant在 mall_knowledge_bge_m3_v1 中检索 ↓ Cosine + HNSW返回 TopK Point ↓Text + title + source ↓作为参考上下文交给 DeepSeekQdrant 不理解“售后业务规则”,也不生成自然语言答案。它只根据向量与过滤条件找到最符合要求的 Point。
9. Payload 不只是用来显示来源
一个知识 Point 可以包含:
{ "knowledgeKey": "return-policy", "title": "退换货政策", "source": "return-policy.md", "chunkIndex": 2, "contentHash": "...", "status": "PUBLISHED"}Payload 有两个作用:
- 命中后把标题、原文和来源交给生成模型;
- 检索前按租户、状态、语言、权限或分类过滤。
当过滤字段使用频繁时,可以为它创建 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最重要的四点是:
- Collection 固定向量维度和距离算法;
- Vector 用于相似度,Payload 保存原文和过滤字段;
- Qdrant 负责检索,MySQL 仍负责文档版本和同步管理;
- 持久化、备份、权限和删除是四件不同的事。
掌握 bge-m3 和 Qdrant 后,再看商城 Agent 的 RAG 入库与查询链路,就不再只是一串框架配置。
参考资料
If this article helped you, please share it with others!
Some information may be outdated






