AgentDepthAgent 工程学习指南
输入概念或问题,检索正文和待编写目录。
第一次学习?从 L0 总览开始 →

Embedding 与 Reranker 入门:先找得到,再排得准

我的学习 →

正在读取学习记录…

Embedding 模型把查询和资料变成可比较的数字表示,常用于从大量资料里先找候选;Reranker 再把查询与少量候选放在一起细看,重新排序。可以把它想成先从书架挑几本可能相关的书,再逐本翻看哪本更合用。

这里先用一张人工分数表走完“找候选 → 重排 → 取证据”的正常路径。之后再看报销查询里的两种问题:正确规定根本没被找出,或者找出了却被排到后面。先读过向量与相似度会更顺;这些手工分数不是模型实测效果。

0. 这两个模型在 Agent 中分别做什么

找一本能回答“报销要哪些材料”的书,可以分两步:先从书架上挑出几本报销相关的书,再逐本翻看哪本写到了所需材料。检索系统也有这两步:Embedding 将查询和文档分别转成向量,用向量比较来挑候选;Reranker 接收“查询 + 候选文档”,给相关性打分并重新排列。

先记住这两个动作:找出候选,排好候选。排在前面的资料接着交给生成模型,用于组织回答。下一节会用具体数量看清两步如何连接。

阅读时查名词:资料集合、文本块与候选数量
名词第一次阅读时先这样理解暂时不要误认为
语料库允许检索的一组原始文档及版本模型参数里自带的全部知识
文本块 chunk从文档切出的可定位片段随便截取的一段字符
Embedding把输入编码为向量的模型或过程正确答案评分器
召回从大范围取候选最终回答
Reranker对现有候选重新评分排序能找回候选集之外的文档
Top-K最多保留 K 个候选质量已经合格的证明
左侧从大书架选出少量资料,右侧只对送到桌上的资料逐份比较,形成先召回再重排的直观比喻。
图1:先从大书架找出候选资料,再对桌上的少量资料逐份比较。
全部文档先经过Embedding召回得到前K个候选,Reranker只在候选内部细排并保留前M个证据
图2:Embedding 先选前 K 个候选,Reranker 只在这 K 个内部细排。

1. 为什么要拆成两个阶段

把允许搜索的资料集合叫作语料库。假设里面有 N 段文本:先从中找出 K 个可能相关的候选,这一步叫召回;再细排这些候选,取前 M 段用于回答。K 是召回数量,M 是最终保留数量,通常 M 不大于 K。例如从 1000 段资料里先找 20 段,再挑 3 段,比每次都逐段细读 1000 段更容易控制计算量。

N 很小时,可以直接对全部段落重排;资料较多时,两阶段能减少需要细读的候选。K 太小则可能漏掉答案,因此要同时比较耗时和召回结果。Sentence Transformers 的官方教程同时展示了两阶段检索和小规模文档直接重排。检索与重排教程

阶段输入输出首先检查什么
准备索引文档、切分规则、版本和访问权限可检索的文本块及元数据目标规定是否真的入库
召回查询、允许访问的文档范围K 个候选及召回分数相关证据是否进入候选
重排查询与每个候选文本新的相关性分数与顺序已召回的正确证据是否靠前
组装上下文候选、Token 预算、来源约束送给模型的证据片段关键限定条件是否被截断
生成与核验问题、证据回答与引用引用是否支持具体结论

从候选到答案还要经过两步:选出完整证据片段,再据此生成并核对回答。重排负责顺序,制度版本和引用内容则在后面分别核对。

2. Embedding:提前把文档放进可比较的空间

Embedding 模型把文本编码成向量。离线阶段,为每段文档算一次向量并保存;查询到来时,计算查询向量,再寻找接近它的文档向量。这样通常不必在每次查询时重新编码全部文档。

“查询”和“文档”不一定使用完全相同的前缀或提示。有些检索模型针对两类输入要求不同模板。关键是遵守该模型训练时的输入约定,并让查询、文档表示处在兼容的检索空间。只检查维度相同不够:两个模型都输出 768 维,也不意味着可以互相比较。

3. Reranker:让查询与候选一起参与判断

