评估 LLM 输出不是指标问题 — 而是哲学问题
大多数构建 LLM 驱动应用的团队都低估了评估工作,直到他们将产品部署到生产环境后才发现”测试中看起来不错”并不是一种方法论。LLM 评估的难度是传统软件测试所不具备的:主观任务没有标准答案,输出是概率性和可变的,且故障模式是定性的而非二元的。本指南涵盖了团队在生产环境中实际使用的评估框架、指标和工具 — 以及那些没有清晰自动化解决方案的评估问题的概念框架。
为什么 LLM 评估确实很困难
传统软件测试有明确的结构:给定输入 X,期望输出 Y。确定性、二元性、可自动化。LLM 评估打破了这一模型的所有假设。
考虑一个客户支持机器人,应该准确且有帮助地回答有关您产品的问题。您如何测试它?”准确”需要知道正确的答案进行比较 — 但对于开放式问题,可能有多个有效答案,没有一个与您的参考答案完全匹配。”有帮助”是一种主观质量评估。而同一个提示发送给同一个模型两次可能会产生明显不同的输出。
这不是一个通过更好的工具就能解决的问题。这是一个需要通过自动化指标、人工判断和生产监控的正确组合来管理的问题 — 要认识到没有单一方法是足够的。
四种评估范式
1. 基于参考的指标
当您有标准答案 — 一组具有已知正确答案的问题时,基于参考的指标会将模型输出与参考答案进行比较。
ROUGE(面向摘要评估的召回率导向辅助工具):衡量生成文本与参考文本之间的 n-gram 重叠。ROUGE-1 比较一元语法,ROUGE-2 比较二元语法,ROUGE-L 比较长公共子序列。最初为摘要评估开发。
BLEU(双语评估辅助工具):类似的 n-gram 重叠指标,最初用于机器翻译。衡量精确度(输出中有多少出现在参考中)而非召回率。
BERTScore:使用 BERT 的上下文嵌入来衡量输出与参考答案之间的语义相似性,而不是基于表面层面的词汇重叠。在捕捉不同措辞的释义和语义等效输出方面表现更好。
from bert_score import score as bert_score
from rouge_score import rouge_scorer
# 示例:评估摘要输出
references = [
"ACME 协议通过挑战-响应验证自动化 TLS 证书颁发。",
]
candidates = [
"ACME 是一种通过验证域名所有权来处理自动证书管理的协议。",
]
# ROUGE 分数
scorer = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=True)
scores = scorer.score(references[0], candidates[0])
print(f"ROUGE-1 F1: {scores['rouge1'].fmeasure:.3f}")
print(f"ROUGE-2 F1: {scores['rouge2'].fmeasure:.3f}")
print(f"ROUGE-L F1: {scores['rougeL'].fmeasure:.3f}")
# BERTScore — 捕捉超越词汇重叠的语义相似性
P, R, F1 = bert_score(candidates, references, lang="en")
print(f"BERTScore F1: {F1.mean():.3f}")
基于参考指标的评估方法有一个根本性局限:它们需要高质量的参考答案,并且衡量的是与这些参考答案的相似性,而非正确性或质量。一个事实正确但表述方式与参考答案不同的输出会得到低分。在你拥有高质量参考答案且输出空间受限的任务中使用这些指标(摘要、翻译、事实性问答)。
2. LLM 作为评判者
目前最广泛使用的定性评估范式是使用一个强大的大语言模型(通常是 GPT-4o 或 Claude Sonnet)来评估另一个大语言模型的输出。评判模型会收到一个评分标准并根据它来评估输出。
import anthropic
client = anthropic.Anthropic()
def evaluate_with_llm_judge(
question: str,
model_output: str,
criteria: list[str]
) -> dict:
"""
使用 Claude 作为评判者来评估大语言模型输出的质量。
返回每个标准的分数和推理。
"""
criteria_text = "n".join(f"{i+1}. {c}" for i, c in enumerate(criteria))
prompt = f"""您正在评估一个 AI 助手回答的质量。
提问的问题: {question}
待评估的回答:
{model_output}
根据以下标准评估回答:
{criteria_text}
对于每个标准,请提供:
- 分数: 1(差),2(可接受),3(好),4(优秀)
- 简短推理(1-2句话)
以 JSON 格式回复:
{{
"scores": {{
"标准名称": {{"score": X, "reasoning": "..."}}
}},
"overall_score": X,
"overall_assessment": "..."
}}"""
message = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}]
)
import json
return json.loads(message.content[0].text)
# 使用示例
result = evaluate_with_llm_judge(
question="如何在 REST API 中实现速率限制?",
model_output="...",
criteria=[
"技术准确性 — 信息是否正确?",
"完整性 — 是否涵盖了关键方法?",
"代码质量 — 代码示例是否正确且符合习惯?",
"清晰度 — 解释是否易于理解?"
]
)
print(f"总体分数: {result['overall_score']}/4")
大语言模型作为评判者在许多任务上与人类判断有很高的相关性(LMSYS 的研究表明与人类偏好评分的一致性超过 80%),但它有文献充分记录的偏见:位置偏见(比较时偏好第一个选项)、冗长偏见(无论质量如何偏好更长的输出)和自我提升偏见(模型在作为评判者时倾向于偏好自己的输出)。
缓解这些问题的方法包括:位置交换测试(运行两次比较,交换候选对象,标记不一致之处)、使用与被评估模型不同的评判模型,以及在代表性样本上根据人类标签校准评判模型。
3. 人工评估
对于高风险任务,人工评估仍然是黄金标准。问题不在于是否使用人工评估,而在于如何使其高效和一致。
可靠人工评估的关键原则:
盲评: 评估者不应知道哪个模型或提示生成了被评分的输出。即使评估者出于好意,知道来源也会引入偏见。
清晰的评分标准与示例:“这个回答有帮助吗?”不是评分标准。”根据有用性对回答进行评分:1 = 没有回答问题,2 = 部分回答了问题,3 = 回答了问题但有遗漏或错误,4 = 完整准确地回答了问题” — 每个级别都有示例 — 可以产生一致的评分。
评分者间一致性:让多个评估者对随机样本进行评分并测量一致性(分类评分使用 Cohen’s Kappa)。低一致性表明你的评分标准不明确,而不是评估者不可靠。
4. 任务特定的自动化指标
对于结构化任务,构建自定义的自动化评估来测试特定属性:
import ast
import subprocess
import tempfile
import os
def evaluate_code_output(
generated_code: str,
test_cases: list[dict]
) -> dict:
"""
通过实际运行测试用例来评估生成的代码。
对于代码评估,比文本相似度更可靠。
"""
results = {
"syntax_valid": False,
"tests_passed": 0,
"tests_total": len(test_cases),
"errors": []
}
# 检查语法有效性
try:
ast.parse(generated_code)
results["syntax_valid"] = True
except SyntaxError as e:
results["errors"].append(f"语法错误: {e}")
return results
# 运行测试用例
for i, test_case in enumerate(test_cases):
test_code = f"""
{generated_code}
# 测试用例 {i+1}
result = {test_case['call']}
expected = {repr(test_case['expected'])}
assert result == expected, f"期望 {{expected}},实际 {{result}}"
print("PASS")
"""
with tempfile.NamedTemporaryFile(
mode='w', suffix='.py', delete=False
) as f:
f.write(test_code)
f.flush()
proc = subprocess.run(
["python3", f.name],
capture_output=True, text=True, timeout=5
)
os.unlink(f.name)
if "PASS" in proc.stdout:
results["tests_passed"] += 1
else:
results["errors"].append(
f"测试 {i+1} 失败: {proc.stderr[:200]}"
)
results["pass_rate"] = results["tests_passed"] / results["tests_total"]
return results
评估框架
RAGAS: RAG 特定评估
对于检索增强生成系统,RAGAS 提供了一个测量四个维度的框架:忠实度(答案是否仅使用检索到的上下文中的信息?)、答案相关性(答案与问题的相关性如何?)、上下文召回率(检索到的上下文是否包含所需信息?)以及上下文精确度(检索到的上下文是否相关?)。
from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
context_recall,
context_precision
)
from datasets import Dataset
# 准备评估数据集
eval_data = {
"question": ["什么是 mTLS?", "ACME 是如何工作的?"],
"answer": ["mTLS 要求客户端和服务器都进行身份验证...", "ACME 使用挑战-响应验证..."],
"contexts": [
["mTLS 或 mutual TLS 是一种双方都进行身份验证的协议..."],
["ACME 协议通过验证来自动化证书颁发..."]
],
"ground_truth": ["相互 TLS 对客户端和服务器都进行身份验证...", "ACME 自动化 TLS 证书颁发..."]
}
dataset = Dataset.from_dict(eval_data)
result = evaluate(dataset, metrics=[
faithfulness, answer_relevancy, context_recall, context_precision
])
print(result)
PromptFoo: 系统化提示测试
PromptFoo 是一个用于跨模型和数据集系统化测试提示的 CLI 工具。它可以集成到 CI 管道中,并能自动对模型输出运行断言。
# promptfooconfig.yaml
prompts:
- file://prompts/code-reviewer.txt
providers:
- id: ollama:qwen2.5-coder:7b
- id: openai:gpt-4o-mini
tests:
- description: "应该识别 SQL 注入"
vars:
code: |
query = f"SELECT * FROM users WHERE id = {user_input}"
cursor.execute(query)
assert:
- type: contains
value: "SQL injection"
- type: llm-rubric
value: "响应识别了 SQL 注入漏洞并提供了一个参数化查询修复"
- description: "不应该标记安全代码"
vars:
code: |
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
assert:
- type: not-contains
value: "SQL injection"
# 运行评估
promptfoo eval
# 比较不同提供商的结果
promptfoo view
生产监控:评估不会在部署时结束
生产环境中的模型行为与评估集中的模型行为不同——有时差异很大。用户会提出您没有预料到的问题,以您没有测试过的格式,并且当您更新提示、更改模型或底层模型被提供商更新时,模型的响应质量也会发生漂移。
LLM 应用程序的生产监控需要:
- 记录所有输入和输出:这是你进行事后分析的基准真相。存储每个请求和响应,包含时间戳、模型版本和提示模板版本。
- 抽样人工审核:每周随机审核生产输出的1-5%。这是在用户发现问题之前发现质量下降的方法。
- 用户反馈信号:点赞/点踩、明确纠正、后续澄清请求——这些是大规模下虽然微弱但真实的质量信号。
- 每次部署的自动化回归测试:在更改提示模板或更新到新模型版本之前,运行完整的评估套件,并要求达到最低质量阈值。
关于LLM评估的不适真相
没有任何指标组合能明确告诉你你的LLM应用运行良好。基于参考的指标会遗漏语义正确的转述。LLM评判者存在偏见。人工评估既昂贵又缓慢。特定任务的指标只覆盖了你想到要测试的内容。
在生产环境中构建可靠LLM应用的团队会综合使用所有这些方法——并且对每种方法都保持健康的怀疑态度。他们大量投入日志记录,以便从生产数据中学习,他们有明确的质量阈值,违反时会阻止部署,他们将评估视为持续实践而非发布前检查清单。
目标不是完美的评估。而是足够好的评估,能在用户发现问题之前发现回归,并生成改进系统所需的反馈循环。
关键要点
- 没有单一的评估指标是足够的。对于有基准真相的结构化任务,使用基于参考的指标;对于定性评估,使用LLM作为评判者;对于代码和结构化输出,使用特定任务的自动化测试。
- LLM作为评判者与人类判断高度相关,但有记录的偏见——通过位置交换测试和与人类标签对比的评判校准来缓解。
- RAGAS为RAG系统提供了标准化的评估维度(忠实度、相关性、召回率、精确度),这些指标难以手动测量。
- PromptFoo将LLM评估集成到CI管道中,使用声明式测试配置和多模型比较。
- 生产监控——日志记录、抽样、用户反馈——不是可选的。评估集无法捕获生产输入的完整分布。
