2026 年,超过 70% 的企业级 AI 应用采用 RAG 或 Agent 架构,但根据 LangChain 最新开发者调查,只有不到 30% 的团队建立了系统化的评测流程。大多数开发者还在用「肉眼看几个 case」的方式验证 LLM 应用的质量——这就像没有单元测试的后端服务一样危险。LLM 应用评测(Evaluation)不是一个可选项,而是你从 Demo 走向 Production 的必经之路。本文将从实战角度,帮你建立一套科学、可自动化的大模型应用评测体系。
📌 **记住:**LLM 应用和传统软件最大的区别是——同样的输入可能产生不同的输出。这意味着传统的断言式测试(
expect(result).toBe(...))几乎无法直接使用。你需要一套全新的评测方法论。
📊 一、LLM 评测的核心指标体系
1.1 为什么「看起来对」不等于「真的对」
大多数开发者的 LLM 评测方式是这样的:跑 5 个问题,看看回答「像不像对的」,然后就上线了。这种方式有两个致命缺陷:
- 样本量太小:5 个 case 根本无法覆盖边界情况
- 主观偏差:你写的 Prompt,你会倾向于认为它的输出是「对的」
LLM 应用评测需要量化指标。以下是生产环境中最常用的评测维度:
| 评测维度 | 指标名称 | 含义 | 适用场景 | 评测方式 |
|---|---|---|---|---|
| 忠实度 | Faithfulness | 回答是否忠于检索到的上下文 | RAG 应用 | LLM-as-Judge |
| 相关性 | Answer Relevancy | 回答是否与问题相关 | 所有 LLM 应用 | LLM-as-Judge |
| 上下文精度 | Context Precision | 检索到的文档是否精准 | RAG 应用 | 与 Ground Truth 对比 |
| 上下文召回 | Context Recall | 是否检索到了所有相关文档 | RAG 应用 | 与 Ground Truth 对比 |
| 有害性 | Toxicity | 回答是否包含有害内容 | 面向用户的应用 | 分类模型 |
| 幻觉率 | Hallucination Rate | 回答中包含未被上下文支持的信息比例 | 所有 LLM 应用 | LLM-as-Judge |
⚠️ **警告:**不要只关注「回答是否正确」这一个维度。一个 RAG 应用回答正确但检索了错误的文档,说明系统存在隐患——只是碰巧答对了而已。必须同时评估检索质量和生成质量。
1.2 Ground Truth 数据集:评测的基石
任何评测体系都需要一个标注好的测试数据集(Ground Truth Dataset)。这是整个评测的基础。
// ✅ 正确写法:结构化的评测数据集
const evaluationDataset = [
{
id: "eval-001",
question: "公司的年假政策是什么?",
groundTruth: "正式员工入职第一年享有 10 天年假,满两年后增至 15 天。",
contexts: [
"根据《员工手册》第3.2条:正式员工入职满一年后享有10天带薪年假,满两年后增至15天。"
],
metadata: { category: "HR政策", difficulty: "easy" }
},
{
id: "eval-002",
question: "如何申请报销差旅费?",
groundTruth: "通过 OA 系统提交差旅报销申请,附上发票照片,直属上级审批后财务 5 个工作日内打款。",
contexts: [
"差旅报销流程:1.登录OA系统 2.选择差旅报销 3.上传发票 4.提交审批 5.财务审核打款(5个工作日内)"
],
metadata: { category: "财务流程", difficulty: "medium" }
}
];
// ❌ 错误写法:随意的评测方式
// 看看回答像不像对的 → 不可量化、不可重复、不可自动化
💡 **提示:**测试数据集建议包含 50-200 条数据,覆盖简单问题、复杂问题、边界情况和对抗性问题(故意误导模型的提问)。数据集的质量直接决定了评测结果的可信度。
🔬 二、自动化评测实战
2.1 LLM-as-Judge:让大模型评价大模型
LLM-as-Judge 是目前最主流的自动化评测方法。核心思路是:用一个强大的 LLM(通常是 GPT-4o 或 Claude)来评判你的应用输出质量。
// LLM-as-Judge 评测实现
async function evaluateWithLLMJudge({ question, answer, context, groundTruth }) {
const judgePrompt = `你是一个严格的 AI 应用评测专家。请根据以下标准打分(1-5分):
## 评测任务
问题:${question}
AI 回答:${answer}
参考上下文:${context}
标准答案:${groundTruth}
## 评分标准
1. **忠实度(Faithfulness)**:回答是否完全基于提供的上下文,没有编造信息
- 5分:完全基于上下文
- 3分:大部分基于上下文,有少量推断
- 1分:大量编造信息
2. **相关性(Relevancy)**:回答是否准确回答了问题
- 5分:完美回答了问题
- 3分:部分回答了问题
- 1分:答非所问
3. **完整性(Completeness)**:回答是否涵盖了标准答案的所有关键信息点
- 5分:覆盖所有关键点
- 3分:覆盖了主要信息但有遗漏
- 1分:严重遗漏
请严格按以下 JSON 格式输出,不要输出任何其他内容:
{
"faithfulness": <1-5>,
"relevancy": <1-5>,
"completeness": <1-5>,
"reasoning": "<简要说明扣分原因>"
}`;
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${process.env.OPENAI_API_KEY}`
},
body: JSON.stringify({
model: "gpt-4o",
messages: [{ role: "user", content: judgePrompt }],
temperature: 0.1, // 评测需要确定性,温度设低
response_format: { type: "json_object" }
})
});
const data = await response.json();
return JSON.parse(data.choices[0].message.content);
}
// 批量评测
async function runBatchEvaluation(dataset) {
const results = [];
for (const item of dataset) {
// 1. 调用你的 RAG 应用获取回答
const answer = await callYourRAGApp(item.question);
// 2. 用 LLM-as-Judge 评测
const scores = await evaluateWithLLMJudge({
question: item.question,
answer,
context: item.contexts.join("\n"),
groundTruth: item.groundTruth
});
results.push({ id: item.id, answer, ...scores });
}
// 3. 计算汇总指标
const avgScores = {
faithfulness: avg(results.map(r => r.faithfulness)),
relevancy: avg(results.map(r => r.relevancy)),
completeness: avg(results.map(r => r.completeness))
};
console.log("评测汇总:", avgScores);
return { results, avgScores };
}
function avg(arr) {
return (arr.reduce((a, b) => a + b, 0) / arr.length).toFixed(2);
}
⚠️ **警告:**LLM-as-Judge 存在 自我偏好偏差(Self-Preference Bias)——GPT-4o 倾向于给 GPT-4o 的输出打更高分。建议用不同厂商的模型做 Judge,例如用 Claude 评测 GPT 的输出,反之亦然。
2.2 RAGAS 框架:标准化的 RAG 评测
RAGAS(Retrieval Augmented Generation Assessment)是目前最流行的 RAG 评测框架,提供了开箱即用的评测指标和标准化流程。
# 安装 RAGAS
pip install ragas
pip install langchain-openai # 需要 LLM 作为评测器
# 使用 RAGAS 评测 RAG 应用
from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
context_precision,
context_recall,
)
from datasets import Dataset
# 准备评测数据(RAGAS 要求的格式)
eval_data = {
"question": [
"公司的年假政策是什么?",
"如何申请报销差旅费?",
"远程办公需要满足什么条件?",
],
"answer": [
# 你的 RAG 应用生成的回答
"正式员工入职第一年有10天年假,满两年后增至15天。",
"通过OA系统提交报销申请,上传发票后等待审批和打款。",
"入职满6个月且绩效评级B+以上可申请远程办公。",
],
"contexts": [
["正式员工入职满一年后享有10天带薪年假,满两年后增至15天。"],
["差旅报销流程:登录OA系统→差旅报销→上传发票→提交审批→财务打款(5个工作日)"],
["远程办公条件:入职满6个月、最近一次绩效评级B+及以上、直属上级同意。"],
],
"ground_truth": [
"正式员工入职第一年享有10天年假,满两年后增至15天。",
"通过OA系统提交差旅报销申请,附上发票照片,直属上级审批后财务5个工作日内打款。",
"入职满6个月且绩效评级B+以上,经直属上级同意后可申请远程办公。",
],
}
dataset = Dataset.from_dict(eval_data)
# 执行评测
result = evaluate(
dataset=dataset,
metrics=[
faithfulness, # 忠实度:回答是否基于上下文
answer_relevancy, # 相关性:回答是否切题
context_precision, # 上下文精度:检索是否精准
context_recall, # 上下文召回:是否遗漏相关文档
],
)
print(result)
# 输出示例:
# {'faithfulness': 0.92, 'answer_relevancy': 0.88,
# 'context_precision': 0.85, 'context_recall': 0.90}
# 导出详细结果
df = result.to_pandas()
df.to_csv("evaluation_results.csv", index=False)
RAGAS 四大核心指标的工作原理和区别:
| 指标 | 评估对象 | 计算方式 | 分数含义 |
|---|---|---|---|
| Faithfulness | 生成质量 | 将回答拆分为声明,逐一验证是否被上下文支持 | 1.0 = 完全忠实,无幻觉 |
| Answer Relevancy | 生成质量 | 用 LLM 从回答反向生成问题,计算与原问题的相似度 | 1.0 = 完全切题 |
| Context Precision | 检索质量 | 相关文档在检索结果中的排名位置 | 1.0 = 相关文档排在最前面 |
| Context Recall | 检索质量 | 标准答案中的信息点是否都被检索到的文档覆盖 | 1.0 = 无遗漏 |
💡 **提示:**Context Precision 和 Context Recall 的区别经常被搞混。简单理解:Precision 关注「检索到的是不是都是有用的」,Recall 关注「有用的有没有都被检索到」。一个高一个低说明你的检索策略有偏向。
🔄 三、Prompt 回归测试与 CI/CD 集成
3.1 为什么 Prompt 改动需要回归测试
Prompt 是 LLM 应用的「代码」。当你修改 Prompt 修复一个问题时,可能会意外影响其他问题的回答质量——这就是 Prompt 回归(Prompt Regression)。
// Prompt 回归测试框架
class PromptRegressionTester {
constructor(config) {
this.dataset = config.dataset; // 评测数据集
this.threshold = config.threshold; // 最低通过分数
this.ragApp = config.ragApp; // 被测应用
this.judge = config.judge; // 评测器
this.history = []; // 历史结果
}
async runTest(promptVersion) {
const results = [];
for (const item of this.dataset) {
const answer = await this.ragApp.query(item.question, {
promptVersion // 指定 Prompt 版本
});
const scores = await this.judge.evaluate({
question: item.question,
answer,
groundTruth: item.groundTruth
});
results.push({
id: item.id,
question: item.question,
answer,
scores,
passed: scores.overall >= this.threshold
});
}
const summary = {
promptVersion,
timestamp: new Date().toISOString(),
total: results.length,
passed: results.filter(r => r.passed).length,
failed: results.filter(r => !r.passed).length,
avgScore: avg(results.map(r => r.scores.overall)),
details: results
};
this.history.push(summary);
return summary;
}
// 对比两个 Prompt 版本的评测结果
compareVersions(versionA, versionB) {
const resultA = this.history.find(h => h.promptVersion === versionA);
const resultB = this.history.find(h => h.promptVersion === versionB);
if (!resultA || !resultB) {
throw new Error("请先运行两个版本的评测");
}
const regressions = [];
const improvements = [];
for (const a of resultA.details) {
const b = resultB.details.find(r => r.id === a.id);
if (!b) continue;
const diff = b.scores.overall - a.scores.overall;
if (diff < -0.3) {
regressions.push({
id: a.id,
question: a.question,
oldScore: a.scores.overall,
newScore: b.scores.overall,
diff
});
} else if (diff > 0.3) {
improvements.push({
id: a.id,
question: a.question,
oldScore: a.scores.overall,
newScore: b.scores.overall,
diff
});
}
}
return {
versionA,
versionB,
overallDiff: resultB.avgScore - resultA.avgScore,
regressions,
improvements,
verdict: regressions.length > 0
? `❌ 发现 ${regressions.length} 个回归,请检查后再合并`
: `✅ 无回归,可以安全合并`
};
}
}
// 使用示例
const tester = new PromptRegressionTester({
dataset: evaluationDataset,
threshold: 3.5,
ragApp: myRagApplication,
judge: llmJudge
});
// 测试新版本 Prompt
const result = await tester.runTest("v2.1");
console.log(`通过率: ${result.passed}/${result.total}`);
console.log(`平均分: ${result.avgScore}`);
// 与旧版本对比
const comparison = tester.compareVersions("v2.0", "v2.1");
console.log(comparison.verdict);
3.2 CI/CD 集成:让评测成为发布流程的一部分
将 LLM 评测集成到 CI/CD 流程中,确保每次 Prompt 修改都经过自动化验证:
# .github/workflows/llm-evaluation.yml
name: LLM Evaluation Pipeline
on:
pull_request:
paths:
- 'prompts/**' # Prompt 文件变更时触发
- 'rag-config/**' # RAG 配置变更时触发
jobs:
evaluate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run LLM Evaluation
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
EVAL_DATASET_PATH: ./eval/dataset.json
run: npm run evaluate
- name: Compare with baseline
run: npm run evaluate:compare -- --baseline main
- name: Upload evaluation report
uses: actions/upload-artifact@v4
with:
name: eval-report
path: ./eval/reports/
- name: Comment PR with results
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const report = JSON.parse(fs.readFileSync('./eval/reports/latest.json'));
const body = `## 🤖 LLM 评测结果\n\n` +
`| 指标 | 得分 | 阈值 | 状态 |\n` +
`|------|------|------|------|\n` +
`| 忠实度 | ${report.faithfulness} | ≥0.85 | ${report.faithfulness >= 0.85 ? '✅' : '❌'} |\n` +
`| 相关性 | ${report.relevancy} | ≥0.80 | ${report.relevancy >= 0.80 ? '✅' : '❌'} |\n` +
`| 上下文精度 | ${report.context_precision} | ≥0.75 | ${report.context_precision >= 0.75 ? '✅' : '❌'} |\n` +
`\n**通过率:** ${report.passed}/${report.total}`;
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body
});
⚠️ **警告:**LLM 评测的运行需要调用 API,有实际成本。建议在 CI 中使用较小的测试集(10-20 条),完整测试集在发布前手动触发。按 GPT-4o 的定价,20 条评测大约花费 $0.5-1.0。
3.3 在线监控:生产环境的质量防线
评测不只在发布前做,上线后也需要持续监控。以下是生产环境 LLM 质量监控的核心策略:
// 生产环境 LLM 质量监控
class LLMQualityMonitor {
constructor(config) {
this.sampleRate = config.sampleRate || 0.1; // 采样 10% 的请求
this.alertThreshold = config.alertThreshold || 3.0;
this.metrics = {
totalRequests: 0,
sampledRequests: 0,
avgFaithfulness: 0,
avgRelevancy: 0,
hallucinationCount: 0,
userComplaints: 0
};
}
async onRequest(question, answer, context) {
this.metrics.totalRequests++;
// 采样策略:不是每个请求都评测(成本太高)
if (Math.random() > this.sampleRate) return;
this.metrics.sampledRequests++;
// 快速检测:关键词匹配 + 简单规则
const quickScore = this.quickCheck(answer, context);
if (quickScore < this.alertThreshold) {
// 低分回答触发详细评测
const detailedScore = await this.detailedEvaluation({
question, answer, context
});
if (detailedScore.overall < this.alertThreshold) {
await this.sendAlert({
question,
answer: answer.substring(0, 200),
score: detailedScore,
severity: detailedScore.overall < 2.0 ? "critical" : "warning"
});
}
// 检测幻觉:回答中出现上下文没有的具体数据
if (detailedScore.faithfulness < 2.0) {
this.metrics.hallucinationCount++;
}
}
this.updateRunningAverage(question, answer, context);
}
// 快速检测(不调用 LLM,零成本)
quickCheck(answer, context) {
let score = 5.0;
// 规则1:回答中出现具体数字但上下文中没有
const numbersInAnswer = answer.match(/\d+(\.\d+)?/g) || [];
const numbersInContext = context.match(/\d+(\.\d+)?/g) || [];
const hallucinatedNumbers = numbersInAnswer.filter(
n => !numbersInContext.includes(n)
);
if (hallucinatedNumbers.length > 0) score -= 1.0;
// 规则2:回答过短(可能信息不完整)
if (answer.length < 20) score -= 0.5;
// 规则3:回答包含「我不确定」「可能是」等不确定表述
const uncertainPhrases = ["不确定", "可能是", "也许", "我猜", "据说"];
if (uncertainPhrases.some(p => answer.includes(p))) score -= 0.5;
return score;
}
getReport() {
return {
...this.metrics,
hallucinationRate: (
this.metrics.hallucinationCount / this.metrics.sampledRequests * 100
).toFixed(1) + "%",
reportTime: new Date().toISOString()
};
}
}
💡 **提示:**生产环境的采样率建议从 5%-10% 开始,根据成本预算调整。如果你使用的是 Claude Haiku 或 GPT-4o-mini 等低成本模型做 Judge,可以适当提高采样率到 20%-30%。
🎯 四、评测最佳实践与避坑指南
4.1 常见的评测陷阱
在实际项目中,以下是团队最容易踩的坑:
-
❌ 只测 happy path:只用精心构造的「标准问题」测试,忽略了用户的真实提问方式(口语化、有错别字、问题模糊)
-
❌ Judge 模型和被测模型相同:GPT-4o 给 GPT-4o 的输出打分偏高,存在自我偏好
-
❌ 一次性评测:上线前做了一次评测就再也不做了,Prompt 环境变化后质量悄悄下降
-
❌ 忽略延迟评测:回答质量很高但响应要 30 秒,用户体验照样很差
-
✅ 测试集包含真实用户问题:从生产日志中抽取真实问题作为测试集
-
✅ 交叉评测:用不同厂商的模型做 Judge
-
✅ 持续评测:每次 Prompt 变更、模型切换都触发回归测试
-
✅ 多维度评测:同时评估质量、延迟、成本三个维度
4.2 评测驱动的开发流程
将评测融入开发流程的推荐方式:
开发者修改 Prompt
↓
本地运行小型评测(10 条 case,30 秒内出结果)
↓
提交 PR → CI 自动运行中型评测(50 条 case,3 分钟)
↓
PR 评论中展示评测结果和回归检测
↓
合并后 → 夜间运行完整评测(200 条 case)
↓
每周生成质量趋势报告
⚡ **关键结论:**评测不是一次性的任务,而是持续的质量保障流程。把它想象成 LLM 应用的「单元测试」——你不会写完一次测试就再也不跑了,对吧?
💰 五、评测成本控制策略
LLM-as-Judge 需要调用 API,评测本身也有成本。以下是经过实践验证的成本优化方案:
| 评测阶段 | 测试集规模 | 推荐 Judge 模型 | 单次成本 | 频率 |
|---|---|---|---|---|
| 本地开发 | 10 条 | GPT-4o-mini | ~$0.02 | 每次修改 |
| PR 审查 | 50 条 | GPT-4o-mini | ~$0.10 | 每次提交 |
| 发布前 | 200 条 | GPT-4o | ~$2.00 | 每次发布 |
| 生产监控 | 10% 采样 | GPT-4o-mini | ~$0.50/天 | 持续运行 |
💡 **提示:**先用便宜模型(GPT-4o-mini / Claude Haiku)做初筛,只对低分回答用强模型做二次评测,可以将评测成本降低 60%-70%,同时不损失评测精度。这种「两级评测」策略在大规模生产环境中非常实用。
另一个常被忽略的成本是标注成本。如果你的测试集需要人工标注 Ground Truth,建议先让 LLM 自动生成初始标注,再由人工审核修正。这样标注效率可以提升 3-5 倍。同时,测试集应该定期更新——从生产日志中挖掘新的代表性问题,淘汰已经过时的 case,保持测试集与真实用户场景的同步。
✅ 总结
LLM 应用评测的核心要点:
- 建立指标体系:不要凭感觉判断,用 Faithfulness、Relevancy、Context Precision、Context Recall 四大指标量化质量
- 构建测试数据集:50-200 条标注好的测试数据是评测的基石,从真实用户问题中抽取
- 自动化评测:用 LLM-as-Judge 或 RAGAS 框架实现自动化,避免人工逐条检查
- CI/CD 集成:每次 Prompt 修改都触发回归测试,防止修一个问题引入三个新问题
- 生产监控:上线后持续采样评测,及时发现质量下降
📎 相关工具推荐
| 工具 | 用途 | 链接 |
|---|---|---|
| RAGAS | RAG 评测框架 | github.com/explodinggradients/ragas |
| DeepEval | 通用 LLM 评测 | github.com/confident-ai/deepeval |
| LangSmith | LangChain 可观测性平台 | smith.langchain.com |
| Promptfoo | Prompt 评测与红队测试 | github.com/promptfoo/promptfoo |
| Braintrust | AI 评测与日志平台 | braintrust.dev |
📌 **记住:**没有评测体系的 LLM 应用就像没有测试的后端服务——它可能现在能跑,但你不知道什么时候会翻车,也不知道翻车有多严重。花一天时间搭建评测体系,可以省下一周的线上排障时间。