Spring AI 2.0 RAG工程优化:Chunking、混合检索与Rerank全攻略
2026/9/24 23:19:15 网站建设 项目流程

Spring AI 2.0 的 RAG 工程优化,说到底是把“能跑”的 demo 打磨成“能上线”的系统。今年我在两个生产级项目里把完整的 RAG 链路重新过了一遍,从文本切分、向量检索、关键词召回一路做到重排序,过程里踩了不少文档里没写清楚的坑。这篇就把我这套工程实践的完整思路、参数选型和代码实现都摊开讲清楚,给正在做 Java 技术栈 RAG 项目的朋友一份可以直接抄的作业。

先说结论:Chunking 决定 RAG 效果的下限,混合检索把召回到位,Rerank 把精度拉满。三个环节各管一段,缺一个都会在真实业务里露馅。Spring AI 2.0 在这个链路上的最大价值,是它把向量数据库接入、Prompt 模板、模型调用这些基础设施标准化了,让我们能把主要精力放在策略调优上,而不是奔波在各种 SDK 的边缘 case 里。

1. 内容整体设计与思路拆解

1.1 为什么 RAG 工程需要分阶段优化

如果你做过几版 RAG demo,大概率会遇到这种尴尬:知识库问答在测试集上表现不错,一上生产就各种答非所问。原因通常不在大模型本身,而在 RAG 的链路设计。信息的流转路径是这样的——文档进来先切块、做向量化,用户提问后走检索,找出最相关的片段,再送给大模型作为上下文生成答案。任何一环的信息损耗,都会在下游被放大。

这就像做菜。食材处理得好不好(Chunking)决定了能不能入味,菜市场的采购网络(检索)决定了能不能买到新鲜食材,出锅前的调味(Rerank)决定了最终口感。光买好食材不处理不行,处理和采购都做好了不调味也不行。RAG 的链路优化逻辑一模一样,而且是强耦合的——前面环节的失误,后面环节再怎么补救都有上限。

所以我把整个优化拆成三大块来思考:

  • Chunking 优化:解决“信息以什么粒度入库”的问题,这是所有后续工作的地基
  • 混合检索:解决“怎么能把相关片段都找到”的问题,通过多路召回保证不遗漏
  • Rerank 精排:解决“找回来的片段里哪些真正有用”的问题,把最相关的内容送到 LLM 面前

这个顺序不能乱。检索策略要跟着 Chunking 方式调,Rerank 的输入质量又取决于检索结果。先定切分,再调召回,最后上精排,是成本最低的推进路径。

1.2 Spring AI 2.0 在 RAG 链路上的定位

Spring AI 2.0 不是来替代 LangChain 的,它的核心价值在于把 Java 生态里做 AI 应用的基础设施标准化了。如果你用 Spring Boot 做后端,之前接大模型是各种 SDK 混着写,今天调 OpenAI 的 HTTP 接口,明天换通义千问又得改一套代码。Spring AI 2.0 用统一抽象把这层问题解决了:不同的 Model、Embedding、VectorStore 都能通过配置切换,业务代码基本不用动。

在 RAG 场景里,Spring AI 2.0 的几个核心抽象是这样的:

  • EmbeddingModel:统一文本向量化接口,可以对接本地部署的 bge-m3,也能切到智谱的 embedding 接口
  • VectorStore:统一向量存储接口,支持 Milvus、PGVector、Redis 等主流实现
  • Advisor:提供了一种类似 Spring AOP 的机制,可以在问答链路上做检索增强、日志记录等横切操作
  • ChatClient:统一对话客户端,底层对接 DeepSeek、通义千问、智谱等模型都有对应的 Starter

这套抽象带来的直接好处是,你在本地调试用的可能是 PostgreSQL 的 pgvector,到生产换 Milvus,代码层面几乎不用动。Embedding 模型从开源切到商用 API,也只是改配置的事。

1.3 方案选型的整体考量

在动手之前,有几个关键选型需要先厘清思路。

