周五下午六点,上线前的最后一轮测试。老李坐在小王工位旁边,看他敲完最后一条测试用例。
“行了,”老李拧开保温杯,语气里带着即将周末的松弛,“我随机抽了二十个问题,回答都挺靠谱的。系统没问题了,下周一上线。”
小王没接话,而是把屏幕往老李那边转了转:“老李,你看看这条。”
屏幕上一个问答记录赫然在目——用户问“公司现任 CEO 是谁”,系统回答“公司现任 CEO 是张伟先生,他于 2019 年接任”。老李的脸立刻僵了。
“张伟是三年前的 CEO!现在的 CEO 是刘总!”老李把保温杯往桌上一顿,“这怎么回事?我们知识库里不是有最新的组织架构文件吗?”
“文件有,也检索到了。但大模型读完之后,把文件里提到的‘张伟曾任 CEO’和‘刘洋现任 CEO’搞混了,自己编了一个看似合理的回答。”
小王叹了口气:“这就是‘看着好’和‘真的好’的区别。你抽的二十条测试,恰好没遇到这种踩坑的问题。但如果就这样上线,每条胡编乱造的回答都是一个潜在的线上事故。”
老李沉默了几秒:“那你有什么办法?总不能所有回答都人工看一遍吧,两千条文档,排列组合出几万种问题,我们团队就五个人。”
“不用人工,让评测框架来干。”小王打开一个新文件,“它叫 RAGAS——专门给 RAG 系统打分的工具。”
三个裁判,各管一摊
“评测 RAG 系统,就像给高考作文评分,”小王在白板上画了三个小人,“你不能只找一个语文老师看一遍就说多少分。得有人看内容是不是跑题,有人看论据是不是编的,有人看引用的材料对不对。RAGAS 正好有三个核心指标,对应这三个裁判。”
第一个裁判:忠实度。 “看的是 AI 有没有胡编乱造。回答里的每一个事实,能不能在检索到的文档里找到依据?如果找不到,就是在编。”
老李点头:“刚才 CEO 那个就是忠实度为零。”
第二个裁判:答案相关性。 “看的是有没有答非所问。你问‘年会几号’,它回答‘年会很热闹’,热闹跟几号有关系吗?没有。相关性就打低分。”
第三个裁判:上下文召回率。 “看的是检索环节有没有把需要的文档找出来。如果正确答案藏在第 5 篇文档里,而检索只取了前 3 篇,那召回率就丢分了。”
图:RAGAS 三个核心指标——召回率衡量检索,忠实度和相关性衡量生成
“这三个分数一跑,RAG 系统哪里掉链子,一目了然。”小王敲了敲屏幕,“比‘我看着挺好’靠谱多了。”
老李哼了一声:“那你这三个裁判,谁来打分?不会又是人工吧?”
“当然是自动的。RAGAS 底层也是用大模型来打分——但它用的是更强的模型,比如 GPT-4,来评估你的 RAG 输出。就像请一个专家来批改作业,比你自己的系统更客观。”
RAGAS 实战:十行代码见分晓
“别光说,”老李催促道,“把你刚才说的跑一遍。”
小王打开终端,调出了他们的评测数据集:
# 用 RAGAS 评测 RAG 系统的三个核心指标
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_recall
from datasets import Dataset
# 准备评测样本:每个样本包含问题、回答、检索到的上下文
eval_data = Dataset.from_dict({
"question": [
"公司现任CEO是谁?",
"年会是什么时候办的?",
"数据库连接池怎么配?"
],
"answer": [
"现任CEO是张伟,2019年接任。", # 这个会被打低分
"2025年11月8日在杭州举办。",
"max_connections建议设置为200。"
],
"contexts": [
["张伟曾任CEO,刘洋2023年接任CEO。"], # 上下文有正确答案
["2025年会于11月8日在杭州XX酒店举行。"],
["max_connections参数建议值200。"]
],
})
result = evaluate(eval_data, metrics=[faithfulness, answer_relevancy, context_recall])
print(result)
# 输出:
# faithfulness: 0.67 ← 第一条拉低了
# answer_relevancy: 0.91
# context_recall: 0.89老李盯着那个 0.67 的忠实度分数:“CEO 那条回答把整组分数拉下来了。如果我只抽另外两条,忠实度可能接近 1.0——这就是你说的‘抽二十条看不出问题’。”
“对。二十条可能碰巧全简单,也可能正好绕开了所有坑。RAGAS 的价值就是帮你把坑找出来,而且不需要你一条一条看。”
评测数据集:不是随便拉几个问题就行
老李拧开保温杯喝了一口,若有所思:“那你的评测数据集是怎么来的?总不能从生产日志里随便扒几条吧。”
“这个问题问到点上了。”小王在白板上画了三个圈,“评测数据集的质量,直接决定了评测结果有没有参考价值。搭数据集的时候要注意三件事——”
“覆盖边界案例。简单的‘年会是哪天’没问题,但你得放一些陷阱题进去——‘上个月的 CEO 是谁’、‘数据库迁移失败的根因是什么’、‘合同编号 XYZ-2024 的内容’。这些才是真实的用户查询,最能暴露系统的弱点。”
“标注上下文依据。每一条评测数据,你得知道‘正确答案在哪篇文档里’。不然你怎么判断上下文召回率?就像考试不公布标准答案,分数就没法算。”
“持续更新。知识库更新了,评测集也要跟着更新。不能拿去年的问题测今年的系统——那个叫刻舟求剑。”
# 一个结构化的评测样本
eval_sample = {
"question": "数据库迁移的POC结果是什么?",
"answer": "POC结果显示Postgres在写入性能上超过MySQL约30%。",
"contexts": [ # 检索到的文档片段
"POC测试:Postgres写入TPS 12000,MySQL写入TPS 9200。结论:迁移可行。",
"迁移风险:老赵担心数据一致性,需进一步验证。"
],
"ground_truth_doc": "doc_migration_poc_v2.pdf", # 标注标准答案来源
}“看到没有,”小王指着最后一行的 ground_truth_doc,“有了这个,评测框架才知道‘正确文档’是哪篇,才能算召回率。没有这行,你就只能靠猜。”
老李往小本本上记了几笔,头也不抬:“那这个评测,上线以后还要跑吗?”
把评测塞进 CI/CD
“当然要跑。”小王打开了项目部署流水线的配置页面,“你想想,我们每改一次分块策略、每换一次嵌入模型、每调整一次 top_k,都在改变 RAG 系统的行为。如果不跑一遍评测就上线,等于闭着眼睛踩油门。”
他在屏幕上画了一个流程:
图:RAG 系统的评测门禁——指标不达标,自动阻断上线
“每次提交代码,自动跑一组评测用例。忠实度低于 0.85,直接阻断部署,发告警给我。这个门禁不需要人工,跑完不到五分钟。”
老李盯着这个流程,沉默了好一会儿。然后他拧开保温杯,枸杞的香气飘了出来。
“行。评测数据集你来搭,RAGAS 集成到 CI 里。下周上线前,把忠实度给我拉到 0.9 以上。”他站起来,走了两步又转过身,“不过你小子是不是早就准备好了这套东西,就等着我拍板?”
小王嘿嘿一笑:“我上周就搭好了,就是怕你说‘不就跑个分吗’然后否掉。”
老李摆摆手,保温杯跟着晃了晃:“下不为例啊。还有——那个忠实度具体是怎么算的?大模型给大模型打分,会不会也有幻觉?你给我看看那个评分 prompt。”
“行,我把评分链路的日志调出来。”小王拉过椅子,“你看,RAGAS 内部是把回答拆成若干个独立陈述,然后逐句去跟上下文对账——就像会计做审计,一笔一笔核。”
窗外华灯初上,办公室只剩他们俩的屏幕亮着。老李的保温杯又续了一轮热水,枸杞在杯底打着转,像是评测框架里那些来回对账的裁判,一刻不停地在检查 AI 交上来的每一份答卷。

