news 2026/9/26 13:51:54

Spring AI + Java + RAG:把知识库变成智能问答服务实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring AI + Java + RAG:把知识库变成智能问答服务实战

先交代一个我最近常被问到的场景:知识库已经有了,文档也整理得很整齐,接下来作为 Java 程序员还能做什么?很多人以为把文档塞进系统就算完事,但实际上,知识库只是原料,真正让用户用自然语言问出答案的,是 RAG 这套技术。我是做 Java 后端的,最近用 Spring AI 把团队里一个“沉睡”的 Wiki 变成了可问答的服务,这篇就围绕 Spring AI、Java、RAG 和知识库这四个关键词,把整个实战过程、踩过的坑和进阶思路写清楚。如果你已经有一个整理好的知识库,想在 Spring Boot 项目里接入智能问答,这篇文章里的方案可以拿来直接用。

1. 知识库和 RAG 的关系:先搞清楚你要解决什么问题

1.1 知识库不等于知识服务

先说一个我观察到的现象:团队里“知识库已经有了”和“知识服务可用”之间,往往隔着一层窗户纸。很多团队用 Obsidian、Wiki、Dify 甚至自研系统攒了一堆文档,文件摆得整整齐齐,但真正要找一个问题的答案时,还是要靠人肉翻目录、猜关键词,最后可能还得问老同事“那个文档到底放哪了”。

知识库本质上只是把分散的内容集中存放了,它解决了“东西丢不了”的问题,但没有解决“东西找得到、问得出”的问题。RAG 做的事,就是把最后这一层打通:用户提问,系统从知识库里检索相关内容,再由大模型组织成自然语言回答。这里有一个关键点——回答必须基于检索到的内容,而不是模型自己脑补,否则“知识库”就变成了“模型自由发挥”。

我用一个生活类比帮团队理解这件事:知识库像一座图书馆的藏书室,里面的书又多又全,但读者不知道哪本书里有答案;RAG 负责把问题在书架上定位到具体的几本书、翻到相关章节,再由一个“话痨”图书管理员把这些内容用口语总结给读者。藏书室是基础,但它不产生服务,服务是检索和生成一起完成的。

1.2 为什么在 Java 侧打通,而不是另起炉灶

我接触到的不少团队,知识库已经存在,但做智能问答的人第一反应是“用 Python 写一个独立服务”。站在技术尝鲜的角度,Python 生态确实有 LangChain、LlamaIndex 这些工具,RAG 相关的轮子也更丰富。但你回头看业务系统的现状:用户体系在 Spring Boot 里,权限体系在 Spring Boot 里,接口暴露也是 Spring Boot。

在一个成熟的 Java 团队里,为了一个问答功能引入第二套技术栈,等于给自己埋了运维和协作上的雷。更顺的路,是直接用 Spring AI 把它嵌进现有服务,复用已有的登录、鉴权、日志链路,让知识问答变成业务系统里的一个普通接口。这也是我把方案定位成“Spring AI RAG”而不是“Python RAG”的原因——不是哪个语言更牛,而是 Java 项目里用 Spring 生态本来就最顺。

1.3 Spring AI 在 RAG 链路里的角色

Spring AI 是 Spring 生态面向 AI 应用的一套抽象层,它不重新发明 RAG,而是把 RAG 链路里的组件都封装成了可替换的接口:EmbeddingModel 负责把文本向量化,VectorStore 负责存取向量,ChatModel 负责对话生成,QuestionAnswerAdvisor 负责把“检索 + 组装上下文 + 回答”串成一条流水线。

你只要搭好这套骨架,具体用哪家模型、哪款向量库,都是配置和注入的问题。对于已经会 Spring Boot 的 Java 程序员,学习成本基本被压到了最低。你要学的不是新框架,而是“RAG 各个环节的语义是什么、参数怎么调”,这一点我会在第 2 部分展开。

2. 拆解 RAG 链路:四个组件决定成败

2.1 Embedding:把文字变成坐标

Embedding 模型把一句文本映射成一组浮点数,维度从几百到几千都有,例如智谱的 embedding-2 是 1024 维,OpenAI 的 text-embedding-3-large 是 3072 维。它的核心特点是语义相近的文本,向量距离也近。你可以把它理解成给每句话做了一张“语义身份证”,两句话意思接近,身份证的坐标就挨得近。

