一、前言:RAG 不只是“向量检索 + 大模型回答”

最近在学习 RAG 的过程中,我一开始对它的理解比较简单:

用户提问 → 问题向量化 → 去向量数据库中查相似文档 → 拼到 Prompt 中 → 交给大模型回答。

这个流程确实是 RAG 的核心,但真正深入以后会发现,一个能用的 RAG 系统和一个效果好的 RAG 系统,中间差距非常大。

尤其是在实际项目中,经常会遇到这些问题:

  • 用户问题表达不清楚,导致召回不到正确文档;
  • 向量检索语义相似,但结果并不一定准确;
  • 关键词很重要,但单纯向量检索可能忽略;
  • 召回了很多内容,但真正有用的片段排在后面;
  • 大模型拿到错误上下文后,容易一本正经地胡说;
  • 没有检索到内容时,大模型仍然可能编造答案。

所以,RAG 的重点不只是“有没有检索”,而是:

如何让系统召回更准、排序更合理、上下文更可信,并最终降低大模型幻觉。


二、RAG 的整体链路回顾

一个完整的 RAG 系统一般可以拆成两条链路:

1. 离线知识入库链路

也就是用户上传文档之后,系统如何把文档处理成可检索的数据。


常见步骤包括:

步骤 作用
文件解析 把 PDF、Word、Markdown 等文件转成纯文本
文本清洗 去掉无用字符、页眉页脚、乱码等
文档切割 把长文档切成多个 Chunk
向量化 使用 Embedding 模型把文本转成向量
存储 存入 Milvus、Elasticsearch、Redis Vector 等检索系统

这里需要注意一点:

如果要做混合检索,通常不只是把数据存进向量库,还要把文本内容同步存入支持关键词检索的系统,比如 Elasticsearch。

这也是之前我疑惑的地方:
如果 RAG 使用混合检索,是不是就要同时维护向量索引和关键词索引?

答案是:是的,通常需要。

因为混合检索本质上是两条召回路径:

  • 一路走向量语义检索;
  • 一路走关键词 / BM25 检索;
  • 最后再融合结果。

这确实会增加系统复杂度,但换来的是更高的召回稳定性。


三、RAG 在线问答链路

当用户真正发起问题时,系统进入在线检索和生成阶段。


这个流程里,最关键的优化点主要有五个:

  1. Query 改写
  2. 多 Query 扩展
  3. 混合检索
  4. RRF 融合
  5. Rerank 重排序

下面逐个展开。


四、Query 改写:让用户问题更适合检索

用户输入的问题,很多时候并不适合直接拿去检索。

比如用户问:

“这个怎么部署?”

如果没有上下文,系统根本不知道“这个”指的是什么。

再比如:

“它和 ES 有啥区别?”

这里的“它”可能是 Milvus,也可能是 Redis Vector,也可能是某个向量数据库。

所以 Query 改写的目的就是:

把用户自然语言问题,改写成更完整、更适合检索的问题。

例如:

原始问题:
它和 ES 有什么区别?

结合上下文后改写:
Milvus 向量数据库和 Elasticsearch 在 RAG 检索场景中的区别是什么?

这样改写后的问题就更适合去召回相关文档。


Query 改写是不是也要用 LLM?

很多时候是的。

这也是我之前比较疑惑的地方:

RAG 最后不是已经要调用大模型回答了吗?为什么前面 Query 改写还要调用一次大模型?

其实这是正常的。

在一个 RAG 系统中,大模型不一定只用一次,它可以在不同阶段承担不同角色:

阶段 大模型作用
Query 改写 把用户问题改成适合检索的形式
多 Query 扩展 从多个角度生成检索问题
最终回答 基于召回上下文生成答案
幻觉校验 判断答案是否有依据

所以可以理解为:

LLM 不只是回答器,也可以是检索前的 Query 理解器。

当然,项目中为了降低成本,也可以不用大模型改写,而是使用规则、模板、历史对话拼接等方式完成简单改写。


五、多 Query 扩展:从多个角度提高召回率

多 Query 扩展可以理解为:

不只用用户原始问题检索,而是让系统从多个角度生成多个问题,再分别检索。

例如用户问:

RAG 中如何优化检索效果?

系统可以扩展成:

1. RAG 检索优化有哪些常见策略?
2. 如何提高向量检索的召回准确率?
3. RAG 中混合检索如何实现?
4. RAG 中 Rerank 重排序有什么作用?
5. 如何降低 RAG 系统中的大模型幻觉?

