type
Post
status
Published
date
May 21, 2026
slug
agent46
summary
tags
Agent
category
icon
password
在 RAG 系统里,Rerank 之后的 TopK 截断不是简单地“取前 K 个 chunk”。它本质上是在三个目标之间做工程折中:
尽量保留足够证据;尽量减少无关上下文;尽量控制 LLM 输入 token、延迟和成本。
一句话总结:
召回阶段的 TopK 是候选池大小,Rerank 之后的 TopK 是最终上下文预算。两者必须分开设计。
LangChain 官方文档中给出的典型模式就是:先用向量检索取较大的候选集,例如 top-20,再 rerank 到 top-5;它也明确提醒 base retriever 应取相对较大的
k,然后由 reranker 缩小结果集。(LangChain 文档) Haystack 的 LLMRanker 文档也把这两个概念拆开:Retriever 的 top_k 决定取回多少文档,Ranker 的 top_k 决定排序后返回多少文档。(Haystack文档)1. Rerank 后为什么还要截断?
很多人会问:既然已经 rerank 了,为什么不把 rerank 后的结果都塞给 LLM?
原因有四个。
第一,LLM 上下文不是越多越好。长上下文研究 “Lost in the Middle” 指出,模型在长输入中使用信息并不稳定,相关信息出现在开头或结尾时效果往往更好,位于中间时性能会下降。(Computer Science) 因此,RAG 不应该把大量“弱相关 chunk”直接堆进 prompt。
第二,reranker 的分数只是排序信号,不等于“都值得进入上下文”。LlamaIndex 的
SentenceTransformerRerank 做法很直接:对候选节点打分、重排,然后只返回 top_n;其源码里 top_n 默认值为 2,并在排序后对结果进行切片。(GitHub)第三,生成模型会受到无关证据干扰。即使 top-10 中第 8、9、10 个 chunk 与问题有一点语义相关,也可能把回答带偏,尤其是法律、金融、运维、代码问答等场景。
第四,成本和延迟是真实约束。cross-encoder reranker 要对每个
(query, document) 对做推理,LangChain 文档也说明 cross-encoder 更准确,但代价是每个文档多一次推理。(LangChain 文档)2. 标准链路:candidate_k 与 final_k 必须分离
RAG 中推荐拆成两个 K:
参数 | 位置 | 含义 | 典型值 |
candidate_k / retrieval_top_k | 初始召回 | 给 reranker 的候选池大小 | 20、40、80 |
final_k / rerank_top_k / top_n | rerank 后截断 | 最终进入 LLM 上下文的 chunk 数 | 3、5、8、12 |
流程如下:
Pinecone 的 reranker 教程也采用类似两阶段做法:先 retrieve 25 个文档,再 rerank 到 top-3。(Pinecone)
3. 成熟框架如何实现 Rerank 后截断?
3.1 LlamaIndex:PostProcessor 模式,rerank 后直接 top_n
LlamaIndex 把 rerank 设计成
NodePostprocessor。以 SentenceTransformerRerank 为例,它会:取候选 nodes;构造(query, node_text);调用 cross-encoder 预测分数;把分数写回 node;按分数降序排序;返回前top_n个节点。
LlamaIndex 源码显示,
SentenceTransformerRerank 的 top_n 字段用于“返回排序后的节点数量”,默认值为 2;核心逻辑是在分数写入后排序,并截取 self.top_n。(GitHub)工程上可以抽象成:
这个实现很清晰,但它默认假设:传进来的候选 nodes 已经足够多。如果上游只召回 3 个,再 rerank 到 3 个,rerank 的意义会非常有限。
3.2 LangChain:ContextualCompressionRetriever,把 Rerank 视为“压缩器”
LangChain 的
ContextualCompressionRetriever 思路是:基础 retriever 先取更多文档,然后 reranker 作为 compressor 压缩上下文。官方文档直接建议“retrieve top-20 via embeddings, rerank down to top-5”,并强调 base retriever 要取较大的 k,由 reranker 缩小结果。(LangChain 文档)伪代码如下:
这个设计值得借鉴:retriever 负责召回率,reranker 负责精排和截断。
3.3 Haystack:Retriever top_k 与 Ranker top_k 明确分层
Haystack 文档也强调,ranker 通常放在 retriever 后面使用;
SentenceTransformersSimilarityRanker 可以通过 top_k 设置返回文档数。(Haystack文档) Haystack API 文档中,多个 Ranker 的 run 方法都暴露了 top_k,用于控制 rerank 后返回的最大文档数量。(Haystack文档)典型 Pipeline:
这说明成熟框架普遍把 Rerank 后 TopK 截断作为一个独立步骤,而不是和初始召回混在一起。
3.4 Dify 的教训:不要让召回 TopK 和 Rerank TopK 共用一个参数
Dify 社区里有一个很典型的问题:用户发现如果初始召回和 rerank 后截断共用同一个
top_k,当 top_k 设置为 2 或 3 时,reranker 只能在很小的集合里重排,几乎失去意义。相关 issue 中用户明确指出:理想流程应该是先拿到 n > k 的候选 chunk,再 rerank、过滤、取 top-k;而不是一开始就只取 top-k。(GitHub)另一个 Dify issue 也指出,retrieval 阶段和 re-ranking 阶段共享
score_threshold 与 top_k 会有问题,因为两类模型的分数分布不同,而且召回阶段通常需要尽可能多拿一些文本,rerank 后再压缩到较小数量以匹配 LLM 上下文。(GitHub)Dify 2026 年的一个 PR 提出了“rerank top-k relaxation”机制:当启用 rerank 时,先动态计算一个更大的
expanded_top_k,初始检索使用 expanded 值,最终 rerank 再用原始 top_k 选出结果。(GitHub) 这其实就是生产级 RAG 的常见做法:4. TopK 截断的核心实现
一个生产级实现通常不只是
sorted(docs)[:k],而是包含 6 步:注意这里的顺序:
先按 rerank 分数排序;再按阈值过滤;再做来源去重;再取 final_k;最后受 token budget 约束。
如果先按 token 截断,可能把高分 chunk 错误丢掉;如果先按来源去重,也可能损失同一文档中的多个关键片段。因此顺序要根据业务调整,但默认推荐先保留排序质量,再处理多样性和预算。
5. 截断值 final_k 怎么确定?
5.1 先用上下文预算反推,而不是拍脑袋
最简单的估算方式:
例如:
项目 | 数值 |
模型上下文窗口 | 32k tokens |
系统提示词 + 工具描述 + 对话历史 | 10k tokens |
预留给回答生成 | 4k tokens |
可用于 RAG 上下文 | 18k tokens |
平均 chunk 长度 | 800 tokens |
理论最大 final_k | 22 |
但理论值不等于推荐值。因为上下文越长,干扰越多,而且 “Lost in the Middle” 问题会放大。(Computer Science) 所以实际 final_k 通常要小于理论最大值。
5.2 按任务类型给默认值
场景 | 推荐 candidate_k | 推荐 final_k | 说明 |
FAQ / 客服问答 | 20~40 | 3~5 | 答案通常集中在少数 chunk |
企业制度 / 合同问答 | 40~80 | 5~8 | 需要保留条款上下文 |
代码库 RAG | 50~100 | 8~15 | 可能涉及多个文件、调用链 |
论文 / 长文档问答 | 40~100 | 6~12 | 需要章节级证据 |
摘要任务 | 80~200 | 10~30 | 不是典型 QA,可能需要覆盖面 |
Agent 任务规划 | 30~80 | 6~12 | 需要背景、约束、历史决策 |
经验起点:
也就是:
Dify 的 rerank top-k relaxation PR 也是类似思想:启用 rerank 后,用 multiplier 和 cap 扩大初始候选池,再由 rerank 选出最终 top_k。(GitHub)
5.3 用评估集网格搜索,而不是只看主观效果
最终值要靠数据定。
建议准备一批真实 query,标注相关 chunk 或参考答案,然后跑网格搜索:
重点观察这些指标:
指标 | 作用 |
Recall@candidate_k | 初始召回池有没有把答案捞上来 |
nDCG@final_k | rerank 后前 K 的排序质量 |
MRR@final_k | 第一个正确证据出现得够不够靠前 |
Answer Faithfulness | 生成答案是否忠实于证据 |
p95 latency | 线上延迟是否可接受 |
Avg context tokens | 成本是否可控 |
SentenceTransformers 的 reranking evaluator 会对 query 与文档列表打分排序,并计算 MRR@10、NDCG@10、MAP 等指标。(SentenceTransformers) MTEB 论文也将 nDCG@10 作为检索任务的主要指标之一。(ACL 杰作集)
6. 更好的做法:动态 TopK,而不是固定 TopK
固定
final_k=5 简单,但不够智能。生产系统可以做动态截断。6.1 基于分数阈值
适合 FAQ、客服、知识库问答。
风险是:不同 reranker 的分数分布不同,阈值不可直接复用。Dify 社区 issue 已经指出 retrieval 模型和 rerank 模型分数分布不同,共用 threshold 会出问题。(GitHub)
6.2 基于分数断崖
如果前几个 chunk 分数很高,后面突然大幅下降,可以在断崖处截断:
适合答案证据集中、相关性分布明显的场景。
6.3 基于 token budget
比固定 K 更稳的是固定 token budget:
适合 chunk 长度差异很大的场景。比如有些 chunk 是 200 tokens,有些 chunk 是 1500 tokens,固定 top-5 可能导致上下文过长或过短。
6.4 基于 query 类型
可以先对 query 做路由:
Agentic RAG 可以让路由器判断任务类型,再决定检索与截断策略。
7. Rerank 后截断的工程细节
7.1 先去重,再截断
Hybrid Search 常见流程是 BM25 + 向量检索 + RRF 融合。此时同一个 chunk 或相邻 chunk 可能重复出现。Haystack 的 Ranker API 文档中也提到,部分 rerank 组件在 ranking 前会按文档 id 去重,并在存在分数时保留分数最高的文档。(Haystack文档)
建议:
7.2 对同一文档设置 max_per_source
否则一个长文档可能霸占所有上下文:
适合企业知识库、合同库、代码仓库。
7.3 对强相关 chunk 做邻居扩展
如果 top-1 chunk 是某个段落中间,答案可能依赖前后文。可以在 rerank 后,对高分 chunk 扩展前后邻居:
但注意:邻居扩展应该占用 token budget,而不是无限追加。
7.4 Parent-Child Chunking 下先命中 child,再注入 parent 摘要
常见做法:
child chunk 用于精确召回和 rerank;parent chunk 或 section summary 用于补充上下文;最终截断时按 token budget 控制 parent 注入量。
这比直接把 parent 全文塞进去更稳。
7.5 Rerank 后不一定保持分数顺序进入 prompt
最终 prompt 中可以采用两种顺序:
顺序 | 适用场景 |
rerank 分数顺序 | QA、客服、事实查询 |
原文档顺序 | 法律条款、论文、代码调用链 |
Haystack 的
LostInTheMiddleRanker 提供了另一种思路:把更相关文档放在上下文开头和结尾,降低长上下文中间位置的损失。(Haystack文档)8. Agent / Coding Agent 给 RAG 截断的启发
Claude Code、OpenClaw、OpenCode 不是传统 RAG 框架,但它们的上下文管理思想对 TopK 截断很有启发。
Claude Code 文档说明,每个 session 都从新的上下文窗口开始,跨会话知识依靠
CLAUDE.md 和 auto memory 进入上下文;同时文档提醒 CLAUDE.md 会消耗 token,建议保持具体、简洁、结构化。(Claude Code) 这对 RAG 的启发是:长期信息不应该全部进入 prompt,而应该经过选择、压缩和预算控制。OpenClaw 的 memory 文档也强调,长期记忆存放在 Markdown 文件中,
MEMORY.md 是紧凑的 curated layer,详细每日记录则放在 memory/*.md,并且当 bootstrap 文件超过预算时,注入模型上下文的副本会被截断。(OpenClaw) 这和 RAG 的 TopK 截断本质一致:原始知识可以很多,但注入上下文的一定是预算内的精选结果。OpenCode 官方介绍它是运行在 terminal、IDE、desktop 中的开源 coding agent,并强调不存储用户代码或上下文数据。(OpenCode) 这类 coding agent 场景下,如果要做代码库 RAG,通常更需要“按任务动态 TopK”:查一个函数可能 top-5 足够,跨文件重构可能需要 top-12 甚至更多。
9. 推荐的生产级配置模板
9.1 默认配置
9.2 高召回配置
适合合同、制度、代码库:
9.3 低延迟配置
适合在线客服:
10. 最佳实践总结
Rerank 后 TopK 截断建议遵循这几条原则:
- 不要让初始召回 TopK 和 Rerank 后 TopK 共用一个参数。
初始召回要保证 recall,最终截断要控制上下文。
- 默认从
candidate_k=40, final_k=5开始。
小知识库可以降到
20 -> 3,复杂知识库可以升到 80 -> 8。- 优先用 token budget 控制,而不是只用 chunk 数。
因为 chunk 长度差异会导致固定 K 不稳定。
- 阈值要按 reranker 单独校准。
不同 reranker 分数分布不同,不要复用 embedding 相似度阈值。
- 用评估集决定最终值。
看
Recall@candidate_k、nDCG@final_k、MRR@final_k、faithfulness、latency 和 token cost。- 对长上下文要考虑位置效应。
最相关内容尽量放在 prompt 前部或尾部,避免被埋在中间。
最终,Rerank 后 TopK 截断的核心不是“取几个 chunk”,而是:
在候选召回率、rerank 精度、上下文预算、模型注意力和线上成本之间找到最优平衡点。
分享
