AI 应用评估实战——如何量化大模型的回答质量
AI 应用评估实战——如何量化大模型的回答质量
适读人群:AI 应用开发者、技术负责人 | 阅读时长:约16分钟 | 核心价值:建立可量化的 AI 评估体系,让"感觉好"变成"数据好"
上线了一个 AI 功能,领导问:这个 AI 效果怎么样?
如果你的回答是"感觉还不错",那你的 AI 项目就没有评估体系,迟早出问题。
这篇文章讲如何建立 AI 应用的量化评估体系,让你能用数据回答这个问题。
为什么需要评估体系
没有评估体系会导致:
- 模型迭代没有基准,不知道改了 Prompt 是变好还是变差
- 上线前没有验收标准,只能靠"感觉"
- 出了问题不知道哪个环节出了问题
- 无法向业务方说明 AI 的价值
有了评估体系:
- Prompt 修改前后有对比数据
- 上线前跑测试集,通过率达到阈值才上
- 持续监控线上表现,异常及时告警
评估维度体系
根据不同场景,评估维度不同。以下是最常用的维度:
通用维度:
| 维度 | 含义 | 评估方式 |
|---|---|---|
| 准确性(Accuracy) | 回答是否正确 | 人工标注 / 自动对比 |
| 相关性(Relevance) | 回答是否切题 | LLM-as-Judge |
| 完整性(Completeness) | 关键信息是否齐全 | 人工评估 / 关键词检测 |
| 忠实性(Faithfulness) | RAG 场景:回答是否基于文档 | LLM-as-Judge |
| 无害性(Harmlessness) | 是否包含有害内容 | 规则过滤 + 模型检测 |
| 格式符合率 | 输出格式是否符合要求 | 自动化规则检测 |
LLM-as-Judge:用大模型评估大模型
这是目前最实用的自动评估方法,用一个"评估模型"来打分:
@Service
public class LlmJudgeService {
private final ChatModel judgeModel; // 用 GPT-4o 做评估
private static final String RELEVANCE_JUDGE_PROMPT = """
你是一个专业的 AI 回答质量评估员。
请评估以下 AI 回答的相关性。
【用户问题】
{question}
【AI 回答】
{answer}
【评分标准】
5分:完全切题,精准回答了用户问题
4分:基本切题,有少量偏差
3分:部分相关,回答了一部分但有遗漏
2分:相关性低,主要内容偏离问题
1分:完全不相关
请返回 JSON 格式:
{
"score": <1-5的整数>,
"reason": "<评分原因,20字以内>"
}
""";
public EvalScore evaluateRelevance(String question, String answer) {
String prompt = RELEVANCE_JUDGE_PROMPT
.replace("{question}", question)
.replace("{answer}", answer);
String result = judgeModel.generate(prompt);
return parseScore(result);
}
private static final String FAITHFULNESS_JUDGE_PROMPT = """
【参考文档】
{context}
【AI 回答】
{answer}
请判断 AI 回答中的每个陈述是否有参考文档的支持。
返回 JSON:
{
"faithfulness_score": <0.0-1.0,支持比例>,
"hallucinated_claims": ["<不在文档中的陈述1>", ...]
}
""";
public FaithfulnessScore evaluateFaithfulness(String context, String answer) {
String prompt = FAITHFULNESS_JUDGE_PROMPT
.replace("{context}", context)
.replace("{answer}", answer);
String result = judgeModel.generate(prompt);
return parseFaithfulnessScore(result);
}
/**
* 批量评估,并发执行提高效率
*/
public List<EvalResult> batchEvaluate(List<EvalSample> samples) {
return samples.parallelStream()
.map(sample -> {
EvalScore relevance = evaluateRelevance(sample.question(), sample.answer());
EvalScore completeness = evaluateCompleteness(
sample.question(), sample.answer(), sample.expectedKeyPoints());
return new EvalResult(
sample.id(),
relevance,
completeness,
(relevance.score() + completeness.score()) / 2.0
);
})
.collect(toList());
}
}评估数据集构建
评估数据集是整个体系的基础,需要认真设计:
@Entity
@Table(name = "eval_datasets")
public class EvalSample {
private String id;
private String datasetName; // 数据集名称
private String question; // 用户问题
private String referenceAnswer; // 参考答案(人工撰写)
private String context; // RAG 场景的参考文档
@ElementCollection
private List<String> keyPoints; // 答案中必须包含的关键点
private String difficulty; // EASY/MEDIUM/HARD
private String category; // 问题类别
private String createdBy;
private String lastVerifiedBy; // 最后验证人
}如何快速构建评估数据集:
- 从真实用户对话里采样:选取有代表性的历史对话
- 覆盖边界情况:特意构造一些容易出错的问题
- 人工标注参考答案:每道题的参考答案由领域专家写
- 定期更新:随着业务变化,数据集也要更新
自动化评估流水线
@Service
@Slf4j
public class EvalPipeline {
private final EvalDatasetService datasetService;
private final RagQueryService ragService;
private final LlmJudgeService judgeService;
private final EvalReportService reportService;
/**
* 运行一次完整评估
*/
@Async
public CompletableFuture<EvalReport> runEvaluation(
String datasetName,
String modelVersion,
String promptVersion) {
log.info("开始评估:dataset={}, model={}, prompt={}",
datasetName, modelVersion, promptVersion);
List<EvalSample> samples = datasetService.loadDataset(datasetName);
List<EvalResult> results = new ArrayList<>();
for (EvalSample sample : samples) {
try {
// 1. 运行 AI
long startTime = System.currentTimeMillis();
String answer = ragService.query(sample.getQuestion(), null).getAnswer();
long latency = System.currentTimeMillis() - startTime;
// 2. 自动评估
EvalScore relevance = judgeService.evaluateRelevance(
sample.getQuestion(), answer);
EvalScore faithfulness = judgeService.evaluateFaithfulness(
sample.getContext(), answer);
// 3. 关键词覆盖率(快速检测)
double keyPointCoverage = calculateKeyPointCoverage(
answer, sample.getKeyPoints());
results.add(EvalResult.builder()
.sampleId(sample.getId())
.answer(answer)
.relevanceScore(relevance.score())
.faithfulnessScore(faithfulness.score())
.keyPointCoverage(keyPointCoverage)
.latencyMs(latency)
.build());
} catch (Exception e) {
log.error("样本 {} 评估失败: {}", sample.getId(), e.getMessage());
results.add(EvalResult.failed(sample.getId(), e.getMessage()));
}
}
// 4. 生成报告
EvalReport report = reportService.generate(
datasetName, modelVersion, promptVersion, results);
log.info("评估完成:总样本={}, 平均相关性={:.2f}, 平均忠实性={:.2f}",
results.size(), report.avgRelevance(), report.avgFaithfulness());
return CompletableFuture.completedFuture(report);
}
private double calculateKeyPointCoverage(String answer, List<String> keyPoints) {
if (keyPoints == null || keyPoints.isEmpty()) return 1.0;
long covered = keyPoints.stream()
.filter(kp -> answer.toLowerCase().contains(kp.toLowerCase()))
.count();
return (double) covered / keyPoints.size();
}
}线上监控指标
评估不只在上线前,线上也要持续监控:
@Component
public class OnlineQualityMonitor {
private final MeterRegistry meterRegistry;
// 用户满意度(基于点踩、重新生成等行为反推)
public void recordUserFeedback(String conversationId, FeedbackType type) {
meterRegistry.counter("ai.feedback",
"type", type.name(),
"result", type == FeedbackType.THUMBS_UP ? "positive" : "negative"
).increment();
}
// 回答长度异常监控(突然变很短可能是 Prompt 问题)
public void recordAnswerLength(int tokenCount) {
meterRegistry.summary("ai.answer.token_count").record(tokenCount);
}
// 拒绝回答率(模型频繁拒绝可能是安全策略过严)
public void recordRefusal(boolean refused, String category) {
if (refused) {
meterRegistry.counter("ai.refusal", "category", category).increment();
}
}
// 工具调用失败率
public void recordToolCallResult(String toolName, boolean success) {
meterRegistry.counter("ai.tool_call",
"tool", toolName,
"status", success ? "success" : "failure"
).increment();
}
}告警规则(Grafana/Prometheus):
# 满意度低于 80% 触发告警
- alert: AISatisfactionLow
expr: rate(ai_feedback_total{result="positive"}[1h]) /
rate(ai_feedback_total[1h]) < 0.8
# 拒绝率超过 5% 触发告警
- alert: AIRefusalRateHigh
expr: rate(ai_refusal_total[1h]) / rate(ai_requests_total[1h]) > 0.05踩坑实录
坑一:LLM-as-Judge 存在偏见
现象:用 GPT-4o 做评估,发现它给 GPT-4o 本身的回答打分明显偏高,给其他模型偏低。
原因:LLM 评估存在"自我偏好"(Self-preference bias),模型倾向于给风格相似的输出打高分。
解法:用不同家的模型做评估(比如用 Claude 评估 GPT 的输出),或者同时用多个模型评估取平均,或者设计更客观的评估 Prompt(给出明确标准,减少主观判断空间)。
坑二:评估数据集"泄露"到模型训练
现象:某些公开的评估数据集,模型在训练时可能已经见过,导致评估结果虚高。
原因:公开数据集(如 MMLU、HellaSwag)可能进入了模型训练数据,模型"记住"了答案而不是真的会推理。
解法:维护自己私有的评估数据集,覆盖你的具体业务场景,不要全依赖公开 Benchmark。
坑三:评估指标好但用户体验差
现象:所有指标评分都在 4.5 分以上,但用户投诉 AI 回答太啰嗦。
原因:评估维度里没有"简洁性",而模型倾向于生成长答案,在完整性上得分高,但用户不喜欢。
解法:把用户真实反馈也纳入评估体系,定期分析差评找共性,补充新的评估维度。
评估工具推荐
开源:
- Ragas:专门针对 RAG 系统的评估框架,支持 Java 接口
- TruLens:端到端 LLM 应用评估
- DeepEval:功能全面,支持多种评估指标
自建建议:核心评估逻辑自己写,用上面的工具做辅助验证。
建立评估文化,而不只是评估系统
说完了工具和系统,我想聊一个更重要的话题:评估文化。
很多团队有了评估系统,但没有评估文化,结果评估沦为摆设。评估文化的建立需要以下几点:
把评估纳入发布流程:
就像单元测试必须通过才能合并 PR 一样,AI 系统的每次更新(不管是 Prompt 还是模型版本),都必须跑评估集并且通过阈值才能发布。
这个要靠 CI/CD 流水线强制执行,而不是靠人自觉。
一个简单的 GitHub Actions 示例:
# .github/workflows/ai-eval.yml
name: AI Quality Check
on:
pull_request:
paths:
- 'src/main/resources/prompts/**'
- 'src/main/java/**/ai/**'
jobs:
eval:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run AI Evaluation
run: |
./gradlew runAiEvaluation
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- name: Check Pass Rate
run: |
python3 scripts/check_eval_results.py --min-pass-rate 0.85如果评估通过率低于 85%,PR 就不能合并。
建立评估数据集的维护责任:
评估数据集不是一次性的,需要有人持续维护。常见的问题是:业务变化了,评估数据集没有更新,导致评估通过了但线上还是出问题。
建议:每个业务场景的评估数据集,指定一个业务负责人(不一定是工程师),由他来定期审查和更新。工程师负责系统实现,业务方负责数据质量。
让评估结果对所有人可见:
评估结果不应该只有工程师知道。建立一个简单的 Dashboard(Grafana 或内部页面),让产品经理、业务方都能看到 AI 质量趋势。
当 PM 能直接看到"这次 Prompt 修改让满意度从 82% 提升到了 88%",他们会对 AI 质量更有感知,也会更支持做评估体系这件事。
一个真实的评估体系建设案例
分享一下我见过的一个做得比较好的客服 AI 评估体系,给大家参考。
这是一家电商公司,客服 AI 上线 3 个月后,他们建立了这样的评估体系:
线下评估(每次发布前):测试集 500 条,覆盖 12 个意图类别,每类 40-50 条。评估维度:意图识别准确率、关键信息提取完整率、拒绝回答的精准率(不该拒绝的没拒绝,该拒绝的没放过)。自动化执行,30 分钟跑完,阈值 90%。
线上实时监控:用户点赞点踩、对话转人工率(AI 解决不了就转人工,这个比率很能说明问题)、平均对话轮次(轮次越少说明 AI 越快解决问题)。
每周复盘:人工抽查当周差评对话前 50 条,分析共性问题,更新测试集,修复问题。
每月迭代:基于复盘结果优化 Prompt 或知识库,重新跑全量评估,发布新版本。
这套体系运行了半年,客服 AI 的用户满意度从 68% 提升到了 84%,转人工率从 45% 降到了 22%。
这就是评估体系的价值——不只是发现问题,而是构建了一个持续改进的闭环。