然后这些 Query 分别去检索,最后合并召回结果。


多 Query 在整个链路中的位置

之前我有一个误区:
我以为多 Query 是向量检索里面的一个小步骤。

但更准确地说:

多 Query 应该处在检索链路的更上层。

也就是:


也就是说,多 Query 不是只服务于向量检索,它可以同时服务于:

  • 向量检索;
  • BM25 关键词检索;
  • Elasticsearch 全文检索;
  • 混合检索;
  • 后续 Rerank。

所以它更像是检索前的 Query 扩展层。


六、向量检索:解决语义相似问题

向量检索的核心是 Embedding。

它会把文本转换成高维向量,比如:

“如何部署 Milvus?”
↓
[0.123, -0.231, 0.456, ...]

然后系统会拿用户问题的向量,去和文档 Chunk 的向量做相似度匹配。

它擅长解决的是:

用户表达和文档表达不完全一样,但语义接近的情况。

比如用户问:

怎么把文件内容存进知识库?

文档中写的是:

系统会先解析上传文件,然后进行文本切割、向量化,并写入向量数据库。

虽然关键词不完全一致,但语义上非常接近,向量检索就能召回。


向量检索的问题

但是向量检索也不是万能的。

它可能存在这些问题:

问题 说明
对精确关键词不敏感 比如错误码、类名、方法名、配置项
语义相似不等于答案正确 召回内容看起来相关,但不一定能回答问题
依赖 Embedding 模型质量 模型不好,向量效果就差
长文本容易语义稀释 Chunk 太大时,向量表达不够精确

比如在 Java 项目中,用户问:

defaultTools 报错怎么解决?

如果只走向量检索,它可能召回到“工具调用”相关内容,但未必能精准找到 defaultTools 这个具体方法变化。

这时候关键词检索就很重要。


七、BM25:关键词检索的经典算法

BM25 是信息检索领域非常经典的关键词相关性算法,常见于 Elasticsearch、Lucene 等搜索系统中。

它解决的问题是:

用户问题中的关键词,和文档中的关键词匹配程度有多高?

简单来说,BM25 会考虑几个因素:

因素 含义
词频 TF 查询词在文档中出现越多,相关性越高
逆文档频率 IDF 越少见的词,区分度越高
文档长度 避免长文档因为词多而天然占优势

举个例子:

用户问:

Spring AI defaultTools 报错原因

如果某个 Chunk 中明确出现了:

defaultTools 在新版本 Spring AI 中已经被调整,需要使用新的工具注册方式。

那么 BM25 很容易把这个 Chunk 排到前面。

因为 Spring AIdefaultTools报错 这些关键词都高度匹配。


BM25 和向量检索的区别

对比项 向量检索 BM25 检索
核心依据 语义相似度 关键词匹配度
适合场景 概念解释、语义模糊问题 类名、方法名、错误码、配置项
优点 能理解相近表达 精准、稳定、可解释
缺点 可能语义相似但不准确 同义词、改写表达能力弱

所以两者不是替代关系,而是互补关系。


八、混合检索:向量检索和 BM25 双路召回

混合检索的核心思想是:

不要只相信一种检索方式,而是让不同检索方式分别召回,再融合结果。

常见结构如下:


比如:

用户问题:Spring AI defaultTools 报错怎么办?

向量检索可能召回:

工具调用、Function Calling、Tool 注册相关内容

BM25 可能召回:

包含 defaultTools、Spring AI 版本升级、API 变更的内容

最终融合后,系统就能同时兼顾:

  • 语义理解能力;
  • 关键词精准匹配能力。

混合检索是不是要同时存两份索引?

一般是的。

比如项目中可以这样设计:


也就是说:

  • Milvus 负责向量检索;
  • Elasticsearch 负责关键词检索;
  • 两边都存 Chunk 的信息;
  • 通过 chunkId、documentId 等字段关联。

这确实会麻烦一点,但在真实项目里是很常见的设计。

因为单纯向量检索很难解决所有问题,尤其是技术文档、日志、报错、代码类场景,关键词检索非常重要。


九、RRF 融合:把多路召回结果合并起来

当系统同时走向量检索和 BM25 检索后,会得到两份排序结果。

例如:

向量检索结果:
A, B, C, D

BM25 检索结果:
C, A, E, F

那么问题来了:

最终应该怎么排序?

这时候可以用 RRF,也就是 Reciprocal Rank Fusion。

它的思想很简单:

