ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Spring AI RAG实战:从零构建企业级文档问答系统

Spring AI RAG实战:从零构建企业级文档问答系统 很多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接口你换数据库不用改业务代码。但起步阶段选哪种实现直接影响开发体验和运行成本。目前社区用得比较多的是这几种向量库适合场景部署成本扩展性PGVectorSpring 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 groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.3.4/version relativePath/ /parent properties java.version17/java.version spring-ai.version1.0.0/spring-ai.version /properties dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies !-- Web 支持 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency !-- Spring AI 核心 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter/artifactId /dependency !-- 文档解析 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-tika-document-reader/artifactId /dependency !-- 向量存储PGVector 实现 -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-vector-store-pgvector/artifactId /dependency !-- OpenAI Chat Embedding -- dependency groupIdorg.springframework.ai/groupId artifactIdspring-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_USERpostgres \ -e POSTGRES_PASSWORDpostgres \ -e POSTGRES_DBrag_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 ListDocument parseMultipartFile(MultipartFile file) throws Exception { Resource resource new InputStreamResource(file.getInputStream()); TikaDocumentReader reader new TikaDocumentReader(resource); ListDocument 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 ListDocument splitDocuments(ListDocument 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(ListDocument 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 Token50条款独立性较强切大块容易混入无关条款技术手册/教程500~700 Token100内容连续性强需要保留语境FAQ问答集200~300 Token30每条问答语义独立小块足够还有一种思路是基于语义边界来拆比如按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-8PDF优先检查字体嵌入情况写入向量库报错向量维度与索引配置不一致核对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跑通了下一步建议拿一份自己手头真实的、乱一点的文档去试试体会一遍完整的上手流程。下次再聊更进阶的内容。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进