这里要强调一个使用原则:知识库里的文档用什么模型向量化,检索时也必须用同一个模型。我见过有人切库时用 A 模型,上线时换了 B 模型,结果整个库的向量全部失效,重启服务后答案质量全崩,这个问题排查起来非常隐蔽。所以我把“Embedding 模型一致性”写成了团队的上线检查项,谁要换模型,必须重新构建一遍知识库索引。

在 Spring AI 里,Embedding 模型就是一个接口。官方实现里有 OpenAiEmbeddingModel、OllamaEmbeddingModel,智谱等国产模型厂商也给出了对应的 Spring AI 集成方式。业务代码统一面向 EmbeddingModel 编程,底层模型换了,上层代码不用动。

2.2 切块策略:别让大文档毁掉检索

为什么一定要切块?因为大模型上下文窗口再大,也不可能把整本手册塞进去,而且如果让模型从一千页文档里找答案,定位精度会非常差。切块的目的,是让知识库变成很多个小段落,检索时只取最相关的几段拼进 Prompt。

切块有两个核心参数:块大小和重叠区。我常用的起手式是块大小 500 个字符、重叠 80 个字符。500 字符约等于正常 300 字上下的一段话,语义完整性尚可;80 字符的重叠区可以减少前后文被切断导致的语义断裂。具体取值要根据文档类型调整:

  • 产品文档、FAQ:按标题层级切,优先保留章节语义,500 字符块起步没问题。
  • 代码示例、配置片段:块要小一些,200 到 300 字符,否则检索到半截代码根本没法用。
  • 表格数据:强烈建议表格单独作为一块,不要让切块器把表头和单元格拆散。

Spring AI 里提供了 TokenTextSplitter,也支持自定义 Splitter。我实践中比较推荐“按标题切 + 超长段落再二次切”这种组合方式。观察下来,固定长度切块在文档结构良好的知识库上效果不错,但如果你的文档结构混乱,先做一轮清洗其实比调参更重要。

2.3 向量存储:从原型到生产的选型思路

向量存储负责把 Embedding 后的向量存起来,并在检索时做相似度搜索。Spring AI 抽象了 VectorStore 接口,切换实现类对业务代码基本透明。我建议按项目阶段选型,不要一上来就上重组件。

方案适用场景优点缺点
SimpleVectorStore本地原型、Demo零依赖,内存实现,几行代码跑通重启丢数据,不适合生产
Redis Vector Store中型项目,已有 Redis部署简单,支持持久化,性能不错高维向量大量写入时需关注内存
Milvus / Elasticsearch大型知识库,多租户容量大、检索性能强、支持过滤组件重,需要专门运维

如果你团队里已有 Elasticsearch,直接用它的向量检索能力是最省事的选择;如果是从零开始,Redis 方案的门槛最低。我原型阶段用的是 SimpleVectorStore,一天内跑通了整个链路,确认方案可行后换成了 Redis。这个决策路径很实用:先验证业务逻辑,再重构基础设施,避免在不确定需求的时候过度设计。

2.4 检索、重排与生成:最后一公里闭环

当用户提问进来,链路是这样的:先把问题向量化,然后在向量库里做相似度搜索,取 TopK 个文档块,再把文档块作为上下文拼进 Prompt,最后让大模型生成回答。

这里有一个容易忽视的环节:重排序。向量相似度检索本质上是“粗筛”,检索结果可能包含语义接近但实际不相关的片段。如果有预算加一个 Reranker 模型,把粗筛出的前 20 个块重新精排,取前 5 个送进 Prompt,回答精度会有肉眼可见的提升。我自己实测,加 Reranker 之后,回答“牛头不对马嘴”的情况至少减少一半。

Prompt 设计也直接影响生成质量。我会在系统提示词里明确写:“仅依据上下文中提供的信息回答问题;如果上下文中没有答案,直接承认不知道;引用内容时指出来源。”这句话看起来简单,但对幻觉的抑制效果非常显著。Spring AI 里可以用 PromptTemplate 把这些约束固化下来,不用每个接口重复写。

3. 实战落地:让已有知识库变成问答服务

3.1 依赖与配置:最先踩坑的地方

Spring AI 的依赖坐标变化比较快,网上很多人粘贴的版本号可能已经过时。我最推荐的方式,是你用 Spring Initializr 生成项目的时候直接勾选 Spring AI 相关依赖,让官方帮你匹配一组兼容版本,比从零散文章里抄坐标稳得多。