一个文档在多个检索结果中排名都靠前,那么它就更可能重要。

简单理解:

文档 A:
向量检索第 1 名
BM25 检索第 2 名
=> 综合分高

文档 C:
向量检索第 3 名
BM25 检索第 1 名
=> 综合分也高

文档 F:
只在 BM25 中第 4 名出现
=> 综合分相对低

RRF 的优势是:

  • 不强依赖不同检索方式的原始分数;
  • 更关注排名位置;
  • 适合融合向量检索、BM25、多 Query 检索结果;
  • 实现相对简单。

RRF 简化公式

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

其中:

参数 含义
d 某个文档
rank(d) 文档在某一路检索结果中的排名
k 平滑参数,通常用于降低排名差距影响

不用太纠结公式,核心记住一句话:

多个检索通道都认为重要的文档,最终排名就应该更靠前。


十、Rerank:对候选结果做精排

RRF 融合之后,通常还会得到一批候选文档。

但是这些候选文档不一定都是最适合最终回答的,所以还需要 Rerank。

Rerank 可以理解为:

先粗召回一批文档,再用更强的模型重新判断这些文档和问题的相关性。

流程如下:


Rerank 一定要用大模型吗?

不一定,但一般会使用专门的 Rerank 模型。

常见方式有:

方式 说明
Cross-Encoder Rerank 输入 Query + 文档,直接判断相关性
BGE Reranker 常见中文 / 多语言重排序模型
Cohere Rerank 云服务 Rerank 接口
LLM Rerank 让大模型判断哪些片段更相关
规则重排 根据关键词、时间、权限、文档类型加权

在项目中比较常见的是:

向量检索 / BM25:负责多召回
Rerank 模型:负责精排序
大模型:负责最终回答

所以 Rerank 不一定是最终回答用的那个大模型。

它可以是一个专门的小模型,成本更低、速度更快,也更适合做相关性判断。


十一、如何避免 RAG 中的大模型幻觉?

RAG 的目标之一就是降低幻觉,但 RAG 本身并不能彻底消灭幻觉。

因为如果召回内容错误、缺失或者无关,大模型仍然可能编造答案。

所以幻觉控制应该从多个层面一起做。


1. 提高召回质量

这是最根本的。

如果检索阶段拿到的上下文就不对,后面再怎么 Prompt 约束也很难救回来。

可以使用:

  • Query 改写;
  • 多 Query 扩展;
  • 混合检索;
  • RRF 融合;
  • Rerank 重排序;
  • 合理的 Chunk 切割;
  • metadata 权限过滤;
  • 文档质量清洗。

一句话:

先让大模型拿到正确材料,再让它回答。


2. Prompt 中明确约束不能编造

可以在系统提示词中加入类似约束:

你必须严格基于提供的上下文回答问题。
如果上下文中没有答案,请明确回答“根据当前知识库资料无法确定”,不要编造。
回答时尽量引用上下文中的关键依据。

这类 Prompt 不能百分百解决幻觉,但能明显降低模型自由发挥的概率。


3. 检索不到结果时拒答

如果系统召回结果为空,或者相关性分数过低,不应该继续让大模型强行回答。

可以设计一个阈值:

if retrievedDocs.isEmpty() or topScore < threshold:
    return "根据当前知识库资料,暂时无法回答该问题。"

这一步非常重要。

很多 RAG 系统幻觉严重,不是因为大模型太差,而是因为:

明明没检索到有效内容,却还让大模型继续回答。


4. 给答案加引用来源

RAG 系统中最好让每段答案都能追溯到来源文档。

例如:

根据《Spring AI 工具调用说明》第 3 节,当前版本推荐通过 ToolCallbackProvider 注册工具。

这样有几个好处:

  • 用户可以验证答案来源;
  • 系统更容易调试;
  • 可以减少模型胡说;
  • 面试中也更能体现工程完整性。

5. 答案生成后做一致性检查

可以在回答生成后,再做一次校验:

问题:用户原始问题
上下文:召回文档
答案:模型生成答案

请判断答案是否完全来自上下文。
如果存在上下文中没有的信息,请指出。

这个阶段可以用 LLM,也可以用规则或事实校验模型。

对于普通项目来说,不一定一开始就做得很复杂,但在简历项目里可以作为亮点:

引入基于上下文一致性的回答校验机制,降低知识库问答中的模型幻觉风险。


十二、一个更完整的 RAG 检索优化方案