常见的交叉编码器(Cross-Encoder)把“查询、候选文本”作为一对输入,让二者在模型内部共同参与计算,输出相关性分数。这和“分别算两个向量,再求余弦”不是同一件事。

因此,更换查询通常需要重新评估每个查询—候选对;不能像独立文档向量一样,提前为文档存一个对所有问题通用的重排分数。批处理可以提高吞吐,但没有消除候选对的计算。实际成本还取决于文本长度、模型、硬件和批大小。

比较点常见双编码召回常见交叉编码重排
文本输入查询和文档分别编码查询和文档成对编码
文档计算的复用文档向量可以提前缓存同一文档面对不同查询通常要重新评分
使用范围大范围候选筛选已有候选的细排
分数含义指定空间和度量下的匹配程度特定模型的相关性评分
主要风险漏召回、空间不匹配、近似检索误差候选外证据无法补回、截断、排序偏差
进阶:其他检索表示与分数变换

稀疏检索常按词项匹配,多向量检索为一段文本保留多个表示,LLM 重排则让语言模型比较候选。研究这些变体时,分别检查表示和评分方法。

如何理解 0.9 的相似分数

不同模型可能输出原始分数,也可能经过 Sigmoid 变换。Sigmoid 是把任意实数映射到0到1的数学函数;它改变数值范围,业务正确率则需要带参考答案的数据检验。BGE Reranker 模型卡展示了两种输出方式。

同样不能直接把余弦 0.81 与重排分数 4.2 相加,便宣称得到了“综合可信度”。如果需要融合,必须明确分数处理和参数,用独立验证集检查;不要凭数字大小猜含义。

4. 手算一次:召回决定了重排能看到什么

把查询明确为:“出差报销没有纸质发票时,可以用什么凭证,需要谁审批?”四份短资料如下:

  • A:出差申请需填写目的地和出行日期,由直属主管确认。
  • B:无纸质发票时,可提交电子发票和付款凭证。
  • C:住宿费用按城市等级设置每日金额上限。
  • D:使用替代凭证报销,需直属主管批准并由财务复核。

B 回答凭证,D 回答审批;两份合起来回答完整问题。A、C 虽然也讲差旅,却没有这两个答案。相关标签先按资料内容判断,再看下面的分数。

下面两列分数都是人为指定。重排分数也不是事实正确率;故意把 B、D 放得较高,是为了观察“有能力把相关内容排前面,但看不到漏掉文档”的情形。

文档内容摘要相关标签召回分数假设的重排分数
A出差申请流程否0.930.10
B无纸质发票时可接受的凭证是0.890.95
C住宿报销金额上限否0.870.15
D替代凭证适用的审批条件是0.830.90

当 K=2,候选只有 A、B。重排后变成 B、A;D 根本没有输入重排器,不能凭它本来可能得到 0.90 就突然出现在结果中。当 K=4,四段都进入候选,重排后的前两段才是 B、D。

K为2时A和B重排成B和A,相关文档D未被召回;K为4时全部文档进入候选,重排前两段才是B和D。
图3:K=2 时 D 没进候选集;扩大到 K=4,重排才有机会看见它。

用检索指标把差异写清楚:

  • 候选 Recall@K:候选中相关文档数量 ÷ 语料中标注相关文档数量。本例 K=2 时为 1/2,K=4 时为 2/2。
  • 最终 Precision@2:保留的两段中相关文档数量 ÷ 2。K=2 时为 1/2,K=4 时为 2/2。
  • 本例最终 Recall@2:最终两段中相关文档数量 ÷ 2,也分别为 1/2 和 2/2。分母相同是因为本例恰有两段相关文档,不是两个指标天然相同。

这只验证了手工数据的逻辑,不能推导出“把 K 调成 4 就能达到 100%”。真实语料的相关文档集合往往还标得不全;此时分母本身就需要说明。

自己运行这组计算

把下方两个完整文件保存为 candidate_boundary.py 和 test_candidate_boundary.py。程序使用上表的固定分数,依次执行候选截取、重排和指标计算。

保存为 candidate_boundary.py:

Python
"""用固定分数观察候选召回与重排的分工。"""
# 每行依次是资料编号、召回分数、重排分数。
rows = [
    ("A", 0.93, 0.10),
    ("B", 0.89, 0.95),
    ("C", 0.87, 0.15),
    ("D", 0.83, 0.90),
]
# 人工参考答案:B 和 D 是相关资料。
relevant = {"B", "D"}