你在 pom.xml 里会看到类似的依赖结构:

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-zhipuai</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-vector-store-redis</artifactId> </dependency>

配置文件里接入智谱或者 OpenAI 风格都类似,下面是一份精简的 application.yml 示例:

spring: ai: model: chat: zhipuai embedding: zhipuai zhipuai: api-key: ${ZHIPU_API_KEY} chat: options: model: glm-4-flash embedding: options: model: embedding-2 vectorstore: redis: index: knowledge-index prefix: knowledge

请注意,不同版本的 starter 对配置项的迁移差异很大,如果启动时报配置绑定错误,就去官方文档翻对应版本的配置清单。这一步我在团队里反复提醒:Spring AI 更新很勤,出了问题先怀疑“版本和配置不匹配”,不要急着怀疑业务代码。

3.2 加载文档与写入向量库

知识库内容加载我用的是 TikaDocumentReader,它基于 Apache Tika,能处理 Markdown、PDF、Word 等常见格式。你只需要把知识库文件的路径或者 Resource 传进去,Spring AI 就能把文本内容提取出来。

@Configuration public class KnowledgeIndexConfig { @Bean VectorStore vectorStore(EmbeddingModel embeddingModel) { return new RedisVectorStore( RedisVectorStore.builder() .embeddingModel(embeddingModel) .indexName("knowledge-index") .prefix("knowledge") .build()); } @Bean CommandLineRunner loadKnowledge(VectorStore vectorStore, EmbeddingModel embeddingModel) { return args -> { FileSystemResource resource = new FileSystemResource("./docs"); TikaDocumentReader reader = new TikaDocumentReader(resource); List<Document> documents = reader.get(); TokenTextSplitter splitter = TokenTextSplitter.builder() .withChunkSize(500) .withChunkOverlap(80) .build(); List<Document> splits = splitter.apply(documents); vectorStore.add(splits); }; } }

这段代码做的事情很简单:读取 docs 目录下的文件,切成 500 字符左右的小块,向量化后写入 Redis。我故意把这段写在 CommandLineRunner 里,因为原型阶段你只需要启动一次,让索引建好就行。后续做增量更新时可以单独抽象一个 IndexService,按文档变更去重建单个文件的块,而不是全量重建。

3.3 实现问答接口

问答接口是我最喜欢的部分,因为 Spring AI 已经把复杂度封装得很干净。你不需要手动完成“检索、拼 Prompt、调用模型”这三步,一个 QuestionAnswerAdvisor 就能串起来。

@RestController public class AskController { private final ChatClient chatClient; public AskController(ChatClient.Builder builder, VectorStore vectorStore) { this.chatClient = builder .defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore).build()) .build(); } @PostMapping("/ask") public String ask(@RequestBody AskRequest request) { return chatClient.prompt() .user(request.question()) .call() .content(); } }

QuestionAnswerAdvisor 会在每次询问时自动完成:问题向量化、向量库检索、取出相关块拼进 Prompt、交给 ChatModel 生成。你甚至可以不写任何检索代码。如果检索结果需要按业务条件过滤,可以在 Advisor 里传入 QueryRequest 的过滤条件,比如只检索某个目录下、某个标签下的文档。这一点对于“知识库里有不同部门文档、不同用户只允许问自己部门内容”的场景特别有用。

3.4 参数选择与效果验证

RAG 的效果不是部署完就结束的,它需要持续调参。我维护了一张验证用例表,每次调完参数后跑一遍,用结果倒推参数合理性:

问题类型示例期望结果常见失败
直接命中“XX 服务的超时时间默认是多少”答出具体数字并说明出处块切得太碎,答案被截断
改写提问“那个服务连不上该怎么办”能理解并定向到 FAQ 文档检索不中,因为表述和库中不一致
跨文档组合“部署流程里前置条件有哪些”能合并多篇文档的信息只检索到其中一篇,答案不完整

TopK 参数的选择也有讲究。默认取 4 到 6 个块比较平衡:太少会漏关键信息,太多会把无关内容一起送进 Prompt,导致模型学会“混搭胡编”。相似度阈值可以设一个兜底:低于 0.7 的结果不要进 Prompt,直接返回“知识库中未找到相关内容”。这个阈值需要在你的向量模型下实测,不要照搬别人项目的数值。

4. 进阶:多轮对话、MCP 与 Agentic RAG