第一,向量数据库选型。生产环境我建议优先考虑 Milvus 或 Elasticsearch。Milvus 在纯向量检索的性能上更极致,ES 的优势是如果你本来就有 ES 集群,它可以同时承担全文检索和向量检索,少维护一套系统。开发环境用 PGVector 最省事,Spring AI 对它的支持很完善,docker 起一个 postgres 镜像就全搞定了。

第二,重排序模型的部署方式。Rerank 模型现在主流选择是 bge-reranker 系列,大一点的用 v2-m3,轻量的用 base。如果你手头只有 Windows 机器,也能通过 Ollama 或者 FastAPI 封装的方式跑起来,后面我会详细讲部署细节。

第三,混合检索的实现路径。Spring AI 2.0 的 VectorStore 接口本身只做向量检索,关键词召回需要自己实现,常见的方案是用 ES 的 BM25 或者直接查数据库做全文索引。两条路都行,取决于你的基础设施。

2. Chunking 优化:决定 RAG 效果下限的环节

2.1 Chunking 的底层逻辑与常见误区

很多教程把 Chunking 讲得很玄乎,实际上它的核心矛盾就一个:块太大,信息密度低,检索精度差;块太小,上下文碎片化,语义不完整

块太大的问题很好理解。你把一篇五千字的文档切成一个块,用户问其中某个细节,向量检索返回的是整篇文档的向量。这个向量被五千字的内容“稀释”了,和问题的相关度被大量无关内容拉低,召回精度自然上不去。

块太小的问题发生在召回之后。假设你把文档切成每句一个块,用户问“这个项目的预算是多少”,相关的那句话可能依赖上下文里的业务背景才能回答。LLM 只拿到了孤零零的一句话,缺少必要的上下文,回答质量就崩了。

我见过很多团队在 Chunking 上的通病是只调 chunk_size 和 chunk_overlap 这两个参数,完全忽略了文档结构。结构化信息被强行切成碎片,Markdown 的标题层级、表格的行列关系、代码块的完整性,全都被破坏掉了。

之前我做过一个运维知识库项目,里面有大量操作手册,用的是 Markdown 格式。一开始用固定 500 字切块,结果调查问题的答案经常在三个不同块里各讲一半,检索怎么调都找不齐。后来针对 Markdown 文档做结构感知切分,按标题层级来分块,同一个二级标题下的内容尽量留在一个块里,效果立竿见影。

2.2 分块策略选型:固定窗口与结构感知的取舍

目前主流的 Chunking 策略可以归成三类:

  • 固定大小切分:按字符数或 token 数硬切,加上 overlap 保证上下文衔接。优点是实现简单,通用性强;缺点是会破坏语义完整性和文档结构。
  • 结构感知切分:解析文档结构(标题、段落、列表、表格),按结构边界切分。优点是语义完整,信息密度高;缺点是解析逻辑复杂,对文档格式的规范性有要求。
  • 语义切分:用 embedding 计算句子间的语义相似度,在语义断裂处切分。优点是最符合语义边界;缺点是需要 embedding 计算开销,而且效果直接取决于 embedding 模型的质量。

实际生产项目中,我用的最多的是“结构感知为主,固定窗口兜底”的组合策略。具体来说:

  • 对于 Markdown 和 HTML 文档,走结构感知切分,按标题层级控制块大小
  • 对于 PDF 和 Word 这类二进制格式,先抽取文本,再用固定窗口切分
  • 对于代码和日志,按代码块边界切分,保证代码语义完整

Spring AI 自带了一个TokenTextSplitter,可以用。但说实话,它的能力比较基础,就是按 token 数硬切。如果文档结构复杂,我更建议自己写一个切分器,基于常见 Markdown 解析库去实现。切分逻辑原则上不难,难的是处理各种边界情况。

2.3 参数选择:chunk_size、overlap 与 embedding 模型的匹配

chunk_sizechunk_overlap不是拍脑袋定的,它们和 embedding 模型的最大输入长度强相关。

以 bge-m3 为例,它的最大输入长度是 8192 个 token,但这不代表你应该把块切到 8000 多 token。你还要考虑 LLM 的上下文窗口、检索精度、存储成本之间的平衡。块越大,embedding 的语义越模糊,检索精度下降;块越大,召回后送进 LLM 的上下文占的 token 越多,成本越高。