def evaluate(k):
    if type(k) is not int or k <= 0:
        raise ValueError("k must be a positive integer")
    # 先按召回分数取前 k 篇资料。
    candidates = sorted(rows, key=lambda row: (-row[1], row[0]))[:k]
    # 重排只对刚才选中的候选重新排序。
    ranked = sorted(candidates, key=lambda row: (-row[2], row[0]))
    top = [row[0] for row in ranked[:2]]
    candidate_ids = {row[0] for row in candidates}
    # 统计相关资料有多少进入候选,以及前两名有多少相关。
    candidate_recall = len(candidate_ids & relevant) / len(relevant)
    precision = len(set(top) & relevant) / len(top)
    assert set(top) <= candidate_ids
    return {"k": k, "top": top, "candidate_recall": candidate_recall, "precision": precision}


if __name__ == "__main__":
    for k in (2, 4):
        result = evaluate(k)
        print(k, result["top"], result["candidate_recall"], result["precision"])
完整代码:test_candidate_boundary.py
Python
# 核对Embedding 与 Reranker 入门的正常结果和错误分支;断言失败时会显示对应的测试。
import unittest

from candidate_boundary import evaluate


class CandidateBoundaryTest(unittest.TestCase):
    def test_small_candidate_set_cannot_recover_missing_document(self):
        result = evaluate(2)
        self.assertEqual(result["top"], ["B", "A"])
        self.assertEqual(result["candidate_recall"], 0.5)
        self.assertEqual(result["precision"], 0.5)

    def test_larger_candidate_set_exposes_both_relevant_documents(self):
        result = evaluate(4)
        self.assertEqual(result["top"], ["B", "D"])
        self.assertEqual(result["candidate_recall"], 1.0)
        self.assertEqual(result["precision"], 1.0)

    def test_non_positive_or_boolean_k_is_rejected(self):
        for value in [0, -1, True, 1.5]:
            with self.subTest(value=value), self.assertRaises(ValueError):
                evaluate(value)


if __name__ == "__main__":
    unittest.main(verbosity=2)

在空文件夹中保存源码后,在终端中先确认路径和文件名,再执行:

Bash
pwd
ls
python --version
python -B -X utf8 candidate_boundary.py
python -B -X utf8 -m unittest -v test_candidate_boundary.py

这两个文件使用标准库。如果得到 can't open file,检查终端当前目录和实际文件名。

Python
# 按固定输入观察Embedding 与 Reranker 入门的处理过程和返回结果。
rows = [
    ("A", 0.93, 0.10),
    ("B", 0.89, 0.95),
    ("C", 0.87, 0.15),
    ("D", 0.83, 0.90),
]
relevant = {"B", "D"}

for k in (2, 4):
    candidates = sorted(rows, key=lambda row: (-row[1], row[0]))[:k]
    ranked = sorted(candidates, key=lambda row: (-row[2], row[0]))
    top = [row[0] for row in ranked[:2]]
    candidate_ids = {row[0] for row in candidates}
    candidate_recall = len(candidate_ids & relevant) / len(relevant)
    precision = len(set(top) & relevant) / len(top)
    print(k, top, candidate_recall, precision)
    assert set(top) <= candidate_ids

对照手算,预期打印 2 ['B', 'A'] 0.5 0.5 和 4 ['B', 'D'] 1.0 1.0。第二排序键是文档 ID,作用是在分数相同时保持确定顺序,不是新的相关性判断。

跟着核心源码理解候选边界

rows 每一行依次保存文档 ID、人工设定的召回分数和人工设定的重排分数;relevant 是独立写下的相关文档集合。第一次 sorted 按召回分数降序取前 K,第二次只在这 K 行中按重排分数再排序:

Python
candidates = sorted(rows, key=lambda row: (-row[1], row[0]))[:k]
ranked = sorted(candidates, key=lambda row: (-row[2], row[0]))

set 表示不重复的名单。candidate_ids & relevant 取两份名单的交集,即“同时在候选里、也在相关名单里”的文档:K=2时只有B,除以相关名单的两篇就是0.5。set(top) <= candidate_ids 检查 top 的每一篇都属于候选名单,这里的 <= 表示集合包含关系。

