衡量重排的影响
运行前后基准测试,对比单阶段检索与采用重排的两阶段检索,并衡量 NDCG、MRR 以及端到端答案质量。
衡量重排的影响 是 CoddyKit 上的免费 AI Engineering Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 AI Engineering Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 AI Engineering Academy 课程共包含 4 节课。
为什么要衡量重新排序的影响
重新排序会增加您的检索流程的延迟和成本。如果不进行衡量,您就无法判断增加的复杂性是否值得。基准测试可以量化检索质量和端到端答案质量的提升,帮助您做出明智的决策。它还可以揭示哪些查询类型受益最大,从而让您能够有选择地应用重新排序,而不是对每个请求都使用。
构建黄金测试集
可靠的基准测试需要一个黄金测试集:其中包含一组查询,以及与每个查询配对的、已知相关的文档 ID。您可以从应用的日志中抽取真实用户查询,然后手动或通过专家标注确定相关文档,并将其整理为结构化格式。对于大多数 RAG 评估场景,包含 50 到 200 个查询的测试集就足够了。
golden_test_set = [
{
'query': 'How does pgvector HNSW indexing improve search speed?',
'relevant_doc_ids': ['doc_042', 'doc_107'],
},
{
'query': 'What is the difference between BM25 and dense retrieval?',
'relevant_doc_ids': ['doc_015'],
},
{
'query': 'How to implement reciprocal rank fusion in Python?',
'relevant_doc_ids': ['doc_093', 'doc_094'],
},
# ... 47 more entries
]
print(f'Test set size: {len(golden_test_set)} queries')
print(f'Avg relevant docs per query: {sum(len(e["relevant_doc_ids"]) for e in golden_test_set) / len(golden_test_set):.1f}')检索指标:NDCG、MRR、命中率
请使用三个互补的指标来评估检索质量。K 值命中率用于衡量前 K 个结果中是否至少出现一个相关文档。MRR(平均倒数排名)用于衡量第一个相关文档排名的倒数的平均值。K 值 NDCG(归一化折损累积增益)用于衡量排序质量,其中排名靠前的位置获得的权重高于靠后的位置。
def compute_retrieval_metrics(results: list[str], relevant_ids: set, k: int = 5):
results_at_k = results[:k]
relevant_found = [r for r in results_at_k if r in relevant_ids]
# Hit rate
hit = 1 if relevant_found else 0
# MRR
rr = 0
for i, doc_id in enumerate(results_at_k, start=1):
if doc_id in relevant_ids:
rr = 1.0 / i
break
# NDCG (binary relevance)
import math
dcg = sum(
1.0 / math.log2(i + 1)
for i, doc_id in enumerate(results_at_k, start=1)
if doc_id in relevant_ids
)
ideal = sum(1.0 / math.log2(i + 1) for i in range(1, min(len(relevant_ids), k) + 1))
ndcg = dcg / ideal if ideal > 0 else 0
return {'hit': hit, 'rr': rr, 'ndcg': ndcg}基线:单阶段密集检索
在衡量重新排序的影响之前,请使用单阶段密集检索建立一个基线。让测试集中的每个查询都通过双编码器检索器,收集排序后的文档 ID,并计算 NDCG、MRR 和命中率的平均值。这个基线可以告诉您当前的起点以及可提升的幅度——如果基线已经达到 0.95 NDCG@5,重新排序的提升空间就很小了。
def evaluate_pipeline(retriever_fn, test_set: list[dict], k: int = 5) -> dict:
all_metrics = []
for entry in test_set:
query = entry['query']
relevant = set(entry['relevant_doc_ids'])
results = retriever_fn(query, top_k=k)
result_ids = [r['id'] for r in results]
metrics = compute_retrieval_metrics(result_ids, relevant, k)
all_metrics.append(metrics)
n = len(all_metrics)
return {
f'hit_rate@{k}': sum(m['hit'] for m in all_metrics) / n,
f'mrr@{k}': sum(m['rr'] for m in all_metrics) / n,
f'ndcg@{k}': sum(m['ndcg'] for m in all_metrics) / n,
}运行前后基准测试
对单阶段检索器,以及加入重新排序的两阶段检索器,运行相同的评估函数。将结果并排输出,让提升(或没有提升)一目了然。除了质量指标,还要跟踪每个查询的延迟——根据应用的 SLA 要求,5% 的检索质量提升可能不值得付出 400 毫秒的延迟增加。
import time
def evaluate_with_latency(retriever_fn, test_set, k=5):
metrics_list = []
latencies = []
for entry in test_set:
t0 = time.perf_counter()
results = retriever_fn(entry['query'], top_k=k)
latencies.append((time.perf_counter() - t0) * 1000)
result_ids = [r['id'] for r in results]
metrics_list.append(compute_retrieval_metrics(
result_ids, set(entry['relevant_doc_ids']), k
))
n = len(metrics_list)
return {
f'hit_rate@{k}': sum(m['hit'] for m in metrics_list) / n,
f'ndcg@{k}': sum(m['ndcg'] for m in metrics_list) / n,
'p50_latency_ms': sorted(latencies)[n // 2],
'p99_latency_ms': sorted(latencies)[int(n * 0.99)],
}
baseline = evaluate_with_latency(dense_retrieval_fn, golden_test_set)
two_stage = evaluate_with_latency(two_stage_fn, golden_test_set)
print('Baseline:', baseline)
print('Two-stage:', two_stage)解读 NDCG 提升
在密集检索中加入交叉编码器重新排序后,NDCG@5 通常会绝对提升 0.05 到 0.15,也就是大约提升 5 到 15 个百分点。以下情况下,提升幅度通常更大:(1) 查询类型多样,且包含许多释义表达;(2) 语料库中有许多近似相关的分块;(3) 第一阶段检索器较弱。如果 NDCG 提升低于 0.02,所带来的收益可能不足以抵消增加的复杂性。
# Interpreting benchmark results
example_results = {
'baseline': {'hit_rate@5': 0.78, 'ndcg@5': 0.64, 'p99_latency_ms': 35},
'two_stage': {'hit_rate@5': 0.89, 'ndcg@5': 0.77, 'p99_latency_ms': 287},
}
delta_ndcg = example_results['two_stage']['ndcg@5'] - example_results['baseline']['ndcg@5']
delta_latency = example_results['two_stage']['p99_latency_ms'] - example_results['baseline']['p99_latency_ms']
print(f'NDCG improvement: +{delta_ndcg:.2f} (+{delta_ndcg/example_results["baseline"]["ndcg@5"]*100:.0f}%)')
print(f'Latency increase: +{delta_latency}ms')
# NDCG improvement: +0.13 (+20%) — clearly worth the 252ms latency cost端到端答案质量衡量
检索指标衡量的是是否检索到了正确的文档,但最终的衡量标准是端到端答案质量。请使用 LLM 评判器评估:根据重新排序后的上下文生成的答案,是否比根据单阶段上下文生成的答案更加正确、更加忠实。请分别从正确性、忠实性和相关性三个方面按 1 到 5 分评分,然后计算测试集上的平均分。
from openai import OpenAI
client = OpenAI()
JUDGE_PROMPT = '''
Rate the following answer on a scale of 1-5 for correctness and faithfulness to the context.
Question: {question}
Context: {context}
Answer: {answer}
Ground truth: {ground_truth}
Return a JSON with fields: {"correctness": int, "faithfulness": int, "explanation": str}
'''
def judge_answer(question, context, answer, ground_truth):
prompt = JUDGE_PROMPT.format(
question=question, context=context,
answer=answer, ground_truth=ground_truth,
)
response = client.chat.completions.create(
model='gpt-4o',
messages=[{'role': 'user', 'content': prompt}],
response_format={'type': 'json_object'},
)
import json
return json.loads(response.choices[0].message.content)按查询类型进行分层分析
平均指标会掩盖不同查询类型之间的重要差异。请将测试集划分为以下类别,并分别计算每组的指标:事实查询(谁、什么、何时)、操作步骤查询(如何做)、概念查询(为什么、解释)以及技术查询(错误代码、API 名称)。在概念和操作步骤查询中,语义理解比关键词匹配更重要,因此重新排序通常对这两类查询帮助最大。
def stratified_eval(retriever_fn, test_set, k=5):
groups = {'factual': [], 'procedural': [], 'conceptual': [], 'technical': []}
for entry in test_set:
q = entry['query'].lower()
if any(w in q for w in ['how to', 'how do', 'steps to']):
groups['procedural'].append(entry)
elif any(w in q for w in ['why', 'explain', 'what is the reason']):
groups['conceptual'].append(entry)
elif any(c.isupper() for c in q.split()) or 'error' in q:
groups['technical'].append(entry)
else:
groups['factual'].append(entry)
for group_name, group_entries in groups.items():
if group_entries:
metrics = evaluate_pipeline(retriever_fn, group_entries, k)
print(f'{group_name} ({len(group_entries)} queries): ndcg@{k}={metrics[f"ndcg@{k}"]:.3f}')通过 CI 集成进行回归测试
将检索基准测试作为 CI 中的回归测试运行。为 NDCG@5、MRR 和命中率设置可接受的最低阈值。任何导致指标低于阈值的流程更改都会使 CI 构建失败,从而阻止检索质量回退的问题发布到生产环境中。在更改分块大小、嵌入模型或重新排序模型后,这一点尤其重要。
# pytest integration for retrieval quality gates
import pytest
MIN_NDCG_5 = 0.70
MIN_HIT_RATE_5 = 0.85
def test_retrieval_quality_meets_threshold():
metrics = evaluate_pipeline(production_retriever_fn, golden_test_set, k=5)
assert metrics['ndcg@5'] >= MIN_NDCG_5, (
f'NDCG@5 {metrics["ndcg@5"]:.3f} below threshold {MIN_NDCG_5}'
)
assert metrics['hit_rate@5'] >= MIN_HIT_RATE_5, (
f'Hit rate {metrics["hit_rate@5"]:.3f} below threshold {MIN_HIT_RATE_5}'
)
# Run with: pytest tests/test_retrieval.py -v可视化检索指标
在多个实验之间比较时,原始数字很难解读。请创建一个简单的对比表或柱状图,将基线、仅混合检索和混合检索加重新排序这几种流程的 NDCG、MRR、命中率和延迟并排列出。随着您不断改进,持续跟踪这些指标,可以建立检索改进历史,为今后的优化决策提供指导。
def print_comparison_table(results: dict[str, dict]):
headers = ['Pipeline', 'NDCG@5', 'MRR@5', 'Hit@5', 'P99 ms']
print('|'.join(f'{h:20}' for h in headers))
print('-' * (len(headers) * 21))
for pipeline_name, metrics in results.items():
row = [
pipeline_name,
f'{metrics.get("ndcg@5", 0):.3f}',
f'{metrics.get("mrr@5", 0):.3f}',
f'{metrics.get("hit_rate@5", 0):.3f}',
f'{metrics.get("p99_latency_ms", 0):.0f}',
]
print('|'.join(f'{v:20}' for v in row))
results = {
'Dense only': {'ndcg@5': 0.64, 'mrr@5': 0.68, 'hit_rate@5': 0.78, 'p99_latency_ms': 35},
'Hybrid RRF': {'ndcg@5': 0.71, 'mrr@5': 0.74, 'hit_rate@5': 0.84, 'p99_latency_ms': 55},
'Hybrid + Rerank': {'ndcg@5': 0.77, 'mrr@5': 0.81, 'hit_rate@5': 0.89, 'p99_latency_ms': 287},
}
print_comparison_table(results)根据基准测试结果采取行动
完成基准测试后,请利用结果做出具体决策。如果重新排序带来的 NDCG 提升低于 0.03,请跳过重新排序,专注于改进第一阶段。如果命中率较低,说明第一阶段漏掉了相关文档——请增加候选集大小,或切换到混合检索。如果端到端答案质量显著提升,而检索指标的提升幅度不大,那么重新排序器可能找出了 LLM 能够有效利用的高度相关句子,即使它们的排名位置没有变化。
快速检查
测试您对本课中检索和重新排序影响衡量方法的理解。
课程回顾
本课中您学习了:在衡量检索质量之前,构建包含已知相关文档的黄金测试集至关重要;NDCG、MRR 和命中率是三个核心检索指标,结合起来可以全面衡量排序质量;使用 LLM 评判器衡量端到端答案质量,是评估流程改进效果的最终标准。请始终将检索基准测试作为 CI 中的回归测试运行。接下来,我们将探索 LLM 流式传输,以便在令牌生成时将其显示出来。
常见问题解答
「衡量重排的影响」课时是免费的吗?
是的 — 「衡量重排的影响」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 AI Engineering Academy 课程的其余内容,请升级到 CoddyKit PRO。 AI Engineering Academy 课程共包含 4 节课。
「衡量重排的影响」这节课中我会学到什么?
运行前后基准测试,对比单阶段检索与采用重排的两阶段检索,并衡量 NDCG、MRR 以及端到端答案质量。 你通过在浏览器中直接运行的动手代码来练习 AI Engineering Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 AI Engineering Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 AI Engineering Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「衡量重排的影响」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 AI Engineering Academy 课中编写并运行代码吗?
能。每节 AI Engineering Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。