我实践下来比较稳妥的几组参数:

场景chunk_sizechunk_overlap说明
通用文档问答500 token50 token平衡精度与语义完整
代码知识库300 token30 token代码块语义相对独立
长文档摘要/分析1000 token100 token需要更完整的上下文
细粒度事实问答250 token25 token精度优先,适合查证类场景

这里 token 数不是字符数。一个中文字符大概占 1 到 2 个 token,英文一个词大概 1 到 2 个 token。如果你用 tiktoken 或者其他 tokenizer 来切,要搞清楚底层统计的是字符还是 token。

另外要注意的是:chunk_overlap 不能省。没有 overlap,紧挨着切分边界的信息就会被割裂。用户问的问题如果刚好落在边界上,两边的块都只有一半的上下文,效果就很差。我一般用 10% 的比例作为 overlap 的起始值,再根据实际情况微调。

2.4 实操:用 Spring AI 实现结构感知切分器

Spring AI 的TextSplitter抽象给了扩展点,实现的接口就一个方法。不过实际项目中,我通常是在文档预处理阶段做切分,而不是在运行时切。离线把文档切好存到数据库里,运行时只做检索,这样能省很多计算资源。

下面给一个相对完整的 Markdown 结构感知切分思路,你可以根据自己的文档格式调整:

public class MarkdownStructureSplitter { public List<Document> split(MarkdownDocument mdDoc) { List<Document> result = new ArrayList<>(); // 1. 解析 Markdown 标题层级,按 H1/H2 划分逻辑区块 List<Section> sections = parseSections(mdDoc.getContent()); for (Section section : sections) { // 2. 检查区块长度,小于阈值直接作为一个块 if (section.tokenCount() <= MAX_CHUNK_SIZE) { result.add(toDocument(section)); continue; } // 3. 超长区块用段落边界做二次切分 List<Section> subSections = splitByParagraph(section, MAX_CHUNK_SIZE); for (Section sub : subSections) { // 4. 保留标题作为前缀上下文,保证 LLM 能理解片段来源 result.add(toDocument(sub.withPrefix(section.getHeading()))); } } return result; } }

实现时有一个细节值得注意:给每个块保留一个元数据字段记录来源和标题路径。比如source(哪个文件)、heading_path(H1 > H2 > H3 的路径)。这两个字段在后续做引用溯源和结果展示时非常有用,没有它们,RAG 的回答就少了可信度支撑。

2.5 实操心得:不同文档类型的切分调优

实际项目里的文档类型往往五花八门,一个统一的切分策略很难打天下。根据我的经验:

