1. 为什么 Java 后端需要一套自己的 RAG 方案
很多 Java 开发者第一次接触大语言模型时,都会遇到一个尴尬的处境:模型能聊、能写,但一问到公司内部文档、产品手册、历史工单,它就开始一本正经地胡说。原因不复杂,大模型的参数里没有你的私有数据,它只能靠训练时见过的通用语料来猜。RAG(检索增强生成)就是解决这个问题的标准思路——先把相关资料检索出来,再让模型基于这些资料回答。
Spring AI 的价值在于,它把这套流程做成了 Spring Boot 开发者熟悉的样子。你不需要切到 Python 生态,也不用自己手写向量检索和提示词拼接,只要会写@Bean、会配application.yml,就能在现有项目里加一个 RAG 问答接口。它提供了统一的ChatClient、VectorStore、Advisor抽象,底层可以对接不同模型服务和向量库,切换成本很低。
这篇文章面向的是已经会 Spring Boot、想快速跑通一个最小可用 RAG 服务的 Java 后端。我会从依赖配置开始,一步步给出可复制的pom.xml、application.yml、ChatClient配置和检索增强的 Controller,最后用一个真实请求验证返回结果。整个过程不涉及复杂部署,本地起一个 Spring Boot 应用就能看到效果。如果你之前被 Python 的 AI 生态挡在门外,这套路径应该会让你舒服很多。
2. 前置准备:模型服务与 TaoToken 接入配置
在写代码之前,先把模型服务的接入信息准备好。Spring AI 本身不绑定某一家模型,它通过 OpenAI 兼容协议对接大量服务。这里我用 TaoToken 作为模型接入层,原因是它提供 OpenAI 兼容的 API 端点,Spring AI 的spring-ai-openai-spring-boot-starter可以直接对接,不需要额外写适配代码。
你需要准备三样东西:Base URL、API Key、Model ID。Base URL 填https://taotoken.net/api,API Key 在控制台的 API Keys 页面创建,Model ID 根据你要用的模型填写,比如gpt-4o-mini这类通用对话模型就够跑通 RAG 示例。这三个值后面会写进application.yml,Spring AI 会自动读取并装配OpenAiChatModel。
这里有个容易踩的坑:Spring AI 的 OpenAI starter 默认会去请求https://api.openai.com,如果你只填了api-key没改base-url,请求会打到默认地址然后失败。所以base-url必须显式配置成 TaoToken 的 API 地址。另外,Spring AI 的版本迭代比较快,spring-ai-bom的版本要和 starter 版本对齐,否则会出现NoSuchMethodError这类运行时错误。我建议用1.0.0-M6或更新的里程碑版本,配合 Spring Boot 3.3+。
如果你还没有 API Key,可以先到控制台创建一个,创建时注意保存,页面关闭后不会再显示完整 Key。模型对话的调试可以在模型对话页面直接试,确认 Key 和模型 ID 能正常返回内容,再进入代码环节,这样能少排查一半问题。
3. 可复制配置:pom.xml、application.yml 与 ChatClient
这一节是全文的核心,所有片段都可以直接复制到你的 Spring Boot 项目里。先看pom.xml,关键是引入spring-ai-bom做版本管理,再引入 OpenAI starter 和向量库 starter。这里我用 Spring AI 自带的SimpleVectorStore做演示,它基于内存,不需要额外部署数据库,适合跑通最小示例。
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.5</version> <relativePath/> </parent> <groupId>com.example</groupId> <artifactId>spring-ai-rag-demo</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <java.version>17</java.version> <spring-ai.version>1.0.0-M6</spring-ai.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-advisors-vector-store</artifactId> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-core</artifactId> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-bom</artifactId> <version>${spring-ai.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> </project>注意spring-ai-core在部分版本里已经合并进 starter,如果编译报找不到,可以去掉这一条。spring-ai-advisors-vector-store是 RAG 顾问QuestionAnswerAdvisor的依赖,必须单独引入,否则运行时会提示类不存在。
接下来是application.yml,把 Base URL、Key、Model ID 三件套配好,同时打开 ChatClient 自动装配和日志,方便排查。
server: port: 8080 spring: application: name: spring-ai-rag-demo ai: openai: api-key: ${TAOTOKEN_API_KEY} base-url: https://taotoken.net/api chat: options: model: gpt-4o-mini temperature: 0.7 chat: client: enabled: true logging: level: org.springframework.ai.chat.client.advisor: DEBUGapi-key我用了环境变量占位,避免把 Key 硬编码进仓库。你本地跑的时候可以设export TAOTOKEN_API_KEY=你的Key,或者直接在 yml 里填。temperature控制随机性,RAG 场景建议 0.3 到 0.7,太高会让模型偏离检索到的资料。
然后是 Java 配置类,装配SimpleVectorStore和带 RAG 顾问的ChatClient。这里把向量库和 ChatClient 分开定义,方便后面往向量库里灌数据。
@Configuration public class RagConfig { @Bean public VectorStore vectorStore(EmbeddingModel embeddingModel) { return SimpleVectorStore.builder(embeddingModel).build(); } @Bean public ChatClient chatClient(ChatClient.Builder builder, VectorStore vectorStore) { return builder .defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore) .searchRequest(SearchRequest.builder() .similarityThreshold(0.5) .topK(3) .build()) .build()) .build(); } }EmbeddingModel由 OpenAI starter 自动装配,SimpleVectorStore用它把文本转成向量。QuestionAnswerAdvisor是 Spring AI 提供的现成 RAG 顾问,它会在每次请求前拿用户问题去向量库检索,把命中的文档拼进提示词。similarityThreshold是相似度阈值,低于这个值的文档会被过滤掉,topK控制最多取几条。这两个参数直接决定回答质量,后面排障会细说。
4. 灌数据与验证:一次检索增强问答的完整请求
配置写完后,先往向量库里放几条文档,再发一个请求看返回。我准备了一个初始化组件,在应用启动时把几段关于 Spring AI 的说明写进向量库。实际项目里这些数据可能来自数据库、Markdown 文件或接口,这里用硬编码模拟。
@Component public class DataInitializer implements CommandLineRunner { private final VectorStore vectorStore; public DataInitializer(VectorStore vectorStore) { this.vectorStore = vectorStore; } @Override public void run(String... args) { List<Document> docs = List.of( new Document("Spring AI 是 Spring 官方推出的 AI 应用开发框架,提供统一的 ChatClient 和 VectorStore 抽象。"), new Document("RAG 即检索增强生成,先检索相关文档再让模型基于文档回答,可减少幻觉。"), new Document("QuestionAnswerAdvisor 是 Spring AI 内置的 RAG 顾问,会自动完成检索和提示词拼接。"), new Document("SimpleVectorStore 是内存向量库,适合本地开发和最小示例验证。") ); vectorStore.add(docs); } }vectorStore.add会调用 EmbeddingModel 把每段文本转成向量并存入内存。启动应用时你会看到日志里出现 embedding 请求,说明数据写入成功。注意Document的构造参数是文本内容,元数据可以后续用Document的 builder 补充。
然后是 Controller,暴露一个/rag/chat接口,接收用户问题,走 ChatClient 返回答案。
@RestController @RequestMapping("/rag") public class RagController { private final ChatClient chatClient; public RagController(ChatClient chatClient) { this.chatClient = chatClient; } @GetMapping("/chat") public String chat(@RequestParam("q") String question) { return chatClient.prompt() .user(question) .call() .content(); } }启动应用后,用 curl 发一个请求:
curl "http://localhost:8080/rag/chat?q=QuestionAnswerAdvisor%20是做什么的"预期返回类似:
QuestionAnswerAdvisor 是 Spring AI 内置的 RAG 顾问,它会在用户提问时自动到向量库检索相关文档,并把命中的内容拼接到提示词中,让模型基于这些资料回答,从而减少幻觉。如果你在日志里看到SimpleLoggerAdvisor输出的 request 和 response,说明顾问链正常执行了。request 里会包含检索到的文档片段,response 里是模型生成的答案。这一步跑通,最小可用的 RAG 服务就成立了。你可以试着问一个向量库里没有的问题,比如「Spring AI 支持哪些向量数据库」,观察模型是否会因为检索不到而给出保守回答——这正是 RAG 在控制幻觉上的作用。
5. 常见报错排查:401、local proxy failed 与 reading choices
跑通之后,实际项目里还会遇到各种报错。这一节整理几个高频问题,都是我在接入过程中真实碰到过的。
第一个是401 Unauthorized。日志里通常伴随invalid_api_key或Incorrect API key provided。原因一般是api-key没配、配错,或者环境变量没生效。排查顺序:先确认application.yml里spring.ai.openai.api-key读到的值非空,再确认这个 Key 在控制台是启用状态。如果你用的是环境变量,注意 Spring Boot 读取的是启动时的环境,IDE 里改完要重启。还有一种情况是 Key 里带了空格或换行,复制时容易带上,建议用echo $TAOTOKEN_API_KEY | wc -c检查长度。
第二个是local proxy failed或连接超时。这类报错通常出现在base-url配置错误时,比如漏了https://,或者写成了带路径的地址。Spring AI 会在base-url后面拼接/v1/chat/completions,所以base-url只写到域名和/api即可,不要自己再加/v1。另外,如果公司网络有出口限制,也可能导致连接失败,这种情况需要确认网络策略是否允许访问该地址。
第三个是Error reading choices或Cannot deserialize value of type ChatResponse。这通常说明返回的 JSON 结构和 Spring AI 期望的不一致。常见原因是 Model ID 填错,比如填了一个不存在的模型名,服务端返回了错误结构。解决方法是先用模型对话页面确认该 Model ID 能正常返回,再填进 yml。另一个原因是 Spring AI 版本和服务端协议版本不匹配,升级或降级spring-ai-bom到稳定版本通常能解决。
第四个是No qualifying bean of type 'EmbeddingModel'。这说明 OpenAI starter 没有自动装配 EmbeddingModel,可能是 starter 版本问题,或者你只引入了 chat 相关依赖。确认spring-ai-openai-spring-boot-starter在依赖里,并且spring.ai.openai.api-key已配置,自动装配才会生效。
第五个是检索结果为空导致模型答非所问。这不是报错,但很常见。similarityThreshold设太高(比如 0.8)会把所有文档都过滤掉,模型拿不到上下文就只能靠自己的知识回答。建议先用 0.5 左右,观察日志里 request 是否包含检索到的文档,再逐步调整。topK也别设太大,3 到 5 条通常够用,太多会挤占上下文窗口。
6. 从最小示例到可用服务:下一步怎么走
最小示例跑通后,你大概已经感受到 Spring AI 在 Java 里做 RAG 的路径了:配置三件套、装配 ChatClient、灌数据、发请求。接下来要把它变成能上线的服务,还有几件事值得做。
向量库从SimpleVectorStore换成持久化方案。内存向量库重启就丢数据,生产环境可以用 Redis、PgVector 或 Milvus,Spring AI 对这些都有 starter,切换时主要改依赖和VectorStore的 Bean 定义,业务代码基本不动。文档加载也可以从硬编码换成DocumentReader,支持 PDF、Markdown、JSON 等格式,配合TokenTextSplitter做分块,分块大小和重叠长度会直接影响检索效果。
检索质量优化是另一个重点。similarityThreshold和topK只是入门参数,进阶可以用混合检索(关键词 + 向量)、重排序(Rerank)和多路召回。Spring AI 的RetrievalAugmentationAdvisor支持自定义DocumentRetriever和QueryAugmenter,你可以把检索逻辑拆开,针对业务做过滤,比如按文档类型、时间范围筛选。
最后是接口层。现在返回的是纯文本,实际项目里可能需要结构化输出,比如让模型返回 JSON 再反序列化成对象。Spring AI 的BeanOutputConverter可以做到这一点,配合ChatClient的.entity()方法,直接把模型输出映射成 Java 对象。这样前端拿到的就是稳定结构,不用再解析自然语言。
如果你想把模型调用统一管理,可以在 TaoToken 控制台创建多个 Key 分配给不同环境,Coding Plan 适合长期编码和 Agent 场景,接入文档里有各语言 SDK 的示例。模型对话页面可以随时验证模型可用性,避免代码里排查半天发现是 Key 的问题。