Java RAG 实战(第 7 篇):使用 Qdrant 保存和检索向量
系列导航
- 所属专栏:《Java 开发者从零实现 RAG 知识库》
- 学习位置:第 7 篇 / 共 12 篇
- 上一篇:《第6篇:Markdown 知识库与 RAG》
- 下一篇:《第8篇:Spring Boot RAG 查询 API》
上一篇已经用内存检索跑通 RAG。本篇把 Chunk 向量预先保存到 Qdrant,并通过官方 Java SDK 完成 Top-K 搜索。
完成进度
- 1. 用 Docker 启动 Qdrant
- 2. 创建 1024 维 Cosine Collection
- 3. 理解 Point、Vector 和 payload
- 4. 把 Markdown Chunk 写入 Qdrant
- 5. 使用 Java SDK 完成 Top-K 查询
- 6. 验证相似度阈值会拦截无关资料
为什么要学
内存版每次运行都要重新读取所有 Chunk 并生成全部向量,知识量增加后会浪费时间和计算资源。Qdrant 可以持久化 Point,并使用专门的向量索引快速查找相似内容。
引入 Qdrant 后,查询阶段只需要向量化用户问题,再从数据库取回相关 Chunk。它负责保存和检索知识,最终答案仍由 qwen3 根据 Prompt 生成。
第 1 步:启动 Qdrant
第一次启动:
dockerrun-d\--nameqdrant-study\-p6333:6333\-p6334:6334\-vqdrant-study-data:/qdrant/storage\qdrant/qdrant:v1.19.0以后容器停止后只需执行:
dockerstart qdrant-study这里的两个端口用途不同:
6333:REST API,适合使用curl查看状态。6334:gRPC,当前 Java 官方 SDK 通过它连接 Qdrant。
确认服务正常:
curl-shttp://localhost:6333/collections|jq第 2 步:创建 Collection
bge-m3生成 1024 维向量,所以 Collection 的向量维度也必须是 1024:
curl-XPUT http://localhost:6333/collections/kubernetes_chunks\-H'Content-Type: application/json'\-d'{ "vectors": { "size": 1024, "distance": "Cosine" } }'检查配置:
curl-shttp://localhost:6333/collections/kubernetes_chunks|jq'{ status: .result.status, points_count: .result.points_count, vector_size: .result.config.params.vectors.size, distance: .result.config.params.vectors.distance }'第 3 步:理解 Point 和 payload
Qdrant 中的一条 Point 包含三部分:
Point ID:唯一编号,例如 2 Vector: bge-m3 生成的 1024 个数字 Payload: title、content、source 等原始资料Vector 用来计算相似度,payload 用来把命中结果还原成人能阅读的知识。RAG 最终交给大模型的是 payload 中的正文,不是 1024 个数字。
本篇的固定示例使用简单数字 ID 方便观察。后面动态入库时,会改用documentId + chunkIndex生成稳定 UUID,避免更新文档时不断产生重复 Point。
第 4 步:把 Markdown 写入 Qdrant
在仓库根目录运行:
mvn-f04-qdrant-rag/pom.xml compile exec:java\-Dexec.args=--index入库流程是:
MarkdownKnowledgeLoader按二级标题拆分文档。OllamaEmbeddingClient用bge-m3生成 Chunk 向量。QdrantPointMapper把向量、标题、正文和来源组成 Point。QdrantIndexer通过官方 SDK 的 gRPC 接口执行 upsert。
检查 Point 数量:
curl-shttp://localhost:6333/collections/kubernetes_chunks|\jq'.result.points_count'当前示例应输出4。
第 5 步:理解搜索代码
查询阶段不再读取并重新向量化全部 Markdown,只生成用户问题的一个向量:
QdrantVectorSearch.search(...) ↓ EmbeddingClient.embed(List.of(query)) ↓ QdrantSearchMapper.toRequest(...) ↓ QdrantClient.searchAsync(...) ↓ QdrantSearchMapper.toResult(...)搜索请求中的关键参数:
limit = 2:最多返回两个结果,也就是 Top-2。scoreThreshold = 0.60:低于 0.60 的结果不返回。withPayload = true:要求结果带回标题、正文和来源。
Qdrant 已经按相似度从高到低返回结果,Java 不需要再自己遍历所有向量计算余弦相似度。
对应源码:
04-qdrant-rag/src/main/java/com/example/ai/rag/QdrantVectorSearch.java 04-qdrant-rag/src/main/java/com/example/ai/rag/QdrantSearchMapper.java查询方法的核心只有三步:
double[]queryEmbedding=embeddingClient.embed(List.of(query)).get(0);SearchPointsrequest=mapper.toRequest(collectionName,queryEmbedding,limit,minimumScore);List<ScoredPoint>points=searchOperation.search(request);returnpoints.stream().map(mapper::toResult).toList();mapper.toRequest(...)会把教程中的三个检索要求真正写入 Qdrant SDK 请求:
returnSearchPoints.newBuilder().setCollectionName(collectionName).addAllVector(vector).setLimit(limit).setScoreThreshold((float)minimumScore).setWithPayload(WithPayloadSelector.newBuilder().setEnable(true).build()).build();withPayload = true不能省略。如果只返回 Point ID 和分数,Java 就无法取回要放入 Prompt 的标题和正文。
第 6 步:运行 Qdrant 版完整 RAG
确认 Ollama 和 Qdrant 已启动,然后执行:
mvn-f04-qdrant-rag/pom.xml compile exec:java知识库内问题的示例输出:
问题:Pod 重建后 IP 会变化,应该怎样提供稳定访问地址? Qdrant 检索结果(最低相似度 0.60): 1. Service,相似度 0.7388,Point ID 2 AI 回答: 可以通过 Service 为 Pod 提供稳定的集群内访问地址和负载均衡。也可以传入自己的问题:
mvn-f04-qdrant-rag/pom.xml compile exec:java\-Dexec.args='密码、令牌和证书应该保存在哪里?'第 7 步:观察阈值保护
查询知识库没有覆盖的内容:
mvn-f04-qdrant-rag/pom.xml compile exec:java\-Dexec.args='Ingress 应该怎样配置 TLS 证书?'如果没有结果达到0.60,程序不会调用聊天模型,而是直接输出:
根据现有资料无法确定。这可以减少无关上下文和不必要的模型调用,但阈值不是越高越好。过高会漏掉有用资料,过低会把无关资料交给模型,需要根据真实问题和知识库评估后调整。
第 8 步:运行 Qdrant 模块测试
mvn-f04-qdrant-rag/pom.xmltest测试会隔离外部 Ollama 和 Qdrant,重点验证:
double[]是否正确转换成 Qdrant 的 float 向量。- Collection、Top-K、阈值和 payload 开关是否正确。
- Qdrant 返回值是否正确还原成 Chunk。
- 搜索过程是否只向量化用户问题。
常见问题
Connection refused: localhost:6334
Qdrant 没有启动,执行:
dockerstart qdrant-studydockerps--filtername=qdrant-studyCollection 不存在
先执行“第 2 步”创建kubernetes_chunks,然后再运行入库程序。
Collection 是空的
Collection 只定义了存储结构,还没有资料。使用“第 4 步”的--index模式完成入库。
向量维度错误
Embedding 模型与 Collection 配置不一致。当前bge-m3输出 1024 维,因此 Collection 的size必须是 1024。
成功标准
kubernetes_chunks状态为green,并且有 4 个 Point。03-markdown-rag能使用内存检索完成一次 RAG 回答。- 默认问题能检索到
Service。 - 知识库外问题不会调用
qwen3:14b。 mvn -f 04-qdrant-rag/pom.xml test全部通过。
下一步
下一阶段可以把命令行程序改造成 Spring Boot HTTP 服务,让浏览器或其他应用通过接口提交问题并获得 RAG 回答。
本篇自测
- Qdrant Point 中的 Vector 和 payload 分别有什么用?
- 查询时为什么只需要向量化用户问题?
- Top-K 和相似度阈值有什么区别?
- Java SDK 为什么连接 6334,而
curl通常访问 6333?
参考答案:Vector 用于相似度计算,payload 用于还原正文;知识向量已经在入库时保存;Top-K 限制最多返回数,阈值排除低分结果;6334 是 gRPC,6333 是 REST。
本篇小结
- Qdrant 预先保存 Chunk 向量,查询时无需重新向量化全部知识。
- Collection 维度必须和 Embedding 模型一致,
bge-m3对应 1024 维。 - 一条 Point 由 ID、Vector 和 payload 构成,Java 通过官方 SDK 执行写入和搜索。
- Top-K 与最低相似度共同控制进入 Prompt 的资料数量和质量。
下一篇
👉 本专栏下一篇:《第8篇:Spring Boot RAG 查询 API》
完整代码都在 GitHub(欢迎 Star ⭐)
本专栏的全部示例代码都已开源,包含 5 个可独立运行的 Maven 模块、自动化测试和完整分篇教程。建议Fork / Clone下来,边读边跑:
🔗 https://github.com/bysbsh/ai-rag-learning-guide
- 代码与教程同步更新,对照每一篇动手实践效果最好。
- 如果这份教程帮到了你,点个Star就是对我最大的支持,也方便你之后找回最新版本。
- 遇到问题或发现错漏,欢迎在仓库提 Issue / PR。项目采用 MIT 协议,可自由学习与二次创作。