负号把高分排在前面;同分时再比较文档 ID。[:k] 保留前k篇:D在K=2时已经被切掉,重排只能比较眼前的A、B,无法把D变回来。

这里用固定分数,单独观察候选范围如何限制重排结果。先只改 K;如果同时换模型、分块和 K,就很难判断哪一步改变了结果。

三项单元测试分别固定 K=2、K=4 和非法 K。前两项防止后续修改悄悄改变候选边界,第三项拒绝 0、负数、布尔值和浮点数。修改候选截取或排序代码时,这三项检查会帮助发现结果变化。

代码依赖这个固定夹具:ID 唯一、相关集合非空、候选不少于两条。扩展成通用函数时必须处理空集合;没有相关标签时不要除以零,更不要自动填成 100%。题库会要求你明确这些边界。

进阶:为索引保留原文、版本与访问范围

一个文本块应当随身带着什么

假设入库的片段编号是 travel-2026-17,只保存向量还不够。至少要能找回原文、文档标题、章节、版本、生效日期和访问控制信息。否则搜索结果虽然有高分,却无法回答“引用的是哪版制度”。

字段示例为什么要保留
chunk_idtravel-2026-17去重、定位和追踪同一片段
document_versiontravel-policy-2026-08防止新旧制度混用
text完整规定及例外条款重排和最终引用都需要原文
source内部文档路径及章节让读者核对依据
embedding_revision模型与编码配置版本判断索引是否需要重建
access_scope已授权部门范围限制候选和外部模型可见内容

向量相似不等于含义完全一致。“无发票允许报销”和“无发票不允许报销”共享许多词,业务结论却相反。因此领域术语、否定、数字、时间和例外条件都应该出现在测试查询中。

5. 从手算走到真实检索实验

候选截取和重排顺序已经看清了。接入真实模型时,把固定分数表替换成模型计算的分数,再用同一份问题集比较效果。

一个可复核实验可以分三组进行:

组别固定项改变项能回答的问题
召回比较语料版本、查询、标签、K词法检索与向量检索谁更容易找全所需证据
重排比较完全相同的候选列表、M不重排与指定重排模型候选相同时排序是否改善
端到端比较问题集与证据判定标准完整检索配置最终回答是否更可靠,代价如何

比较两种重排器时,保持切分、Embedding 和 K 相同,把变化集中在重排器上,就容易解释差异来自哪里。记录模型完整标识与 revision、库版本、设备、输入截断长度、批大小、查询模板、语料校验值及原始排序结果。模型下载和计算也有资源成本,应提前指定磁盘缓存目录,不在入门代码里隐式下载大型权重。

查询集应包含简称、错别字、否定、跨段条件、过期制度、查无此据和越权请求。调参数用验证集,最后再看保留测试集;如果看过测试集后反复调整阈值,它就不能继续充当未见测试集。

算出“找回了多少相关资料”和“返回的资料里有多少真正相关”之后,再看结果出现在第几位。例如用户通常只看前三条,第十条才出现正确政策也会影响体验。评估报告还应列出样本数量、标签怎么定、失败案例、响应时间和运行条件;一张“准确率提高”的图不足以说明原因。需要更细的排名指标时,再按任务选择 MRR 或 nDCG 并核对各自的计算方式。

6. 找不到答案时,按这张表排查

症状优先查什么下一步操作
目标规定不在任何结果中入库是否成功、版本和权限、切分是否丢掉原文按文档 ID 查找原文与文本块
原文在库,但不在前 K查询模板、关键词基线、向量空间、K、索引配置对同一查询打印召回名单
在候选中,重排后靠后查询—文档对、输入截断、领域适配、分数方向核对传入文本与模型分数定义
排在前面却没有进入回答上下文预算、去重、片段拼接和最终请求查看真正发给模型的资料
引用了过期或越权内容生效日期和授权过滤、缓存隔离在检索和缓存读取时筛选可用内容
文档相关但结论错误否定、条件、引用范围及生成过程对照原文条件核对回答

权限过滤应发生在未授权内容进入后续可见范围之前,尤其不能先把内容发给外部重排服务,再在页面上隐藏。缓存也需要考虑授权范围,否则可能把别人的候选结果复用过来。

