Rerank
rerank 在 AI4J 里的位置很容易被写浅。
它既不是“第二个 retriever”,也不是“最后给模型的 prompt 优化器”,而是夹在 retrieval 和 context assembly 之间的一层排序修正。
从源码看,这一层的职责非常具体:
- 输入一组已经召回的
RagHit - 根据 query 再排一次序
- 可选保留原始命中尾部
- 把 rerank 分数写回结果
1. 源码入口在哪里
这一页最关键的类有:
rag/DefaultRagService.javarag/Reranker.javarag/NoopReranker.javarag/ModelReranker.javarag/RagHitSupport.javaservice/IRerankService.java
provider 侧还有几个实现:
platform/standard/rerank/StandardRerankService.javaplatform/doubao/rerank/DoubaoRerankService.javaplatform/jina/rerank/JinaRerankService.javaplatform/ollama/rerank/OllamaRerankService.java
但真正把 rerank 接进 RAG 主链的,是 DefaultRagService.search(...)。
2. 在默认 RAG 链里,rerank 精确发生在哪一步
DefaultRagService.search(query) 的主线是:
retriever.retrieve(query)RagHitSupport.prepareRetrievedHits(...)- 拷贝一份 hits 作为
rerankInput reranker.rerank(query.getQuery(), rerankInput)RagHitSupport.prepareRerankedHits(...)trim(..., query.finalTopK)contextAssembler.assemble(query, finalHits)
这条顺序很重要,因为它说明:
- rerank 是在 retrieval 之后
- rerank 是在 context 拼装之前
finalTopK是在 rerank 之后才裁剪
所以如果你只把 topK 调大,但不做 rerank,最后给模型的上下文不一定更准;
反过来,如果你 rerank 了,但 finalTopK 太小,也可能把有价值的尾部命中再次截断。
3. 为什么默认给你的是 NoopReranker
DefaultRagService 的默认构造器是:
this(retriever, new NoopReranker(), new DefaultRagContextAssembler());
NoopReranker 的实现也很直白:
- hits 为空就返回空
- 否则原样拷贝一份返回
这说明框架作者在这一层的默认态度是:
Rerank 是可选增强,不是 RAG 链路成立的前置条件。
这么做有两个工程好处:
- 没有额外服务时,链路照样可用
- 你不会被强制绑到某个 rerank provider
但代价也很明显:
- 纯 dense / hybrid 的前几名,不一定是最终最适合回答的问题上下文
- query 与 chunk 的细粒度相关性修正,默认并没有发生
4. ModelReranker 到底怎么工作
ModelReranker 当前实现是最值得看的主干。
它做的事情分 6 步:
- 校验
rerankService和model必填 - 如果 hits 为空,直接返回空
- 如果 query 为空,直接返回原 hits 拷贝
- 把
RagHit转成RerankDocument - 调
IRerankService.rerank(...) - 根据 provider 返回的
index把结果映射回原始命中
这里有几个关键实现细节。
4.1 文档主键用的是 stableKey
ModelReranker 给 RerankDocument.id 填的是:
RagHitSupport.stableKey(hit)
这和 hybrid 去重使用的稳定 key 逻辑是一脉相承的。
也就是说,命中标识是否稳定,会同时影响:
- hybrid 合并
- rerank 文档标识
- trace 排障
4.2 结果映射靠的是 provider 返回的 index
ModelReranker 真正回写命中时,不是按文档 id 查,而是按返回结果的 index 去原始 hits 里取。
这意味着 provider 结果必须和送入 documents 的顺序保持一致语义。
如果某个 provider:
- 返回了空 index
- index 越界
- 顺序语义不一致
这些结果都会被忽略。
4.3 appendRemainingHits 默认是 true
默认构造器里:
this(rerankService, model, null, null, false, true);
也就是说,即便 rerank provider 只返回了前 topN 个结果,剩余未被 rerank 命中的文档,默认也会按原顺序追加回尾部。
这个默认值有利于“尽量不丢召回”,但也意味着:
- rerank 并不是强裁剪
- 你后面最好配合
finalTopK - 否则最终结果里仍然可能混入未真正重排过的尾部命中
5. topN 和 finalTopK 不是一回事
这是最常见的概念混淆。
ModelReranker.topN 表示:
- 发给 rerank provider 时,希望它重点返回多少条结果
RagQuery.finalTopK 表示:
DefaultRagService在 rerank 之后,最终保留多少条 hits
两者不相等时,表现会很不一样:
topN < finalTopK:尾部可能来自appendRemainingHitstopN > finalTopK:rerank 做了更多工作,但最终被再裁掉topN = null:默认按全部 hits 做 rerank
所以生产里通常不要只调一个数字,不看另一个。
6. rerank 后哪些字段会变化
RagHitSupport.prepareRerankedHits(...) 会把两份数据合并:
- 原始 retrieved hits
- reranker 返回的 hits
然后做几件事:
- 合并 metadata 与来源字段
- 若 rerank 生效,则写
rerankScore - 重新分配
rank - 用
normalizeEffectiveScore(...)把score归一成当前阶段有效分
当前有效分优先级是:
rerankScorefusionScoreretrievalScorescore
这意味着 rerank 一旦生效,score 的含义就变了。
所以排障时你不能只说“这个命中 score 高”,还要问:
- 这是 retrieval score?
- fusion score?
- 还是 rerank score?
7. returnDocuments 会带来什么副作用
ModelReranker 支持:
returnDocuments
如果打开它,并且 provider 返回了 document.content,代码会:
copy.setContent(result.getDocument().getContent());
这意味着 rerank 阶段不仅可能改顺序,还可能改内容。
这个能力很强,但也要谨慎:
- 如果 provider 返回的是清洗后内容,citation snippet 可能变
- 如果 provider 对原文做了截断或标准化,trace 对比会更难解释
所以默认值是 false,是合理的保守策略。
8. Trace 到底能看到什么
当 query.includeTrace = true 时,DefaultRagService 会把:
retrievedHitsrerankedHits
塞进 RagTrace。
这不是“模型最终回答 trace”,而是 RAG 排序链 trace。
它能帮助你回答的问题是:
- 哪些命中被召回了
- rerank 后顺序怎么变了
- 某条命中的分数语义怎么变化了
它不能直接告诉你:
- 模型为什么最终用了某条上下文
- provider 内部是怎么算 rerank 的
- prompt 阶段发生了什么
9. 当前这一层没有替你做什么
AI4J 的 rerank 层当前没有直接替你做:
- query rewrite
- 业务规则过滤
- 按 source 白名单/黑名单打分
- 失败自动降级重试
- 并行比较多个 rerank provider
它提供的是一个很清楚的框架位点,让你把“二次相关性排序”插进 RAG 主链。
10. 最容易踩坑的 5 个点
10.1 把 rerank 当成 retriever
Rerank 不负责召回新文档,它只重新排列已经召回出来的 hits。
10.2 忘了 embedding / hybrid 质量仍然是上游前提
如果召回集合本身就错了,rerank 也救不回来。
10.3 只开 rerank,不设 finalTopK
这样尾部未重排命中可能仍会一路进入 context。
10.4 误读 score
Rerank 生效后,score 通常已经不是原始检索分。
10.5 以为默认链路已经“自动接了大模型重排”
默认其实是 NoopReranker,没有任何远端重排服务。
11. 什么时候值得引入 rerank
下面这些场景,通常值得上 rerank:
- dense/hybrid 能召回,但前几条排序经常不稳
- 文档块比较大,需要更强 query-aware 排序
- 你想把召回集先放宽,再用更贵的模型精排
- 你已经有 trace / 评估体系,能量化排序收益
反过来,如果你的语料很小、query 很简单、召回本身已经稳定,默认 NoopReranker 也是完全合理的起点。
12. 这页最该记住的结论
AI4J 当前的 rerank,不是另一套检索框架,而是 DefaultRagService 中 retrieval 与 context assembly 之间的一层排序修正:
- 默认不开启远程重排,走
NoopReranker - 真正的模型重排由
ModelReranker + IRerankService完成 score会在这一层被改写成 rerank 有效分finalTopK是在 rerank 之后才生效
把这一层看清楚之后,你再调 dense、hybrid、citation 才不会混层。