ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring AI + Qdrant + RAG:Java 知识库问答系统实战指南

Spring AI + Qdrant + RAG:Java 知识库问答系统实战指南 1. 项目核心价值拆解为什么“Spring AI 向量数据库 RAG”是当下最值得复现的组合先聊一个很多 Java 开发者都会有的困惑现在 AI 相关的技术栈Python 那边已经卷得不行了LangChain、LlamaIndex 一套接一套Java 这边是不是已经跟不上节奏了说实话这个问题在一年前问答案可能还带点悲观色彩但放到现在Spring AI 这个项目的出现已经非常明确地把答案改写了——Java 生态不仅没有掉队反而凭借 Spring Boot 积累的工程化能力走出了一条更适合企业落地的 AI 集成路线。这个标题里藏着的核心关键词有三个Spring AI、向量数据库、RAG。把它们串起来的场景非常典型——“搜索扩展”。你可能见过很多知识库问答系统、智能客服、企业级文档检索工具这些产品背后的技术骨架基本就是这三样东西。Spring AI 负责把大模型接入 Java 工程向量数据库负责存储和检索“语义相似的内容”RAG 则解决大模型“只会背书、不会查资料”的问题。三者组合起来效果就是让 AI 能基于你私有的文档、知识库、历史工单给出有依据的回答而不是张嘴就编。这篇文章适合谁来读我建议这么划分如果你是 Java 后端开发者想快速理解“AI 应用到底怎么落地到现有业务系统”这篇文章能帮你建立一条从零到一的技术路径如果你已经用 Python 栈做过一些 RAG 项目这篇文章也可以用 Spring 生态的视角帮你对比一下两套方案的工程差异哪怕你只是对“向量数据库是什么”有概念性好奇后文也会用尽量不打官腔的方式把这个东西讲透。需要提前说明的是这篇是“上篇”我会先把整体架构、向量数据库的选型与安装、Spring AI 工程初始化这三块基础层的内容讲清楚下篇再重点拆 RAG 的完整链路、Prompt 模板设计、以及如何把它接进一个真实的知识库项目里。现在直接开始。2. 整体架构与设计思路先弄明白 RAG 到底在解决什么问题2.1 大模型的“固有缺陷”和 RAG 的出现逻辑要理解为什么需要 RAGRetrieval-Augmented Generation检索增强生成得先正视大模型的一个天然短板它的知识截止时间是固定的而且它对自己的“不知道”毫无感知。你问它“我这个项目的部署文档里运维手册第 12 页写的告警阈值是多少”它大概率会一本正经地给你编一个数字。这不是它故意骗人而是它的训练目标就是“生成最流畅合理的文本”而不是“从某份私有文档里精确查找某条信息”。那 RAG 的做法是什么思路其实不复杂——先把你的私有文档拆成小段转成向量存进向量数据库当用户提问时先把问题也转换成向量然后到数据库里找“语义上最接近的几段内容”把这些检索出来的文本片段拼进 Prompt 里再让大模型基于这些片段作答。这样一来大模型不需要“记住”你的私有知识它只需要学会“阅读理解”把喂给它的参考材料组织成通顺的答案。这个设计思路最大的好处是不微调模型、不重新训练、不暴露原始文档结构只需要一个向量检索模块和一个可控的 Prompt 模板就能让大模型“借用”外部知识来回答问题。对于企业场景来说这意味着知识更新只需要重新跑一遍文档入库流程成本远低于微调。2.2 为什么 Spring AI 适合用来做这件事你可能会问这套流程 Python 也能做为什么非要拉上 Spring我的看法是如果只是做一个技术 DemoPython 确实更快但如果目标是“把 AI 能力嵌进一个已经有用户体系、权限模型、日志链路、监控告警的 Java 微服务系统里”Spring AI 的优势就非常明显了。Spring AI 在设计上借鉴了 Spring 生态一贯的抽象思路它对大模型提供商做了统一封装你可以在 OpenAI、Azure OpenAI、Ollama、通义千问等之间切换而业务代码只需要面向 ChatClient 或 ChatModel 接口编程不需要关心底层 API 差异。这和当年 JDBC 统一数据库访问、Spring Data 统一持久层操作的思路如出一辙。再加上它对向量存储也做了 Store API 抽象使得“换一个向量数据库”不再是一场伤筋动骨的改造。另外一个容易被忽略的点是Spring AI 提供了比较完整的 Observability 支持可以和 Micrometer Tracing、Spring Boot Actuator 配合把每次 AI 调用的 token 消耗、耗时、检索命中情况暴露为指标。这点在老牌 Java 团队里非常加分因为不是所有团队都能接受“引入一个新技术却变成一个监控黑洞”的代价。2.3 方案选型自己写、LangChain4j 还是 Spring AI我知道很多人看到“Java 做 AI”会想到另一个框架 LangChain4j。这个框架也不错社区很活跃API 风格更贴近 Python 的 LangChain早期在 Java 圈子里普及度比 Spring AI 还高。那为什么我推荐先用 Spring AI主要基于三点考虑。第一Spring AI 是 Spring 官方项目版本迭代节奏和 Spring Boot 的兼容性有官方保证。不像第三方框架遇到 Spring Boot 大版本升级容易出现适配滞后。第二Spring AI 的抽象层级更适合从零开始做 RAG 的团队它把 embedding 模型、向量存储、ChatModel、Prompt 模板这些环节都收敛到 Spring 容器里依赖注入、配置绑定、自动装配这些 Boot 特性全部继承下来写起来更“Spring”。第三从长期维护角度看官方项目更容易吸引周边生态配套比如 spring-ai-alibaba 这类国内适配项目也在最近开始活跃起来后面接入国产大模型会更方便。当然LangChain4j 也不是没有优势它的组件命名更贴近 LangChain 原版迁移成本低如果你是从 Python 转过来的看它的文档会觉得更亲切。所以我的建议是新项目、以 Spring Boot 为主的技术栈优先试 Spring AI如果你在维护老项目顺便想加一点 AI 能力LangChain4j 作为轻量集成也完全可以。下文的实操都是基于 Spring AI 展开。3. 向量数据库选型Qdrant、Milvus 和 Redis 的取舍3.1 向量数据库到底在库里干了什么在动手安装之前有必要先把“向量数据库”这个黑盒拆开看一眼。它的核心工作只有两件事存向量、找相似。听起来简单但难点在“找相似”的规模和速度上——如果你的知识库拆出了十万段文本那就意味着有十万条高维向量你要在几十毫秒内找出与查询向量最相近的 TopK 条这需要专门的索引结构而不是普通的 B 树能搞定的。常见的索引类型有 HNSWHierarchical Navigable Small World、IVFInverted File、DiskANN 等。HNSW 是当前使用最广泛的方案之一原理通俗理解就是把向量点组织成一张多层图高层图负责快速跳到“大概正确的区域”底层图负责精确定位既保证召回率又控制响应延迟。所以你选向量数据库的时候第一件要确认的事就是它支不支持 HNSW 索引、参数可调不可调、构建索引的速度能不能接受。另外一些向量数据库还支持 Metadata 过滤也就是把“只在这批文档里检索”这种条件压到查询层。这个功能放在 RAG 项目里非常实用比如多租户场景下租户 A 的知识不能混进租户 B 的回答里就可以通过 metadata 过滤在检索时做隔离。3.2 Qdrant 的特色和最推荐的落地方式Qdrant 是目前我比较偏爱的一个选择原因有几个它用 Rust 写的性能出众部署方式极其简单一个 Docker 容器就能跑起来它提供了完整的 REST API 和 gRPC 接口Java 客户端也维护得不错更关键的是它对 metadata 过滤支持得非常好向量相似度检索可以叠加复杂的过滤条件这一点很契合企业场景。Qdrant 的安装方式相当友好。如果你的机器已经有 Docker一条命令就能启动一个带 Web 控制台的实例docker run -p 6333:6333 -p 6334:6334 \ -v $(pwd)/qdrant_storage:/qdrant/storage \ qdrant/qdrant如果你是在本地开发环境也可以直接下载二进制包解压运行。下载地址去官方 GitHub Releases 页面找对应平台的包即可。启动后默认开放两个端口6333 是 HTTP API6334 是 gRPC。浏览器访问http://localhost:6333/dashboard可以看到管理界面用来查看 collection 状态、测试查询都很方便。启动容器后我会建议立刻创建一个 collection把向量长度定下来。这个长度不是随便定的它取决于你选的 embedding 模型——比如 OpenAI 的 text-embedding-3-small 输出 1536 维Ollama 里常见的 nomic-embed-text 输出 768 维。collection 的向量维度必须和模型的输出维度一致否则插入数据时会直接报错。3.3 Milvus、Redis 和轻量级方案怎么选Milvus 是另一个很主流的开源向量数据库功能更强支持分布式部署、多种索引类型、混合查询适合数据量非常大、并发要求非常高的场景。代价是架构更复杂组件多coordinator、proxy、query node、data node 等一堆角色本机调试起来没有 Qdrant 那么轻。如果你的项目刚起步知识库就几千几万段文档这个重量级有点浪费。Redis 也很值得注意。新版本的 Redis 有了 Redis Stack内置了向量检索能力。如果你系统里本来就依赖 Redis 做缓存再让 Redis 顺手承担向量检索确实能省掉一个中间件。不过要客观看待Redis 的向量检索在功能丰富度上比如过滤条件、索引类型、标量字段混合查询和专用数据库还是有差距。我的建议是实验阶段可以用 Redis 快速跑通但如果你要做的知识库会持续增长还是把它换回更专业的存储。还有一个方向是用纯 Java 的内嵌式方案比如在内存里用 CosineSimilarity 做暴力检索只适合几百条数据的技术验证。当年我做第一个 RAG Demo 就这么干过写起来快但数据一多就崩毫无工程价值。所以后续内容默认基于 Qdrant 展开因为它在“上手难度”和“工程可用性”之间平衡得最好。3.4 向量维度、距离度量和相似度阈值怎么定创建一个 collection 时除了维度还要选择距离度量方式。Qdrant 里常用的有 Cosine、Dot、Euclid。绝大多数文本向量场景首选 Cosine 相似度它的特点是只关注向量方向、不关注模长而 embedding 模型生成的向量通常对“语义方向”更敏感。Dot点积在向量已经做过归一化处理时和 Cosine 等价但通常你无法保证所有模型都输出归一化向量所以不较真的话就选 Cosine。关于相似度阈值这里容易踩坑。很多人会定一个所谓的“最低分”低于这个分数就不算命中。但实际上不同 embedding 模型的分数分布差异很大同一个模型在不同文档集上分数也完全不同。我常用的做法是先跑几十条真实测试 query打印出命中分数的分布再结合业务可接受度定阈值。比如发送一个完全不相关的 query看它的最高得分是多少——这个值往往就是“噪音基线”你定的阈值要明显高于它否则 RAG 会把无关内容也喂给大模型。4. Spring AI 工程初始化从一张白纸到可调通接口4.1 创建项目与依赖配置我默认你用的是 Spring Boot 3.x 及以上版本Spring AI 目前也是基于 Boot 3 设计的。建议直接用 Spring Initializrstart.spring.io生成一个基础项目Java 版本选 17 或 21依赖先只加 Web 和 Spring AI 相关项。Spring AI 的依赖坐标要注意版本一致性。比如当前常用的是dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version1.0.0/version typepom/type scopeimport/scope /dependency然后在 dependencies 里添加你需要的模块dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-qdrant-store/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-openai/artifactId /dependency如果你用的是 Ollama 本地模型只需要把 OpenAI 依赖换成spring-ai-ollama配置方式类似。有一点要强调Spring AI 的版本更新很快不同小版本的 API 可能有细微差异如果官网示例跑不通很有可能是版本不匹配——这时候直接打开对应版本的官方文档或源码对照比到处搜索有效得多。4.2 配置核心参数与理解 Bean 装配逻辑在application.yml里核心要配置两块模型相关和向量库相关。以 OpenAI Qdrant 为例spring: ai: openai: api-key: ${OPENAI_API_KEY} chat: options: model: gpt-4o-mini temperature: 0.7 embedding: options: model: text-embedding-3-small vectorstore: qdrant: host: localhost port: 6333 collection-name: knowledge_base这里面的关键设计是Spring AI 把 ChatModel、EmbeddingModel、VectorStore 都抽象成了 Spring Bean你只要在配置里声明好参数容器就会自动装配好。使用时直接在 Service 里注入即可不用自己管连接池、不用手工建 HTTP 客户端。有一点要特别提醒api-key这类敏感信息千万别硬编码在 yml 里用环境变量占位符是底线。另外temperature在 RAG 场景下建议不要调太高因为它控制的是“胡说八道的程度”知识库问答更看重稳定和忠实0.30.7 是比较常见的选择。4.3 编写最基础的向量写入与检索代码配置完成后可以写一个非常简单的 Service 来验证链路通不通。先创建一个文档入库的方法Service public class KnowledgeService { private final VectorStore vectorStore; private final EmbeddingModel embeddingModel; public KnowledgeService(VectorStore vectorStore, EmbeddingModel embeddingModel) { this.vectorStore vectorStore; this.embeddingModel embeddingModel; } public void addDocument(String content, String documentId) { Document doc Document.builder() .text(content) .metadata(Map.of( documentId, documentId, createTime, System.currentTimeMillis() )) .build(); vectorStore.add(List.of(doc)); } }这段代码做的就是“把一段文本塞进向量库”Spring AI 内部会自动调用 embedding 模型把 text 转成向量再连同 metadata 一起存进 Qdrant。注意这里我没手动调 embeddingModel因为VectorStore.add()会自动完成这个步骤。metadata 很有用后面做文档级过滤、删除单条文档都会用到。再来写一个检索方法public ListDocument search(String query, int topK) { SearchRequest request SearchRequest.builder() .query(query) .topK(topK) .similarityThreshold(0.5) .build(); return vectorStore.similaritySearch(request); }similarityThreshold就是前面聊到的相似度阈值建议先设低一点跑通再根据实际效果调高。topK表示返回几条一般 RAG 场景取 35 条就够太多会影响大模型的注意力反而降低回答质量。到这里Spring AI 的“最小可用链路”已经打通一段文本进库一个 query 检索回来。下一步就是把它组织成 RAG 的完整流程这部分放在下篇展开。不过在这之前有几个我在实际调通链路时踩过的坑非常值得先分享出来。5. 实操中的常见问题与排查心得5.1 向量维度不匹配的问题这是新手最容易遇到、报错信息又比较隐晦的问题。你创建 Qdrant collection 时定的维度是 768但实际 embedding 模型输出是 1536插入第一条数据时 Qdrant 会直接拒绝写入报错类似 “Vector dimension mismatch”。解决办法是要么删除重建 collection要么在创建 collection 之前确认好模型的输出维度。怎么确认一个模型输出多少维最简单的方法是先在本机跑一段代码把任意一段文本传给 embeddingModel打印返回向量的长度EmbeddingResponse response embeddingModel.embedForResponse(List.of(test)); int dim response.getResult().getOutput().size(); System.out.println(dim);我在刚接触 Spring AI 时就是因为没做这步确认来回折腾了好几次集合重建。后来我习惯把“向量维度”写成一个配置常量和 collection 创建脚本共用避免两处手写不一致。5.2 Qdrant 容器启动后连不上如果你用 Docker 启动了 Qdrant但 Spring 应用一直报连接超时先别急着查代码。大概率是端口没映射对或者容器启动失败。排查顺序建议是先docker ps看容器状态再确认映射端口因为新版 Qdrant 同时暴露 6333 和 6334偶发遇过只映射了 HTTP 而代码里用的是 gRPC导致连不上最后用 curl 直接打一下 API 验证curl http://localhost:6333/collections能看到 JSON 响应就说明服务没问题。剩下再看 Spring 的 host/port 配置是否对应。5.3 检索结果看起来很相关但大模型回答依旧不对这个坑更隐性。很多情况下向量检索确实把正确文档捞回来了但大模型给出的回答还是不行。问题可能出在 Prompt 模板上——如果你只是简单把文档拼接在问题后面没有告诉大模型“只能根据参考内容回答不能自行发挥”它依然会走自己最习惯的“自由生成”路线。Spring AI 支持用 PromptTemplate 来组织系统提示词和用户消息我给出的参考模板下文会提到但先记住这个原则Prompt 必须明确来源边界。比如在系统提示里加一句“如果参考内容无法回答问题请直接说明‘未在知识库中找到相关信息’”。这一句话就能显著减少幻觉回答。5.4 关于版本选择与官方文档的“正确打开方式”Spring AI 还在快速迭代期官方文档偶尔会滞后于代码或者示例用了旧版 API。我踩过几次坑后的经验是优先看 GitHub 仓库的main分支示例代码而不是搜索引擎搜到的旧文章。尤其是VectorStore相关 API版本间变化比较大搜索引擎里很多文章还是上一个版本的写法直接复制会报编译错误。另外一个建议是每次升级 Spring AI 版本都去看 Release Notes 里有没有 breaking change。比如早期版本的SearchRequest.build()参数顺序、相似度阈值字段名称都可能和现在不同。做这种新生态项目要有“今天写的代码半年后可能要改”的心理准备。6. 下一步RAG 完整链路还能怎么做到目前为止我们已经把 Spring AI、Qdrant、文档写入和向量检索这几块地基打好了。但这只是 RAG 的前半段——真正的“检索增强生成”流程还要把这些检索结果交给大模型经过 Prompt 组织、上下文压缩、回答生成才算完整闭环。这也是我在下篇里将要展开的重点。我可以先预告一下整体流程图景用户 query 进入后端先向量化再到 Qdrant 检索相关文档块取回 TopK随后用 PromptTemplate 把用户 query 和文档块拼成完整 Prompt交给 ChatModel 生成答案最后把答案和引用文档块一起返回给前端。这个流程还会涉及几个细节问题文档如何分块chunk size 定多少比较合理、metadata 如何设计实现文档级权限隔离、如何把引用来源展示给用户提升可信度以及如何评估 RAG 效果而不只是“看着还行”。此外如果你想在这个基础之上做一个更完整的知识库系统还可以考虑加入 Agent 能力。Spring AI 里的ChatClient已经支持工具调用function calling这意味着 AI 可以自主决定“要不要调用一次向量检索”“要不要先查数据库再回答”。基于 RAG 的智能体就是一个把检索工具、模型决策、业务动作串起来的典型应用场景。这个话题如果展开可以单独写一篇我计划后续在专栏里继续更新。如果你现在正准备动手搭建自己的 RAG 知识库我建议把注意力先放在“把链路跑通”上不要一上来就研究各种高级特性。毕竟 RAG 的工程难点不在单个环节而在于把各个环节串起来之后如何保证回答的稳定性、检索的准确性和用户的可信度。先把地基打牢后面的一切都好说。
返回列表