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 生成的答案和标准答案的接近程度,需要两类数据:

  1. question:喂给 RAG 的问题;
  2. 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 模型把 answerground_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:召回切片中相关条目排名是否靠前、占比是否够高。侧重信噪比和排序

计算这两个指标需要三列数据:questioncontexts(召回的切片列表)、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.0context_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 correctnessrecall/precision 都高但此指标低问题在生成环节:优化 prompt、调低 temperature、换更强的模型,或做微调

这张表的价值在于把"RAG 效果不好"这个模糊抱怨,拆成了可执行的诊断流程:先看 recall,再看 precision,最后看 correctness,每一层都指向明确的改动点。

七、别忽略评测集本身的质量

框架和指标是工具,评测体系的地基是评测集。两条硬标准:

  1. 问题来自真实用户。从线上提问抽样、从客服工单里捞,而不是工程师拍脑袋想出来的。真实问题往往残缺、口语化(“教研部”“请假”),这恰恰是最需要覆盖的形态。
  2. 标准答案来自业务专家。技术人员能判断"检索有没有命中",但只有业务专家知道"这个答案在业务上对不对、全不全、合不合规"。专家把答案写进 ground_truth,自动化评测才能规模化复用专家的判断。

流程上坚持评测先行:先建评测集拿到基线分数,再做优化,优化后对比分数验证。没有基线的优化是盲改——你甚至说不出这次改动有没有把别的地方改坏(一处优化常常会引发跷跷板效应,某个指标涨了另一个跌了)。同时记住算法指标是手段,业务指标(比如用户满意度)才是目标,两层之间要对齐,别为了刷 context recall 而优化。

小结:RAG 应用的优化不能靠手感。这一章的路径是——先用"大模型当测试员"建立最朴素的自动化判断,再引入 Ragas 拿到三个量化指标:context recall 管"有没有漏"、context precision 管"噪音多不多、排序好不好"、answer correctness 管"最终答案准不准"。分数低在哪一层,就往哪一层开药:检索层补知识库、换 embedding、加 rerank;生成层改 prompt、换模型。最后别忘了,评测集的问题要来自真实用户,标准答案要来自业务专家——工具再好,地基是数据。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