Lazy loaded image
Agent丨Rerank 之后的 TopK 截断如何实现,截断值怎么确定?
Words 4182Read Time 11 min
2026-5-21
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 源码显示,SentenceTransformerReranktop_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_thresholdtop_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 截断建议遵循这几条原则:
  1. 不要让初始召回 TopK 和 Rerank 后 TopK 共用一个参数。
    1. 初始召回要保证 recall,最终截断要控制上下文。
  1. 默认从 candidate_k=40, final_k=5 开始。
    1. 小知识库可以降到 20 -> 3,复杂知识库可以升到 80 -> 8
  1. 优先用 token budget 控制,而不是只用 chunk 数。
    1. 因为 chunk 长度差异会导致固定 K 不稳定。
  1. 阈值要按 reranker 单独校准。
    1. 不同 reranker 分数分布不同,不要复用 embedding 相似度阈值。
  1. 用评估集决定最终值。
    1. Recall@candidate_knDCG@final_kMRR@final_k、faithfulness、latency 和 token cost。
  1. 对长上下文要考虑位置效应。
    1. 最相关内容尽量放在 prompt 前部或尾部,避免被埋在中间。
最终,Rerank 后 TopK 截断的核心不是“取几个 chunk”,而是:
在候选召回率、rerank 精度、上下文预算、模型注意力和线上成本之间找到最优平衡点。
回到首页