关于 llm-evaluation
llm-evaluation 是评估 LLM 应用的技能,涵盖自动指标、人工评审、LLM 评审和实验。它帮助团队比较模型或提示词、跟踪回归,并结合验证与数据安全注意事项解读结果。
主要功能
- 01文本、分类和检索自动指标
- 02人工评估维度与一致性
- 03LLM 评审评分模式
- 04A/B 与回归测试指导
- 05基准测试和评估实践
适用场景
- 01比较模型或提示词方案
- 02部署前检查性能回归
- 03调查模型异常行为
- 04持续跟踪评估基线
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ sickn33/ agentic-awesome-skills --skill llm-evaluation安装后在 Claude Code 中输入/llm-evaluation确认已加载。
附带文件references/benchmarking.md
详细介绍
LLM 评估
掌握 LLM 应用的全面评估策略,从自动化指标到人工评估和 A/B 测试。
不要在以下情况下使用此技能
- 任务与 LLM 评估无关
- 你需要此范围之外的其他领域或工具
说明
- 明确目标、约束和所需输入。
- 运用相关最佳实践并验证结果。
- 提供可执行的步骤和验证方法。
- 使用以下示例;此处未附带单独的实现指南。
在以下情况下使用此技能
- 系统地衡量 LLM 应用的性能
- 比较不同模型或提示词
- 在部署前检测性能回退
- 验证提示词更改带来的改进
- 增强对生产系统的信心
- 建立基线并跟踪长期进展
- 调试意外的模型行为
核心评估类型
1. 自动化指标
使用计算得分进行快速、可重复、可扩展的评估。
文本生成:
- BLEU:N 元词组重合度(翻译)
- ROUGE:侧重召回率(摘要)
- METEOR:语义相似度
- BERTScore:基于嵌入的相似度
- Perplexity:语言模型置信度
分类:
- 准确率:预测正确的百分比
- 精确率/召回率/F1:特定类别的表现
- 混淆矩阵:错误模式
- AUC-ROC:排序质量
检索(RAG):
- MRR:平均倒数排名
- NDCG:归一化折损累计增益
- Precision@K:前 K 个结果中的相关结果
- Recall@K:前 K 个结果的覆盖率
2. 人工评估
手动评估难以自动化的质量方面。
维度:
- 准确性:事实是否正确
- 连贯性:逻辑是否流畅
- 相关性:是否回答了问题
- 流畅度:自然语言质量
- 安全性:不包含有害内容
- 有用性:对用户是否有帮助
3. LLM-as-Judge
使用更强大的 LLM 评估较弱模型的输出。
方法:
- 逐项评分:为单条回答评分
- 成对比较:比较两条回答
- 基于参考的评估:与黄金标准比较
- 无参考评估:在没有标准答案的情况下评判
快速开始
from llm_eval import EvaluationSuite, Metric
# Define evaluation suite
suite = EvaluationSuite([
Metric.accuracy(),
Metric.bleu(),
Metric.bertscore(),
Metric.custom(name="groundedness", fn=check_groundedness)
])
# Prepare test cases
test_cases = [
{
"input": "What is the capital of France?",
"expected": "Paris",
"context": "France is a country in Europe. Paris is its capital."
},
# ... more test cases
]
# Run evaluation
results = suite.evaluate(
model=your_model,
test_cases=test_cases
)
print(f"Overall Accuracy: {results.metrics['accuracy']}")
print(f"BLEU Score: {results.metrics['bleu']}")
自动化指标实现
BLEU 分数
from nltk.translate.bleu_score import sentence_bleu, SmoothingFunction
def calculate_bleu(reference, hypothesis):
"""Calculate BLEU score between reference and hypothesis."""
smoothie = SmoothingFunction().method4
return sentence_bleu(
[reference.split()],
hypothesis.split(),
smoothing_function=smoothie
)
# Usage
bleu = calculate_bleu(
reference="The cat sat on the mat",
hypothesis="A cat is sitting on the mat"
)
ROUGE 分数
from rouge_score import rouge_scorer
def calculate_rouge(reference, hypothesis):
"""Calculate ROUGE scores."""
scorer = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'], use_stemmer=True)
scores = scorer.score(reference, hypothesis)
return {
'rouge1': scores['rouge1'].fmeasure,
'rouge2': scores['rouge2'].fmeasure,
'rougeL': scores['rougeL'].fmeasure
}
BERTScore
from bert_score import score
def calculate_bertscore(references, hypotheses):
"""Calculate BERTScore using pre-trained BERT."""
P, R, F1 = score(
hypotheses,
references,
lang='en',
model_type='microsoft/deberta-xlarge-mnli'
)
return {
'precision': P.mean().item(),
'recall': R.mean().item(),
'f1': F1.mean().item()
}
自定义指标
def calculate_groundedness(response, context):
"""Check if response is grounded in provided context."""
# Use NLI model to check entailment
from transformers import pipeline
nli = pipeline("text-classification", model="microsoft/deberta-large-mnli")
result = nli(f"{context} [SEP] {response}")[0]
# Return confidence that response is entailed by context
return result['score'] if result['label'] == 'ENTAILMENT' else 0.0
def calculate_toxicity(text):
"""Measure toxicity in generated text."""
from detoxify import Detoxify
results = Detoxify('original').predict(text)
return max(results.values()) # Return highest toxicity score
def calculate_factuality(claim, knowledge_base):
"""Verify factual claims against knowledge base."""
# Implementation depends on your knowledge base
# Could use retrieval + NLI, or fact-checking API
raise NotImplementedError("A verified fact-checking adapter is required")
LLM-as-Judge 模式
单项输出评估
def llm_judge_quality(response, question):
"""Use GPT-5 to judge response quality."""
prompt = f"""Rate the following response on a scale of 1-10 for:
1. Accuracy (factually correct)
2. Helpfulness (answers the question)
3. Clarity (well-written and understandable)
Question: {question}
Response: {response}
Provide ratings in JSON format:
{{
"accuracy": <1-10>,
"helpfulness": <1-10>,
"clarity": <1-10>,
"reasoning": "<brief explanation>"
}}
"""
result = openai.ChatCompletion.create(
model="gpt-5",
messages=[{"role": "user", "content": prompt}],
temperature=0
)
return json.loads(result.choices[0].message.content)
成对比较
def compare_responses(question, response_a, response_b):
"""Compare two responses using LLM judge."""
prompt = f"""Compare these two responses to the question and determine which is better.
Question: {question}
Response A: {response_a}
Response B: {response_b}
Which response is better and why? Consider accuracy, helpfulness, and clarity.
Answer with JSON:
{{
"winner": "A" or "B" or "tie",
"reasoning": "<explanation>",
"confidence": <1-10>
}}
"""
result = openai.ChatCompletion.create(
model="gpt-5",
messages=[{"role": "user", "content": prompt}],
temperature=0
)
return json.loads(result.choices[0].message.content)
人工评估框架
标注指南
class AnnotationTask:
"""Structure for human annotation task."""
def __init__(self, response, question, context=None):
self.response = response
self.question = question
self.context = context
def get_annotation_form(self):
return {
"question": self.question,
"context": self.context,
"response": self.response,
"ratings": {
"accuracy": {
"scale": "1-5",
"description": "Is the response factually correct?"
},
"relevance": {
"scale": "1-5",
"description": "Does it answer the question?"
},
"coherence": {
"scale": "1-5",
"description": "Is it logically consistent?"
}
},
"issues": {
"factual_error": False,
"hallucination": False,
"off_topic": False,
"unsafe_content": False
},
"feedback": ""
}
评估者间一致性
from sklearn.metrics import cohen_kappa_score
def calculate_agreement(rater1_scores, rater2_scores):
"""Calculate inter-rater agreement."""
kappa = cohen_kappa_score(rater1_scores, rater2_scores)
# Ordered ranges: Boolean dictionary keys overwrite each other.
interpretation = "Undefined"
for upper, label in [(0, "Poor"), (0.2, "Slight"), (0.4, "Fair"),
(0.6, "Moderate"), (0.8, "Substantial"),
(float("inf"), "Almost Perfect")]:
if kappa < upper:
interpretation = label
break
return {
"kappa": kappa,
"interpretation": interpretation
}
A/B 测试
统计检验框架
from scipy import stats
import numpy as np
class ABTest:
def __init__(self, variant_a_name="A", variant_b_name="B"):
self.variant_a = {"name": variant_a_name, "scores": []}
self.variant_b = {"name": variant_b_name, "scores": []}
def add_result(self, variant, score):
"""Add evaluation result for a variant."""
if variant == "A":
self.variant_a["scores"].append(score)
else:
self.variant_b["scores"].append(score)
def analyze(self, alpha=0.05):
"""Perform statistical analysis."""
a_scores = self.variant_a["scores"]
b_scores = self.variant_b["scores"]
if len(a_scores) < 2 or len(b_scores) < 2:
raise ValueError("At least two observations per independent group are required")
if not np.all(np.isfinite(a_scores)) or not np.all(np.isfinite(b_scores)):
raise ValueError("Require finite observations")
# Illustrative independent-group analysis; use a paired design for matched tasks.
# T-test
t_stat, p_value = stats.ttest_ind(a_scores, b_scores)
# Effect size (Cohen's d)
pooled_std = np.sqrt((np.std(a_scores)**2 + np.std(b_scores)**2) / 2)
cohens_d = (np.mean(b_scores) - np.mean(a_scores)) / pooled_std if pooled_std > 0 else None
return {
"variant_a_mean": np.mean(a_scores),
"variant_b_mean": np.mean(b_scores),
"difference": np.mean(b_scores) - np.mean(a_scores),
"relative_improvement": ((np.mean(b_scores) - np.mean(a_scores)) / np.mean(a_scores)
if np.mean(a_scores) != 0 else None),
"p_value": p_value,
"statistically_significant": p_value < alpha,
"cohens_d": cohens_d,
"effect_size": self.interpret_cohens_d(cohens_d) if cohens_d is not None else "undefined",
"winner": (("B" if np.mean(b_scores) > np.mean(a_scores) else "A")
if np.isfinite(p_value) and p_value < alpha and np.mean(b_scores) != np.mean(a_scores)
else "inconclusive")
}
@staticmethod
def interpret_cohens_d(d):
"""Interpret Cohen's d effect size."""
abs_d = abs(d)
if abs_d < 0.2:
return "negligible"
elif abs_d < 0.5:
return "small"
elif abs_d < 0.8:
return "medium"
else:
return "large"
回归测试
回归检测
class RegressionDetector:
def __init__(self, baseline_results, threshold=0.05, directions=None):
import math
if not baseline_results or not math.isfinite(threshold) or threshold < 0:
raise ValueError("Require baseline metrics and a finite nonnegative threshold")
self.baseline = baseline_results
self.threshold = threshold
self.directions = directions or {}
def check_for_regression(self, new_results):
"""Detect if new results show regression."""
import math
regressions = []
missing_metrics = []
for metric in self.baseline.keys():
baseline_score = self.baseline[metric]
new_score = new_results.get(metric)
if new_score is None:
missing_metrics.append(metric)
continue
if not math.isfinite(baseline_score) or not math.isfinite(new_score):
raise ValueError("Metrics must be finite")
# Calculate relative change
if baseline_score <= 0:
raise ValueError("Relative regression requires a positive nonzero baseline; use an absolute threshold instead.")
relative_change = (new_score - baseline_score) / baseline_score
# Default is higher-is-better; explicitly mark latency/cost as lower.
direction = self.directions.get(metric, "higher")
if direction not in {"higher", "lower"}:
raise ValueError("Unknown metric direction")
is_regression = (relative_change < -self.threshold if direction == "higher"
else relative_change > self.threshold)
if is_regression:
regressions.append({
"metric": metric,
"baseline": baseline_score,
"current": new_score,
"change": relative_change
})
return {
"has_regression": True if regressions else (None if missing_metrics else False),
"analysis_complete": not missing_metrics,
"missing_metrics": missing_metrics,
"regressions": regressions
}
基准测试
阅读本地基准测试示例,了解有界运行器、适配器前置条件和不完整数据处理方式。
本地资源和执行边界
本地基准测试参考资料包含一个示例运行器,而不是已安装的评估框架或数据集。以下代码展示的模式必须根据项目的实际依赖进行调整,并在使用前验证;llm_eval 接口是伪代码,并不代表存在已安装的软件包。请检查现有 SDK 版本,不要原样照搬过时的提供商调用。
使用经过脱敏且具有代表性的数据,并明确评估预算。模型下载、托管推理、付费评审调用和遥测数据导出,都需要获得针对相关数据和费用的授权。优先使用已缓存的模型;不要将提示词视为能够防御评估输出中嵌入指令的安全边界。请使用人工标注样本校准评审分数,并检查错误、偏差和分歧。重合度分数不能证明结果正确、安全或已达到生产就绪状态。
最佳实践
- 多种指标:使用多种指标进行全面评估
- 有代表性的数据:使用多样化的真实示例进行测试
- 基线:始终与基线性能进行比较
- 统计严谨性:使用适当的统计检验进行比较
- 持续评估:将评估集成到 CI/CD 管道中
- 人工验证:结合自动化指标与人工判断
- 错误分析:调查失败情况,了解薄弱之处
- 版本控制:持续跟踪评估结果
常见陷阱
- 执着于单一指标:为了优化一项指标而牺牲其他指标
- 样本量过小:根据过少的示例得出结论
- 数据污染:使用训练数据进行测试
- 忽略方差:未考虑统计不确定性
- 指标不匹配:使用与业务目标不一致的指标
局限性
- 仅当任务明确符合上述范围时,才使用此技能。
- 不要将输出视为针对特定环境进行验证、测试或专家审查的替代品。
- 如果缺少必要的输入、权限、安全边界或成功标准,请暂停并请求澄清。
评论
支持 Markdown