综合来看,一个比较完整的 RAG 检索链路可以这样设计:


这个链路相比最基础的 RAG,多了很多工程优化点:

模块 作用
Query 改写 解决用户问题不完整
多 Query 提高召回覆盖率
向量检索 解决语义匹配
BM25 解决关键词精准匹配
RRF 融合多路召回结果
Rerank 提高最终上下文质量
阈值判断 避免无依据回答
引用来源 增强答案可信度
一致性校验 降低幻觉风险

十三、结合 Java / Spring AI 项目的理解

如果放到 Java 项目中,可以把 RAG 检索链路理解成几个核心服务:

QueryRewriteService      // 问题改写
MultiQueryService        // 多 Query 扩展
VectorSearchService      // 向量检索
KeywordSearchService     // BM25 / ES 检索
FusionService            // RRF 融合
RerankService            // 重排序
PromptBuildService       // 构造 Prompt
ChatService              // 调用大模型生成回答

伪代码可以这样理解:

public String chat(String userQuestion) {

    // 1. Query 改写
    String rewrittenQuery = queryRewriteService.rewrite(userQuestion);

    // 2. 多 Query 扩展
    List<String> queries = multiQueryService.expand(rewrittenQuery);

    // 3. 多路召回
    List<DocumentChunk> vectorResults = vectorSearchService.search(queries);
    List<DocumentChunk> keywordResults = keywordSearchService.search(queries);

    // 4. RRF 融合
    List<DocumentChunk> fusedResults = fusionService.rrf(vectorResults, keywordResults);

    // 5. Rerank 精排
    List<DocumentChunk> rerankedResults = rerankService.rerank(userQuestion, fusedResults);

    // 6. 判断是否有有效上下文
    if (rerankedResults.isEmpty()) {
        return "根据当前知识库资料,暂时无法回答该问题。";
    }

    // 7. 构造 Prompt
    String prompt = promptBuildService.build(userQuestion, rerankedResults);

    // 8. 调用大模型回答
    return chatClient.prompt()
            .user(prompt)
            .call()
            .content();
}

当然,真实项目中还要考虑:

  • 用户权限过滤;
  • 文档所属知识库;
  • 组织标签;
  • TopK 控制;
  • 分数阈值;
  • 历史对话;
  • Token 长度限制;
  • 日志记录;
  • 异常兜底。

这些都是把 RAG 从 Demo 变成项目的关键点。


十四、面试中可以怎么表达?

如果面试官问:

你们的 RAG 是怎么优化检索效果的?

可以这样回答:

我们不是单纯使用向量检索,而是设计了一套多阶段检索链路。

首先,在用户提问后,会结合历史对话对 Query 做改写,解决指代不明和问题不完整的问题。然后通过多 Query 扩展,从多个角度生成检索问题,提高召回覆盖率。

在召回阶段,我们同时使用向量检索和 Elasticsearch 的 BM25 关键词检索。向量检索负责语义相似召回,BM25 负责方法名、错误码、配置项等精确关键词匹配。两路结果召回后,通过 RRF 进行融合,再使用 Rerank 模型对候选片段进行精排,最终选出 TopK 文档片段构造 Prompt。

同时,我们在 Prompt 中约束大模型必须基于上下文回答,并在检索结果为空或相关性低时进行拒答,避免模型在没有依据的情况下编造答案。

这段回答比只说“我们用了向量数据库做 RAG”要强很多。

因为它体现了:

  • 你理解 RAG 的完整链路;
  • 你知道向量检索的缺点;
  • 你知道混合检索的价值;
  • 你知道 Rerank 的作用;
  • 你考虑过幻觉控制;
  • 你有工程落地思维。

十五、总结

今天这部分学习让我对 RAG 的理解更进一步。

最开始我以为 RAG 的核心只是:

把文档向量化,然后根据用户问题召回相似内容。

但现在看,真正的 RAG 优化重点在于:

让检索结果更准,让上下文更可信,让大模型少胡说。

可以简单总结为一句话:

基础 RAG 解决的是“能不能检索到”,高级 RAG 解决的是“检索得准不准、排序合不合理、答案有没有依据”。

一个可靠的 RAG 系统,通常需要组合使用:

  • Query 改写;
  • 多 Query 扩展;
  • 向量检索;
  • BM25 关键词检索;
  • 混合检索;
  • RRF 结果融合;
  • Rerank 重排序;
  • Prompt 约束;
  • 阈值拒答;
  • 来源引用;
  • 答案校验。
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