RAG检索准不准怎么量化:recall@k和MRR实操
先把结论摆前头:判断 RAG 好不好用,别盯着「最终答案对不对」看,那一步掺了大模型的发挥,水太浑。真正该先量的是检索这一层——你召回的那几段,到底有没有把答对问题需要的资料捞上来。量化它就两个最常用的指标,recall@k 和 MRR,今天把怎么算、怎么落地讲清楚。
我是怎么栽进这个坑的。前阵子做一个内部制度问答,模型答错,我第一反应是 prompt 没写好,调了三天 prompt,时好时坏。后来一个老同事问我一句:你确定检索那步把对的段落捞回来了吗?我当场愣住——压根没量过。扒了日志才发现,三成的问题,正确答案所在的那段根本没进 top5。prompt 调出花来也没用,菜是从厨房就坏的。
recall@k:该捞的捞回来了吗
定义很白话:对一个问题,标准答案藏在某几段原文里(这叫相关文档),你检索回 top k 段,里头命中了几段相关的,比上总共该命中的,就是 recall@k。
def recall_at_k(retrieved_ids, relevant_ids, k): hit = len(set(retrieved_ids[:k]) & set(relevant_ids)) return hit / len(relevant_ids) if relevant_ids else 0落地的麻烦不在公式,在于你得先有一份「标注集」:几十上百个真实问题,每个标好它的答案出自哪几个 chunk。我第一版偷懒,自己拍脑袋标了 30 条,后来发现远不够,覆盖不到长尾问法。现在我固定从线上真实对话里抽,每周补一二十条,标注这活儿确实烦,没有捷径。
recall@5 我一般要求拉到 0.9 以上,意思是九成的问题,对的段落能进前五。低于 0.8 别急着动 prompt,先回头查分块和 embedding。
MRR:对的那段排得够靠前吗
recall 只看「在不在 top k 里」,不看排第几。可第 1 段和第 5 段对模型差别很大——靠后的容易被它忽略。MRR(平均倒数排名)就补这个,它取每个问题里第一个命中相关段落的排名,取倒数再平均。命中在第 1 位记 1,第 2 位记 0.5,第 3 位记 0.33。
def mrr(all_retrieved, all_relevant): total = 0 for retrieved, relevant in zip(all_retrieved, all_relevant): for rank, rid in enumerate(retrieved, 1): if rid in relevant: total += 1 / rank break return total / len(all_retrieved)指标 | 看什么 | 多少算及格 |
recall@5 | 对的段有没有进前五 | ≥0.9 |
MRR | 对的段排得靠不靠前 | ≥0.7 |
两个一起看才完整。我遇到过 recall 挺高但 MRR 拉胯的情况——该捞的都捞回来了,可全堆在第 4、5 位,前面塞着一堆沾边没用的。后来加了重排,MRR 从 0.6 抬到 0.82,答案质量跟着稳了一截。
把量化变成日常
光算一次没意义,得让它跑成流水线。我的做法:标注集存成一个表,每次改了分块策略、换了 embedding、加了重排,就把整套标注集重跑一遍,recall@k 和 MRR 各出一个数,跟上一版比。涨了留下,跌了回滚,全凭数据,不靠手感。
搭这套我没怎么写后端。智能体和知识库是在一个零代码就能配 RAG 的平台上拖出来的,文档传上去自动切片向量化,检索结果能导出带 chunk_id 的明细,我拿这份明细跟标注集一比就出指标了。省了我自己搭向量库、写检索接口的活。当然平台也只管到「把段落捞回来」,标注集、判分脚本还得自己写,它替不了你定义什么叫「答对」。
一点真实的代价
最大的坑是标注集本身。它就是你的尺子,尺子歪了,量出来的全是自欺欺人。我前期标得潦草,指标好看得很,线上照样翻车。后来老老实实从真实日志抠问题、人工核对答案出处,标注集才慢慢可信,但那是实打实耗工时的体力活,急不来。
(模型和 RAG 我都走的讯飞星辰 MaaS,现成大模型和托管知识库直接调,没自己部署)
你们评 RAG 是只看终答案,还是也量检索层?recall 和 MRR 你卡在多少?评论区交流下,我这套标注流程总觉得还能再省点人力。