  • 产品文档/操作手册:结构感强,适合结构感知切分。按章节切,标题带上,信息密度高。
  • FAQ 类数据:一条 FAQ 是一个天然的最小语义单元。直接按条目切,每条一个块,overlap 设 0 就行。
  • 合同/公告:有固定的条款结构,按条款切比按字数切靠谱得多。如果是 PDF 表格,还要先考虑表格解析的准确性。
  • 代码仓库:按文件和函数切,注释和代码不要拆散。代码和自然语言混合的场景,函数级切分效果最好。

还有一个很多人忽视的坑:PDF 解析质量会直接决定切分质量。PDF 解析出来是乱序文本,切分得再花哨也没用。这块建议前期多花点时间调研解析方案,常见的开源库比如 PDFBox、pdfplumber 各有优缺点,要针对自己的 PDF 类型做测试。如果是扫描版 PDF,还要先做 OCR,这些成本要在项目排期里预留出来。

3. 混合检索:从单路召回走向多路融合

3.1 为什么纯向量检索不够用

纯向量检索的思路是:把文档和问题都转成向量,然后计算余弦相似度或内积来找最相关的块。这条路在语义搜索场景效果不错,但对精确匹配的场景就力不从心了。

典型的例子是查订单号、身份证号、错误码这类包含精确 token 的场景。比如用户问“错误码 E10023 怎么解决”,向量检索很可能把注意力放在“错误码”“解决”这些语义词上,对“E10023”这个精确串不敏感。如果把文档里的E10023E10032搞混了,向量上几乎区分不出来。

还有代码变量名、专业缩写这类词汇,embedding 模型的词表里可能根本没有对应的好表示。向量化之后,这些关键信息被平均稀释了,检索结果自然不满意。

这个问题的根源在于,embedding 模型擅长的是语义相关性,不擅长精确匹配和稀有 token 的命中。而全文检索(比如 BM25)恰恰擅长这个——它基于词频和逆文档频率来计算相关性,对精确词项的命中非常敏感。

所以混合检索的思路很直接:向量检索负责语义层面的召回,全文检索负责词项层面的召回,两路结果合并后再统一排序

3.2 混合检索的两种主流实现路径

在 Java 技术栈里,混合检索的落地路径主要有两条:

路径一:Elasticsearch 一站式方案

如果你的系统已经用了 ES,这条路成本最低。ES 同时支持 BM25 全文检索和 dense_vector 向量检索,可以在一个查询里同时跑两路召回:

{ "query": { "bool": { "should": [ { "match": { "content": "错误码 E10023" } }, { "knn": { "embedding": { "vector": [...], "k": 10 } } } ] } } }

这个方案的好处是架构简单,一套系统全搞定;缺点是 ES 的向量检索性能在超大数据量下不如专业向量数据库。

路径二:向量数据库 + 关键词检索组合方案

向量检索走 Milvus/PGVector,关键词召回走数据库的全文索引或独立的 ES 集群。两路结果在应用层合并。

这个方案灵活度高,每一路都可以选最适合的引擎,但要多一次网络调用和一次结果融合的逻辑。

我个人在中小型项目里推荐路径二,因为改造灵活,可以先只做向量检索,后续再把关键词路加进来,不影响现有架构。如果你的项目已经重度依赖 ES,那就直接上路径一,不要为了“专业”而多维护一套系统。

3.3 检索融合策略:RRF 还是加权评分

两路召回的结果怎么合并成一路?这一步叫“结果融合”,常用的有两种策略。

RRF(Reciprocal Rank Fusion,倒数排名融合)的原理很优雅:不关心每路的原始得分,只看排名位置。

score(d) = Σ 1 / (k + rank_i(d))

其中rank_i(d)是文档 d 在第 i 路召回结果中的排名,k 是平滑常数,通常取 60。这个公式的含义是:名次越靠前,贡献的分数越高;两路都排前面的文档,总分自然最高。

RRF 的好处是不需要调权重,而且对每路召回的打分尺度不敏感——因为只看排名。坏处是它没有利用每路的原始相关度信息,理论上信息利用率低一些。

加权评分融合则是把每路的分数归一化后加权求和:

score(d) = α * norm(vector_score(d)) + β * norm(bm25_score(d))

这里的αβ是权重,需要根据业务调优。一般从α = 0.7, β = 0.3起步,语义为主、词项为辅的场景这个比例比较稳。如果业务里精确匹配多,就加大 β。

我的实际建议是:先用 RRF 起步,因为它零调参,上线后通过观察失败案例再决定要不要换成加权评分。大多数场景 RRF 已经够用,盲调权重反而容易过拟合到测试集上。

3.4 实操:Spring AI 2.0 中实现混合检索

Spring AI 的VectorStore接口只负责向量召回。要做混合检索,需要自己在 Service 层把两路检索编排起来。下面是我在项目里用的简化版实现:

@Service public class HybridSearchService { private final VectorStore vectorStore; private final KeywordSearchService keywordSearchService; public List<Document> hybridSearch(String query, int topK) { // 1. 向量召回 List<Document> vectorResults = vectorStore.similaritySearch( SearchRequest.builder() .query(query) .topK(topK) .build() ); // 2. 关键词召回,通过 ES 或数据库全文索引实现 List<Document> keywordResults = keywordSearchService.search(query, topK); // 3. RRF 融合 return RrfFusion.merge(vectorResults, keywordResults, topK); } }

关键词召回的部分,如果用了 ES 就很直接,构造一个 BM25 查询即可。如果没用 ES,PostgreSQL 自带全文检索也能顶一顶。SQL 大概是这样的:

SELECT id, content, ts_rank(to_tsvector('chinese', content), query) AS rank FROM documents, plainto_tsquery('chinese', ?) AS query WHERE to_tsvector('chinese', content) @@ query ORDER BY rank DESC LIMIT ?;

中文全文检索在 PG 里的关键是分词。默认的中文分词效果一般,建议装一个zhparser或者pg_jieba扩展,否则“中华人民共和国”会被拆成“中华”“人民”“共和国”,精确匹配的效果会打折扣。

3.5 实测效果:混合检索到底能提升多少

之前把一个技术工单知识库从纯向量检索切成混合检索后,做了一轮评测。评测集是 200 个真实工单问题,指标是 Recall@10(前 10 条结果中包含正确答案的比例):

检索方式Recall@10说明
纯向量检索68.5%bge-m3 embedding
纯 BM25 检索61.0%词项召回能力不错,语义泛化不行
混合检索(RRF)81.5%两路互补,融合后显著提升

召回率从不到 70% 拉到 80% 以上,这 13 个百分点的差距,对最终回答质量的影响非常大。因为如果正确答案根本没被召回,后面 Rerank 和大模型再强也白搭。

混合检索的核心收益在于:语义路能兜住“换一种说法也能找到”的场景,词项路能兜住“精确匹配不遗漏”的场景,两条腿走路,比单腿稳得多。

4. Rerank 精排:把最相关的片段送到 LLM 面前

4.1 Rerank 解决的问题与工作原理

先理解一个前提:向量检索和混合检索的目标是“别漏掉正确答案”,而 Rerank 的目标是“把最正确答案排到最前面”

检索阶段的结果是按向量相似度或者词项相关度排的,这个排序和“真正能回答用户问题”之间还有一段距离。原因在于,检索阶段的相似度计算和 LLM 理解上下文的能力之间存在模式差异。bge-m3 的向量相似度算的是语义接近程度,但某个块是不是真的能支撑回答这个问题,需要更精细的语义匹配判断。

Rerank 模型做的事情就是打这个补丁。它把“用户问题”和“候选文档块”拼在一起输入模型,输出一个相关度分数。这个分数是模型基于深度语义交互算出来的,比单纯向量相似度精确得多。

打个比方:向量检索是初选面试官,简历方向大致对口就放进候选池;Rerank 是终面面试官,面对面聊一轮,才知道这个人到底是真合适还是只是简历好看。

4.2 Rerank 模型选型与部署方案

目前用的最多的是 BGE 系列的 Rerank 模型。选型上主要看数据规模、机器算力和延迟要求。

模型特点推荐场景
bge-reranker-v2-m3多语言能力强,效果最好生产环境首选,机器够好就上
bge-reranker-base轻量,CPU 也能跑机器资源紧张,或对延迟敏感
bge-reranker-large效果和速度的中间档单语言场景性价比较高

部署方式上,生产环境建议用专门的推理服务封装模型。用 FastAPI 包一层 HTTP 接口,Spring AI 侧通过 RestClient 来调用,是比较常见的做法。如果你用 Ollama,从 0.4 版本开始也支持 rerank 模型了,Windows 本机调试最省事。

很多人在 Windows 上部署 Rerank 模型时会遇到坑,主要集中在这几个地方:

  • Python 版本和 PyTorch 版本不匹配:torch 的 CPU 版本装错会导致无法加载模型。建议直接用官方提供的 Docker 镜像方案部署,一次性解决环境问题。
  • 模型下载慢或失败:可以从 ModelScope 的镜像站下载模型权重,然后改成从本地路径加载。
  • 内存不足:bge-reranker-v2-m3 的模型文件大约 2GB 左右,加载起来内存峰值可能到 8GB 以上,机器配置要留意。

4.3 实操:在 Windows 环境部署 Rerank 模型

在 Windows 本机调试时,我是用 FastAPI 包了一层模型服务。代码不复杂,几百行搞定。核心部分大概是这样的:

from fastapi import FastAPI, Request from sentence_transformers import CrossEncoder import torch app = FastAPI() model = CrossEncoder("BAAI/bge-reranker-v2-m3", device="cuda" if torch.cuda.is_available() else "cpu") @app.post("/rerank") async def rerank(request: Request): data = await request.json() query = data["query"] documents = data["documents"] # 构造 query-document 对,输入模型计算相关度 pairs = [[query, doc] for doc in documents] scores = model.predict(pairs) # 返回按分数降序的索引和分数 results = sorted(zip(range(len(documents)), scores.tolist()), key=lambda x: x[1], reverse=True) return {"results": [{"index": idx, "score": score} for idx, score in results]}

启动之后,Spring AI 侧通过 WebClient 或者 RestClient 调用这个 HTTP 接口即可。需要注意的是,Rerank 接口每次请求的候选文档数量要控制在 20 到 50 条以内。如果混合检索返回了 100 条候选,别一股脑全丢给 Rerank,先截断到 top 30 再精排,性能和效果都更平衡。

4.4 Spring AI 2.0 集成 Rerank 的工程实现

Spring AI 2.0 没有现成的 Rerank 抽象,所以我在项目里直接用 WebClient 调用 Rerank 服务,逻辑封装在 Service 层。这样依赖最小,后面要换模型服务也方便。

@Service public class RerankService { private final WebClient webClient; public List<Document> rerank(String query, List<Document> candidates, int topK) { // 1. 构造请求体,只传文档内容,控制传输量 Map<String, Object> body = Map.of( "query", query, "documents", candidates.stream().map(Document::getContent).toList() ); // 2. 调用 rerank 服务 RerankResponse response = webClient.post() .uri("/rerank") .bodyValue(body) .retrieve() .bodyToMono(RerankResponse.class) .block(); // 3. 按分数排序,返回 topK 个文档 return response.results().stream() .sorted(Comparator.comparing(RerankItem::score).reversed()) .limit(topK) .map(item -> candidates.get(item.index())) .toList(); } }

这个实现的注意点是:响应里的 index 是原候选列表的下标,排序后要记得按原下标映射回 Document,别直接按 Rerank 返回的顺序取,否则文档就错乱了。

4.5 Rerank 对最终回答效果的提升实测

还是拿工单知识库来说,加了 Rerank 之后,回答质量的提升很直观。评测方式是让大模型基于 RAG 上下文回答 200 个问题,由人工判断答案是否正确:

链路准确率
纯向量检索 + LLM62.5%
混合检索 + LLM71.0%
混合检索 + Rerank + LLM78.5%

Rerank 在这条链路上贡献了 7.5 个百分点的提升。原理也不难理解:之前排在第一位但不相关的内容,经过 Rerank 后被挤出 topK,相关的内容被顶上来。LLM 拿到的上下文更精准,回答质量自然水涨船高。

5. 工程化落地的关键细节与避坑指南

5.1 大模型接入的工程实践:以 DeepSeek 和智谱为例

RAG 链路里的最后一步是调用大模型生成答案。Spring AI 2.0 对接国内大模型的生态已经比较成熟了,DeepSeek 和智谱都有对应的 Starter 可以用。

对接智谱的一个标准 POM 配置:

<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-starter-model-zhipuai</artifactId> <version>2.0.0</version> </dependency>

配置文件里把 key 和模型名配上:

spring: ai: zhipu: api-key: ${ZHIPU_API_KEY} chat: options: model: glm-4-plus

对接本地部署的 DeepSeek 也是一样的套路,重点在配置 Base URL:

spring: ai: openai: base-url: http://localhost:11434/v1 api-key: dummy chat: options: model: deepseek-r1

这里有个容易踩的坑:本地模型服务如果没有兼容 OpenAI 的 API 格式,Spring AI 的 OpenAI Starter 是连不上的。好在大多数本地推理框架(比如 Ollama、vLLM)都做了 OpenAI 兼容层,配置里指对 base-url 就行。

5.2 缓存策略:别让 RAG 链路的每一环都重复计算

线上 RAG 服务如果每次都走完整的“检索 → 精排 → 生成”链路,成本和延迟都是不小的压力。工程上必须做分层缓存。

