个人主页: > for_ever_love__ <(欢迎各位大佬莅临😊)
其他栏目: > 大模型开发从0到1 <
其他栏目: > iOS项目总结大全 <
其他栏目: > 我想学python了 <
其他栏目: > iOS UI <
文章目录
- [忠实度(Faithfulness):怎么判断 RAG 的答案有没有瞎编](#忠实度(Faithfulness):怎么判断 RAG 的答案有没有瞎编)
-
- 一、忠实度到底在衡量什么
-
- [1.1 定义](#1.1 定义)
- [1.2 与"答案正确性"(Answer Correctness)的区别](#1.2 与"答案正确性"(Answer Correctness)的区别)
- [1.3 为什么需要单独衡量](#1.3 为什么需要单独衡量)
- 二、怎么计算忠实度
-
- [2.1 RAGAS 的经典算法](#2.1 RAGAS 的经典算法)
- [2.2 用代码跑(RAGAS)](#2.2 用代码跑(RAGAS))
- [2.3 自己实现一个轻量版](#2.3 自己实现一个轻量版)
- [2.4 不用 LLM 的近似方法](#2.4 不用 LLM 的近似方法)
- 三、分数低了怎么排查
-
- [3.1 原因一:上下文里确实没有答案](#3.1 原因一:上下文里确实没有答案)
- [3.2 原因二:提示词没有约束住](#3.2 原因二:提示词没有约束住)
- [3.3 原因三:上下文太长,模型找不到关键信息](#3.3 原因三:上下文太长,模型找不到关键信息)
- [3.4 原因四:模型在"合理推断"](#3.4 原因四:模型在"合理推断")
- [3.5 原因五:temperature 太高](#3.5 原因五:temperature 太高)
- [3.6 排查流程](#3.6 排查流程)
- 四、忠实度和其它指标的组合解读
- 五、生产环境怎么用
-
- [5.1 离线:作为回归测试指标](#5.1 离线:作为回归测试指标)
- [5.2 在线:抽样监控](#5.2 在线:抽样监控)
- [5.3 在线:做生成后自检(对高风险场景)](#5.3 在线:做生成后自检(对高风险场景))
- 六、忠实度指标的局限
-
- [6.1 依赖评判 LLM 的质量](#6.1 依赖评判 LLM 的质量)
- [6.2 "陈述提取"这一步本身有主观性](#6.2 "陈述提取"这一步本身有主观性)
- [6.3 它不衡量答案是否有用](#6.3 它不衡量答案是否有用)
- [6.4 它不衡量材料本身的正确性](#6.4 它不衡量材料本身的正确性)
- 小结
忠实度(Faithfulness):怎么判断 RAG 的答案有没有瞎编
RAG 系统的答案看起来很流畅、引用了材料、语气自信------但它是真的来自你的知识库,还是模型自己编的?
这个问题靠人眼看很难判断,因为编造的部分往往写得很像回事。忠实度(Faithfulness,也叫 Groundedness / groundedness)就是专门衡量这点的指标:答案中的每一个事实性陈述,能否在给定的上下文中找到依据。
这一篇讲清楚忠实度怎么定义、怎么自动计算、分数低了怎么排查、以及它和"答案正确性"的区别。
一、忠实度到底在衡量什么
1.1 定义
忠实度 = 答案中能被上下文支撑的陈述数 / 答案中的陈述总数
它衡量的是答案与给定上下文的一致性,而不是答案与客观事实的一致性。
这一点非常重要,也很容易被误解。举个例子:
- 上下文说:"公司 2023 版差旅标准规定,一线城市住宿上限 500 元/晚。"
- 答案说:"一线城市住宿上限是 500 元/晚。"
忠实度 = 1.0(完全被上下文支撑)。
即使现实中已经改成了 800 元 ------ 那是上下文过时的问题,不是忠实度的问题。忠实度只管"有没有照着材料说"。
1.2 与"答案正确性"(Answer Correctness)的区别
这是两个不同的维度,必须分开看:
| 维度 | 衡量什么 | 判断依据 | 低分意味着 |
|---|---|---|---|
| 忠实度 | 答案是否忠于给定材料 | 答案 vs 上下文 | 模型在编造(幻觉) |
| 答案正确性 | 答案是否符合事实 | 答案 vs 标准答案 | 可能检索错了,也可能材料本身错了 |
| 上下文召回率 | 材料是否包含所需信息 | 上下文 vs 标准答案 | 检索环节没召到 |
这三个指标可以两两组合出各种失败模式,我们后面会讲。
1.3 为什么需要单独衡量
你可能会想:直接看答案对不对不就行了?
不行。原因是答案正确性会掩盖幻觉。模型用自身知识答对了,但那不是从你的知识库来的------对于企业知识库场景,这恰恰是最危险的情况:你不知道它这次蒙对了,下次可能蒙错,而且你无法追溯来源。
忠实度的价值就在于:它把"照着材料说"这件事单独拎出来衡量,不管材料本身对不对。
二、怎么计算忠实度
2.1 RAGAS 的经典算法
RAGAS 框架的做法分两步:陈述提取 → 逐条判定。
第一步:把答案拆成原子陈述
输入答案:
"公司差旅标准规定,一线城市住宿上限为 500 元每晚,二线城市为 350 元。超标需提前申请特批,由部门负责人审批。"
提取结果:
S1: 公司差旅标准规定
S2: 一线城市住宿上限为 500 元每晚
S3: 二线城市住宿上限为 350 元
S4: 超标需要提前申请特批
S5: 特批由部门负责人审批
第二步:逐条判断能否从上下文推出
S1: 可推出 ✓
S2: 可推出 ✓
S3: 可推出 ✓
S4: 可推出 ✓
S5: 上下文未提及 ✗
第三步:算分
Faithfulness = 4 / 5 = 0.8
2.2 用代码跑(RAGAS)
python
from ragas import evaluate
from ragas.metrics import faithfulness
from datasets import Dataset
data = Dataset.from_dict({
"question": ["一线城市住宿标准是多少,超标怎么办?"],
"answer": ["一线城市住宿上限 500 元/晚,超标需部门负责人审批。"],
"contexts": [[
"差旅标准(2023版):一线城市住宿上限 500 元/晚,"
"二线城市 350 元/晚。超标部分需提前提交特批申请。"
]],
})
result = evaluate(data, metrics=[faithfulness])
print(result["faithfulness"]) # 0.75 左右
RAGAS 内部就是用 LLM 做陈述提取和判定,所以需要一个评判用的 LLM(通常用 GPT-4 级别的模型,便宜模型做评判会不准)。
2.3 自己实现一个轻量版
不想引入 RAGAS 的话,核心逻辑不难,自己写也就几十行:
python
import json
EXTRACT_PROMPT = """把下面的回答拆成若干个独立的原子陈述。
要求:
1. 每个陈述只包含一个事实点;
2. 陈述要自包含,不依赖其他陈述;
3. 只输出事实性陈述,忽略客套话、过渡句;
4. 输出 JSON 数组。
回答:{answer}
JSON:"""
JUDGE_PROMPT = """判断下面的陈述能否从材料中推出。
材料:{context}
陈述:{statement}
判定标准:
- 材料中明确提到,或能从材料直接推出 → true
- 材料未提及,或需要额外知识才能得出 → false
只输出 JSON:{{"supported": true/false, "reason": "一句话说明"}}"""
def faithfulness_score(answer, context, llm):
# 1. 拆陈述
raw = llm.invoke(EXTRACT_PROMPT.format(answer=answer)).content
statements = json.loads(clean_json(raw))
if not statements:
return 1.0, []
# 2. 逐条判定
results = []
for s in statements:
raw = llm.invoke(JUDGE_PROMPT.format(context=context,
statement=s)).content
r = json.loads(clean_json(raw))
results.append({"statement": s, **r})
# 3. 算分
supported = sum(1 for r in results if r["supported"])
return supported / len(results), results
自己实现的好处是可以定制判定标准(比如企业内部对"推断"的容忍度不同),也能省掉 RAGAS 的一些依赖。
2.4 不用 LLM 的近似方法
LLM 判定成本高、有随机性。有几个便宜的近似方案:
方法一:NLI(自然语言推理)模型
用一个 entailment 模型判断"上下文 → 陈述"是否成立:
python
from transformers import pipeline
nli = pipeline("text-classification",
model="MoritzLaurer/mDeBERTa-v3-base-xnli-multilingual-nli")
def nli_faithfulness(answer_sents, context_sents):
scores = []
for a in answer_sents:
# 对每条答案句,找最支持它的上下文句
best = 0
for c in context_sents:
r = nli(f"{c}</s>{a}") # premise=上下文, hypothesis=答案句
if r["label"] == "ENTAILMENT":
best = max(best, r["score"])
scores.append(best)
return sum(scores) / len(scores)
优点:便宜、快、可复现、支持中文。缺点:对长句和复杂推理判断不准,分数是连续值而非二值。
方法二:字符串/token 重叠
最简单:算答案 n-gram 在上下文中的覆盖率。太粗糙,只能作为粗筛,不适合作为正式指标。
方法三:只用于抽检
生产环境里最务实的方案:全量用便宜方法粗筛,低分的送 LLM 精判,再对低分做人工复核。成本可控,覆盖也够。
三、分数低了怎么排查
忠实度低,说明模型在编。但为什么会编,有几种完全不同的原因,排查方向也不一样。
3.1 原因一:上下文里确实没有答案
模型被逼着答,只能编。
判据:同时看上下文召回率(Context Recall)。如果召回率也低,说明是检索的问题,不是生成的问题。
对策:修检索(换 embedding、调切分、加混合检索、加多查询),或者让系统学会拒答(见我写的拒答那篇)。
3.2 原因二:提示词没有约束住
提示词里只有"请基于以下材料回答",没有禁止使用自身知识。
对策:加硬约束------
严格规则:
1. 只使用【材料】中的信息,不得使用你自己的知识;
2. 材料中未提及的内容,明确说明"材料未提供",不要补充;
3. 每个事实性陈述必须能对应到材料中的具体段落。
实测:加上这类约束,忠实度通常能从 0.6~0.7 提到 0.85+。
3.3 原因三:上下文太长,模型找不到关键信息
Lost in the Middle 效应------答案在上下文里,但模型没"看到"。
判据:减少上下文长度后忠实度明显上升。
对策:重排 + 减少送入文档数 + 关键材料放末尾。
3.4 原因四:模型在"合理推断"
模型做了材料没说但看起来合理的延伸。比如材料说"支持 MySQL 5.7+",模型答"支持 MySQL 8.0"------逻辑上没错,但材料没明说。
这种情况最麻烦,因为它介于编造和推理之间。
对策:在判定提示词里明确"需要额外知识才能得出 → false",把这个标准写死。业务上如果允许合理推断,就放宽标准,但要意识到风险。
3.5 原因五:temperature 太高
生成随机性大会增加编造概率。评估和生产环境都应该用低 temperature(0~0.2),尤其是事实型问答。
3.6 排查流程
一个实用的决策树:
忠实度低
├─ 上下文召回率也低? → 检索的问题 → 修检索
├─ 上下文很长(>10篇)? → 位置效应 → 重排 + 减量
├─ 提示词无硬约束? → 加约束
├─ temperature > 0.3? → 调低
└─ 以上都排除 → 换更强的模型 / 加自检层
四、忠实度和其它指标的组合解读
单独看忠实度不够,要和其他指标组合起来看。这张表值得存下来:
| 忠实度 | 上下文召回率 | 答案正确性 | 诊断 | 对策 |
|---|---|---|---|---|
| 高 | 高 | 高 | ✅ 健康 | --- |
| 高 | 高 | 低 | 材料本身错了/过时 | 修知识库 |
| 高 | 低 | 低 | 检索没召到,但模型照材料说了 | 修检索 |
| 低 | 高 | 低 | 材料有答案,模型却编了 | 修生成(提示词/长度/模型) |
| 低 | 低 | 低 | 检索和生成都有问题 | 先修检索 |
| 低 | 低 | 高 | 模型靠自身知识蒙对了 | 危险,必须修 |
最后一行是最危险的状态:答案看起来是对的,但完全不是从你的知识库来的。这种情况下系统完全不可信,只是暂时运气好。
倒数第三行(低-高-低)是最值得修的:材料明明有,模型却没照着说------这是纯粹的生成侧问题,通常加提示词约束就能显著改善。
五、生产环境怎么用
5.1 离线:作为回归测试指标
每次改 prompt、换模型、调检索参数,跑一遍评测集,看忠实度有没有下降:
python
# 建一个小而精的评测集,50~200 条
# 每条包含:question, contexts, answer(系统生成的), ground_truth
# CI 里跑,忠实度低于阈值就告警
关键是评测集要覆盖你真实的失败模式。随便找 100 个问题没用,要把历史上出过问题的案例加进去。
5.2 在线:抽样监控
全量算忠实度成本太高(每条要 2 次 LLM 调用)。做法是:
- 每天随机抽 5%~10% 的线上问答,异步算忠实度;
- 忠实度低于 0.5 的,进人工复核队列;
- 按周看趋势,突然下降说明某次改动引入了问题。
5.3 在线:做生成后自检(对高风险场景)
对医疗、法务这类场景,可以在生成后同步做一次忠实度检查,不通过就降级为拒答或转人工:
python
answer = generate(question, context)
score, details = faithfulness_score(answer, context, judge_llm)
if score < 0.8:
# 要么剔除无依据的句子,要么降级
answer = strip_unsupported(answer, details)
代价是一次额外 LLM 调用 + 延迟增加,但换来的可靠性在敏感场景是值得的。
六、忠实度指标的局限
要清楚这个指标本身的不足,别迷信它。
6.1 依赖评判 LLM 的质量
用 GPT-3.5 评判 GPT-4 的输出,本身就不太可靠。评判模型应该至少不弱于被评模型。这也是 RAGAS 默认要求用 GPT-4 级别模型的原因------成本不低。
6.2 "陈述提取"这一步本身有主观性
同一句话可以拆成 3 条也可以拆成 5 条,拆法不同分数就不同。这可能带来 ±0.1 的波动。
缓解 :固定提示词、固定评判模型、temperature=0,让结果可复现;关注相对变化而不是绝对值。
6.3 它不衡量答案是否有用
忠实度 1.0 的答案可能完全没回答用户的问题(比如模型照抄了一段材料)。所以必须配合**答案相关性(Answer Relevancy)**一起看。
6.4 它不衡量材料本身的正确性
前面说过,忠实度只管"照没照材料说"。材料错了,忠实度照样满分。所以知识库的内容质量是另一个独立的战场。
小结
- 忠实度衡量"答案是否忠于给定上下文",不是"答案是否符合客观事实"。这两者必须分开衡量。
- 经典算法:拆原子陈述 → 逐条判定能否从上下文推出 → 被支撑数 / 总数。RAGAS 是标准实现,也可以自己实现或用 NLI 模型近似。
- 分数低要先定位原因:看上下文召回率区分是检索问题还是生成问题;再检查提示词约束、上下文长度、temperature。
- 组合解读更有价值:忠实度低 + 召回率高 = 纯生成侧问题(最值得修);忠实度低 + 正确率高 = 模型在蒙(最危险)。
- 生产用法:离线做回归测试指标,在线做抽样监控,高风险场景做生成后自检。
- 指标本身有局限:依赖评判模型质量、陈述拆分有主观性、不衡量答案有用性、不衡量材料正确性。
一句话:忠实度是 RAG 系统里最重要的安全指标。它不高的话,你的系统就是个随时可能编造内容的黑盒,其他指标再好看也没意义。