LLM 评估框架 本节摘要:精确匹配与 F1 漏掉语义等价,人工评审无法规模化,LLM-as-judge 是生产答案——但要配上足够的校准才能信任这个数字。你的 RAG 系统答「June 29th, 2007」,金标准是「June 29, 2007」,精确匹配给 0、F1 约 75%、人会给 100%;再乘以一万条用例,再乘以每次改检索器、分块、提示、模型,你需要一个懂含义、规模便宜、不在回归上撒谎、能暴露正确失败模式的评估器。
本节摘要:精确匹配与 F1 漏掉语义等价,人工评审无法规模化,LLM-as-judge 是生产答案——但要配上足够的校准才能信任这个数字。你的 RAG 系统答「June 29th, 2007」,金标准是「June 29, 2007」,精确匹配给 0、F1 约 75%、人会给 100%;再乘以一万条用例,再乘以每次改检索器、分块、提示、模型,你需要一个懂含义、规模便宜、不在回归上撒谎、能暴露正确失败模式的评估器。2026 有三个框架把持这个问题:RAGAS 给出 RAG 四维(忠实度、答案相关、上下文精确、上下文召回)配 NLI+LLM-judge 后端;DeepEval 是「LLM 的 pytest」,G-Eval、任务完成、幻觉、偏见指标原生融入 CI/CD;G-Eval 是一种方法(也是 DeepEval 的指标)——带思维链、自定义准则、0~1 评分的 LLM-as-judge。三者都靠 LLM-as-judge,本节讲透这个方法及其外围的信任层:为何它工作(GPT-4o-mini 约 0.003 美元/条,千样本回归不到 5 美元),为何它会静默失败(评判者偏置、JSON 解析失败、模型版本漂移),以及为何在拿到与人标注的 Spearman 相关 >0.7 之前绝不要信任原始评分。
对应原课程:Phase 5 · Lesson 27 ·
llm-evaluation-frameworks(原英文phases/05-nlp-foundations-to-advanced/27-llm-evaluation-frameworks/docs/en.md)。前置依赖:第 13 节(问答系统)、第 14 节(信息检索)。
阅读完本节,你应当能够:
你的 RAG 系统答:「June 29th, 2007.」金标准是:「June 29, 2007.」精确匹配 0 分,F1 约 75%,人会打 100%。
再乘以一万条测试用例。再乘以每次对检索器、分块、提示、模型的改动。你需要一个懂含义、规模便宜、不在回归上撒谎、能暴露正确失败模式的评估器。
2026 有三个框架把持这个问题:
三者都靠 LLM-as-judge。本节为这个方法及其外围信任层建立直觉。
LLM-as-judge:用一个 LLM 在评分量表(rubric)下给输出打分,取代静态指标。给定 (查询, 上下文, 答案),提示一个评判 LLM:「按忠实度打 0~1」,返回分数。
为何它工作:LLM 以极低成本逼近人类判断。GPT-4o-mini 约 0.003 美元/条,千样本回归评估不到 5 美元。
为何它会静默失败:
RAG 四维:
| 指标 | 问题 | 后端 |
|---|---|---|
| 忠实度 | 答案里每条论断是否来自检索上下文? | 基于 NLI 的蕴含 |
| 答案相关 | 答案是否回应了问题? | 从答案生成假设问题,与真问题对比 |
| 上下文精确 | 检索块里,相关的占多少? | LLM-judge |
| 上下文召回 | 检索是否返回了所需的一切? | 对金答案的 LLM-judge |
G-Eval:定义一个自定义准则:「答案是否引用了正确来源?」框架自动扩成思维链评估步骤,再打 0~1。适合 RAGAS 覆盖不到的领域专用质量维度。
💡 校准:在与人类标签建立相关之前,绝不要信任原始评判分数。跑 100 个手标样例,绘制评判 vs 人,算 Spearman rho。若 rho < 0.7,你的评判 rubric 还需打磨。
from typing import Callable from transformers import pipeline nli = pipeline("text-classification", model="MoritzLaurer/DeBERTa-v3-large-mnli-fever-anli-ling-wanli", top_k=None) # `llm` 是任何可调用对象:提示字符串 -> 生成字符串。 # 例:llm = lambda p: client.messages.create(model="claude-haiku-4-5", ...).content[0].text LLM = Callable[[str], str] def atomic_claims(answer: str, llm: LLM) -> list[str]: prompt = f"""Break this answer into simple factual claims (one per line): {answer} """ return llm(prompt).splitlines() def faithfulness(answer: str, context: str, llm: LLM) -> float: claims = atomic_claims(answer, llm) if not claims: return 0.0 supported = 0 for claim in claims: result = nli({"text": context, "text_pair": claim})[0] entail = next((s for s in result if s["label"] == "entailment"), None) if entail and entail["score"] > 0.5: supported += 1 return supported / len(claims)
把答案拆成原子论断,逐条对检索上下文做 NLI,忠实度 = 被支持的比例。
import numpy as np from sentence_transformers import SentenceTransformer # encoder: 任何实现 .encode(texts, normalize_embeddings=True) -> ndarray 的模型 # 例:encoder = SentenceTransformer("BAAI/bge-small-en-v1.5") def answer_relevance(question: str, answer: str, encoder, llm: LLM, n: int = 3) -> float: prompt = f"Write {n} questions this answer could be the answer to:\n{answer}" generated = [line for line in llm(prompt).splitlines() if line.strip()][:n] if not generated: return 0.0 q_emb = np.asarray(encoder.encode([question], normalize_embeddings=True)[0]) g_embs = np.asarray(encoder.encode(generated, normalize_embeddings=True)) sims = [float(q_emb @ g_emb) for g_emb in g_embs] return sum(sims) / len(sims)
若答案暗示的问题与所问的不同,相关度就掉。
from deepeval.metrics import GEval from deepeval.test_case import LLMTestCaseParams, LLMTestCase metric = GEval( name="Correctness", criteria="The answer should be factually accurate and match the expected output.", evaluation_steps=[ "Read the expected output.", "Read the actual output.", "List factual claims in the actual output.", "For each claim, mark supported or unsupported by the expected output.", "Return score = fraction supported.", ], evaluation_params=[LLMTestCaseParams.INPUT, LLMTestCaseParams.ACTUAL_OUTPUT, LLMTestCaseParams.EXPECTED_OUTPUT], ) test = LLMTestCase(input="When was the first iPhone released?", actual_output="June 29th, 2007.", expected_output="June 29, 2007.") metric.measure(test) print(metric.score, metric.reason)
评估步骤就是 rubric。显式步骤比隐式的「打 0~1」更稳。
import deepeval from deepeval.metrics import FaithfulnessMetric, ContextualRelevancyMetric def test_rag_system(): cases = load_regression_cases() faith = FaithfulnessMetric(threshold=0.85) rel = ContextualRelevancyMetric(threshold=0.7) for case in cases: faith.measure(case) assert faith.score >= 0.85, f"faithfulness regression on {case.id}" rel.measure(case) assert rel.score >= 0.7, f"relevancy regression on {case.id}"
作为 pytest 文件发布,每个 PR 上跑,在回归上挡合并。
见 code/main.py。纯标准库的忠实度近似(答案论断与上下文的重叠)与相关度近似(答案 token 与问题 token 的重叠)。非生产用,展示形状。
2026 的栈:
| 用例 | 框架 |
|---|---|
| RAG 质量监控 | RAGAS(4 指标) |
| CI/CD 回归门 | DeepEval + pytest |
| 自定义领域准则 | DeepEval 内的 G-Eval |
| 在线实时流量监控 | RAGAS 无参考模式 |
| 人在环抽检 | LangSmith 或 Phoenix 配标注 UI |
| 红队 / 安全评估 | Promptfoo + DeepEval |
💡 典型栈:RAGAS 监控、DeepEval 做 CI、G-Eval 做新维度。三者都跑,它们有用的分歧。
保存为 outputs/skill-eval-architect.md:
--- name: eval-architect description: Design an LLM evaluation plan with calibrated judge and CI gates. version: 1.0.0 phase: 5 lesson: 27 tags: [nlp, evaluation, rag] --- Given a use case (RAG / agent / generative task), output: 1. Metrics. Faithfulness / relevance / context-precision / context-recall + any custom G-Eval metrics with criteria. 2. Judge model. Named model + version, rationale for cost vs accuracy. 3. Calibration. Hand-labeled set size, target Spearman rho vs human > 0.7. 4. Dataset versioning. Tag strategy, change log, stratification. 5. CI gate. Thresholds per metric, regression-window logic, bottom-quantile alert. Refuse to rely on a judge untested against ≥50 human-labeled examples. Refuse self-evaluation (same model generates + judges). Refuse aggregate-only reporting without bottom-10% surfacing. Flag any pipeline where judge upgrade lands without parallel baseline eval.
下一节,我们把评估视角推到极限——进入「长上下文评估」,看「迷失在中间」、针插草堆、RULER 与 LongBench 如何测一个模型在十万 token 窗口里到底记住了多少。