RAG 结果总是答偏?我用 LangChain4j 把 RRF 和 Rerank 彻底搞明白了
目录
六、Rerank 的好处,为什么很多高质量 RAG 都少不了它?
第三步:再引入 ReRankingContentAggregator
十、结尾:RAG 的关键,不只是找到文档,而是找到“该给模型看的文档”
最近做 LangChain4j 项目时,我又撞上了 RAG 里那个经典老坑:
召回看起来挺努力,回答却依然跑偏。
你大概也见过这些场面:
相似度 Top5 里,真正有用的文档只有一两条;
问题本来不复杂,但只要 RAG 第一步召回歪了,大模型就会一本正经地胡说八道;
想上 rerank,又担心接入复杂、延迟飙升、成本失控。
后来我才慢慢意识到,很多 RAG 系统的问题,不是“没检索到”,而是检索到了,但排序不对。
而在 LangChain4j 里,想把这件事做好,绕不开两个核心能力:
RRF(Reciprocal Rank Fusion,倒数排名融合)
Rerank(重排 / 精排)
一个负责把“多个召回结果”揉成更靠谱的候选集,另一个负责从候选里再挑出最值得送给大模型的内容。
这俩东西一前一后配起来,才像是给 RAG 装上了脑子,而不是继续让它像没睡醒一样乱翻资料。
一、为什么 RAG 不是“检索完就结束了”?
很多人刚做 RAG 时,会天然把注意力都放在 Retriever 上:
-
embedding 模型选哪个?
-
向量库用啥?
-
chunk 怎么切?
-
topK 设多少?
这些当然重要,但它们只解决了一个问题:
“能不能把可能相关的文档捞上来?”
真正影响回答质量的,还有下一个问题:
“捞上来的这些文档,谁应该排前面?”
在 LangChain4j 当前的 RAG 架构里,ContentRetriever 负责取回候选内容,而 ContentAggregator 负责把多个检索结果做聚合和排序;其中默认聚合器 DefaultContentAggregator 会执行两阶段 RRF,另一个常用选项 ReRankingContentAggregator 则基于 ScoringModel 做重排。(GitHub)
这件事非常关键。因为对大模型来说,它并不会替你自动修正一个很差的上下文。你喂进去的文档越偏,它答得越像在迷雾里开拖拉机。
二、RRF 是什么?为什么它看起来朴素,却特别能打?
第一次认真研究 RRF,是因为我们在 review 搜索效果时,发现一个很扎心的现象:
向量检索排第一的文档,不一定最适合回答问题;
关键词检索排第一的文档,也不一定语义最匹配;
但那些在多个召回列表里都排得不错的文档,往往更靠谱。
这就是 RRF 的用武之地。
1)RRF 的核心思想
RRF,全称 Reciprocal Rank Fusion,中文一般翻成倒数排名融合。
它不关心不同召回器打出来的“原始分数”能不能直接比较,而是只看一件事:
某个文档在各个结果列表里排第几。
然后根据排名位置给它一个分数,再把多个列表的分数加起来。
最常见的公式是:

