总目录
第 4 章 Faithfulness、Context Recall...RAGAS 核心指标怎么算
第 5 章 生成侧指标与指标选型:别再乱用 Faithfulness
第 8 章 让评估贯穿研发:实验追踪与 @experiment/Dataset 工作流
第 9 章 从笔记本到 CI:把 RAGAS 做成生产级流水线
第 10 章 内置不够用?手搓 RAGAS 自定义指标(含 SQL/MultiHop)
第 11 章 RAGAS 不是唯一:6 大评估工具横向对比(功能篇)
第 12 章 怎么选?不同团队画像的选型决策矩阵
第 13 章 团队落地 RAGAS 的五阶段路线图(可直接抄作业)
在上一篇文章《从笔记本到 CI:把 RAGAS 做成生产级流水线》中,我们把评估做成了团队流水线的一环。但当内置指标覆盖不了你的业务语义时怎么办?接下来手搓 RAGAS 自定义指标(含 SQL / MultiHop 实战)。
读者对象 :已学完前 8 章,掌握
faithfulness、answer_relevancy、context_precision/recall等内置指标,并能在EvaluationDataset上跑评估的工程师。本章目标:当内置指标无法覆盖你的业务语义时,学会用 RAGAS 的扩展点做定制化评估------从"让 LLM 当裁判"到"自己写一个指标类"。
本章约定(先读)
- 【事实】 标注的是已验证的 API 行为(基于
ragas==0.4.3实测/官方文档)。 - 【观点】 标注的是作者给出的最佳实践建议,可按团队情况调整。
- 版本表述谨慎 :截至
ragas==0.4.3,官方迁移指南对evaluate()的"移除版本"表述并不一致(部分文档称 removed in v1.0 ,迁移指南仅称 a future release )。【事实】evaluate()在 v0.4.3 会触发DeprecationWarning;【观点】 其确切移除版本请以你本地实测为准,不要凭文档猜测写死。 - 【事实】
ragas.metrics中的小写单例(如faithfulness、answer_relevancy)官方明确在 v1.0 移除 ;【观点】 新代码一律改用ragas.metrics.collections中的类式 API(Faithfulness(...)等)。 - 官方仓库 :主仓库已迁至
vibrantlabsai/ragas(https://github.com/vibrantlabsai/ragas);旧镜像explodinggradients/ragas(https://github.com/explodinggradients/ragas)内容一致,可作为旧路径引用。 - 【事实】 模型注入现行写法:
from ragas.llms import llm_factory; llm = llm_factory("gpt-4o-mini", client=OpenAI())。
9.1 内置指标不够用时怎么办:扩展点总览
内置指标覆盖的是"通用质量维度":忠实度、相关性、上下文精度/召回等。【观点】 一旦你的业务出现下面这些信号,就该考虑超越内置指标:
- 需要判断回答是否合规 / 礼貌 / 安全(业务专属语义,无法用忠实度表达);
- 需要按**评分量规(rubric)**做 1--5 分制的人工式打分;
- 答案是翻译、摘要、抽取式事实,更适合 BLEU / ROUGE / Exact Match 这类确定性指标;
- 你的系统是 Text-to-SQL / 代码生成,要检查 SQL 是否可执行、结果是否正确;
- 测试集需要 多跳(multi-hop) 问题来压测检索与推理;
- 以上都不够,要从零写一个指标。
RAGAS 为这些场景提供了清晰的扩展点,下表按"是否需要 LLM"和"是否内置"归类:
| 扩展点 | 是否需要 LLM | 是否内置 | 导入位置(v0.4.3) | 典型用途 |
|---|---|---|---|---|
AspectCritic |
是 | 是 | ragas.metrics |
自定义二元维度裁判(礼貌/合规/有害) |
RubricsScore |
是 | 是 | ragas.metrics |
按 rubric 做离散分制打分 |
BleuScore / RougeScore / ExactMatch |
否 | 是 | ragas.metrics.collections |
翻译/摘要/抽取式答案的确定性评估 |
| SQL / 领域指标 | 否或视情况 | 否(需自写) | 自定义类 | 检查 SQL 可执行性/正确性 |
TestsetGenerator + MultiHop 合成器 |
是 | 是 | ragas.testset |
合成多跳测试集 |
自定义 Metric 子类 |
视情况 | 否(需自写) | ragas.metrics.base |
任何上面都覆盖不了的需求 |
【重要事实】关于 API 分代:RAGAS 现在同时有两套指标 API 并存------
- Legacy(V1) :
ragas.metrics下的小写单例与AspectCritic、RubricsScore等类,调用方式多为scorer.single_turn_ascore(sample),返回float。 - Modern(V2 / collections) :
ragas.metrics.collections下的类式指标(如Faithfulness、BleuScore),调用方式多为result = await scorer.ascore(user_input=..., response=..., ...),返回MetricResult(含.value与.reason)。
【事实】AspectCritic 与 RubricsScore 截至 v0.4.3 仍住在 ragas.metrics,尚未迁移到 ragas.metrics.collections (迁移矩阵中 AspectCritic 标注为 removed / use @discrete_metric() ,即 collections 里暂无对应类)。因此本章 9.2、9.3 必须用 from ragas.metrics import ...,不要误写成从 collections 导入。其余内置指标(含非 LLM 指标)则推荐 collections 类式 API。
参考:RAGAS 迁移指南 https://docs.ragas.io/en/stable/howtos/migrations/migrate_from_v03_to_v04/ ;指标 API 总览(DeepWiki,旧镜像)https://deepwiki.com/explodinggradients/ragas/12.2-metrics-api
小结(9.1) :先用对扩展点分类;新代码优先 collections 类式 API,但 AspectCritic/RubricsScore 仍在 ragas.metrics。
动手检查点(9.1) :确认你本地 ragas.__version__ == "0.4.3";在 Python 里分别执行 from ragas.metrics import AspectCritic 与 from ragas.metrics.collections import Faithfulness,验证两者都能导入成功(前者 legacy、后者 modern)。
9.2 AspectCritic:按自定义维度让 LLM 当裁判批评
【事实】 AspectCritic 用于按你用自然语言定义的"维度(aspect)"对回答做二元裁判 (通过=1 / 不通过=0)。它本质是多次 LLM 调用 + 多数投票:对每个样本,critic 会用 strictness(默认 3,建议 2--4)次独立判断,再取多数票得到 0/1。
【重要·来自审稿 REVISE】导入路径务必写成:
python
from ragas.metrics import (
AspectCritic,
) # ✅ 正确(v0.4.3)
# from ragas.metrics.
# collections import AspectCritic # ❌
# 错误:collections 未导出该类,尚未迁移
也就是说,
ragas.metrics.collections模块在 v0.4.3 没有AspectCritic,不要从 collections 导入。
9.2.1 它适合什么
- 合规:回答是否泄露了隐私/内部信息;
- 礼貌:回答语气是否礼貌、专业;
- 安全:是否含有害/误导内容;
- 任何"能用一句话说清标准"的维度。
9.2.2 完整可运行示例(依赖:ragas==0.4.3、openai)
python
# 依赖与版本:
# pip install "ragas==0.4.3" openai
# 运行前设置环境变量:export OPENAI_API_
# KEY=sk-...
from openai import OpenAI
from ragas.llms import llm_factory
from ragas.metrics import (
AspectCritic,
) # ← legacy 位置,非 collections
from ragas.dataset_schema import (
SingleTurnSample,
)
# 模型注入(v0.4.3 现行写法)
evaluator_llm = llm_factory(
"gpt-4o-mini", client=OpenAI()
)
# 例 1:礼貌度(自定义维度)
politeness = AspectCritic(
name="politeness",
definition=(
"回答是否礼貌、"
"专业,"
"且没有傲慢或冒犯性措辞?"
"符合返回 1,"
"否则返回 0。"
),
llm=evaluator_llm,
# 多数投票次数,建议 2--4
strictness=3,
)
# 例 2:合规(是否泄露个人隐私)
no_pii = AspectCritic(
name="no_pii_leak",
definition=(
"回答是否包含任何"
"员工的手机号、"
"身份证号、"
"家庭住址等个人隐私信息?"
"包含返回 0,"
"不包含返回 1。"
),
llm=evaluator_llm,
)
sample = SingleTurnSample(
user_input=(
"你们公司负责这笔"
"退款的同事联系方"
"式能给我吗?"
),
response=(
"抱歉,"
"出于隐私保护我无法提供同事"
"的个人联系方式,"
"但您可以拨打客服"
"热线 400-xxx "
"处理退款。"
),
reference="",
)
# 【事实】legacy 指标返回 float(0.
# 0 或 1.0),不是 MetricResult
score = (
await politeness.single_turn_ascore(
sample
)
)
print("politeness:", score) # 例如 1.0
score_pii = (
await no_pii.single_turn_ascore(
sample
)
)
print(
"no_pii_leak:", score_pii
) # 例如 1.0(未泄露)
注意对比 :如果你用 collections 里的现代指标(如 Faithfulness),调用会返回 MetricResult,要取 .value;而 AspectCritic 作为 legacy 类,返回的是裸 float。【观点】 这是"它尚未迁移"的直接体现,写评估流水线时要区分处理。
小结(9.2) :AspectCritic 用自然语言定义维度、LLM 多数投票给 0/1;必须从 ragas.metrics 导入 ;返回 float。
动手检查点(9.2) :给你的客服场景定义一个 AspectCritic(name="tone_friendly", ...),对 5 条真实回答打分,观察 strictness 取 2 与取 5 时分数是否更稳定(投票次数越多通常越稳,但成本更高)。
9.3 Rubrics:按评分量规(rubric)打分
当"通过/不通过"太粗、你想要 1--N 分制 的细粒度评估时,用 RubricsScore。【事实】 你提供一个 rubrics 字典,键为 score{k}_description(k 从 1 到 N),值为该分数对应的详细描述;LLM 依据描述把回答归到某一档,返回离散整数。
【事实】 与 AspectCritic 一样,RubricsScore 在 v0.4.3 也住在 ragas.metrics(legacy),调用 single_turn_ascore 返回整数。
9.3.1 完整可运行示例(依赖同上)
python
from openai import OpenAI
from ragas.llms import llm_factory
from ragas.metrics import RubricsScore # ← 同样在 ragas.metrics
from ragas.dataset_schema import SingleTurnSample
evaluator_llm = llm_factory("gpt-4o-mini", client=OpenAI())
# 定义一个"回答完整性"量规(1--5 分)
rubrics = {
"score1_description": "回答完全错误,且未触及参考内容的任何要点。",
"score2_description": "回答部分正确,但存在重大错误或明显遗漏,影响对参考内容的理解。",
"score3_description": "回答大体准确,但缺少清晰度、完整性或若干次要细节。",
"score4_description": "回答准确清晰,仅有轻微遗漏或细微不准确。",
"score5_description": "回答完全准确、清晰,且无遗漏地覆盖了参考内容。",
}
completeness = RubricsScore(
name="answer_completeness",
llm=evaluator_llm,
rubrics=rubrics,
)
sample = SingleTurnSample(
user_input="地球绕太阳转吗?",
response="地球是平的,不绕太阳转。",
reference="科学共识确认地球是球形行星,绕太阳公转,证据来自天文观测、卫星影像与引力测量。",
)
score = await completeness.single_turn_ascore(sample)
print("completeness score:", score) # 例如 1(完全错误)
【观点】 rubric 的描述要彼此互斥且覆盖连续梯度(1 分最差、N 分最好),否则 LLM 容易在边界档位犹豫、分数抖动。写 rubric 时建议拉一位领域专家过一遍措辞。
进阶 :若每条样本需要各自的 rubric(而非全班同一把尺子),可用 InstanceRubrics(按样本给 rubric),见 https://docs.ragas.io/en/v0.4.3/concepts/metrics/available_metrics/general_purpose/ 。
小结(9.3) :RubricsScore 用 score{k}_description 字典定义分制,返回离散整数;适合细粒度、可解释打分;仍在 ragas.metrics。
动手检查点(9.3):为"答案专业性"写一套 1--5 分量规,对 10 条回答打分,统计分布;再让另一位同事独立用同一 rubric 手工打分,计算你们与 LLM 评分的一致率(简单做法:皮尔逊/斯皮尔曼相关)。
9.4 非 LLM 指标:BLEU / ROUGE / Exact Match
有些任务不该 用 LLM 当裁判。【观点】 当答案有"标准文本"或"标准结构"时,确定性指标更便宜、更可复现、且无评委偏见:
- 翻译 / 摘要 :
BleuScore、RougeScore比较生成文本与参考文本的 n-gram 重叠; - 抽取式事实 / 代码 / SQL 片段 :
ExactMatch判断是否逐字一致(通常先归一化大小写、去空白)。
【事实】 这些指标在 ragas.metrics.collections 中,且不需要 LLM ;调用返回 MetricResult,通过 .value 取分、.reason 取解释(此处 .reason 通常为空或简短说明)。
9.4.1 完整可运行示例(依赖:ragas==0.4.3)
python
from ragas.metrics.collections import (
BleuScore,
RougeScore,
ExactMatch,
)
# 非 LLM 指标无需注入 llm
bleu = BleuScore()
rouge = RougeScore()
exact = ExactMatch()
# 例:摘要评估
summary_resp = (
"巴黎是法国的首都,也是最大城市。"
)
summary_ref = (
"巴黎为法国首都及最大城市。"
)
r_bleu = await bleu.ascore(
response=summary_resp,
reference=summary_ref,
)
r_rouge = await rouge.ascore(
response=summary_resp,
reference=summary_ref,
)
print(
"BLEU:", r_bleu.value
) # float ∈ [0, 1]
print(
"ROUGE:", r_rouge.value
) # float ∈ [0, 1]
# 例:抽取式事实(Exact Match,
# 先归一化更易通过)
fact_resp = (
"The capital of France is Paris."
)
fact_ref = (
"the capital of france is paris."
)
r_em = await exact.ascore(
response=fact_resp,
reference=fact_ref,
)
print(
"ExactMatch:",
r_em.value,
# 1.0(取决于内部归一化;
# 未归一化时为 0)
)
9.4.2 怎么解读(避免误用)
- 【观点】 BLEU/ROUGE 高分 ≠ 语义正确。它们只衡量 n-gram 重叠,对同义改写极不友好;在开放生成任务上单独看会误导。
- 【事实】 它们对语言/形态敏感:中文不经过分词预处理时,字符级 n-gram 与英文词级不可直接横比。
- 【观点】 用法建议------翻译/摘要用 BLEU/ROUGE 做回归监控 (和上周比,别和"正确率"画等号);抽取式答案、标识符、SQL 片段用
ExactMatch;开放问答仍以 LLM 裁判类指标为主。
小结(9.4) :非 LLM 指标便宜可复现,住在 ragas.metrics.collections、返回 MetricResult;适合有标准文本/结构的任务,但别把它当成"语义正确"的代理。
动手检查点(9.4) :对同一批摘要,分别计算 BLEU 与 answer_relevancy(LLM 类),画散点图------你会观察到两者相关性通常不高,这正是"确定性指标不可替代 LLM 裁判"的直观证据。
9.5 SQL 类指标(自定义实战:可执行 / 正确)
Text-to-SQL、NL2SQL 或"数据问答"系统有一个特有需求:生成的 SQL 到底能不能跑、跑出来的结果对不对 。【事实】 RAGAS 在 v0.4.3 没有 内置 SQL 指标,必须用 9.7 的"自定义 Metric"机制自己写------这恰恰是最好的练手场景。
下面给出两个确定性(非 LLM) SQL 指标,以及一个可选的 LLM 语义等价裁判。
9.5.1 指标一:SQL 可执行性(是否能在目标库跑通)
python
# 依赖与版本:pip install "ragas==0.4.
# 3" openai
# 本指标为非 LLM 指标,
# 只需 ragas + 一个 sqlite(或你的业务库
# )连接
from dataclasses import dataclass, field
import typing as t
import sqlite3
from ragas.metrics.base import (
MetricType,
SingleTurnMetric,
)
from ragas.dataset_schema import (
SingleTurnSample,
)
from ragas.callbacks import Callbacks
@dataclass
class SQLExecutableMetric(
SingleTurnMetric
):
(
"""检查生成的 SQL """
"""能否在目标数据库"""
"""上成功执行。"""
"""可执行=1.0,否则=0."""
"""0。"""
)
name: str = "sql_executable"
_required_columns: t.Dict[
MetricType, t.Set[str]
] = field(
default_factory=lambda: {
MetricType.SINGLE_TURN: {
"response"
}
}
)
dsn: str = (
# sqlite 连接串;
# 生产可换成你的库 DSN
":memory:"
)
async def _single_turn_ascore(
self,
sample: SingleTurnSample,
callbacks: Callbacks = None,
) -> float:
sql = (
sample.response or ""
).strip()
if not sql:
return 0.0
try:
conn = sqlite3.connect(
self.dsn
)
conn.execute(
sql
# 仅编译/执行,不关心返
# 回行数
)
conn.close()
return 1.0
except Exception:
return 0.0
9.5.2 指标二:SQL 结果一致性(生成 SQL 与参考 SQL 结果是否相同)
仅"能跑"不够------还要"跑出对的答案"。最稳妥的确定性做法是:在同一份数据库上分别执行生成 SQL 与参考 SQL,比较结果集。
python
@dataclass
class SQLResultMatchMetric(
SingleTurnMetric
):
(
"""比较生成 SQL """
"""与参考 SQL """
"""的执行结果集是否一致("""
"""集合语义)。"""
"""一致=1.0。"""
)
name: str = "sql_result_match"
_required_columns: t.Dict[
MetricType, t.Set[str]
] = field(
default_factory=lambda: {
MetricType.SINGLE_TURN: {
"response",
"reference",
}
}
)
dsn: str = ":memory:"
async def _single_turn_ascore(
self,
sample: SingleTurnSample,
callbacks: Callbacks = None,
) -> float:
gen_sql = (
sample.response or ""
).strip()
ref_sql = (
sample.reference or ""
).strip()
try:
conn = sqlite3.connect(
self.dsn
)
gen_rows = conn.execute(
gen_sql
).fetchall()
ref_rows = conn.execute(
ref_sql
).fetchall()
conn.close()
# 用集合语义比较(忽略行的顺
# 序)
return (
1.0
if set(
map(tuple, gen_rows)
)
== set(
map(tuple, ref_rows)
)
else 0.0
)
except Exception:
return 0.0
9.5.3 指标三(可选):LLM 语义等价裁判
当参考 SQL 不存在、或两条 SQL 写法不同但语义等价时,可用 LLM 判断"是否回答同一问题"。这里 AspectCritic 的思路可直接复用:
python
from ragas.metrics import AspectCritic
sql_equiv = AspectCritic(
name="sql_semantic_equivalent",
definition=(
"给定用户问题与两条 SQL(response 与 reference),"
"若两条 SQL 在语义上等价(查询同一意图、返回等价结果集),返回 1;"
"否则返回 0。仅因写法/别名不同但语义一致也应判为 1。"
),
llm=evaluator_llm, # 复用 9.2 的 llm_factory 实例
)
9.5.4 跑起来
python
sample = SingleTurnSample(
user_input=(
"查一下订单表里的" "订单总数"
),
response=(
"SELECT COUNT(*) "
"FROM orders;"
), # 生成的 SQL
reference=(
"SELECT COUNT(1) "
"FROM orders;"
), # 参考 SQL(语义等价)
)
exe = SQLExecutableMetric()
match = SQLResultMatchMetric()
print(
"executable:",
await exe._single_turn_ascore(
sample
),
) # 1.0
print(
"result_match:",
await match._single_turn_ascore(
sample
),
) # 1.0(结果集相同)
【观点】 真实项目中推荐"可执行性 + 结果一致性"作为门禁(gate) (CI 里 0 分直接挂红灯),把 LLM 语义裁判作为"参考 SQL 缺失时"的兜底。别忘了 SQL 注入与权限:【事实】 上面的示例用 :memory: 内存库演示,生产环境务必指向只读副本/影子库,绝不能拿线上主库做评估执行。
小结(9.5):SQL 指标 RAGAS 没内置,需自写;最稳的是"可执行 + 结果集一致"双指标(确定性),语义等价再上 LLM。
动手检查点(9.5) :造 20 条 (生成SQL, 参考SQL) 样本,其中混入 5 条语法错误、5 条语法对但结果错;分别跑 SQLExecutableMetric 与 SQLResultMatchMetric,确认两类错误都能被区分捕获。
9.6 MultiHop 查询合成与高级测试集
普通测试集多是"单跳"问题(一个文档就能答)。【观点】 要压测检索与多步推理,必须刻意合成**多跳(multi-hop)**问题------需要跨多个文档/节点拼接信息才能回答。
【事实】 RAGAS 的 ragas.testset 用**知识图谱(KnowledgeGraph)**驱动查询合成,并提供两类多跳合成器:
MultiHopAbstractQuerySynthesizer:跨多个主题相似节点的抽象问题(需推理综合);MultiHopSpecificQuerySynthesizer:两个实体重叠 节点间的具体问题(需拼接多段上下文)。
参考(DeepWiki,旧镜像):https://deepwiki.com/explodinggradients/ragas/7.3-multi-hop-synthesizers
9.6.1 完整可运行示例(依赖:ragas==0.4.3、openai、langchain-core)
python
# 依赖与版本:
# pip install "ragas==0.4.3" openai langchain-core
# 运行前设置环境变量:export OPENAI_API_KEY=sk-...
from openai import OpenAI
from ragas.llms import llm_factory
from ragas.embeddings.base import embedding_factory # 现代工厂;无参时按环境变量自动选择
from ragas.testset import TestsetGenerator
from ragas.testset.synthesizers import (
SingleHopSpecificQuerySynthesizer,
MultiHopAbstractQuerySynthesizer,
MultiHopSpecificQuerySynthesizer,
)
from langchain_core.documents import Document
# 1) 模型注入(v0.4.3 现行写法)
llm = llm_factory("gpt-4o-mini", client=OpenAI())
embeddings = embedding_factory() # 自动读取 OPENAI_API_KEY,使用默认 openai 嵌入
# 2) 准备语料(这里用纯文本构造,无需外部 loader)
docs = [
Document(page_content="埃菲尔铁塔位于法国巴黎,由古斯塔夫·埃菲尔的公司设计,1889 年建成。"),
Document(page_content="巴黎是法国首都,也是该国人口最多的城市,位于塞纳河畔。"),
Document(page_content="古斯塔夫·埃菲尔还是自由女神像内部结构的设计者,该雕像位于美国纽约。"),
Document(page_content="塞纳河横穿巴黎,沿岸有包括埃菲尔铁塔在内的多处地标。"),
]
# 3) 构造生成器
generator = TestsetGenerator(llm=llm, embedding_model=embeddings)
# 4) 刻意偏向多跳:提高两类 MultiHop 合成器的权重
query_distribution = [
(SingleHopSpecificQuerySynthesizer(llm=llm), 0.2), # 单跳 20%
(MultiHopAbstractQuerySynthesizer(llm=llm), 0.4), # 多跳抽象 40%
(MultiHopSpecificQuerySynthesizer(llm=llm), 0.4), # 多跳具体 40%
]
# 5) 生成测试集(内部会自动建知识图谱并应用 transform)
testset = generator.generate_with_langchain_docs(
documents=docs,
testset_size=12,
query_distribution=query_distribution,
)
# 6) 查看并筛选多跳样本
df = testset.to_pandas()
print(df["synthesizer_name"].value_counts()) # 应能看到 multi_hop_* 占多数
multihop = df[df["synthesizer_name"].str.contains("multi_hop")]
print(multihop[["user_input", "reference", "synthesizer_name"]].head())
关键字段 :生成的 testset 每行含 user_input(合成问题)、reference_contexts(理想检索上下文)、reference(理想答案)、synthesizer_name(合成器类型,用来确认多跳占比)。
【观点】 多跳样本天然更难,适合作为"进阶测试集"单独跟踪;【事实】 合成问题往往是对源文档语句的转述,记得补充少量人工编写的边界样本(反事实、跨语言、长尾实体)以避免评测集偏见。
小结(9.6) :用 TestsetGenerator + MultiHop* 合成器、通过 query_distribution 权重刻意制造多跳问题;结果用 synthesizer_name 校验分布。
动手检查点(9.6) :把 query_distribution 改成全单跳(权重 1.0 给 SingleHopSpecificQuerySynthesizer)与全多跳两种极端,各生成 10 条,分别喂给你的 RAG,对比 context_recall------多跳集上的召回通常明显更低,这正说明它需要被单独优化。
9.7 自定义 Metric 编写实战(本节为本次修订补全,原稿空缺)
前面 SQL 指标已经"半只脚"踏进自定义指标。本节从零 走一遍:实现一个简单、确定性的自定义指标,并说明怎么升级成 LLM 版。我们会写一个 KeywordCoverageMetric:检查回答是否覆盖了所有"必填关键词",覆盖比例作为分数(0--1)。
为什么选它:确定性、无 LLM、逻辑一目了然,最适合作为"第一个自定义指标";之后你只需把打分内核换成 LLM 调用,就能得到业务裁判指标。
9.7.1 编写指标类
python
# 依赖与版本:pip install "ragas==0.4.3" openai
from dataclasses import dataclass, field
import typing as t
from ragas.metrics.base import MetricType, SingleTurnMetric
from ragas.dataset_schema import SingleTurnSample, EvaluationDataset
from ragas.callbacks import Callbacks
@dataclass
class KeywordCoverageMetric(SingleTurnMetric):
"""【实战】检查回答是否覆盖全部必填关键词;覆盖比例 = 分数。"""
name: str = "keyword_coverage"
# 自定义参数:必填关键词列表(由使用方传入)
required_keywords: t.List[str] = field(default_factory=list)
# 【事实】必须声明该指标需要哪些字段;缺失时 RAGAS 会报错提示
_required_columns: t.Dict[MetricType, t.Set[str]] = field(
default_factory=lambda: {MetricType.SINGLE_TURN: {"response"}}
)
async def _single_turn_ascore(self, sample: SingleTurnSample, callbacks: Callbacks = None) -> float:
if not self.required_keywords:
return 1.0
text = (sample.response or "").lower()
hit = sum(1 for kw in self.required_keywords if kw.lower() in text)
return hit / len(self.required_keywords)
要点(【事实】 来自官方"Write your own Metrics":https://docs.ragas.io/en/v0.3.8/howtos/customizations/metrics/_write_your_own_metric/ ):
- 继承
SingleTurnMetric(单轮)或MultiTurnMetric(多轮);需要 LLM 就再混入MetricWithLLM。 - 必须定义
_required_columns,告诉框架样本里缺了哪些字段。 - 实现
_single_turn_ascore(sample) -> float作为打分内核。
9.7.2 在样本上跑
python
samples = [
SingleTurnSample(
user_input="埃菲尔铁塔在哪?",
response=(
"埃菲尔铁塔位于法"
"国巴黎。"
), # 覆盖 巴黎/铁塔
),
SingleTurnSample(
user_input="埃菲尔铁塔在哪?",
response=(
"这是一座著名的铁" "塔。"
), # 只覆盖 铁塔
),
]
metric = KeywordCoverageMetric(
required_keywords=["巴黎", "铁塔"]
)
for s in samples:
print(
await metric.single_turn_ascore(
s
)
)
# 第 1 条 ≈ 1.0,第 2 条 = 0.5
# 也可以包装成 EvaluationDataset 批量使
# 用
dataset = EvaluationDataset(
samples=samples
)
# 【观点】生产评估推荐走 collections 类
# 式 / experiment 流水线,
# 避免触发 evaluate()
# 的 DeprecationWarning
9.7.3 升级成 LLM 版(自定义业务裁判)
把打分内核换成 LLM 调用即可------这正是 AspectCritic 的内部思路。骨架:
python
from ragas.metrics.base import (
MetricWithLLM,
SingleTurnMetric,
)
@dataclass
class ComplianceJudgeMetric(
MetricWithLLM, SingleTurnMetric
):
name: str = "compliance_judge"
_required_columns: t.Dict[
MetricType, t.Set[str]
] = field(
default_factory=lambda: {
MetricType.SINGLE_TURN: {
"response"
}
}
)
async def _single_turn_ascore(
self,
sample: SingleTurnSample,
callbacks: Callbacks = None,
) -> float:
# 用 self.llm(由 MetricWithLLM
# 提供,经 llm_factory 注入)
# 调用评审提示
# 返回 0.0 / 1.
# 0 或其它归一化分数
...
【观点】 写 LLM 指标时务必控制方差:同一输入多次评分可能浮动 ±5%,建议聚合 ≥30 条样本再下结论,并对关键维度加 strictness 式多数投票。
9.7.4 小结
- 自定义指标 = 继承
SingleTurnMetric/MultiTurnMetric(+MetricWithLLM)+ 声明_required_columns+ 实现_single_turn_ascore。 - 确定性指标(如关键词覆盖、SQL 可执行性)不依赖 LLM,便宜可复现,优先用于门禁。
- LLM 指标灵活但需关注成本与方差。
9.7.5 动手实验
- 把
KeywordCoverageMetric改成大小写/标点归一化后再匹配,验证对"Paris."与"paris"也能命中; - 给
KeywordCoverageMetric增加partial_credit=False开关:任一词缺失即判 0 分(硬门禁); - 进阶:参考 9.5,把打分内核换成"在 sqlite 上执行回答里出现的 SQL 并校验非空",做一个
AnswerContainsRunnableSQLMetric。
本章小结
- 扩展点地图 :
AspectCritic(自定义二元裁判)、RubricsScore(rubric 分制)、BleuScore/RougeScore/ExactMatch(确定性)、SQL/领域指标(自写)、TestsetGenerator多跳合成、自定义Metric类。 - 【事实】导入分代 :
AspectCritic、RubricsScore在 v0.4.3 仍位于ragas.metrics,不要 从ragas.metrics.collections导入(后者未导出AspectCritic);其余内置指标推荐 collections 类式 API,返回MetricResult(.value/.reason)。 - 【事实】
ragas.metrics小写单例官方明确 v1.0 移除;evaluate()在 v0.4.3 触发DeprecationWarning,确切移除版本以实测为准。 - 模型注入 :
from ragas.llms import llm_factory; llm = llm_factory("gpt-4o-mini", client=OpenAI())。 - 自定义指标是兜底扩展点:确定性优先做门禁,LLM 版做灵活业务裁判。
全章动手检查点(Checklist)
- 9.1:确认
ragas==0.4.3,分别导入AspectCritic与 collections 的Faithfulness成功。 - 9.2:为你的场景定义 1 个
AspectCritic,观察strictness影响。 - 9.3:写一套 1--5 分量规并做人机一致率统计。
- 9.4:对摘要同时算 BLEU 与
answer_relevancy,验证二者弱相关。 - 9.5:造 20 条 SQL 样本,用"可执行 + 结果一致"双指标区分两类错误。
- 9.6:生成"全单跳 vs 全多跳"两套测试集,对比
context_recall。 - 9.7:实现并运行
KeywordCoverageMetric,完成 3 个动手实验。
参考链接
- 官方仓库(主):https://github.com/vibrantlabsai/ragas
- 旧镜像(内容一致):https://github.com/explodinggradients/ragas
- 模型工厂(llm_factory / embedding_factory):https://docs.ragas.io/en/v0.4.0/howtos/customizations/customize_models
- AspectCritic 文档:https://docs.ragas.io/en/v0.2.6/concepts/metrics/available_metrics/aspect_critic/
- 通用指标(Rubrics / 非 LLM):https://docs.ragas.io/en/v0.4.3/concepts/metrics/available_metrics/general_purpose/
- 自定义指标编写:https://docs.ragas.io/en/v0.3.8/howtos/customizations/metrics/_write_your_own_metric/
- 迁移指南(V3→V4):https://docs.ragas.io/en/stable/howtos/migrations/migrate_from_v03_to_v04/
- 多跳合成器(DeepWiki,旧镜像):https://deepwiki.com/explodinggradients/ragas/7.3-multi-hop-synthesizers
- 自定义指标(DeepWiki,旧镜像):https://deepwiki.com/explodinggradients/ragas/4.5-creating-custom-metrics