
知识库已经建好了向量库也灌进去了检索接口也能返回片段了——然后呢我见过太多团队卡在这一步Demo 演示时效果惊艳一上生产就露馅答非所问、引用错乱、多轮对话直接失忆。问题从来不在有没有知识库而在于检索之后那一整套编排逻辑没人认真做。这篇就围绕 Spring AI 的 RAG 实战把 Java 程序员在知识库落地之后真正要补的功课讲透从 VectorStore 选型、Embedding 模型取舍到检索增强的查询改写、重排、上下文拼装再到多轮会话与效果评估。适合已经跑通基础 RAG Demo、准备往生产环境推进的 Java 后端也适合想搞清楚 RAG 到底难在哪的技术负责人。1. 先搞清楚知识库建好之后缺口到底在哪很多人对 RAG 的理解停留在文档切块 → 向量化 → 存库 → 相似度检索 → 塞给大模型。这条链路本身没错但它只是最小可行版本。真正上线后你会发现用户的问题和文档里的表述往往对不上检索回来的 Top-K 里混着一堆语义相近但答非所问的片段模型拿着这些半对半错的上下文输出自然飘。1.1 检索质量才是 RAG 的天花板我做过一个内部技术文档问答最初用最朴素的向量检索Top-5 命中率大概只有六成。也就是说十次提问里有四次正确答案压根没进上下文窗口。这种情况下你换再强的模型都没用——模型只能基于你给它的材料回答材料错了神仙难救。这里有个反直觉的结论RAG 的效果瓶颈八成在检索侧两成在生成侧。所以 Java 程序员在知识库建好之后第一件要做的事不是去调 prompt而是回头审视检索链路切块策略合不合理、Embedding 模型选得对不对、要不要加关键词混合检索、要不要上重排。1.2 从能检索到检索得准的三道坎我把这段经历拆成三道坎你可以对照自己的项目看看卡在哪阶段典型表现核心矛盾能检索接口能返回片段但相关性差切块粒度与语义完整性冲突检索得准Top-K 命中率提升但排序不稳纯向量相似度无法表达重要性检索得稳多轮、多意图下依然可靠查询与文档的语义鸿沟第一道坎是切块。按固定字符数硬切很容易把一段完整逻辑拦腰截断导致检索到的片段缺头少尾。第二道坎是排序向量相似度高不代表对回答有用需要重排模型介入。第三道坎是查询本身用户口语化的提问和文档书面化的表述之间存在鸿沟得靠查询改写来弥合。1.3 Java 程序员的独特优势在哪别觉得自己在 AI 浪潮里吃亏。RAG 这套东西本质上是一个数据管道 服务编排的问题而这恰恰是 Java 后端的强项。向量库的读写、检索结果的后处理、多路召回的结果融合、缓存与降级、并发与限流——这些工程问题写惯了 Spring 生态的人上手极快。Spring AI 的价值就在于它把这些能力用 Java 程序员熟悉的方式封装了出来VectorStore抽象、Advisor链、ChatClient流式接口都是 Spring 一贯的依赖注入和面向切面风格。你不需要去啃 Python 生态用现有的工程能力就能把 RAG 做扎实。2. VectorStore 与 Embedding选型决定了下限选型这一步很多人是拍脑袋定的结果后面怎么调都别扭。我建议在动手写代码前先把 VectorStore 和 Embedding 这两件事想清楚因为它们决定了整个系统的能力下限。2.1 VectorStore 选型别一上来就上重型方案Spring AI 支持多种 VectorStore 实现常见的有内存版、Redis、PGVector、Milvus、Elasticsearch 等。选型时我一般看三个维度数据规模、运维成本、是否需要混合检索。SimpleVectorStore内存版适合本地开发和单元测试重启即丢千万别上生产。PGVector如果你已经在用 PostgreSQL这是性价比最高的选择向量和业务数据同库事务一致性天然有保障。Redis适合已有 Redis 集群、对延迟敏感的团队但要注意内存成本和持久化策略。Milvus / Elasticsearch数据量上千万、需要复杂过滤和混合检索时再考虑运维复杂度也相应上升。我的经验是中小规模百万级向量以内优先 PGVector。理由很实在——你不需要为向量单独维护一套中间件备份、监控、扩容都复用现有 PG 的能力团队学习成本几乎为零。等数据量真的撑不住了再迁移Spring AI 的VectorStore接口是统一的换实现基本只改配置。2.2 Embedding 模型中文场景要特别当心Embedding 模型负责把文本转成向量它的质量直接决定检索的语义匹配能力。这里有个坑我必须提醒很多英文表现优秀的模型在中文语义上会明显掉档。选型时我会关注几个点向量维度维度越高表达能力越强但存储和计算成本也越高。常见的有 768、1024、1536 维。最大输入长度决定了单次能编码多长的文本直接影响切块策略。中文语义能力最好用你自己的业务语料做个小规模对比测试别只看榜单。是否支持批量批量编码能大幅降低灌库时间。提示Embedding 模型一旦确定灌库时用的模型和查询时用的模型必须完全一致。中途换模型意味着整个向量库要重新生成这个成本一定要提前评估。2.3 切块策略比模型选择更容易被忽视切块Chunking是 RAG 里最不起眼、却最影响效果的环节。我见过太多项目直接用固定长度切结果检索出来的片段读起来莫名其妙。我的做法是按语义结构切而不是按字符数切。具体来说Markdown 文档按标题层级切保证每个块是一个完整的语义单元。代码文档按方法或类切别把函数拦腰截断。长段落设置重叠区overlap一般取块大小的 10%~20%避免边界信息丢失。块大小也要权衡太小则语义不完整太大则噪声多、检索精度下降。我一般从 500~800 字符起步配合 100 字符左右的重叠然后根据实际检索效果微调。这个参数没有标准答案必须用你的真实数据去试。3. 检索增强让 Top-K 真正有用知识库和向量检索跑通之后很多人就停在这里了。但正如前面说的朴素向量检索的命中率往往不够看。这一章讲三个我实测有效的增强手段。3.1 查询改写弥合口语与书面语的鸿沟用户提问是口语化的这个功能咋用啊而文档里写的是功能调用说明。这两句话向量相似度可能并不高导致检索不到。解决办法是在检索前先让模型把用户问题改写成更接近文档表述的形式。在 Spring AI 里你可以用一个前置的ChatClient调用来完成改写把原始问题转成若干个检索友好的查询然后并行检索、合并结果。这就是常说的多查询检索Multi-Query。// 伪代码示意查询改写后再检索 String rewritten chatClient.prompt() .user(u - u.text(把下面的问题改写成适合文档检索的查询只输出查询本身{q}) .param(q, originalQuestion)) .call() .content(); ListDocument docs vectorStore.similaritySearch( SearchRequest.builder().query(rewritten).topK(5).build());改写的好处是召回率明显提升代价是多一次模型调用延迟增加。我的做法是只在检索结果置信度低时才触发改写兼顾效果和成本。3.2 混合检索向量 关键词双管齐下纯向量检索对专有名词、型号、代码标识符很不友好。比如用户搜OrderService 超时向量检索可能返回一堆泛泛而谈的服务超时处理而真正包含OrderService的文档反而排后面。这时候就要引入关键词检索BM25 之类与向量检索的混合两路召回后用 RRFReciprocal Rank Fusion之类的算法融合排序。Spring AI 本身对混合检索的支持还在演进实践中我通常自己写一层融合逻辑向量检索取 Top-N关键词检索取 Top-N按排名加权合并。检索方式优势短板向量检索语义匹配强专有名词弱关键词检索精确匹配强无法理解同义混合检索兼顾两者需要融合调参3.3 重排把真正有用的片段顶上来检索回来的 Top-K顺序往往不理想。重排Rerank就是用一个小而精的模型对候选片段重新打分排序。它比向量相似度更能判断这段内容和问题到底相不相关。典型流程是向量检索先粗召回 20~50 条重排模型精排后取前 3~5 条塞进上下文。这样既保证了召回率又保证了上下文质量。重排模型通常比生成模型小得多延迟可控是性价比很高的一环。注意重排不是万能的。如果粗召回阶段正确答案压根没进来重排也救不了。所以召回率和精确率要分开优化先保证召回再靠重排提精确。4. 上下文拼装与多轮会话别让模型失忆检索做完了接下来是把片段拼成 prompt 喂给模型。这一步看似简单实则暗藏玄机尤其是涉及多轮对话的时候。4.1 上下文拼装给模型清晰的材料边界我见过不少项目把检索到的片段直接字符串拼接中间连个分隔都没有模型根本分不清哪段是哪段。正确做法是给每个片段加上明确的标识和来源让模型知道自己在引用什么。一个我常用的模板结构是这样的你是一个严谨的技术助手只能基于下面提供的资料回答问题。 如果资料中没有相关信息请明确说明资料中未提及不要编造。 【资料1】来源xxx.md 片段内容 【资料2】来源yyy.md 片段内容 【用户问题】 question这样做有两个好处一是模型有据可依减少幻觉二是方便做引用溯源回答里可以标注根据资料1用户能点回去看原文。引用溯源在生产环境里非常重要它直接决定了用户对系统的信任度。4.2 多轮会话查询改写要带上历史单轮问答好办多轮就麻烦了。用户第二句问那它呢这个它指代什么只有结合历史才能知道。如果直接拿那它呢去检索必然一塌糊涂。解决办法是在检索前做查询改写时把对话历史一起带上让模型把指代消解掉生成一个自包含的查询。Spring AI 提供了会话记忆ChatMemory的能力你可以把历史消息注入改写 prompt。// 伪代码带历史的查询改写 String standalone chatClient.prompt() .system(根据对话历史把用户最新问题改写成不依赖上下文、可独立检索的查询。) .user(u - u.text(历史{history}\n最新问题{q}) .param(history, historyText) .param(q, latestQuestion)) .call() .content();这一步做完多轮场景下的检索质量会有质的提升。我实测下来加了历史改写之后多轮追问的命中率能提升三成以上。4.3 会话记忆的存储与裁剪会话记忆不能无限增长否则 token 成本飙升、还会稀释关键信息。我的策略是滑动窗口 摘要保留最近 N 轮完整对话更早的内容压缩成一段摘要。Spring AI 的ChatMemory支持自定义实现你可以按会话 ID 存到 Redis设置合理的过期时间。提示会话记忆和知识库是两回事。记忆存的是这次对话聊了什么知识库存的是业务知识。别把两者混在一个存储里否则清理和扩容都会很痛苦。5. 效果评估与调优没有度量就没有优化RAG 最怕的就是感觉还行。没有量化指标你根本不知道改动是变好了还是变差了。这一章讲怎么建立一套可落地的评估体系。5.1 建立评估集从真实问题里来评估集不要自己拍脑袋编要从真实用户提问里采样。我一般收集 100~200 条真实问题人工标注每条问题的标准答案和应该命中的文档片段。这就是你的金标准。有了评估集你就可以量化两个核心指标检索命中率RecallK正确答案是否出现在 Top-K 里。回答准确率最终回答是否正确、是否有幻觉。5.2 用模型做自动评估人工评估成本高可以用模型来做初筛。让一个模型扮演裁判对比生成答案和标准答案给出相关性、准确性、完整性打分。虽然不如人工精准但胜在快、可批量、可回归。// 伪代码模型裁判评估 String score chatClient.prompt() .system(你是评估裁判对比标准答案和模型回答从准确性、完整性两个维度打1-5分只输出分数。) .user(u - u.text(标准答案{std}\n模型回答{ans}) .param(std, standardAnswer) .param(ans, modelAnswer)) .call() .content();每次改动检索策略或 prompt都跑一遍评估集看指标是涨是跌。这就是 RAG 的回归测试没有它你的优化就是盲人摸象。5.3 常见问题的排查思路我把实践中遇到的高频问题整理成一张排查表方便你对照定位现象可能原因排查方向答非所问检索没命中看 Top-K 是否含正确答案回答笼统片段太碎或太泛调整切块粒度引用错乱上下文拼装无标识加来源标注多轮失忆未做历史改写引入查询改写专有名词搜不到纯向量检索加关键词混合检索排查时我习惯先看检索结果再看生成结果。因为绝大多数问题出在检索侧把检索结果打印出来一看往往就真相大白了。6. 生产化落地那些 Demo 阶段不会暴露的问题Demo 跑通和生产可用之间隔着一条鸿沟。这一章讲几个上线后才会真正疼的点。6.1 延迟与成本流式输出和缓存是刚需RAG 一次问答涉及多次模型调用改写、检索、重排、生成延迟很容易堆到好几秒。用户体验上流式输出SSE几乎是必须的让用户先看到字一个个蹦出来感知延迟会大幅降低。Spring AI 的ChatClient支持流式返回配合 Spring MVC 的SseEmitter或 WebFlux 就能实现。成本方面Embedding 和生成都是按量计费的。我的做法是检索结果缓存相同或相似查询直接命中缓存省掉检索和重排。Embedding 缓存文档灌库时对相同内容去重避免重复编码。分级模型改写、重排用小模型最终生成用大模型。6.2 降级与容错模型挂了怎么办模型服务不是永远可用的。超时、限流、报错都可能发生。生产系统必须有降级策略检索失败时退化为纯关键词检索。生成失败时返回检索到的原文片段让用户自己看。设置合理的超时和重试避免请求堆积拖垮整个服务。这些在 Spring 生态里都有成熟方案Resilience4j做熔断限流Retryable做重试都是现成的。6.3 数据更新知识库不是一次性的业务文档会更新知识库必须能增量更新。我的建议是给每个文档块打上版本和时间戳更新时按文档 ID 先删后插避免旧数据残留。如果用的是 PGVector可以结合业务表做软删除和版本管理检索时只查最新版本。注意增量更新时Embedding 模型必须和初始灌库时保持一致否则新旧向量不在同一语义空间检索结果会错乱。7. 我踩过的几个坑你可以直接绕开最后分享几个实打实踩过的坑都是文档里不会写、但能让你少熬几个通宵的经验。第一个坑切块时把表格切碎了。技术文档里表格很常见按字符数硬切会把表格拦腰截断检索出来的片段完全没法读。后来我改成按 Markdown 结构解析表格整体作为一个块问题就解决了。第二个坑Top-K 设太大。一开始觉得多召回点总没错结果上下文塞了一堆无关片段模型反而被干扰回答质量下降。后来发现Top-K 不是越大越好3~5 条精排后的片段往往比 10 条杂乱的更有效。第三个坑忽略了查询和文档的语言风格差异。用户问得随意文档写得正式直接检索效果差。加了查询改写之后命中率肉眼可见地提升。这个改动成本很低收益却很高强烈建议早点做。第四个坑没有评估集改来改去全凭感觉。这是最致命的。有了评估集之后每次改动都能看到指标变化优化方向一下子就清晰了。建立评估集这件事越早做越好哪怕只有几十条也比没有强。RAG 这套东西说难不难说简单也不简单。知识库建好只是起点真正的功夫在检索增强、上下文编排和工程化落地。Java 程序员在这件事上有天然优势——你擅长的服务编排、缓存降级、并发治理恰恰是 RAG 生产化最需要的。把 Spring AI 的抽象用起来把工程能力发挥出来这套系统就能真正跑稳。