RAG 自动化评测:用 Ragas 给答疑机器人打分
RAG 自动化评测:用 Ragas 给答疑机器人打分
一、先说清楚:为什么答疑机器人需要"考试"
前面几章我们把答疑机器人跑通了,看起来能答上来不少问题。但只要多问几句,就会撞到这种情况:问「张伟是哪个部门的」,机器人给出了回答,可这个回答到底对不对?是检索环节没找到资料,还是找到了资料但模型没答对?每次都靠人肉翻日志去排查,既慢又容易漏。
传统软件开发有单元测试兜底,改一行代码跑一遍测试就知道有没有改坏东西。RAG 应用也应该有同样的机制:准备一批测试问题,每次优化后自动跑一遍,量化地看效果是变好还是变坏。这样优化才有依据,不至于"感觉变好了"就上线。
这一章的路线是:先造一个最朴素的自动化测试,暴露它的不足,再引入专门的评测框架 Ragas,最后讲怎么根据评测分数定位优化方向。
二、最朴素的方案:让大模型当测试员
2.1 一个真实的问题
问题出在员工查询上。公司里有两个张伟,一个在 IT 部,一个在教研部,但机器人回答「张伟是哪个部门的」时只说了 IT 部,答案不完整。更糟的版本是:机器人直接说"没有提到张伟所在的部门"——说明检索环节根本没把相关资料捞回来。
排查方法是先看检索结果。LlamaIndex 的响应对象里有 source_nodes,存着这次回答参考的所有文本切片:
# ch02-RAG/inspect_contexts.py
contexts = [node.get_content() for node in response.source_nodes]
print(contexts)
打印出来一看,召回的切片里只有零散的员工表内容,没有一条完整覆盖"张伟的部门"。定位结论:这是检索效果的问题,不是模型胡说。但这个排查动作每次都做一遍太费时间,于是有了下一步——把"判断回答对不对"这件事也交给大模型。
2.2 两个最简单的测试函数
思路很直接:大模型既然能回答问题、能检查代码错误,当然也能判断一段回答有没有效。只要在提示词里给它问题、回答,并把输出格式限制死,它就是一个免费的测试员。
# ch02-RAG/simple_eval.py
import os
from openai import OpenAI
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
)
def llm_invoke(prompt: str) -> str:
resp = client.chat.completions.create(
model="qwen-max",
messages=[{"role": "user", "content": prompt}],
)
return resp.choices[0].message.content
def test_answer(question: str, answer: str) -> str:
prompt = ("你是一个测试人员。\n"
"你需要检测下面的这段回答是否有效回答了用户的问题。\n"
"回复只能是:有效回答 或者 无效回答。请勿给出其他信息。\n"
"------"
f"回答是 {answer}"
"------"
f"问题是: {question}")
return llm_invoke(prompt)
val = test_answer(
"张伟是哪个部门的",
"根据提供的信息,没有提到张伟所在的部门。如果您能提供更多关于张伟的信息,我可能能够帮助您找到答案。")
print(val) # 输出:无效回答
检索环节也要测。test_contexts 换个判断对象,检查召回的参考资料对回答问题有没有帮助:
# ch02-RAG/simple_eval.py
def test_contexts(question: str, contexts: str) -> str:
prompt = ("你是一个测试人员。\n"
"你需要检测下面的这些参考资料是否能对回答问题有帮助。\n"
"回复只能是:参考信息有用 或者 参考信息无用。请勿给出其他信息。\n"
"------"
f"参考资料是 {contexts}"
"------"
f"问题是: {question}")
return llm_invoke(prompt)
到这里,一个"大模型测试工程"的雏形就有了:回答有效性一个维度,检索资料有效性一个维度。
说明:
llm_invoke是最小实现,替代课程里封装好的私有工具函数,换任何一个 OpenAI 兼容接口都能跑。
2.3 这个方案的边界
朴素方案能跑,但天花板很明显:
- 大模型有幻觉,可能编出一个"看起来很真"的错误答案,
test_answer这种二分判断抓不住"部分正确"或"精确捏造"; - "参考信息有用/无用"太粗。相关信息占比多少(信噪比)、排名靠不靠前,这些细粒度的信号全丢了。
要做更细的打分,就该用专门的评测框架了。
三、主流 RAG 评测框架怎么选
业界成熟的评测框架主要有三个,侧重点不同:
| 框架 | 核心思路 | 适合场景 |
|---|---|---|
| Ragas | 让大模型当评委,按指标打分 | 快速拿到量化分数,指标贴合 RAG 痛点 |
| TruLens | 评估 + 可观测性,记录每次调用的完整链路 | 需要追溯运行过程、可视化排查 |
| DeepEval | 把单元测试/TDD 思想搬进 RAG,支持 Pass/Fail 阈值 | 想接入 CI/CD 做自动化回归测试 |
三者的指标大同小异,都覆盖了 RAG 评测的四个核心维度:
- 召回质量:检索到的切片相关吗?排名靠前吗?
- 答案忠实度(Faithfulness):答案是不是基于检索到的内容说的,有没有幻觉?
- 答案相关性(Answer Relevancy):答案有没有正面回应用户的问题?
- 上下文利用效率:模型有没有用好喂给它的上下文?
本文重点用 Ragas,因为它是纯 Python 库,几行代码就能接进现有工作流,且指标定义和 RAG 的故障模式一一对应,最适合拿来定位问题。
四、Answer Correctness:给最终答案打分
4.1 准备数据
Answer Correctness 衡量 RAG 生成的答案和标准答案的接近程度,需要两类数据:
question:喂给 RAG 的问题;ground_truth:你预先知道的正确答案。
为了看出分数差异,针对「张伟是哪个部门的」准备三种典型回答:检索失败的无效回答、编造部门的幻觉回答、正确回答。
4.2 跑评测
# ch04/Test03_correctness.py
from langchain_community.llms.tongyi import Tongyi
from langchain_community.embeddings import DashScopeEmbeddings
from datasets import Dataset
from ragas import evaluate
from ragas.metrics import answer_correctness
import pandas as pd
import os
# 最小实现:加载 API Key(课程原版封装在私有工具模块里)
assert os.getenv("DASHSCOPE_API_KEY"), "请先设置 DASHSCOPE_API_KEY"
data_samples = {
'question': [
'张伟是哪个部门的?',
'张伟是哪个部门的?',
'张伟是哪个部门的?'
],
'answer': [
'根据提供的信息,没有提到张伟所在的部门。如果您能提供更多关于张伟的信息,我可能能够帮助您找到答案。',
'张伟是人事部门的',
'张伟是教研部的'
],
'ground_truth':[
'张伟是教研部的成员',
'张伟是教研部的成员',
'张伟是教研部的成员'
]
}
if __name__ == '__main__':
dataset = Dataset.from_dict(data_samples)
score = evaluate(
dataset=dataset,
metrics=[answer_correctness],
llm=Tongyi(model='qwen-max'),
embeddings=DashScopeEmbeddings(model="text-embedding-v4")
)
df = score.to_pandas()
print(df)
注意 evaluate 的两个参数:llm 是评委大模型,负责做事实比对;embeddings 负责算语义相似度。评分类指标基本都要这两个模型配合。
实测输出(数值每次运行会有微小波动):
| 回答类型 | answer_correctness |
|---|---|
| 无效回答(没答上) | 0.145 |
| 幻觉回答(编了个部门) | 0.181 |
| 正确回答 | 0.993 |
分数排序和直觉完全一致:正确回答接近满分,无效和幻觉回答都在 0.2 以下。
4.3 分数是怎么算出来的
Answer Correctness 由两部分加权得到:语义相似度和事实准确度,权重为 0.25 * 语义相似度 + 0.75 * 事实准确度。
语义相似度好理解:用 embedding 模型把 answer 和 ground_truth 转成向量,算余弦相似度。
事实准确度更值得展开。考虑这组回答:
- answer:张伟是教研部负责大模型课程的同事
- ground_truth:张伟是教研部负责大数据方向的同事
两句一半对一半错——部门一致,方向不同。这种细粒度差异靠简单的相似度算不准,Ragas 的做法是让大模型把两句话各自拆成观点列表:
- answer 拆成:[“张伟是教研部的”, “张伟负责大模型课程”]
- ground_truth 拆成:[“张伟是教研部的”, “张伟负责大数据方向”]
然后逐条比对,分进三个桶:
- TP:answer 的观点在 ground_truth 中有依据(“张伟是教研部的”);
- FP:answer 的观点在 ground_truth 中找不到依据(“张伟负责大模型课程”);
- FN:ground_truth 的观点在 answer 中缺失(“张伟负责大数据方向”)。
最后按 F1 公式汇总:f1 = tp / (tp + 0.5 * (fp + fn))。本例是 1 / (1 + 0.5 * (1+1)) = 0.5,正好对应"半对半错"的直觉。
五、Context Recall 与 Context Precision:给检索环节打分
答案不对,问题可能出在生成,也可能出在检索。Ragas 用两个指标把检索环节单独拎出来评:
- Context Recall:标准答案里的观点,有多大比例能被召回的切片支撑。侧重有没有漏;
- Context Precision:召回切片中相关条目排名是否靠前、占比是否够高。侧重信噪比和排序。
计算这两个指标需要三列数据:question、contexts(召回的切片列表)、ground_truth。
# ch04/context_metrics.py(结构与 Test03_correctness.py 一致,仅换指标和数据)
from langchain_community.llms.tongyi import Tongyi
from datasets import Dataset
from ragas import evaluate
from ragas.metrics import context_recall, context_precision
data_samples = {
'question': ['张伟是哪个部门的?'] * 3,
'answer': [
'根据提供的信息,没有提到张伟所在的部门。',
'张伟是人事部门的',
'张伟是教研部的'
],
'ground_truth': ['张伟是教研部的成员'] * 3,
'contexts': [
['提供行政管理与协调支持,优化行政工作流程。', '绩效管理部 韩杉 人力资源'],
['李凯 教研部主任', '牛顿发现了万有引力'],
['牛顿发现了万有引力', '张伟 教研部工程师,他最近在负责课程研发'],
],
}
dataset = Dataset.from_dict(data_samples)
score = evaluate(
dataset=dataset,
metrics=[context_recall, context_precision],
llm=Tongyi(model='qwen-plus'))
print(score.to_pandas())
第三行数据的结果最有代表性:context_recall = 1.0,context_precision = 0.5。
- recall 是 1,因为 ground_truth 的观点"张伟是教研部的"在 contexts 里能找到依据(“张伟 教研部工程师……”),该召回的没漏;
- precision 是 0.5,因为两个切片里有一个(“牛顿发现了万有引力”)完全无关,信噪比只有一半。
recall 高、precision 低的组合,恰恰是最值得警惕的信号:正确答案就在召回结果里,但被无关内容稀释、淹没,模型很可能"视而不见"——这就是 RAG 里常说的 Lost in the Middle 现象。
两个指标的计算过程也简述一下:
- Context Recall:大模型把 ground_truth 拆成观点列表,逐条判断能否在 contexts 中找到依据,能找到的比例就是分数;
- Context Precision:按顺序给每个切片打相关性分(相关 1 分、不相关 0 分),然后累计算"前缀平均 precision",最后除以相关切片数。相关切片排名越靠前,分数越高。
六、拿到分数之后:对着指标开药方
评测的终点不是分数,是优化方向。三个指标分数低,对应三条不同的优化路线:
| 分数低的指标 | 说明问题出在哪 | 优化手段 |
|---|---|---|
| context recall | 检索阶段:该召回的没召回 | ① 补充知识库内容;② 换更强的 embedding 模型(能识别深层语义,“负责课程研发的是谁"也能召回"张伟是教研部的成员”);③ 用大模型做 query 改写,补全"教研部"这类残缺提问 |
| context precision | 检索阶段:召回了但排序差、噪音多 | 在 recall 的手段之上,检索链路加 rerank(重排序),把相关切片顶到前排 |
| answer correctness | recall/precision 都高但此指标低 | 问题在生成环节:优化 prompt、调低 temperature、换更强的模型,或做微调 |
这张表的价值在于把"RAG 效果不好"这个模糊抱怨,拆成了可执行的诊断流程:先看 recall,再看 precision,最后看 correctness,每一层都指向明确的改动点。
七、别忽略评测集本身的质量
框架和指标是工具,评测体系的地基是评测集。两条硬标准:
- 问题来自真实用户。从线上提问抽样、从客服工单里捞,而不是工程师拍脑袋想出来的。真实问题往往残缺、口语化(“教研部”“请假”),这恰恰是最需要覆盖的形态。
- 标准答案来自业务专家。技术人员能判断"检索有没有命中",但只有业务专家知道"这个答案在业务上对不对、全不全、合不合规"。专家把答案写进 ground_truth,自动化评测才能规模化复用专家的判断。
流程上坚持评测先行:先建评测集拿到基线分数,再做优化,优化后对比分数验证。没有基线的优化是盲改——你甚至说不出这次改动有没有把别的地方改坏(一处优化常常会引发跷跷板效应,某个指标涨了另一个跌了)。同时记住算法指标是手段,业务指标(比如用户满意度)才是目标,两层之间要对齐,别为了刷 context recall 而优化。
小结:RAG 应用的优化不能靠手感。这一章的路径是——先用"大模型当测试员"建立最朴素的自动化判断,再引入 Ragas 拿到三个量化指标:context recall 管"有没有漏"、context precision 管"噪音多不多、排序好不好"、answer correctness 管"最终答案准不准"。分数低在哪一层,就往哪一层开药:检索层补知识库、换 embedding、加 rerank;生成层改 prompt、换模型。最后别忘了,评测集的问题要来自真实用户,标准答案要来自业务专家——工具再好,地基是数据。
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)