4.1 RAG 和 MCP 到底有什么区别

这个热搜词出现得很频繁,说明不少人把这两个概念搞混了。我用一句话区分:RAG 解决的是“怎么把外部知识喂给模型”,MCP 解决的是“模型怎么调用外部工具和系统”。RAG 是查资料给你看,MCP 是帮你办事。

举例来说,同样面对一个“报销审批卡住了”的问题,RAG 会从知识库里检索报销流程文档,告诉你“应该走哪个审批节点”;MCP 则会直接调用审批系统的接口,帮你查出单子卡在谁那里,甚至替你触发催办。一个偏知识回答,一个偏能力调用,两者不是对立关系,而是可以配合:模型先用 RAG 了解规则,再用 MCP 操纵系统。

如果你只是让知识库能问答,那用 RAG 就够了,不需要上 MCP。MCP 意味着你要把自己的业务系统工具暴露给模型,涉及权限、审计和安全性设计,复杂度高很多。Java 团队不要为了赶热点而引入多余复杂度,先把 RAG 跑稳,再谈 MCP。

4.2 多轮对话的三种设计方式

知识问答一旦带上“多轮”,检索策略就必须调整。用户在对话里说“那第二点呢”“它有什么缺点”,这些问题的指代依赖于前文,直接拿去向量检索几乎什么都查不到。

我实测下来有三种方案,按复杂度和效果递增排序。第一种是简单拼接:把最近几轮对话文本拼到当前问题后面,一起送去检索,实现简单但噪声大,Token 消耗也高。第二种是查询改写:先用大模型把“那第二点呢”改写成独立问题“告警通知的第二点注意事项是什么”,再用改写后的文本去检索。这种方式效果好很多,也是我推荐的做法。第三种是使用 Spring AI 的 ChatMemory,用 MessageWindowChatMemory 维护最近 N 轮对话,让模型可以访问历史上下文,相当于由模型自己决定怎么理解当前问题。

生产环境我目前用的是“查询改写 + ChatMemory”组合:改写保证检索质量,ChatMemory 保证生成连贯。成本上会多一次模型调用,但换回来的问答体验是值得的。

4.3 Agentic RAG:当知识库不止一个

如果你只有一个知识库,基础 RAG 够用。但企业里常见的情况是:产品文档、工单系统、人事制度、FAQ 分散在多个地方,用户的问题根本不知道应该去哪个库查。这时候可以做 Agentic RAG,也就是让模型自己决定查哪里。

Spring AI 已经支持 Tool Calling,可以把“检索产品文档”“检索工单系统”“查询员工制度”分别封装成函数,模型根据用户意图决定调用哪个工具。这种方式相对灵活,但也有明显代价:路由决策不准时,会变得又慢又贵。我的建议很直接:先把单库 RAG 跑顺,指标都稳定了,再升级成多库路由。不要一上来就 Agentic,否则你会同时面对检索调参和工具调度的双重问题,排查成本翻倍。

5. 常见问题与排查技巧实录

5.1 检索结果不相关,优先查这几点

我在调试 RAG 时踩过最多的坑,总结起来就是三个原因。第一,切块不合理:答案明明在文档里,但被拦腰切断,检索回来的块没有关键信息,这时候要把块调大一点或者改成按标题切。第二,Embedding 模型不一致:索引构建和查询用了不同模型,向量空间对不上。第三,TopK 和阈值设置不当:阈值太严导致查不到,阈值太松导致答案被不相关内容污染。排查顺序建议是:先看检索回来的原文块是否合理,再看模型输入 Prompt 的实际内容,最后再调参数。很多问题在“看 Prompt 实际内容”这一步就能定位。

5.2 模型幻觉严重怎么办

幻觉的根源是模型在上下文中找不到答案,于是开始自由发挥。除了在 Prompt 里强制约束“仅依据上下文回答”之外,我还有两个实用技巧。一是在 Prompt 尾部要求“回答中必须包含所引用文档的标题或 ID”,这样可以倒逼模型只输出它真正看到的内容,也方便用户回到原文验证。二是用 Reranker 压缩上下文,把不相干的候选块过滤掉,减少模型被误导的可能。如果这些做完仍然幻觉,大概率是知识库里真的没有答案,可以考虑告诉用户“该问题不存在于当前知识库”,并附上相似问题推荐。

5.3 性能与成本怎么控