重排服务超时怎么办?可以评估退回原召回顺序,但应明确记录降级状态,不把原分数冒充重排分数。在高风险场景,停止回答、提示证据不足可能比悄悄降级更合适。策略取决于实际后果,需要测试,不是一条通用默认值。

7. 用开源工具实现召回和重排

把刚才的手工分数换成模型计算,可以从以下工具的教程和模型卡入手。先看一个完整的两阶段检索例子,再了解具体编码器和重排器的输入输出。

顺序项目或资料建议阅读内容对应的知识点
1Sentence Transformers官方 Retrieve & Re-Rank 教程输入输出、候选范围、双编码与交叉编码的差别
2FlagEmbeddingReranker 使用入口与评估资料如何构造查询—文档对,哪些配置影响分数
3BGE Reranker 模型卡Usage、分数映射与模型限制原始分数与映射分数的区别,自己的任务需要验证什么

先看模型卡的任务和使用说明:编码器输入一段文字、输出向量;重排器输入查询与文档对、输出相关性分数。按目标选择对应接口。FlagEmbedding 提供编码器和重排器实现,可以按相应模型卡继续研究。

8. 练习与实践作品

课后练习展开习题收起习题

4 道练习 · 客观题规则评分 · 其余题目待批改

正在读取本地记录…

不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。

练习 1 / 4 · v2.0.0

选择题 · 召回和重排有什么区别?

粗检索选出20段候选后,reranker对它们重新评分。哪项说法正确?

对应知识点:L0-embeddings

选择一个答案
练习 2 / 4 · v2.0.0

简答题 · 重排分数是正确概率吗?

某重排模型把原始分数经过 Sigmoid 变成0.9。产品想展示“该回答90%正确”。说明这个结论缺少什么,并解释为什么不能直接把它与余弦分数相加。

对应知识点:L0-embeddings

练习 3 / 4 · v2.0.0

代码题 · 验证候选集的上限

使用正文A/B/C/D夹具,将候选截取和重排封装为函数。验证K=2和K=4的输出分别为[B,A]和[B,D];加入空候选、空相关集合和同分测试,明确未定义指标如何表示。

对应知识点:L0-embeddings

练习 4 / 4 · v2.0.0

场景题 · 召回增加,延迟也增加

K从20调到100后候选召回率提高,但重排延迟超标。你会怎样设计对照实验,区分K、模型与文本截断的影响?如果重排超时,如何选择和记录降级?

对应知识点:L0-embeddings

先沿用四条资料与两组固定分数,把候选数量 K 从2改为4,比较 D 是否进入候选。再把 K 设为3:前三名仍是 A、B、C,D 还是不在重排范围里。这个中间值可以帮助你理解,改善召回依赖“正确资料进入”,不只是数字变大。

把正确文档移出候选集合,再运行重排:它会出现在最终结果里吗?随后增加候选数量,观察它是否重新进入比较。能解释这两次差异,就理解了“先找候选、再排顺序”的分工。

9. 总结与讨论

Embedding 召回解决大范围筛选,常见 Cross-Encoder 在候选内部重新判断相关性。先问正确证据有没有进入候选,再问顺序是否合理,最后问上下文与引用是否保留了条件。这样才能把“回答不好”拆成可检查的环节。

试着给同学解释:如果增加 K 提高了召回,却使重排延迟超出要求,你会先改变什么?把语料规模、查询类型、候选数量和延迟测量条件写在答案旁。只说“换更强的模型”,别人无法复现你的判断。练习请使用教学数据,不放公司原文或个人信息。

这里最重要的反例是文档 D:它是正确资料,却在 K=2 时没进候选集。重排器只能比较眼前这两篇,无法把 D 从库里“变回来”。把 K 改成 4,在固定手工分数下,候选召回从 0.5 变成 1。

留笔记时请画两张短名单,标上每篇的 ID、人工标签、召回顺序和重排顺序。这样一眼就能看出错在“没找着”还是“排得靠后”。两阶段方法可参照 Sentence Transformers Retrieve & Re-Rank。下一篇读模型 API 配置。

参考资料

图片查看器
100%

文章配图 · 按 Esc 关闭