这里:
-
d是某篇文档 -
rank_i(d)是它在第 i 个结果列表中的排名 -
k是平滑参数,工程里常见默认值是 60
也就是说,某篇文档如果在多个召回列表里都排名靠前,它的总分就会更高;如果它只在某一个列表里偶然冲上来,但在其他列表里完全消失,那它未必能赢。LangChain4j 官方 RAG 教程也明确说明,默认的 DefaultContentAggregator 采用的是两阶段 RRF。(GitHub)
2)举个最直观的例子
假设你有两个召回结果:
向量召回 A:
doc1, doc2, doc3
关键词召回 B:
doc3, doc2, doc4
那 doc2 的 RRF 分数就是:
-
在 A 中排第 2:
1 / (60 + 2) -
在 B 中排第 2:
1 / (60 + 2)
总分约等于:
1/62 + 1/62 ≈ 0.0323
而 doc1 虽然在 A 中排第一,但它没出现在 B 里,所以它的总分只有:
1/61 ≈ 0.0164
这就意味着:
doc2 会因为“多路召回都认可它”而被提到更靠前的位置。
这招非常实用,尤其适合下面这些场景:
-
向量检索 + BM25 混合召回
-
多个索引源同时检索
-
多个 query rewrite 结果合并
-
需要低成本提升召回稳定性
3)RRF 为什么适合工程落地?
RRF 的优点很接地气:
-
不依赖不同检索器分数的统一量纲
-
不需要训练,直接可用
-
计算成本低,线上好部署
-
对“多路检索融合”很友好
它不是魔法,但非常像一把顺手的扳手。朴素,耐操,还不怎么闹脾气。
三、LangChain4j 里怎么理解 RRF?
这里有个很容易写错的点。
如果你现在写 LangChain4j 相关文章,别再把重点放在一些偏旧的“包一层 Retriever 做 rerank”的说法上了。按照当前官方 RAG 教程,检索增强流程里更核心的概念是:
-
ContentRetriever:负责召回内容 -
QueryRouter:决定 query 发给哪些 retriever -
ContentAggregator:聚合并重排多个结果列表 -
ContentInjector:把最终选中的内容注入 prompt -
DefaultRetrievalAugmentor:把这些模块串起来
其中,默认的 DefaultContentAggregator 就内置了两阶段 RRF。(GitHub)
换句话说,在 LangChain4j 里,RRF 更像是“聚合阶段”的能力,而不是单独某个花哨插件。
这个设计挺合理。因为 RRF 本来就适合做“多来源结果合并”,放在聚合层最顺手。
四、Rerank 又是什么?它和 RRF 有什么本质区别?
如果说 RRF 解决的是:
“多个候选结果怎么融合得更稳?”
那 Rerank 解决的就是:
“这些候选里,到底谁最适合当前问题?”
它们最大的区别在于:
RRF 更像“规则融合”
它只看排名位置,不真正理解 query 和文档的细粒度语义关系。
Rerank 更像“语义精排”
它会把 query 和候选文档成对比较,重新给出相关性分数,再决定最终顺序。
在 LangChain4j 当前文档里,精排对应的是 ReRankingContentAggregator,它依赖 ScoringModel 来完成评分与重排,比如文档里就直接提到了可以使用类似 Cohere 这样的 scoring / reranking model。(GitHub)
这就意味着,Rerank 不再是“谁碰巧在多个列表里都出现过”,而是更进一步问:
“对于这个用户问题,这篇文档到底值不值得进上下文?”
这个判断,显然更聪明,也更贵。
五、LangChain4j 中更准确的重排写法
你原文这一段,建议改成更贴近当前 LangChain4j 设计的版本。
1)一个更合理的思路
先让 Retriever 多召回一些候选,再交给 ReRankingContentAggregator 去精排,最后只保留最相关的少数文档给大模型。
核心配置思路大概是这样:
ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder()
.embeddingStore(embeddingStore)
.embeddingModel(embeddingModel)
.maxResults(20)
.build();
ScoringModel scoringModel = CohereScoringModel.builder()
.apiKey(System.getenv("COHERE_API_KEY"))
.modelName("rerank-english-v3.0")
.build();
ContentAggregator contentAggregator = ReRankingContentAggregator.builder()
.scoringModel(scoringModel)
.minScore(0.8)
.maxResults(5)
.build();
RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder()
.contentRetriever(contentRetriever)
.contentAggregator(contentAggregator)
.build();
这套思路和 LangChain4j 官方教程中对 ReRankingContentAggregator 的定位是一致的:它工作在 ContentAggregator 层,依赖 ScoringModel 做重排。GitHub 相关 issue 也能看到典型配置方式是“retriever 先放宽候选数,reranker 再决定最终返回多少条”。(GitHub)
2)为什么要“先多召回,再少返回”?
这是很多人第一次接 rerank 时最容易掉进去的坑。
假设你的最终目标是给大模型 5 条文档,你不能让前面的 retriever 也只取 5 条。因为那样 reranker 根本没有施展空间,它只是对 5 个有限候选重新排一下座位,像是在矮子里拔将军。
更合理的做法通常是:
-
Retriever 先取 20~50 条候选
-
RRF 先做一轮融合收缩
-
Rerank 再从里面挑 3~8 条最有价值的
LangChain4j 社区讨论里,也有人明确提到这个实践:如果前面召回结果太少,ReRankingContentAggregator 即使做了筛选,也可能不够你想要的最终结果数,因此前置 retriever 往往要把候选放宽一些。(GitHub)
这就像面试。你总不能只叫 5 个人来,然后还指望能从中精准选出最优的 5 个。那不叫筛选,那叫走流程。
六、Rerank 的好处,为什么很多高质量 RAG 都少不了它?
Rerank 最大的价值,不是“看起来更高级”,而是它真的能修掉很多召回阶段的毛病。
1)它更关注“回答这个问题是否有用”
向量相似度高,不代表对回答最有帮助。
有些文档在语义空间里离 query 很近,但真正回答关键点的能力一般。
Rerank 会更偏向挑出“能回答问题”的内容,而不是“看起来像这个问题”的内容。
2)它能减少无效上下文占位
大模型上下文窗口不是垃圾桶。
你塞进去的废料越多,真正有效的信息就越容易被淹没。
Rerank 的意义之一,就是把那些“看起来沾边,其实没用”的段落踢出去。
3)它对复杂查询更友好
尤其是那种带有条件、限定词、业务语境的问题,比如:
-
“Spring Boot 中 Kafka 重试配置的最佳实践”
-
“合同解除条款里关于违约责任的约定”
-
“医疗问答里某个并发症的诊断依据”
这种 query,不只是语义近似能解决的,还需要更细的相关性判断。ReRankingContentAggregator 正是为这种“召回后再精排”的场景准备的。(GitHub)
七、Rerank 的代价也很真实,别把它当仙丹
技术世界里有个朴素真理:
凡是更聪明的东西,通常都更贵。
Rerank 的代价主要有三类。
1)延迟更高
你本来一次向量检索就能拿到结果,现在还要再做一轮打分重排。
无论是走 SaaS API,还是本地模型推理,响应时间都会上涨。
2)成本更高
如果你用的是云端 scoring / reranking model,那每次重排都可能产生额外调用成本。
LangChain4j 当前文档把这类能力单独归在 “Scoring (Reranking) Models” 集成中,也说明它本质上是一类额外模型能力,而不是白送的系统魔法。(docs.langchain4j.dev)
3)参数更讲究
你得同时考虑:
-
retriever 的
maxResults -
reranker 的
maxResults -
minScore -
文档切片大小
-
最终注入 prompt 的 token 预算
这些参数不是独立的,而是彼此牵一发动全身。调不好,系统会像一锅炖过头的技术乱炖。
八、RRF 和 Rerank,到底该怎么选?
这部分建议你写得更工程化一点,而不是单纯“谁更强”。
适合优先上 RRF 的场景
-
你有多个召回源,比如向量检索 + BM25
-
你想先把召回稳定性抬起来
-
你对延迟和成本比较敏感
-
你希望先用低成本方法做一轮质量增强
因为 LangChain4j 默认聚合器本身就用了两阶段 RRF,所以这条路线天然适合做“轻量增强版 RAG”。(GitHub)
适合优先上 Rerank 的场景
-
你的业务对准确率要求极高
-
错答成本高,比如法律、医疗、金融、企业知识问答
-
你已经有不错的候选召回,但最后一步总差口气
-
你愿意为质量多付一些延迟和成本
最推荐的组合
真正工程里最常见、也最稳的一套思路,通常是:
多路召回 → RRF 融合 → Rerank 精排 → 注入 LLM
先用 RRF 把候选集合做稳,再用 rerank 把最值钱的内容挑出来。
前者解决“广而不乱”,后者解决“准而不偏”。
这套组合拳打下来,RAG 才像是真的在“检索 + 理解”,而不是只做了一个看起来很现代的文档搜索框。
九、一个更靠谱的工程建议
如果你正在用 LangChain4j 做企业知识库或者业务问答,我建议按下面这个顺序推进:
第一步:先把基础召回做对
-
chunk 切分别太碎,也别太大
-
embedding 模型先选稳定方案
-
召回数量先放宽
-
日志里把召回结果打印出来看
第二步:利用默认聚合器吃到 RRF 红利
因为 DefaultContentAggregator 默认就是两阶段 RRF,所以你甚至不用一上来就搞复杂模型,也能先把多路召回的融合质量提升一截。(GitHub)
第三步:再引入 ReRankingContentAggregator
当你确认“召回已经基本覆盖,但前几条排序经常不准”时,再上 rerank,收益会非常明显。
第四步:把评估做起来
别只看“感觉答案更聪明了”。
最好至少统计这些指标:
-
TopK 命中率
-
MRR / nDCG
-
最终回答是否引用正确来源
-
用户追问率
-
错答率
没有评估的优化,很容易变成技术玄学。到最后你会发现自己不是在调 RAG,而是在和随机性谈恋爱。
十、结尾:RAG 的关键,不只是找到文档,而是找到“该给模型看的文档”
做到这里,你会发现一个很重要的认知转变:
RAG 从来不是“搜一下就完事”。
它本质上是一个联合优化过程:
-
检索负责尽可能别漏
-
融合负责尽可能别乱
-
重排负责尽可能别偏
-
注入负责尽可能别塞废话
在 LangChain4j 里,这套链路已经被拆得很清楚:ContentRetriever、QueryRouter、ContentAggregator、ContentInjector 各司其职;默认聚合器用两阶段 RRF,精排可以通过 ReRankingContentAggregator + ScoringModel 来完成。(GitHub)
所以,真正把 RAG 做好的关键,并不是一味追求“更大的模型”或者“更强的向量库”。
而是你要认真回答这个朴素问题:
到底哪些内容,值得被送到大模型眼前?
RRF 解决“多方意见怎么综合”;
Rerank 解决“最终谁最值得留下”。
前者像个稳重的裁判,后者像个挑剔的面试官。
两个都用上,RAG 才不至于继续像个热心但不靠谱的实习生。
更多推荐


所有评论(0)