基于SpringAI与pgvector的企业知识库RAG问答系统实战

基于SpringAI与pgvector的企业知识库RAG问答系统实战 简介检索增强生成RAG通过将外部知识库与大模型结合有效缓解了模型私有知识缺失和幻觉问题。SpringAI作为Spring生态的AI开发框架简化了与通义千问等大模型的集成提供了统一的ChatClient、EmbeddingClient等抽象接口。PostgreSQL配合pgvector扩展实现了向量数据与业务数据的统一存储与高效检索支持HNSW索引和相似度搜索。这一技术组合在企业知识库场景中尤为适用能够基于内部文档提供可溯源的智能问答服务。本文从需求拆解、环境搭建、数据入库、问答接口实现到性能调优完整呈现了一套可落地的企业级RAG系统构建方案帮助Java后端团队快速上手。 我之前在团队内部做过一个类似的企业知识库问答系统当时选型就纠结了很久看到这个标题的时候还挺有共鸣的。从标题里的信息来看这应该是一个典型的Java后端团队在落地RAG应用时的完整技术方案SpringAI做应用框架、通义千问做语义理解与生成、PostgreSQL配合pgvector做向量存储与检索整套链路非常清晰。这篇文章我就围绕这套方案的完整落地过程来展开从需求拆解、环境准备、核心实现到调优避坑把能直接参考的细节都整理出来。1. 项目整体设计与技术选型思路1.1 标题背后的真实业务需求先把这个项目到底在解决什么问题说清楚。企业级知识库智能问答系统本质上是要搭建一个能“读懂”企业内部文档比如产品手册、规章制度、技术文档、FAQ等然后让用户用自然语言提问系统给出有依据的答案的系统。它和传统的关键词搜索最大的区别在于它能理解语义能把“这个月请假流程怎么走”和文档里的“员工请假管理办法”关联起来。从技术演进路径来看早期团队可能会用ES做全文检索但ES对语义相似度的支持太弱用户换个说法就搜不到。后来有人尝试直接让大模型答题但大模型不掌握企业内部私有知识而且会一本正经地胡说八道。RAGRetrieval-Augmented Generation检索增强生成的出现就是为了解决这两个痛点先从知识库中检索出和用户问题最相关的文档片段再把片段和问题一起拼给大模型让大模型基于给定的资料作答。1.2 为什么选这套技术栈标题里每选一个组件背后都是有理由的这也是整套方案最值得参考的地方。SpringAI这是Spring官方推出的AI应用开发框架目的就是把AI能力以Spring Boot的方式集成进Java生态。之前用Java调大模型API都是裸写HTTP客户端各种参数拼接、JSON解析、流式输出处理非常繁琐。SpringAI把这些封装成了类似JdbcTemplate一样的使用体验同时提供了ChatClient、EmbeddingClient、VectorStore等抽象接口代码可以很整洁地组织起来。对于已经有Spring Boot技术积累的团队来说学习成本几乎可以忽略。通义千问选择通义千问更多是从国内生产环境的合规和网络稳定性考虑。直接调用OpenAI的接口在国内网络环境下并不稳定而且数据出境合规是一个企业级系统必须考虑的硬门槛。通义千问的API完全兼容OpenAI的接口风格HTTP调用方式几乎一致SpringAI也提供了官方支持切换成本很低。更重要的是企业私有数据做RAG检索时Embedding模型和Chat模型的API都在国内的阿里云上数据链路和合规边界都清晰。PostgreSQL pgvector这是整套方案中最有技术含量的一环。PostgreSQL本身就是企业级关系型数据库拥有完善的权限控制、备份恢复机制很多企业内部的数据资产都已经沉淀在PostgreSQL里。pgvector是PostgreSQL的向量检索扩展实现了vector数据类型和ivfflat、hnsw两类索引算法能把知识库的向量数据和业务结构化数据放在同一个数据库里统一管理。对比专门的向量数据库如Chroma、Milvus、Pineconepgvector有两个突出的优点不用额外维护一套基础设施数据事务性有保证——向量数据和业务数据能保持强一致性这对于企业级系统来说价值极大。1.3 技术架构的三个核心模块整套系统从逻辑上可以拆成三个互相独立又互相配合的部分知识库管理与数据入库模块负责读取企业内部文档将文本切成合适的块Chunk调用Embedding模型把文本块转成向量向量连同原文、元数据一起存入PostgreSQL的pgvector表中。语义检索模块用户提问时把问题也做一次Embedding转换然后到向量表中执行相似度检索取回TopK个最相关的文本片段。生成回答模块将用户问题、检索到的文本片段、提示词模板一起交给通义千问大模型由模型生成答案同时在答案中标注引用了哪些文档片段做到回答有据可溯。这套架构里RAG的三个关键词——检索Retrieval、增强Augmented、生成Generation——正好对应了上面三个模块协同工作而不是各干各的。2. 环境准备与基础依赖配置2.1 PostgreSQL安装与pgvector扩展启用先说我测试时的环境Windows 11PostgreSQL 16.x这个步骤在Linux上同样适用。Windows安装PostgreSQL很简单去官网下载PostgreSQL 16的Windows安装包一路Next即可。安装完成后需要配置一个系统环境变量PG_HOME指向安装目录并把PG_HOME\bin加入PATH这样命令行里就能直接使用psql命令了。这里提醒一下安装过程中会让你设置postgres用户的密码一定要保存好后面所有连接都会用到。安装完毕后在命令行验证一下psql -U postgres -h localhost -p 5432输入密码能进入交互式命令行说明PostgreSQL服务运行正常。接下来是pgvector扩展的安装。pgvector并不包含在PostgreSQL默认安装包里需要单独下载。Windows上最简单的方式是从PostgreSQL的EDB安装工具中搜索安装或者在pgvector的GitHub Release页面下载对应版本的安装包。如果是在Linux或者macOS上用包管理器一行命令就能完成# Ubuntu/Debian sudo apt install postgresql-16-pgvector # macOS (Homebrew) brew install pgvector安装完成后进入数据库创建扩展psql -U postgres -h localhost -p 5432 CREATE DATABASE knowledge_base; \c knowledge_base CREATE EXTENSION vector;记得单独创建一个专用数据库不要让应用和向量数据混在默认的postgres数据库里。执行CREATE EXTENSION vector后可以验证一下SELECT vector([1,2,3]) - vector([4,5,6]) AS distance;能返回一个数值说明扩展生效了可以开始建表。2.2 Spring Boot与SpringAI依赖引入项目使用Spring Boot 3.2.x SpringAI 0.8.x版本组合注意SpringBoot 3.x要求JDK 17所以确保本机安装的JDK版本不低于17。在pom.xml里引入关键依赖dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-qwen/artifactId version0.8.1/version /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-vector-store-pgvector/artifactId version0.8.1/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency dependency groupIdorg.postgresql/groupId artifactIdpostgresql/artifactId scoperuntime/scope /dependencySpringAI的依赖包还没有同步到Maven中央仓库需要在pom.xml里额外配置Spring的里程碑仓库。这一步是新手最容易踩坑的地方——不加仓库配置依赖永远拉不下来。repositories repository idspring-milestones/id nameSpring Milestones/name urlhttps://repo.spring.io/milestone/url snapshots enabledfalse/enabled /snapshots /repository /repositories引入依赖后在application.yml里配置通义千问的API密钥和模型参数。API密钥需要在阿里云百炼平台申请模型名这里用的qwen-plus对中文语义理解效果不错且价格适中。spring: ai: qwen: api-key: ${QWEN_API_KEY} chat: options: model: qwen-plus temperature: 0.2 embedding: options: model: text-embedding-v3 pgvector: index-type: hnsw similarity-top-k: 5 initialize-schema: true datasource: url: jdbc:postgresql://localhost:5432/knowledge_base username: postgres password: ${POSTGRES_PASSWORD}temperature: 0.2这个参数值得展开说明一下大模型的temperature控制生成内容的随机性范围是0到1之间。知识库问答这种场景我们追求的是准确和稳定不是创意所以temperature设低一点让模型尽可能基于检索到的资料作答减少自由发挥的概率。2.3 向量存储表结构设计PostgreSQL里用pgvector建表表结构设计直接影响检索效率和后续维护成本。我实际项目中的建表语句如下CREATE TABLE knowledge_chunks ( id BIGSERIAL PRIMARY KEY, content TEXT NOT NULL, metadata JSONB DEFAULT {}::jsonb, embedding vector(1024), created_at TIMESTAMP WITH TIME ZONE DEFAULT now() ); CREATE INDEX ON knowledge_chunks USING hnsw (embedding vector_cosine_ops) WITH (m 16, ef_construction 64);这里的几个设计要点content字段保存原始的文本块因为向量检索命中的是向量但最终要交给大模型阅读的内容必须是原始文本。metadata字段用JSONB类型可以灵活存储来源文档名称、章节标题、上传时间等业务属性将来做过滤检索会用到。比如HR问答系统里可以过滤出“文档类型员工手册”的数据再检索。embedding维度为1024对应的是通义千问text-embedding-v3模型输出的向量维度。如果换用其他Embedding模型维度必须和模型输出一致否则会报错。索引选择HNSW算法这是目前pgvector最推荐的索引算法检索精度和速度都有不错表现。3. 知识库数据入库与向量化处理3.1 文档切块策略这是RAG效果的分水岭在把文本转成向量之前先要把原始文档切成文本块。这块做得好不好直接决定了检索效果的上限。切太粗整篇文档一个向量查出来的内容不够精准丢给模型的信息里噪声大切太细语义被割裂模型拿到不完整上下文答案质量也会下降。我用的切块策略是“按标题段落感知切块”简单的说就是把文档按空行拆成段落每个段落作为候选文本块然后将相邻的小段落拼成大块保证每个块在300到500个Token之间同时保留段落间的链接不让上下文断裂。实现可以先拿SpringAI的DocumentReader做基础读取再用TokenTextSplitter按Token数切割这是我验证过的一个可行配置Bean public TokenTextSplitter tokenTextSplitter() { return new TokenTextSplitter(400, 100, 5, 1000, true); }这四个参数分别代表目标块大小400个Token、块重叠大小100个Token、最小块大小5个Token、最大块大小1000个Token、是否启用启发式切分。这里最关键的参数是“块重叠”让相邻的两个块之间存在部分重叠区域这样如果一句话刚好被切在边界上前后两个块都能覆盖到该信息避免关键信息因切分而丢失。3.2 文档导入完整流程在SpringAI里文档导入就是读取、切分、向量化、入库四步。实际代码如下Service public class KnowledgeImportService { private final VectorStore vectorStore; public KnowledgeImportService(VectorStore vectorStore) { this.vectorStore vectorStore; } public void importDocuments(String filePath) { // 读取文档这里以纯文本为例实际项目中会用到Tika来解析PDF和Word ListDocument documents new TextFileReader(filePath).get(); // 切块 ListDocument splittedDocs tokenTextSplitter.apply(documents); // 存入向量数据库会自动调用EmbeddingClient做向量化 vectorStore.add(splittedDocs); } }vectorStore.add()这一步在内部完成的事情是先把每个文本块调用通义千问的Embedding接口转成向量再和文本内容一起写入knowledge_chunks表。因为要调用远程API所以在文档量比较大的时候这个过程会比较慢建议加一个断点续传机制比如每处理完一批文档就标记一下状态已入库的文档跳过不处理。3.3 元数据标注实践向量检索之后要能做到“溯源”也就是答案能对应到企业知识库里的具体来源文档。这个能力在RAG系统里被称为“引用溯源”背后依赖的正是入库时候的元数据设计——把每个文本块能对应到哪个文档、哪个章节、哪个页面都在元数据里精细记录。Document doc new Document(content, Map.of( sourceFile, 产品手册_v2.0.docx, chapter, 第三章 故障处理, page, 58, updateTime, LocalDate.now().toString() ));这些元数据传给大模型之后大模型在生成答案时就能在回答中主动引用来源比如“根据《产品手册_v2.0》第三章该故障的处理步骤如下……”。我还在项目里加了一个逻辑检索结果返回时把命中的来源文件列表单独提取出来作为问答接口的响应字段一并返回前端可以直接把这些来源展示给用户。4. 核心功能实现问答接口开发4.1 基于向量检索的直接问答核心问答接口的设计看起来简单就是在Controller里调用封装好的问答服务类。我用的SpringAI新版API风格如下RestController RequestMapping(/api/chat) public class ChatController { private final AiService aiService; public ChatController(AiService aiService) { this.aiService aiService; } PostMapping(/ask) public ResultString ask(RequestBody QuestionRequest request) { String answer aiService.ask(request.question()); return Result.success(answer); } }服务层里先把问题向量化然后从向量库检索最相关的文档片段再拼接到提示词里发个通义千问Service public class AiService { private final ChatClient chatClient; private final VectorStore vectorStore; public String ask(String question) { // 1. 检索知识库 ListDocument documents vectorStore.similaritySearch(question); String context documents.stream() .map(Document::getText) .collect(Collectors.joining(\n\n)); // 2. 组装提示词并调用大模型 String answer chatClient.prompt() .system(你是企业知识库助手请仅根据提供的知识库内容回答问题。 如果知识库中没有相关内容明确说明不知道不要编造。) .user(知识库内容如下\n context \n\n用户问题 question) .call() .content(); return answer; } }很多人在这一步会把检索出来的所有内容一股脑全塞给模型这是一个典型的错误。如果检索返回的5个文本块里只有1个和问题相关其余4个大概率会把模型带偏模型不仅会引用无关内容答非所问还会把这个问题域的边界彻底模糊掉。我的经验是先设置一个相似度阈值比如余弦相似度低于0.7的结果直接丢弃如果过滤后剩余文档少于2个直接返回“知识库中没有找到相关内容”不再调用大模型。这样能节省API费用还能避免模型胡扯。阈值的高低和Embedding模型的特性相关需要拿真实业务问题跑一批测试数据来调实际操作时用0.7作为起点上下浮动找到最适合自己项目数据的值。4.2 流式输出提升用户等待体验直接等待大模型回答完毕再返回结果在对话场景下体验并不好。用户提问后可能要等好几秒才能看到答案这种“卡住”的感觉会让用户觉得系统性能差。我改成流式输出把大模型边生成边返回的内容通过SSEServer-Sent Events实时推送给前端用户能在2秒内看到第一个字出来体感上会顺畅很多。SpringAI对这块做了很好的封装PostMapping(value /stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public FluxString stream(RequestBody QuestionRequest request) { return chatClient.prompt() .system(你是企业知识库助手。) .user(request.question()) .stream() .content(); }前端用EventSource或者fetch的ReadableStream接口就能逐字展示模型生成的内容。这里的Flux是响应式编程里的数据流类型调用stream()方法后会返回一个Flux对象前端页面订阅这个流内容生成一段就推送一段体验上就像在和真人聊天。4.3 多轮对话与记忆管理简易的问答接口一次回答完就结束但在实际使用中用户会追问“那退款流程呢”这里“那”指代的是之前提到的某个场景。如果不带上历史对话内容大模型并不知道“那”是什么。SpringAI提供了MessageMemory接口来做会话记忆管理。我这里实现一个基于内存的方案Service public class MemoryAiService { private final ChatMemory chatMemory; public String askWithMemory(String question, String conversationId) { ChatClient chatClient ChatClient.builder(chatModel) .defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory) .chatMemoryRetrievalWindow(10) .build()) .build(); return chatClient.prompt() .user(question) .call() .content(); } }这里chatMemoryRetrievalWindow(10)的意思是只保留最近10轮对话历史。太长的历史不仅会浪费大模型的上下文窗口还会引入过多噪声让模型不知道该重点关注哪条信息。如果是在生产环境ChatMemory还需要换成Redis实现不然内存会随着会话数增加而膨胀。5. 基于实时数据的RAG扩展让问题驱动SQL查询标题里只有向量数据库和RAG这两个关键词但企业知识库系统做到后期一定会遇到一个痛点向量检索只能回答“有明确文档描述”的问题对于“上季度各部门报销总额是多少”这类需要查结构化数据的问题让模型直接从文档片段找答案是找不出来的。我项目中加了一个模块叫“Text-to-SQL RAG扩展”简单说就是让大模型根据用户问题生成SQL查询语句然后在PostgreSQL里执行并把查询结果组织成文本再让大模型基于查询结果做进一步的解释和总结。这个过程的优势在于可以把“语义理解”能力和“结构化数据查询能力”打通实现从文档知识到业务数据的统一问答入口。实现的核心思路public String dataQuery(String question) { // 1. 让大模型生成SQL带表结构信息作为上下文 String sqlPrompt 根据用户的商业问题生成SQL查询。 数据库表结构如下 {schema} 用户问题{question} 请只输出SQL不要任何解释。 ; String sql chatClient.prompt() .user(sqlPrompt.replace({schema}, schemaInfo).replace({question}, question)) .call().content(); // 2. 校验并执行SQL if (!isQueryOnly(sql)) { throw new SecurityException(仅允许SELECT查询); } ListMapString, Object rows jdbcTemplate.queryForList(sql); // 3. 把查询结果交给大模型组织成自然语言回答 String finalAnswer chatClient.prompt() .user(查询结果如下请用自然语言回答用户问题 question 结果 rows) .call().content(); return finalAnswer; }这里有一个绝对的安全底线不能让大模型生成的SQL直接执行至少要做一个“仅允许SELECT”的校验更严谨的做法是把连接数据库账号的权限设置为只读。不然用户诱导大模型生成一条DROP TABLE或者UPDATE语句后果不堪设想。Text-to-SQL 的准确率和成功率跟表结构的描述质量强相关比如字段命名混乱的库大模型就很容易生成错误SQL。建议提前把表结构整理成“表名字段名字段注释业务说明”的元数据描述让大模型有足够上下文来理解和生成SQL。6. 前端界面设计与完整演示6.1 简洁聊天页面实现一个完整的企业级系统不能只有API还需要一个能让用户直接使用的界面。我用一个原生HTML页面实现了聊天界面没有引入任何前端框架部署时直接放到Spring Boot的static目录下就能访问。!DOCTYPE html html head meta charsetUTF-8 title企业知识库问答系统/title style body { max-width: 800px; margin: 0 auto; padding: 20px; font-family: system-ui; } #chatBox { height: 500px; border: 1px solid #ddd; border-radius: 8px; overflow-y: auto; padding: 15px; } .message { margin: 8px 0; padding: 10px; border-radius: 8px; } .user { background: #e3f2fd; text-align: right; } .assistant { background: #f5f5f5; } #sources { font-size: 12px; color: #666; margin-top: 5px; } #inputArea { display: flex; margin-top: 10px; } #question { flex: 1; padding: 10px; border: 1px solid #ddd; border-radius: 4px; } button { padding: 10px 20px; margin-left: 8px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; } /style /head body h2企业知识库智能问答/h2 div idchatBox/div div idinputArea input typetext idquestion placeholder请输入您的问题... button onclicksendQuestion()发送/button /div script async function sendQuestion() { const question document.getElementById(question).value.trim(); if (!question) return; const chatBox document.getElementById(chatBox); chatBox.innerHTML div classmessage user question /div; document.getElementById(question).value ; const resp await fetch(/api/chat/ask, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ question }) }); const data await resp.json(); chatBox.innerHTML div classmessage assistant data.data /div; chatBox.scrollTop chatBox.scrollHeight; } /script /body /html实际项目中前端需要显示答案的来源引用上面的代码只是骨架演示效果相当于一个能用的原型。6.2 完整功能演示流程启动项目后按这个流程过一遍可以看到完整效果准备一份测试文档company_policy.txt内容包含请假制度、报销流程、考勤规定等约5000字的企业内部制度。调用/api/import接口导入该文档日志里能看到文本块被切分并写入向量库的过程。打开浏览器访问http://localhost:8080在输入框问“员工请病假需要提供什么材料”。后端先检索到文档中相关章节、拼入提示词调通义千问前端收到答案后展示答案里能正确引用“根据企业请假管理制度”等相关内容。再问一个“报销发票的抬头应该填什么”系统能准确回答出“公司全称”因为检索到的文本块就是报销管理章节里的内容。整个演示流程走下来基本就能验证这套RAG系统的链路是通的。7. 系统优化与常见问题排查7.1 检索质量调优三板斧第一板斧是调整similarity-top-k参数也就是召回几条文档片段送给大模型。调太少了可能漏信息调太多了噪声多。我测试下来企业知识库场景5到8条是比较合适的区间。第二板斧是调整切块参数。TextSplitter的目标块大小和重叠Token数直接关系到检索结果能不能完整表达一个语义单元。块太小模型看不到前因后果块太大检索结果太粗糙不够精细。建议先用400到500个Token做一次基准测试观察回答质量再决定要不要调。第三板斧是引入重排Rerank机制。向量检索是第一轮粗筛它的排序可能不是最优的。粗筛后可以把召回的多个文本块交给一个专门的Rerank模型或者让大模型自己打分排序按匹配度重新排序后再截取Top3交给生成模型。这一步能把回答精确度提升不少但费用也会上浮。我的方案是把重排模式做成可配置调试期全量开启上线后根据预算选择是否开启。7.2 高并发与查询性能优化最早做压测的时候一个简单的文本上报告诉我单机部署的瓶颈很明确向量检索的HNSW索引虽然快但每条查询都要调一次通义千问的Embedding接口导致检索延迟中外部API的调用占了大头我的服务自身基本没耗费什么时间在检索上。这个问题可以从两个方向解决。一个是引入对象存储来分层管理文档原文同时将知识库元数据存入Redis缓存降低热数据的查询压力。另一个方向是把通义千问的Embedding调用做成批量异步任务入库的时候多条文本块合并成一个Batch请求一次发送查询的时候由于用户的查询是即时行为没法做成异步但可以通过连接池控制并发上限避免触发API限流。我在并发测试中把SpringAI默认的RestClient连接池调到了最大100个连接。没有这个设置的时候并发超过30个请求就会开始报连接超时。额外在API密钥的配置上建议创建多个密钥轮换使用避免单密钥触发阿里云那边的QPS限制。7.3 踩坑实录pgvector安装与使用中的典型问题问题一Windows上pgvector安装失败最常见的报错是ERROR: could not open extension control file原因是pgvector没有真正安装成功。Windows下需要特别注意下载的安装包版本和PostgreSQL版本必须完全一致比如PostgreSQL 16.4就对应pgvector 0.7.x的特定版本。版本不一致时扩展文件虽然能复制到目标目录但加载时因为动态库依赖不匹配会直接报错。问题二向量维度不匹配vector(1024)表和实际Embedding模型返回的向量维度不一致时执行INSERT操作会直接报错expected 1024 dimensions, not 1536。我在调试阶段发生过一次原因是前期用OpenAI的Embedding模型测通了后来切换成通义千问的text-embedding-v3忘记重新建表。解决方法是确认Embedding模型后重建表中embedding列的维度或者直接删表重建。问题三HNSW索引构建慢文档量达到十万级时HNSW索引的构建时间会明显变长实测可能要几分钟。如果在数据入库之前就创建了索引那么每条文档写入时都在同步更新索引效率反而不高。我的经验是先全量导入数据最后统一创建索引或者在导入过程中先用ivfflat索引过渡等数据稳定后再换成HNSW。问题四检索返回结果不稳定同一个问题在系统里查两次返回结果不同这通常是因为没有过滤掉无关的文本块。加上相似度阈值过滤后大部分问题都能稳定下来。如果还是不稳检查一下文档切分是否有重叠以及查询的时候是否使用了正确的距离算子余弦距离和欧氏距离的排序结果会完全不同。7.4 系统安全与合规注意事项企业级知识库系统还有一个必须处理的问题权限控制。不是所有员工都应该能检索到所有文档比如薪资制度就不应该开放给全员。我的做法是在metadata里增加权限标签检索时在SQL层面加过滤条件要求数据块的权限标签必须包含当前用户所属角色。这个过滤要在向量检索阶段执行等检索完之后再过滤会浪费引擎的计算资源而且有可能把最相关的文档过滤掉导致最终没有足够资料喂给模型。另外API密钥千万不要直接写在配置文件里提交到Git仓库。用环境变量的方式加载密钥或者接入配置中心。我也在系统里加了操作审计日志记录每个用户提了什么问题、系统返回了哪些内容一旦出现内容合规风险能被及时追踪排查。8. 项目后续扩展与个人实践心得最后聊聊这套系统的扩展方向。项目上线后我陆续接到几个改造需求都验证了当初选型的扩展性一是把知识库从单一文本扩展到PDF、Word等格式直接接入Apache Tika做格式解析。由于SpringAI本身就基于Tika抽象了文档加载器换格式的时候通过调整DocumentReader实现就能无缝接入不用改后续逻辑。二是引入了Agentic RAG的尝试。单纯问答是“检索-生成”两条路径而Agentic RAG是让大模型扮演一个自主Agent它能根据用户意图自主决定要检索几次、是否查询数据库、是否需要改写问题后再检索。这套方案对复杂问题处理更好架构也和这个问题完全兼容只是Prompt和工具调用逻辑会复杂一些。三是做知识库的定期更新机制。企业文档是持续变化的业务负责人需要定期把新的制度文档上传到系统老的版本自动下线。我在metadata里加了version和effectiveDate字段入库时做版本比对旧版本的数据物理删除确保模型永远基于最新版本文档作答。我个人在实际操作中最深的一个体会是RAG系统的效果不是靠某一个环节的完美达成的而是一条链路上所有细节叠加的结果。切块策略、Embedding选型、检索阈值、提示词模板、模型参数每一步看起来都在影响几个百分点的效果但这些百分点叠加起来就是“能用”和“不能用”的天壤之别。如果你正在做类似的项目我建议先从一个小规模知识库把链路跑通然后用真实业务问题一点一点调优不要迷信某一个组件的“最佳实践”你自己的业务数据才是最可靠的调优依据。本文还有配套的精品资源点击获取