RAG 的成本大头在 Embedding 和 LLM 调用。控制成本的经验有几个:第一,高频问题加缓存,同一个标准化问题在短时间内重复问,直接命中缓存返回;第二,Embedding 结果落库缓存,同一个文档块不要每次启动都重新向量化;第三,控制上下文长度,不要无脑把大量候选块塞进 Prompt,块越多 Token 越贵,回答不一定更准。并发方面,调用外部模型 API 时要做好限流和超时,Spring AI 的 RetryTemplate 和超时配置要提前调好,避免第三方抖动拖垮业务线程。

5.4 从原型到生产要注意什么

原型阶段我用 SimpleVectorStore 跑通后,上了生产前做了一次完整的“工程化体检”。第一,内存向量库换成 Redis 或者 Elasticsearch,保证持久化和容量;第二,建立增量更新机制,知识库文档每天都有改动,全量重建索引成本太高,至少要给每个文档记录一份版本号或更新时间戳,按变化去更新对应块;第三,加监控看板,重点关注问答接口耗时、向量库命中率、LLM 调用 Token 消耗。知识库索引不是静态数据,它是会“过期”的,维护策略决定了这个系统能不能长期好用。

最后再分享一个我个人的实际体会:知识问答系统上线后,我让团队用“十个真实业务问题”作为验收基线,每天跑一遍,把每次调参的影响都记录下来。RAG 和传统接口最大的不同,是它的效果需要持续调,不存在“一次搞定”的版本。知识库已经有了,设备也搭好了,真正决定这件事上限的,是你愿不愿意去持续迭代检索质量和回答体验。做下去,文档就不再是一堆文件,而是真正能回答问题的“团队记忆”。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 13:49:55

Vue3 搭配后端连接数据库:TaoToken 统一 Key 配置与联调验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 13:49:49

AI Agent实战:Hermes、Claude Code、Codex接入DeepSeek全攻略

1. 九月榜单的"分水岭"信号&#xff1a;从聊天竞赛到干活竞赛9 月的 AI 圈&#xff0c;风向转了一个很有意思的弯&#xff1a;大家茶余饭后讨论的&#xff0c;不再是哪个大模型又在评测集上刷了多少分&#xff0c;而是一份 AI Agent 排行。Hermes 冲到第一&#xff0…

作者头像 李华
网站建设 2026/9/26 13:49:18

SSM+JSP在线考试系统与LD算法编程题自动判分实战

简介&#xff1a;这是一套基于SSM框架&#xff08;SpringSpringMVCMyBatis&#xff09;、JSP前端与MySQL数据库开发的在线考试系统&#xff0c;核心亮点在于集成LD&#xff08;Levenshtein Distance&#xff09;字符串编辑距离算法实现编程题自动判分&#xff0c;有效支撑代码相…

作者头像 李华
网站建设 2026/9/26 13:49:18

OpenClaw 接入 DeepSeek V4 实战:本地 Agent 配置与避坑指南

1. 为什么我要折腾 OpenClaw 接 DeepSeek V4先说结论&#xff1a;OpenClaw 是目前开源 Agent 框架里&#xff0c;把"本地工具调用 多模型路由"做得最顺手的一个&#xff0c;而 DeepSeek V4 在代码理解和长上下文推理上的表现&#xff0c;让我这种天天跟配置文件打交…

作者头像 李华
网站建设 2026/9/26 13:48:44

Claude Code 模板化开发:从 CLAUDE.md 到自动化 AI 协作工作流

以前用 Claude Code 的时候&#xff0c;最头疼的就是每次打开一个新项目&#xff0c;都得把项目背景、代码风格、注意事项重新交代一遍。问多了它记不住&#xff0c;问少了我又不放心&#xff0c;经常是聊了十几轮才进入正题。后来我把目光转向了claude-code-templates这套东西…

作者头像 李华
网站建设 2026/9/26 13:46:41

SQL注入工具sqlmap安装教程:Windows/Linux/macOS环境配置与验证

1. 为什么值得花时间把 sqlmap 装明白sqlmap 是一款开源的 SQL 注入自动化检测工具&#xff0c;用 Python 写成&#xff0c;支持对多种数据库的注入点识别、指纹判断、数据提取乃至权限提升。它的核心价值在于把大量重复性的注入探测工作自动化&#xff0c;让安全测试人员能把精…

作者头像 李华