  • Embedding 缓存:文档内容不变,向量就不变。向量入库时以内容 hash 为 key 做幂等,避免重复向量化。
  • 检索结果缓存:相同或高度相似的问题,在短时间内返回相同的检索结果。可以用 Redis 做缓存,TTL 设 15 到 30 分钟。
  • Prompt 级缓存:有语义缓存方案可以直接缓存 LLM 生成的答案,命中了就跳过整个链路。但要注意业务对实时性的要求,知识库内容频繁更新时,缓存 TTL 要相应调短。

5.3 可观测性:RAG 链路的日志与指标监控

RAG 链路比普通接口多了好几个环节,每环都可能出问题,必须把可观测性做起来。我的团队在实践中沉淀了一套最小可观测方案:

  • 日志埋点:每个环节记一条结构化日志,包含 query_id、环节名、耗时、返回条数、top1 文档 ID。有 traceId 贯穿全链路,排查问题特别方便。
  • 埋点指标:Rerank 前后的 top1 命中率变化、平均检索耗时、平均生成耗时、token 消耗量。
  • 失败样本收集:用户在问答后面的“赞同/反对”反馈,是优化 RAG 链路最宝贵的信号。

有一个成本很低但收益很大的做法:把线上 badcase 定时回流到评测集。每次用户点了“反对”或者超时,把这个问题连同当时的检索结果和模型回答存下来。每周跑一次回归评测,链路优化的进度就能量化。

我在项目里做了个简单的 Badcase 回放工具,用一个脚本把近七天的失败样本重新过一遍新链路,对比回答质量。这个工具帮我们发现了不少隐藏问题,比人工翻日志高效得多。

5.4 常见问题速查:我踩过的坑和排查思路

最后把我在 RAG 工程化过程中遇到的高频问题整理成一张表,方便大家对照排查:

问题现象可能原因排查与解决办法
召回结果总是不相关Chunking 粒度不合适先用知识库抽 20 个典型问题做评测,检查切块语义完整性,调 chunk_size
精确匹配场景答错纯向量检索漏召回加 BM25 关键词路,做混合检索
Rerank 服务响应慢候选数太多或模型太大限制候选数量在 30 条内,模型换成 base 版本
答案总是“不知道”上下文送入太少,信息不足增加 topK、调大 chunk_size、检查 Rerank 阈值是否过滤太狠
中文效果差分词或 embedding 不支持中文换支持中文的分词插件,embedding 换 bge-m3 这类中文友好模型
文档更新后回答没变化缓存没失效检查检索结果缓存和向量入库的幂等逻辑

这里特别提醒一个容易忽略的点:向量数据库的数据更新策略。很多团队在做知识库更新时只删旧数据、插新数据,忽略了还在缓存里的旧检索结果。如果业务对数据实时性要求高,建议缓存时间设短一些,或者在数据更新时主动清理相关缓存。

5.5 从 demo 到生产的完整落地清单

写到这里,把整个 RAG 工程化落地过程中我认为最关键的检查项整理一个清单,每一个都是我踩过坑之后才意识到的:

