news 2026/10/15 9:13:00

Spring AI 技术分享:用 Java 与 Spring Boot 构建大语言模型 RAG 应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring AI 技术分享:用 Java 与 Spring Boot 构建大语言模型 RAG 应用

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: DEBUG

api-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 的问题。

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

企业级多agent知识库系统源码拆解:Agentic RAG与Multi-Agent协作落地

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

作者头像 李华
网站建设 2026/10/15 9:12:31

HBase数据迁移方案全解析:Export/Import、Snapshot、CopyTable与BulkLoad实战

1. 为什么HBase数据迁移总让人头疼干过几年大数据运维的人&#xff0c;几乎都经历过HBase表迁移的折磨。业务要上云、集群要换机房、硬件要升级、或者单纯想把测试环境的数据同步到生产环境&#xff0c;这些场景都绕不开一个核心动作&#xff1a;把HBase里的数据从一个地方搬到…

作者头像 李华
网站建设 2026/10/15 9:12:29

WinForm与WPF技术选型指南:工业自动化实战解析

1. 项目概述作为一名在工业自动化领域摸爬滚打十年的老码农&#xff0c;我见证了C#上位机开发技术的多次迭代。最近在准备2026年的新项目技术选型时&#xff0c;发现WinForm和WPF的争论又有了新变化。这篇文章将结合最新技术动态和实战经验&#xff0c;帮你理清这两个框架的真实…

作者头像 李华
网站建设 2026/10/15 9:12:10

前端面试高频考点拆解:JavaScript、浏览器机制与手写代码题精讲

面试题整理这件事&#xff0c;我见过太多候选人走进一个误区&#xff1a;以为把网上流传的题库背一遍就能过关。实际上&#xff0c;面试官手里的题目看起来眼熟&#xff0c;但考察的点根本不是标准答案本身&#xff0c;而是你面对一个常见问题时&#xff0c;能不能把背后的原理…

作者头像 李华
网站建设 2026/10/15 9:12:09

Diagram Design:系统化表达的三大核心原则与落地工作流

1. “diagram-design”不是画图软件的代名词&#xff0c;而是一套被严重低估的系统化表达方法论很多人看到“diagram-design”第一反应是&#xff1a;“哦&#xff0c;不就是用draw.io、Excalidraw或者Visio画个流程图&#xff1f;”——这恰恰是我在过去八年带教三十多个跨领域…

作者头像 李华
网站建设 2026/10/15 9:12:08

Python绘制Cell Metabolism风格配对连线散点图

1. 项目概述最近在整理多组学数据可视化时&#xff0c;发现Cell Metabolism期刊上常见的那种配对连线散点图特别适合展示干预前后的代谢组学变化。这种图表不仅能清晰呈现个体变化趋势&#xff0c;还能直观比较组间差异。今天我就来分享如何用Python的SeabornMatplotlib组合&am…

作者头像 李华