很多Spring开发者第一次接触RAG时,第一反应都是“这不就是给大模型配个资料库嘛”,真上手才发现坑比想象中多:文档怎么拆、向量存哪里、检索出来不相关怎么办、中文乱码、第一次问答能跑通但连着问几轮就开始胡说八道。这个“第4集:让AI学会看文档:Spring AI RAG入门实战”正好是大家最需要的那类教程,从零开始、不绕弯子、把Spring AI这条技术路线完整走一遍。我这次就顺着这个主题,把RAG落地的完整思路、实现步骤和排查心得全部拆开讲清楚,全程基于Java和Spring生态,不涉及任何其他语言或平台,力求让你看完就能在自己的项目里复制出来。
1. 整体设计思路
1.1 为什么Spring开发者应该优先考虑Spring AI
先聊一个很多人纠结的问题:搞RAG到底该自己拼装还是用现成框架。我看过不少项目组,目录里同时躺着手写的向量检索工具类、自己封装的OpenAI SDK、还有一套半路从Python那边抄过来的LangChain逻辑,最后维护成本全花在接口对接和类型转换上了。如果你本身就在Spring Boot项目里,Spring AI才是真正“少折腾”的那条路。
Spring AI的核心定位是“把AI能力变成Spring风格的Bean”。Embedding模型、Chat模型、向量库、文档解析器,全部以自动配置的形式放进容器里。你不需要关心OpenAI接口参数怎么拼、向量库驱动怎么写,只需要声明依赖、配置属性、注入客户端。这一点跟当年Spring Data对JPA的封装非常像,思路就是“管好连接、暴露模板、让你专注业务”。
相比之下,自己拼装的方案初期看起来自由,实际上每一步都要你自己兜底:模型接口版本升级怎么办、Embedding维度不一致怎么处理、批量写入向量库失败要不要重试、多租户环境下的数据隔离怎么做,这些问题框架层都替你考虑过一遍。Spring AI把RAG链路里最繁琐的部分全部抽象成了几个核心组件:DocumentReader负责读文档,TextSplitter负责拆文档,VectorStore负责存和检索,QuestionAnswerAdvisor负责把检索结果注入Prompt。你用的时候就是在组装这些组件,代码量少一个量级。
1.2 RAG的核心流程到底在做什么
RAG的全称是Retrieval-Augmented Generation,检索增强生成。一句话解释:先根据用户问题去资料库找出相关内容,再把“问题+内容”一起交给大模型生成回答。跟直接问大模型相比,RAG不是让模型“凭空想”,而是让它“看着资料写”,所以回答能被约束在文档范围内,幻觉少、答案可追溯。
整个流程可以拆成两个阶段。第一个阶段是入库:把文档读进来、拆成小块、每一块转成向量、存进向量数据库。第二个阶段是问答:用户提问时,先把问题也转成向量,去向量库里做相似度检索,挑出最相关的几个文档片段,拼到Prompt里,连同原始问题一起交给大模型生成回答。
这两个阶段缺一不可。如果只做入库不做检索,那跟把文件堆在硬盘里没区别;如果只做检索不做生成,那又回到了传统的关键词搜索。RAG的关键就在于“先检索,后生成”,检索的质量直接决定最终答案的质量。你后面调试系统时遇到的大部分问题,本质上都出在检索环节——召回率不够、相关性太低、分块切断语义。所以实现的时候脑子里始终绷着一根弦:RAG的瓶颈不在模型,在检索。
1.3 技术选型的关键判断依据
Spring AI一个比较大的优势是它对向量库做了统一的Store接口,你换数据库不用改业务代码。但起步阶段选哪种实现,直接影响开发体验和运行成本。
目前社区用得比较多的是这几种:
| 向量库 | 适合场景 | 部署成本 | 扩展性 |
|---|---|---|---|
| PGVector | Spring Boot + PostgreSQL项目 | 极低,PG装个插件就行 | 中,适合百万级向量 |
| Redis | 已有Redis、需要低延迟检索 | 低 | 中 |
| Elasticsearch | 已有ES、需要复杂过滤和全文检索混用 | 中高 | 高 |
| Milvus | 海量数据、高并发、专业向量检索 | 高 | 极高 |
| Spring AI的SimpleVectorStore | 本地测试、小规模原型 | 零成本 | 低 |
我的建议是,项目里已经用了PostgreSQL就直接上PGVector,事务、备份、权限体系全部复用,运维也不用额外背一套新组件。如果只是本地跑通演示Demo,可以用SimpleVectorStore,它在内存里实现了一个简易向量检索,连数据库都不用装。等数据量上来了再平滑切到专业向量库不迟。
Embedding模型的选择也同理。有条件调用OpenAI接口就直接用OpenAI的text-embedding-3-small,效果稳定、维度低、成本也低。如果数据敏感、需要本地部署,Ollama上有很多开源Embedding模型能跑。我把两种方案的配置都放在后面,你按自己的场景选。
2. 环境准备与项目骨架搭建
2.1 基础环境要求
先把环境约束说清楚。Spring AI目前要求JDK 17及以上,Spring Boot 3.x项目比较稳妥。我演示用的版本组合是Spring Boot 3.3.x + Spring AI 1.0.0,这套组合目前最稳定,网上资料也最多。Spring AI 2.0已经出了,但API变化比较大,如果你不是非要尝鲜,建议先跟我这套走通,之后再升不迟。
还需要准备一个向量数据库和一个Embedding服务,二选一即可。预算充足、不涉及数据外发就选OpenAI;想要完全本地化就装Ollama。本地方案我后面会专门说。
注意:Spring AI的依赖坐标经常变动,直接去Spring官方文档的“Dependency Versions”页面复制对应版本的BOM坐标最保险。别用Maven中央仓库里搜到的旧版本坐标。
2.2 初始化项目并引入依赖
先建一个普通的Spring Boot项目,然后修改pom.xml。我这里以Maven为例,Gradle的写法会放在代码块里说明。核心就是要引入Spring AI的BOM和几个starter。
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.3.4</version> <relativePath/> </parent> <properties> <java.version>17</java.version> <spring-ai.version>1.0.0</spring-ai.version> </properties> <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> <dependencies> <!-- Web 支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring AI 核心 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter</artifactId> </dependency> <!-- 文档解析 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-tika-document-reader</artifactId> </dependency> <!-- 向量存储:PGVector 实现 --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-vector-store-pgvector</artifactId> </dependency> <!-- OpenAI Chat & Embedding --> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-openai</artifactId> </dependency> </dependencies>这里有一个很容易踩的坑:Spring AI的starter和普通Spring Boot starter不太一样,你需要额外配置仓库地址。因为部分依赖发布在Spring的里程碑仓库里,Maven中央仓库不一定同步。具体加哪个仓库取决于你用的版本是里程碑版还是正式版,正式版通常不需要额外配置。你如果引入依赖后Maven报红,优先去确认仓库配置。
2.3 配置模型与向量库连接
依赖加好之后,在application.yml里写配置。我同时给出OpenAI和Ollama两个版本,注释里说明各自的生效条件。
spring: application: name: spring-ai-rag-demo ai: # 使用 OpenAI 时打开这段配置 openai: api-key: ${OPENAI_API_KEY} chat: options: model: gpt-4o-mini temperature: 0.2 embedding: options: model: text-embedding-3-small # 使用 Ollama 本地模型时打开这段配置 ollama: base-url: http://localhost:11434 chat: options: model: qwen2.5:7b embedding: options: model: nomic-embed-text vectorstore: pgvector: index-type: HNSW distance-type: COSINE_DISTANCE dimensions: 1536 datasource: url: jdbc:postgresql://localhost:5432/rag_demo username: postgres password: postgres几个参数我要重点解释一下。temperature设为0.2,是因为RAG问答场景里我们希望模型尽量忠实于文档内容,不要自由发挥。温度越高越有创造性,但对资料问答来说创造性就是幻觉的来源。dimensions: 1536对应OpenAI的text-embedding-3-small向量维度,如果换本地Embedding模型,这个值也要跟着换,比如nomic-embed-text是768维,改配置不匹配会导致读写向量时报错。
PGVector这边还需要在你的PostgreSQL里提前启用插件。我建议直接用Docker起一个带PGVector的实例,一步到位。
docker run --name pgvector-rag \ -e POSTGRES_USER=postgres \ -e POSTGRES_PASSWORD=postgres \ -e POSTGRES_DB=rag_demo \ -p 5432:5432 \ -d pgvector/pgvector:pg16Spring AI的PGVector实现会在应用启动时自动建表,这一点非常省心。它会监控实体类的注解生成对应的schema。你在第一次启动时如果看到日志里有vector表的DDL输出,说明连接没问题。
3. 核心代码实战
3.1 文档解析:把PDF、Word、TXT变成纯文本
文档入库的第一步是读取。我现在用的这套方案基于Apache Tika,它能统一处理PDF、Word、HTML、Markdown、TXT等各种格式,省去为每种格式写解析器的麻烦。
import org.springframework.ai.document.Document; import org.springframework.ai.reader.tika.TikaDocumentReader; import org.springframework.core.io.InputStreamResource; import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import java.util.List; @Service public class DocumentImportService { public List<Document> parseMultipartFile(MultipartFile file) throws Exception { Resource resource = new InputStreamResource(file.getInputStream()); TikaDocumentReader reader = new TikaDocumentReader(resource); List<Document> documents = reader.get(); return documents; } }Tika解析出来的Document对象包含两个部分:content是纯文本正文,metadata里是文档的元信息,比如文件名、文件类型、作者、修改时间等。这个metadata跟你后面做权限隔离、来源追踪有直接关系,稍后我会演示怎么利用它。
实测经验:Tika对扫描版PDF无能为力,因为里面是图片不是文字。如果你的业务里有大量扫描件,得单独接入OCR管道,这一步不属于Spring AI的职责范围,需要你自己用OCR服务预处理。
3.2 文档拆分:为什么必须拆、怎么拆才合理
一份几十页的文档直接塞给模型是不可能的,模型有上下文长度限制,而且“长文一刀切”的检索效果也很差。所以必须把文档拆成语义完整的小块,这个过程叫Chunking。
我推荐用Spring AI自带的TokenTextSplitter,不用自己写正则硬拆。我们要做的是配置好分块参数,让它按Token边界切分。
import org.springframework.ai.document.Document; import org.springframework.ai.transformer.splitter.TokenTextSplitter; import org.springframework.ai.transformer.splitter.SplitterProperties; import java.util.List; @Service public class DocumentChunkService { public List<Document> splitDocuments(List<Document> documents) { SplitterProperties properties = new SplitterProperties( 500, // 每个块的最大Token数 100, // 相邻块的重叠Token数 null, true // 是否启用分句感知 ); TokenTextSplitter splitter = new TokenTextSplitter(properties); return splitter.apply(documents); } }这里的两个核心参数值得多说几句。块大小决定了每个包裹里放多少内容,块太长检索精度会下降,把不相关内容卷进同一块;块太短会切断语义,导致模型看到的信息不完整。按我的经验,中文场景下500个Token大概对应700~800个汉字,适配大部分知识库场景。重叠Token是为了防止语义边界被切断——我让前后两个块共享一部分内容,切分线附近的语义不会丢失。如果把“今天天气很好适合出去运动”从“运动”前面切开,前一块只到“适合出去”,后一块从“运动”开始,语义就碎了。重叠部分能有效缓解这个问题。
真实项目里具体调多少,还是要跟你文档类型挂钩。合同、制度类文档结构清晰,可以适当调大;产品手册、操作指南这类连续性强的文档,重叠部分也要调大。后面我会给你们出一张参数速查表。
3.3 向量化与写入向量库
分块完成后,就要把每个块转成向量并写入向量库。Spring AI里这一步被封装得非常优雅,你先拿到一个实现VectorStore接口的Bean,然后直接调用add方法,内部会自己调用Embedding模型把文本向量化并入库。
import org.springframework.ai.vectorstore.VectorStore; import org.springframework.ai.document.Document; import org.springframework.stereotype.Service; import java.util.List; @Service public class VectorStoreService { private final VectorStore vectorStore; public VectorStoreService(VectorStore vectorStore) { this.vectorStore = vectorStore; } public void saveDocuments(List<Document> documents) { vectorStore.add(documents); } }这里表面上只有一行代码,但Spring Boot背后做了几件事:根据配置拿到OpenAI或Ollama的EmbeddingClient,把每个Document的content转换成一个浮点数组,调用PGVector的SQL把向量和metadata一起写入。你不需要关心这些,但要知道一个潜在风险:大批量文档入库时,这里会循环调用Embedding模型的API,如果文档量大,耗时和费用都需要提前评估。
我建议你在导入数百个文档时在saveDocuments外层做分批提交,每批50~100个Document,中间加一点延迟或做失败重试。写得粗暴一点没关系,关键是别让内存炸掉。
OpenAI的Embedding接口对单次Batch大小有限制,具体要看官方文档,把一批几千条直接塞进去会报错。分批提交是刚需,不是可选项。
3.4 检索问答的核心实现
数据入库之后,真正的重头戏来了。用户来问问题,我们怎么把最相关的文档片段找出来,再交给大模型生成回答。Spring AI提供了一种简洁到近乎优雅的实现方式,直接在ChatClient的调用链上加上一个顾问组件,让它拦截请求、注入上下文。
import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.advisor.QuestionAnswerAdvisor; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.stereotype.Service; @Service public class RagChatService { private final ChatClient chatClient; public RagChatService(ChatClient.Builder chatClientBuilder, VectorStore vectorStore) { QuestionAnswerAdvisor advisor = new QuestionAnswerAdvisor( vectorStore, SearchRequest.builder() .query("") .topK(4) .similarityThreshold(0.5) .build() ); this.chatClient = chatClientBuilder .defaultAdvisors(advisor) .build(); } public String ask(String question) { return chatClient.prompt() .user(question) .call() .content(); } }这个代码的逻辑是这样的:当prompt发出时,QuestionAnswerAdvisor会拦截这份请求,把用户问题拿去向量库里做相似度检索,取回topK条相关文档片段,然后把它们和用户问题拼成一个增强Prompt,再发给LLM。你不需要手写Prompt拼接,框架内部有默认模板。
这里最值得关注的参数是相似度阈值和topK。topK是取回片段数,取太多会让噪音混进上下文,取太少可能漏掉关键信息。相似度阈值则是“最低门槛”,低于这个相似度的片段直接丢弃。两个参数配合,能有效控制检索质量。0.5是我在OpenAI Embedding下的常用起点,不同模型需要调。你们项目上线后可以拿一批真实问题从头测试,把相似度打印出来,观察问题和答案的关系,再反复微调这两个值。
提示:如果发现答案风马牛不相及,先别急着改Prompt,看一眼检索回来的文档片段是不是相关的。把检索质量调对,80%的问题都能解决。
3.5 流式输出:让AI回答边生成边显示
前面演示的.call().content()是一次性拿到全部回答。真实产品里用户等十几秒不出字,体验非常糟糕。我建议直接换成流式接口,边生成边推送,至少让用户看到“它已经开始写了”。
import org.springframework.ai.chat.client.ChatClient; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; @Service public class StreamRagChatService { private final ChatClient chatClient; public StreamRagChatService(ChatClient.Builder chatClientBuilder, VectorStore vectorStore) { QuestionAnswerAdvisor advisor = new QuestionAnswerAdvisor( vectorStore, SearchRequest.builder().topK(4).similarityThreshold(0.5).build() ); this.chatClient = chatClientBuilder.defaultAdvisors(advisor).build(); } public SseEmitter streamAsk(String question) { SseEmitter emitter = new SseEmitter(0L); chatClient.prompt() .user(question) .stream() .content() .doOnComplete(emitter::complete) .subscribe( content -> { try { emitter.send(content); } catch (IOException e) { emitter.completeWithError(e); } }, emitter::completeWithError, emitter::complete ); return emitter; } }Controller里把它暴露成SSE接口,前端用EventSource或fetch流式读取。要注意的是Spring AI的Reactor流和WebMVC的SseEmitter对接时,一定要处理背压和取消逻辑,否则前端断连后后端流还在跑,白白浪费Token。工程上更稳妥的是直接用WebFlux的Flux返回类型,Spring MVC的SseEmitter兼容性偶尔会调皮。
4. 进阶:让RAG更聪明的几个优化点
4.1 用元数据过滤把答案圈在一个范围内
基础RAG跑通之后,你会遇到一个新问题:知识库里什么都有,用户问“上个季度华东区的销售数据”,结果它从全国文档里到处翻。真实业务场景里,文档是有归属、有范围的,直接全局检索不合适。
解决方案是利用Document的metadata。你在入库时给每个文档打上标签,比如部门、区域、文章类型、时间范围。检索时在SearchRequest里加上过滤表达式,把范围圈定,检索精度会显著提升。
import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder; public SearchRequest buildSearchRequest(String question, String department) { FilterExpressionBuilder b = new FilterExpressionBuilder(); return SearchRequest.builder() .query(question) .topK(4) .similarityThreshold(0.5) .filterExpression(b.in("department", department).build()) .build(); }实际项目中这个过滤条件通常是动态拼的,来自登录用户的权限体系。比如普通员工只能检索自己部门的文档,管理岗可以检索全公司。这样RAG系统就同时兼顾了权限控制和知识检索,数据越权的问题在检索层就挡住了。
4.2 多轮对话:别让AI失忆
基础版本能答问题,但“追问”就露馅了。用户问“第一季度毛利率是多少”,系统回答了;再问“那第二季度呢”,系统又回到全局检索,完全忘了前面聊过第一季度的背景。这就是缺乏对话记忆。
Spring AI里可以用ChatMemory来解决。把历史对话保存下来,每次提问时把最近几轮对话也注入上下文,让模型知道“之前聊到哪里”。
import org.springframework.ai.chat.memory.ChatMemory; import org.springframework.ai.chat.memory.InMemoryChatMemory; import org.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor; @Service public class ConversationRagChatService { private final ChatClient chatClient; public ConversationRagChatService(ChatClient.Builder chatClientBuilder, VectorStore vectorStore) { ChatMemory chatMemory = new InMemoryChatMemory(); MessageChatMemoryAdvisor memoryAdvisor = new MessageChatMemoryAdvisor(chatMemory, "default", 10); QuestionAnswerAdvisor advisor = new QuestionAnswerAdvisor( vectorStore, SearchRequest.builder().topK(4).similarityThreshold(0.5).build() ); this.chatClient = chatClientBuilder .defaultAdvisors(memoryAdvisor, advisor) .build(); } }InMemoryChatMemory适合单机演示,生产环境建议用Redis或PostgreSQL持久化,避免重启丢记忆。注意对话记忆的轮数不能太多,每轮对话都会消耗Token,超出模型上下文窗口反而会干扰当前回答。10轮是我常用的值,既保留了上下文连贯性,又控制了Token消耗。
4.3 分块策略怎么调整:别用一个参数打天下
前面我给了一个种子参数,但真实场景里不同文档的分块需求差异很大。我拿三种常见类型举例:
| 文档类型 | 建议块大小 | 建议重叠数 | 理由 |
|---|---|---|---|
| 合同/制度条款 | 300~400 Token | 50 | 条款独立性较强,切大块容易混入无关条款 |
| 技术手册/教程 | 500~700 Token | 100 | 内容连续性强,需要保留语境 |
| FAQ问答集 | 200~300 Token | 30 | 每条问答语义独立,小块足够 |
还有一种思路是基于语义边界来拆,比如按Markdown标题、按段落标记切。Spring AI社区有些高级拆分器支持这种策略,但稳定度还在打磨。如果你们的文档结构非常规整,可以自己写拆分器,效果会好很多。判断依据就一条:切出来的每个块,是否让检索出来时像一条完整的信息。
4.4 本地轻量部署方案:Ollama + Spring AI
数据敏感、不允许外传的场景里,OpenAI这条路走不通,这时候就要上本地模型。配置上你把application.yml切到Ollama,代码一行不用改。
先在本地装Ollama,然后拉取两个模型:
ollama pull qwen2.5:7b ollama pull nomic-embed-text第一个是对话模型,第二个是Embedding模型。然后确认Ollama服务跑在11434端口,Spring AI会通过base-url自动连上去。
这套方案在效果上跟OpenAI有差距是必然的,但优势是数据不出内网、延迟稳定、零成本。如果你卡在Embedding模型上花了半天时间,我建议直接用nomic-embed-text,中文效果尚可,部署起来最省事。想要更好可以试试bge-m3或智谱的Embedding模型,但配置步骤会更多一点。
5. 常见问题排查与实战心得
5.1 高频问题速查表
我把实操过程中最常遇到的现象、原因和解决办法整理成一张表,建议直接存下来对照排查。
| 问题现象 | 根本原因 | 解决办法 |
|---|---|---|
| 回答与文档无关 | 向量检索没召回相关内容 | 检查向量维度是否匹配,检查分块是否过大或过小,降低相似度阈值尝试 |
| 回答全在编造 | 相似度阈值设太高/太低,检索不到就硬答 | 先打印检索到的片段看召回,适当降低topK时阈值;必要时加Prompt兜底“找不到就说不知道” |
| 中文乱码 | 文档解析编码不对或Tika识别错误 | 确保源文件编码为UTF-8,PDF优先检查字体嵌入情况 |
| 写入向量库报错 | 向量维度与索引配置不一致 | 核对Embedding模型输出维度,和PGVector配置的dimensions保持一致 |
| 首包响应很慢 | 用了非流式接口,或Embedding模型第一次调用初始化慢 | 换成流式接口;本地模型评估是否提前常驻显存 |
| 向量库表没创建 | PGVector插件未启用 | 执行CREATE EXTENSION IF NOT EXISTS vector; |
| 单批导入太多失败 | Embedding接口有批量上限 | 循环分批提交,每批50~100个Document,加上失败重试 |
| 多轮对话串味 | ChatMemory丢失或轮数过多 | 检查是否注册了MessageChatMemoryAdvisor,减少保留的对话轮数 |
5.2 纠偏经验:先怀疑检索,别急着换模型
新手最容易犯的错是:问答效果不好,第一时间怀疑模型不行,把GPT-4o换成更贵的模型。我每次调试RAG都遵循一个排查顺序:先看检索,再看生成。
具体做法是在开发阶段把检索到的Document内容打印出来,人工看一眼这些片段是否跟问题相关。如果检索结果本身就跑偏了,换什么模型都没用。如果检索结果是对的但回答不好,才需要调Prompt模板或换更强的生成模型。这套排查逻辑项目上线后同样适用,问题定位快很多。
另外一个容易忽略的点是初始化加载和增量更新。你不可能每来一个新文档都全量重建向量库。实际做法是:启动时全量导入存量文档,后续定时任务监听文件变动,增量写入。为了保证不脏读,建议在Document的metadata里维护一个文档的版本号或更新时间,检索时把它作为过滤条件,过期内容就能被排除。
5.3 本地跑通到上线的差距:别忽视可观测性
开发环境能问答不代表能上线。RAG生产化的隐形成本主要在这几块:检索质量评估、模型调用追踪、向量库容量规划。我每次给客户做RAG项目,都会在应用里埋一堆日志,记录每次用户提问、检索到的文档ID、相似度分数、最终答案。
有了这些日志,你可以自己做一个极简的评估集,准备几十个代表性的问题,每次改动完参数后跑一遍,观察命中率和回答质量。这个工作很枯燥但最有价值,比调一万次Prompt都有效。
注意:线上环境的API Key安全、调用频率限制、Token花费监控,这些都要提前考虑。AI功能上线前一定要先估算成本,RAG每问一次虽然只多出4个文档片段的上下文,但累积下来费用是实打实的。
6. 扩展方向:从入门RAG走向Agentic RAG
到这里,一个能用的Spring AI RAG系统已经跑通了。但别急着停在这个阶段,RAG这个方向还在快速演化,三个趋势值得你持续关注。
第一个是Spring AI Alibaba。这是面向国内云原生和阿里云生态的一套扩展方案,对通义千问、DashVector这些国内组件做了更好的适配。如果你公司的技术栈国内体系更重,这个扩展比原生Spring AI更顺手。第二个是GraphRAG/本体RAG方向,现在搜索热词里ontology rag、wiki rag出现得很频繁。这类方案不满足于“相似文本块”的检索,把文档里的实体、关系构建成知识图谱,用结构化的逻辑链回答跨段落问题。这里如果你们行业对逻辑推导要求很高,比如金融研报、法律条款分析,可以重点关注。第三个是Agentic RAG,核心变化是把RAG从“一个提问对应一次检索”升级成“有个Agent帮你判断什么时候该检索、检索几次、是否调用工具”。比如用户问“对比A产品和B产品”,简单RAG可能拆成两次独立检索;Agentic RAG会自己规划子任务,分步查完再汇总。
对刚入门的人来说,先把基础的RAG链路理解清楚,再往三个方向延伸都来得及。技术选型上也不必迷信“别人都在用”,每一家的RAG框架本质都在围绕“解析、拆分、向量化、检索、生成”这几个环节做优化,底层原理是通用的。
我个人的体会是,RAG项目最大的门槛从来不是代码,而是对文档内容本身的理解。你越清楚自己的文档结构、语义边界、用户提问习惯,越能做出让用户愿意天天用的知识库。Spring AI已经把繁琐的技术实现收敛得很好了,剩下的空间,就是你业务层面的发挥。
这期就聊到这里。如果你照着步骤把Demo跑通了,下一步建议拿一份自己手头真实的、乱一点的文档去试试,体会一遍完整的上手流程。下次再聊更进阶的内容。