  • 文档切分有没有针对文档类型做策略选择?还是无脑固定大小?
  • 向量化的 embedding 模型是否支持你的业务语言和领域词汇?
  • 检索链路有没有关键词召回兜底?还是纯向量一条路走到黑?
  • 混合检索的结果融合用的是 RRF 还是加权?上线后有没有对比数据?
  • Rerank 服务的容量和延迟有没有压测过?并发上来会不会挂?
  • LLM 接入的 Base URL、API Key、模型名、超时参数是否都配置正确?
  • 缓存 TTL 和数据更新策略是否匹配业务实时性要求?
  • 链路的日志和指标埋点是否完整?Badcase 是否能闭环回流?

这套清单看起来简单,但每一条背后都有真实的线上事故。RAG 项目别急着往上叠功能,先把基础链路打扎实,后续加东西才稳。我个人做完几个 RAG 项目的体会是,系统工程最大的成本从来不在模型选型,而在这些看起来不起眼的细节里。把 Chunking、混合检索、Rerank 这三个核心环节吃透,Spring AI 2.0 就能真正变成你手里的趁手工具,而不是又一个跑通就吃灰的 demo。最后再分享一个建议:生产环境上线前,至少准备一两百条真实业务问题做回归集,每次改完链路跑一遍,你会发现这个习惯能帮你省掉太多线上排查的时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询