【SpringBoot 3.x 第214节】AI 结果评估体系——准确率、相关性、幻觉率,一文带你了解!
🏆本文收录于《滚雪球学SpringBoot 3.x》,专门攻坚指数提升,本年度国内最系统+最专业+最详细(永久更新)。
该专栏致力打造最硬核 SpringBoot3 从零基础到进阶系列学习内容,🚀均为全网独家首发,打造精品专栏,专栏持续更新中…欢迎大家订阅持续学习。 如果想快速定位学习,可以看这篇【SpringBoot3教程导航帖】,你想学习的都被收集在内,快速投入学习!!两不误。
若还想学习更多,可直接订阅 《Spring Boot实战合集》,一次订阅,持续学习,后续更新内容无需重复付费,适合长期收藏与系统进阶。
演示环境说明:
- 开发工具:IDEA 2021.3
- JDK版本: JDK 17(推荐使用 JDK 17 或更高版本,因为 Spring Boot 3.x 系列要求 Java 17,Spring Boot 3.5.4 基于 Spring Framework 6.x 和 Jakarta EE 9,它们都要求至少 JDK 17。)
- Spring Boot版本:3.5.4(于25年7月24日发布)
- Maven版本:3.8.2 (或更高)
- Gradle:(如果使用 Gradle 构建工具的话):推荐使用 Gradle 7.5 或更高版本,确保与 JDK 17 兼容。
- 操作系统:Windows 11
全文目录:
-
- 一、为什么 AI 系统不能只看“能不能答出来”?
- 二、评估体系的核心目标:准确率、相关性、幻觉率
- 三、主观评估与自动评估为什么要结合?
- 四、Groundedness、Faithfulness、Relevance 的含义与差异
- 五、如何建立评测集与回归测试集
- 六、Spring Boot 3.x 下的评估服务设计
- 七、代码实战一:定义评估模型与评分接口
- 八、代码实战二:构建一个可运行的评估 API
- 九、代码实战三:RAG 场景下的 Groundedness 与 Faithfulness 评分
- 十、代码实战四:评测集回归测试与结果持久化
- 十一、评估体系的工程化落地:日志、版本、阈值、告警
- 十二、用流程图画出整体评估流程
- 十三、再看一张“指标关系图”
- 十四、一个完整的评估闭环应该长什么样
- 十五、常见误区与最佳实践
- 十六、从 Spring Boot 3.x 的角度看,为什么这套体系适合工程化
- 十七、继续拓展延伸
- 十八、总结
- 🧧 学习福利 · 限时开放 🧧
- 🫵 Who am I?
一、为什么 AI 系统不能只看“能不能答出来”?
很多刚接触大模型的团队,第一反应往往是:只要模型能答出来就行。这个判断在 demo 阶段看似成立,但一旦进入真实业务,就会迅速失效。
原因很简单:“答出来”只是最弱的目标,“答得对、答得稳、答得可追溯”才是上线标准。
例如,你让 AI 回答“某个产品是否支持退款”,模型可能给出一段看起来非常流畅的答案,但其中可能混入了:
- 过时的制度信息;
- 与当前文档无关的“常识性补充”;
- 把别的产品规则套到了当前产品上;
- 甚至直接编造一个并不存在的条款。
这些问题在用户眼里不只是“答错了”,而是会直接破坏信任。AI 系统的风险不是“不会说话”,而是“会说得太像真的”。
所以,AI 评估不能只盯着最终输出文本是否通顺,而要看三个更关键的问题:
- 答案是否准确:和标准答案、事实依据是否一致?
- 答案是否相关:是否真正回答了用户问题?有没有跑题?
- 答案是否幻觉:是否引用了不存在的信息,或脱离上下文胡乱推断?
这三点,分别对应我们今天的核心主题:准确率、相关性、幻觉率。
二、评估体系的核心目标:准确率、相关性、幻觉率
AI 结果评估可以被看成一个多维度评分系统。不是单一分数,而是多个维度组合后的综合画像。
1)准确率:答案是否“对”
准确率强调的是输出与事实之间的一致性。
在不同场景中,准确率的定义会略有不同:
- 在问答场景中,答案是否命中标准答案;
- 在分类场景中,预测标签是否正确;
- 在检索增强生成(RAG)场景中,答案是否与检索到的证据一致;
- 在业务流程场景中,AI 是否给出了可执行且正确的建议。
准确率不是单纯的“对或错”,而是可以拆成多个层次:
- 事实是否正确;
- 数值是否正确;
- 实体是否正确;
- 逻辑关系是否正确。
2)相关性:答案是否“答到点上”
相关性更关注“有没有回答用户真正的问题”。
一个回答可能事实正确,但如果它回答的是另一个问题,就没有价值。比如用户问的是“如何配置 Spring Boot 3.x 的默认时区”,模型却开始长篇讲“Java 时间类型的历史演进”,这就属于相关性不足。
相关性通常包括:
- 主题是否一致;
- 回答层次是否匹配问题复杂度;
- 是否紧扣上下文;
- 是否包含用户最关心的点。
3)幻觉率:答案中“编出来”的成分有多少
幻觉率是 AI 评估里最值得警惕的指标之一。
所谓幻觉,不一定是模型完全胡说八道,更常见的是:
- 把没见过的信息说得非常确定;
- 引用了不存在的文档、接口、字段、政策;
- 逻辑推断超出证据范围;
- 用“看起来合理”的语言掩盖事实上的空白。
在业务系统中,幻觉率高,意味着模型虽然“流畅”,但不可用。
三、主观评估与自动评估为什么要结合?
很多团队在评测 AI 时容易走两个极端:
- 只靠人工打分,成本高、速度慢、难扩展;
- 只靠自动指标,虽然高效,但容易失真。
因此,真正可落地的方案通常是:主观评估 + 自动评估结合。
主观评估的价值
主观评估适合判断一些“机器很难完整理解”的问题,例如:
- 回答是否自然;
- 语气是否符合品牌风格;
- 是否真正满足用户意图;
- 多步骤推理是否有逻辑漏洞。
它的优点是能捕捉细节,缺点是主观性强、规模化困难。
自动评估的价值
自动评估擅长做批量、重复、可回归的判断,比如:
- 关键词匹配;
- 语义相似度;
- 引用证据覆盖率;
- 答案是否包含上下文中的关键事实;
- 是否出现禁用词、敏感词、无关内容。
它的优点是稳定、高效、可持续,缺点是有时并不能代表真实体验。
最合理的组合方式
可以把评估体系设计成两层:
- 基础层:自动评估,负责大规模筛查和回归;
- 人工层:主观评估,负责抽样复核、模型升级审核、争议样本判定。
这样做的好处是:
- 自动指标提供“趋势”;
- 人工评审提供“校准”;
- 两者结合可以降低误判。
四、Groundedness、Faithfulness、Relevance 的含义与差异
这三个词在 AI 评估里经常同时出现,但它们并不完全一样。
1)Groundedness:答案是否“扎根于证据”
Groundedness 关注的是:答案里的陈述,是否能在给定材料中找到支撑。
例如,RAG 场景中模型读取了三段文档,然后生成答案。我们要检查答案中的每个关键断言,是否都能在文档中找到来源。
如果答案包含文档里没有的内容,即使它“听起来对”,也可能 groundedness 不足。
2)Faithfulness:答案是否“忠实于输入与证据”
Faithfulness 关注的是:模型有没有背离输入内容、有没有曲解上下文、有没有为了组织语言而篡改事实。
Groundedness 更偏向“有没有依据”;Faithfulness 更偏向“有没有忠实表达依据”。
举个例子:
- 文档说“Spring Boot 3.x 要求 Java 17 及以上”;
- 模型回答“Spring Boot 3.x 推荐 Java 11”。
这里它不是完全无依据地胡编,而是对证据进行了错误转述,所以 faithfulness 下降。
3)Relevance:答案是否“和问题有关”
Relevance 关注的是答案是否回答了用户意图。
一个答案可以 grounded、faithful,但仍然 irrelevant。比如用户问“如何关闭某个功能”,模型却详细解释这个功能的原理而没有给出关闭步骤。
三者区别可以这样理解
- Groundedness:有没有证据支撑;
- Faithfulness:有没有忠实表达证据;
- Relevance:有没有回答问题本身。
这三个维度经常一起用,因为只看一个指标会失真。
五、如何建立评测集与回归测试集
AI 评估真正落地的关键,不在于“有没有一个分数”,而在于“有没有稳定的评测集”。
没有评测集,评估就会变成随机抽样;没有回归测试集,模型升级后你根本不知道是变好了还是变坏了。
1)评测集是什么
评测集是用来衡量模型效果的标准样本集合。每条样本通常包含:
- 用户问题;
- 参考答案或参考事实;
- 上下文材料;
- 期望输出类型;
- 评价维度与权重。
2)回归测试集是什么
回归测试集是从真实线上场景中挑出来的、最能代表风险和典型问题的样本集合。
它的目标不是覆盖所有问题,而是覆盖:
- 高频问题;
- 历史误判问题;
- 高风险问题;
- 模型升级后容易退化的问题。
3)评测集设计原则
一个好的评测集至少应该满足四个原则:
- 代表性:覆盖核心业务问题;
- 分层性:包含简单、中等、复杂样本;
- 稳定性:标准答案清晰,减少歧义;
- 可追踪性:每个样本能追溯来源和版本。
4)样本应如何分类
建议按如下维度分类:
- 按任务类型:问答、总结、分类、抽取、生成;
- 按风险等级:低风险、中风险、高风险;
- 按数据来源:知识库、业务文档、用户输入、上下文窗口;
- 按评估目标:准确性、相关性、幻觉、合规性。
5)为什么要版本化
评测集不是一次性文件,而是持续演进的资产。
随着业务发展,你会不断遇到:
- 新产品;
- 新规则;
- 新问法;
- 新失败模式。
所以评测集必须版本化。每次模型变更、提示词变更、检索策略变更、知识库更新,都要能回放到相同评测集上做对比。
六、Spring Boot 3.x 下的评估服务设计
Spring Boot 3.x 非常适合承载这类“评测平台服务”,因为它天然适合构建:
- REST API;
- 批处理任务;
- 定时回归测试;
- 指标上报;
- 结果持久化;
- 可观测性接入。
在工程上,我们可以把系统拆成以下几层:
- API 层:接收评估请求,返回评估结果;
- Service 层:组织评估流程,调用多个评分器;
- Scorer 层:负责单项评分,如相关性、groundedness;
- Repository 层:保存评测记录、样本、版本;
- Scheduler 层:定时跑回归;
- Observability 层:日志、指标、链路追踪。
设计目标
- 评估逻辑可插拔;
- 指标定义可扩展;
- 样本可版本化;
- 结果可复现;
- 支持手工评审和自动评分并存。
七、代码实战一:定义评估模型与评分接口
下面先搭建最小可用的模型层。这个示例是 Spring Boot 3.x 风格,适合后续扩展。
1)评估结果模型
package com.example.aieval.model;
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* 评估结果对象
* 用于承载一次 AI 输出的综合评分与细分评分
*/
public class EvaluationResult {
private String requestId;
private double accuracyScore;
private double relevanceScore;
private double groundednessScore;
private double faithfulnessScore;
private double hallucinationRate;
private double overallScore;
private String level;
private LocalDateTime evaluatedAt;
private Map<String, Object> details = new LinkedHashMap<>();
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public double getAccuracyScore() {
return accuracyScore;
}
public void setAccuracyScore(double accuracyScore) {
this.accuracyScore = accuracyScore;
}
public double getRelevanceScore() {
return relevanceScore;
}
public void setRelevanceScore(double relevanceScore) {
this.relevanceScore = relevanceScore;
}
public double getGroundednessScore() {
return groundednessScore;
}
public void setGroundednessScore(double groundednessScore) {
this.groundednessScore = groundednessScore;
}
public double getFaithfulnessScore() {
return faithfulnessScore;
}
public void setFaithfulnessScore(double faithfulnessScore) {
this.faithfulnessScore = faithfulnessScore;
}
public double getHallucinationRate() {
return hallucinationRate;
}
public void setHallucinationRate(double hallucinationRate) {
this.hallucinationRate = hallucinationRate;
}
public double getOverallScore() {
return overallScore;
}
public void setOverallScore(double overallScore) {
this.overallScore = overallScore;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public LocalDateTime getEvaluatedAt() {
return evaluatedAt;
}
public void setEvaluatedAt(LocalDateTime evaluatedAt) {
this.evaluatedAt = evaluatedAt;
}
public Map<String, Object> getDetails() {
return details;
}
public void setDetails(Map<String, Object> details) {
this.details = details;
}
}
代码解析
这个模型的设计思路很简单:
accuracyScore:准确率;relevanceScore:相关性;groundednessScore:是否有证据支撑;faithfulnessScore:是否忠实于上下文;hallucinationRate:幻觉率;overallScore:综合分;details:保留评估明细,方便排查。
这里的关键是:不要把评估结果设计成一个单一分数。单一分数太粗,会掩盖很多问题。
2)评分接口
package com.example.aieval.scorer;
import com.example.aieval.model.EvaluationContext;
/**
* 评分器接口
* 不同维度的评分逻辑都可以实现这个接口
*/
public interface Scorer {
/**
* 计算评分
* @param context 评估上下文
* @return 0~1 之间的得分
*/
double score(EvaluationContext context);
/**
* 返回评分器名称
*/
String name();
}
3)评估上下文
package com.example.aieval.model;
import java.util.List;
/**
* 评估上下文
* 包括用户问题、模型回答、参考答案、检索证据等信息
*/
public class EvaluationContext {
private String requestId;
private String question;
private String answer;
private String referenceAnswer;
private List<String> evidenceList;
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public String getQuestion() {
return question;
}
public void setQuestion(String question) {
this.question = question;
}
public String getAnswer() {
return answer;
}
public void setAnswer(String answer) {
this.answer = answer;
}
public String getReferenceAnswer() {
return referenceAnswer;
}
public void setReferenceAnswer(String referenceAnswer) {
this.referenceAnswer = referenceAnswer;
}
public List<String> getEvidenceList() {
return evidenceList;
}
public void setEvidenceList(List<String> evidenceList) {
this.evidenceList = evidenceList;
}
}
代码解析
EvaluationContext 的作用是统一上下文。后续不管是规则评分、语义评分、人工标注还是模型评估,都可以共用同一套输入对象。
这样做可以避免每个评分器都重新定义参数,保持架构清晰。
八、代码实战二:构建一个可运行的评估 API
下面我们搭建一个简单但可运行的 Spring Boot 3.x API,用来接收用户输入并返回综合评估结果。
1)项目依赖建议
pom.xml 可参考如下依赖:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2)请求对象
package com.example.aieval.api;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import java.util.List;
/**
* 评估请求
*/
public class EvaluationRequest {
@NotBlank(message = "问题不能为空")
private String question;
@NotBlank(message = "答案不能为空")
private String answer;
private String referenceAnswer;
@NotEmpty(message = "证据列表不能为空")
private List<String> evidenceList;
public String getQuestion() {
return question;
}
public void setQuestion(String question) {
this.question = question;
}
public String getAnswer() {
return answer;
}
public void setAnswer(String answer) {
this.answer = answer;
}
public String getReferenceAnswer() {
return referenceAnswer;
}
public void setReferenceAnswer(String referenceAnswer) {
this.referenceAnswer = referenceAnswer;
}
public List<String> getEvidenceList() {
return evidenceList;
}
public void setEvidenceList(List<String> evidenceList) {
this.evidenceList = evidenceList;
}
}
3)响应对象
package com.example.aieval.api;
import java.time.LocalDateTime;
import java.util.Map;
/**
* 评估响应
*/
public class EvaluationResponse {
private String requestId;
private double accuracyScore;
private double relevanceScore;
private double groundednessScore;
private double faithfulnessScore;
private double hallucinationRate;
private double overallScore;
private String level;
private LocalDateTime evaluatedAt;
private Map<String, Object> details;
public String getRequestId() {
return requestId;
}
public void setRequestId(String requestId) {
this.requestId = requestId;
}
public double getAccuracyScore() {
return accuracyScore;
}
public void setAccuracyScore(double accuracyScore) {
this.accuracyScore = accuracyScore;
}
public double getRelevanceScore() {
return relevanceScore;
}
public void setRelevanceScore(double relevanceScore) {
this.relevanceScore = relevanceScore;
}
public double getGroundednessScore() {
return groundednessScore;
}
public void setGroundednessScore(double groundednessScore) {
this.groundednessScore = groundednessScore;
}
public double getFaithfulnessScore() {
return faithfulnessScore;
}
public void setFaithfulnessScore(double faithfulnessScore) {
this.faithfulnessScore = faithfulnessScore;
}
public double getHallucinationRate() {
return hallucinationRate;
}
public void setHallucinationRate(double hallucinationRate) {
this.hallucinationRate = hallucinationRate;
}
public double getOverallScore() {
return overallScore;
}
public void setOverallScore(double overallScore) {
this.overallScore = overallScore;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public LocalDateTime getEvaluatedAt() {
return evaluatedAt;
}
public void setEvaluatedAt(LocalDateTime evaluatedAt) {
this.evaluatedAt = evaluatedAt;
}
public Map<String, Object> getDetails() {
return details;
}
public void setDetails(Map<String, Object> details) {
this.details = details;
}
}
4)评估服务
package com.example.aieval.service;
import com.example.aieval.api.EvaluationRequest;
import com.example.aieval.api.EvaluationResponse;
import com.example.aieval.model.EvaluationContext;
import com.example.aieval.model.EvaluationResult;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID;
/**
* 评估服务
* 这里演示一个最小可运行版本
*/
@Service
public class EvaluationService {
public EvaluationResponse evaluate(EvaluationRequest request) {
EvaluationContext context = new EvaluationContext();
context.setRequestId(UUID.randomUUID().toString());
context.setQuestion(request.getQuestion());
context.setAnswer(request.getAnswer());
context.setReferenceAnswer(request.getReferenceAnswer());
context.setEvidenceList(request.getEvidenceList());
// 这里为了便于演示,使用简单规则打分
double relevanceScore = calculateRelevance(context);
double groundednessScore = calculateGroundedness(context);
double faithfulnessScore = calculateFaithfulness(context);
double accuracyScore = calculateAccuracy(context);
double hallucinationRate = round2(1.0 - groundednessScore * 0.6 - faithfulnessScore * 0.4);
double overallScore = round2(
accuracyScore * 0.35 +
relevanceScore * 0.25 +
groundednessScore * 0.2 +
faithfulnessScore * 0.2
);
EvaluationResult result = new EvaluationResult();
result.setRequestId(context.getRequestId());
result.setAccuracyScore(accuracyScore);
result.setRelevanceScore(relevanceScore);
result.setGroundednessScore(groundednessScore);
result.setFaithfulnessScore(faithfulnessScore);
result.setHallucinationRate(hallucinationRate);
result.setOverallScore(overallScore);
result.setLevel(toLevel(overallScore));
result.setEvaluatedAt(LocalDateTime.now());
Map<String, Object> details = new LinkedHashMap<>();
details.put("questionLength", context.getQuestion() == null ? 0 : context.getQuestion().length());
details.put("answerLength", context.getAnswer() == null ? 0 : context.getAnswer().length());
details.put("evidenceCount", context.getEvidenceList() == null ? 0 : context.getEvidenceList().size());
result.setDetails(details);
EvaluationResponse response = new EvaluationResponse();
response.setRequestId(result.getRequestId());
response.setAccuracyScore(result.getAccuracyScore());
response.setRelevanceScore(result.getRelevanceScore());
response.setGroundednessScore(result.getGroundednessScore());
response.setFaithfulnessScore(result.getFaithfulnessScore());
response.setHallucinationRate(result.getHallucinationRate());
response.setOverallScore(result.getOverallScore());
response.setLevel(result.getLevel());
response.setEvaluatedAt(result.getEvaluatedAt());
response.setDetails(result.getDetails());
return response;
}
private double calculateRelevance(EvaluationContext context) {
if (context.getQuestion() == null || context.getAnswer() == null) {
return 0.0;
}
// 简单演示:问题和答案有共同关键词时,认为相关性较高
String question = context.getQuestion().toLowerCase();
String answer = context.getAnswer().toLowerCase();
int hitCount = 0;
if (question.contains("spring boot") && answer.contains("spring boot")) {
hitCount++;
}
if (question.contains("ai") && answer.contains("ai")) {
hitCount++;
}
if (question.contains("评估") && answer.contains("评估")) {
hitCount++;
}
return round2(Math.min(1.0, 0.4 + hitCount * 0.2));
}
private double calculateGroundedness(EvaluationContext context) {
if (context.getEvidenceList() == null || context.getEvidenceList().isEmpty() || context.getAnswer() == null) {
return 0.0;
}
// 简单演示:答案中出现证据中的关键词越多,groundedness 越高
String answer = context.getAnswer().toLowerCase();
int matched = 0;
for (String evidence : context.getEvidenceList()) {
if (evidence != null && answer.contains(evidence.toLowerCase().trim())) {
matched++;
}
}
return round2((double) matched / context.getEvidenceList().size());
}
private double calculateFaithfulness(EvaluationContext context) {
if (context.getReferenceAnswer() == null || context.getAnswer() == null) {
return 0.5;
}
// 演示:参考答案与模型答案文本相似度越高,faithfulness 越高
String ref = context.getReferenceAnswer().toLowerCase();
String ans = context.getAnswer().toLowerCase();
int hit = 0;
String[] keywords = ref.split("\\s+");
for (String keyword : keywords) {
if (keyword.length() > 1 && ans.contains(keyword)) {
hit++;
}
}
return round2(Math.min(1.0, (double) hit / Math.max(5, keywords.length)));
}
private double calculateAccuracy(EvaluationContext context) {
if (context.getReferenceAnswer() == null || context.getAnswer() == null) {
return 0.5;
}
// 演示:如果答案与参考答案有较多共同内容,则认为准确率较高
String ref = context.getReferenceAnswer().toLowerCase();
String ans = context.getAnswer().toLowerCase();
int matched = 0;
String[] tokens = ref.split("\\s+");
for (String token : tokens) {
if (token.length() > 1 && ans.contains(token)) {
matched++;
}
}
return round2(Math.min(1.0, (double) matched / Math.max(5, tokens.length)));
}
private String toLevel(double score) {
if (score >= 0.85) {
return "A";
}
if (score >= 0.7) {
return "B";
}
if (score >= 0.5) {
return "C";
}
return "D";
}
private double round2(double value) {
return Math.round(value * 100.0) / 100.0;
}
}
代码解析
这个服务故意采用了“简单可跑”的规则评分方式,便于理解架构。
虽然在真实项目里你会使用更复杂的语义相似度模型、LLM-as-a-judge、RAG 证据校验器,但基础架构基本一致:
- 先构建上下文;
- 再拆分评分维度;
- 最后综合输出。
这也是 Spring Boot 3.x 的优势之一:业务逻辑可以非常清晰地分层组织。
5)控制器层
package com.example.aieval.controller;
import com.example.aieval.api.EvaluationRequest;
import com.example.aieval.api.EvaluationResponse;
import com.example.aieval.service.EvaluationService;
import jakarta.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.RequestBody;
/**
* 评估接口
*/
@RestController
@RequestMapping("/api/evaluations")
public class EvaluationController {
private final EvaluationService evaluationService;
public EvaluationController(EvaluationService evaluationService) {
this.evaluationService = evaluationService;
}
@PostMapping
public ResponseEntity<EvaluationResponse> evaluate(@Valid @RequestBody EvaluationRequest request) {
return ResponseEntity.ok(evaluationService.evaluate(request));
}
}
6)启动类
package com.example.aieval;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Spring Boot 3.x 应用启动类
*/
@SpringBootApplication
public class AiEvaluationApplication {
public static void main(String[] args) {
SpringApplication.run(AiEvaluationApplication.class, args);
}
}
7)测试请求样例
请求:
POST /api/evaluations
Content-Type: application/json
{
"question": "Spring Boot 3.x 如何设计 AI 结果评估体系?",
"answer": "可以从准确率、相关性、Groundedness 和 Faithfulness 四个维度来构建评估体系,并结合评测集做回归测试。",
"referenceAnswer": "Spring Boot 3.x 下应建立多维度评估体系,重点关注准确率、相关性、幻觉率,并结合评测集和回归测试。",
"evidenceList": ["准确率", "相关性", "回归测试", "评测集"]
}
这个请求会返回一个综合评分结果,适合用作你后续增强的基础版本。
九、代码实战三:RAG 场景下的 Groundedness 与 Faithfulness 评分
在 RAG 场景里,AI 不只是“凭空回答”,而是“检索 + 生成”。这时评估重点会更偏向 groundedness 和 faithfulness。
1)为什么 RAG 更需要证据校验
因为 RAG 的目标本来就是减少幻觉。模型需要依据检索到的文档来回答问题,所以:
- 文档中有的内容,答案应尽量引用;
- 文档中没有的内容,不应编造;
- 结论应与证据一致,不能偷换概念。
2)RAG 评分器示例
package com.example.aieval.scorer;
import com.example.aieval.model.EvaluationContext;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* RAG 场景的证据评分器
*/
@Component
public class RagGroundednessScorer implements Scorer {
@Override
public double score(EvaluationContext context) {
if (context.getAnswer() == null || context.getEvidenceList() == null || context.getEvidenceList().isEmpty()) {
return 0.0;
}
String answer = context.getAnswer().toLowerCase();
List<String> evidences = context.getEvidenceList();
int matched = 0;
for (String evidence : evidences) {
if (evidence != null && !evidence.isBlank() && answer.contains(evidence.toLowerCase().trim())) {
matched++;
}
}
// 证据命中率越高,groundedness 越高
return Math.round(((double) matched / evidences.size()) * 100.0) / 100.0;
}
@Override
public String name() {
return "groundedness";
}
}
3)Faithfulness 评分器示例
package com.example.aieval.scorer;
import com.example.aieval.model.EvaluationContext;
import org.springframework.stereotype.Component;
/**
* 忠实度评分器
* 主要判断答案是否偏离参考内容
*/
@Component
public class FaithfulnessScorer implements Scorer {
@Override
public double score(EvaluationContext context) {
if (context.getReferenceAnswer() == null || context.getAnswer() == null) {
return 0.5;
}
String reference = context.getReferenceAnswer().toLowerCase();
String answer = context.getAnswer().toLowerCase();
int hit = 0;
String[] words = reference.split("\\s+");
for (String word : words) {
if (word.length() > 1 && answer.contains(word)) {
hit++;
}
}
return Math.round(((double) hit / Math.max(5, words.length)) * 100.0) / 100.0;
}
@Override
public String name() {
return "faithfulness";
}
}
4)为什么这个实现是“演示版”而不是最终版
因为在真实生产中,单纯的字符串包含判断远远不够。你还需要:
- 向量相似度;
- 句子级别对齐;
- 事实抽取;
- 断言分解;
- NLI(自然语言推理)模型判断蕴含、矛盾、未知。
但作为专栏入门示例,这样的版本足够帮助读者理解整个体系。
十、代码实战四:评测集回归测试与结果持久化
真正让评估体系进入工程化阶段的关键,是“自动回归测试”。
模型更新后,不要靠感觉判断,而是直接跑一遍回归集。
1)评测样本对象
package com.example.aieval.dataset;
/**
* 评测样本
*/
public class EvaluationSample {
private String id;
private String question;
private String expectedAnswer;
private String category;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getQuestion() {
return question;
}
public void setQuestion(String question) {
this.question = question;
}
public String getExpectedAnswer() {
return expectedAnswer;
}
public void setExpectedAnswer(String expectedAnswer) {
this.expectedAnswer = expectedAnswer;
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
}
}
2)回归测试服务
package com.example.aieval.service;
import com.example.aieval.dataset.EvaluationSample;
import com.example.aieval.model.EvaluationContext;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* 回归测试服务
* 模拟对评测集进行统一跑分
*/
@Service
public class RegressionTestService {
public List<EvaluationSample> loadSamples() {
List<EvaluationSample> samples = new ArrayList<>();
EvaluationSample sample1 = new EvaluationSample();
sample1.setId("S001");
sample1.setQuestion("Spring Boot 3.x 有哪些关键变化?");
sample1.setExpectedAnswer("Spring Boot 3.x 迁移到 Jakarta 命名空间,最低 Java 17,支持更好的原生镜像与可观测性。");
sample1.setCategory("upgrade");
samples.add(sample1);
EvaluationSample sample2 = new EvaluationSample();
sample2.setId("S002");
sample2.setQuestion("什么是 AI 结果评估中的幻觉率?");
sample2.setExpectedAnswer("幻觉率用于衡量模型输出中脱离证据、编造事实的比例。");
sample2.setCategory("ai_eval");
samples.add(sample2);
return samples;
}
public double runRegression(List<EvaluationSample> samples) {
if (samples == null || samples.isEmpty()) {
return 0.0;
}
int passed = 0;
for (EvaluationSample sample : samples) {
// 这里演示固定返回通过,真实项目中应接入模型服务和评分器
if (sample.getExpectedAnswer() != null && !sample.getExpectedAnswer().isBlank()) {
passed++;
}
}
return Math.round(((double) passed / samples.size()) * 100.0) / 100.0;
}
}
3)为什么要做回归测试
回归测试的意义不是“把模型打分”,而是保证:
- 你今天改的提示词,没有把昨天的效果弄坏;
- 你今天升级的模型,没有让旧问题退化;
- 你今天更新的知识库,没有引入新的幻觉。
这和传统软件测试是同一思路:变更后必须验证旧功能是否仍然正常。
十一、评估体系的工程化落地:日志、版本、阈值、告警
一套真正能在企业里用的 AI 评估体系,必须具备工程化能力。
1)日志:记录每次评估输入输出
日志至少要记录:
- 请求 ID;
- 用户问题;
- 模型回答;
- 证据列表;
- 评分结果;
- 模型版本;
- 提示词版本;
- 知识库版本。
这样一旦出现问题,就能复盘。
2)版本:让结果可追溯
AI 系统不是静态程序,而是持续变化的系统。你需要明确:
- 评估的是哪个模型版本;
- 用的是哪版提示词;
- 检索了哪个知识库;
- 是否启用了重排;
- 是否做了后处理。
3)阈值:决定是否进入灰度或发布
例如你可以定义:
- overallScore ≥ 0.85:允许上线;
- 0.7 ~ 0.85:需要人工复核;
- < 0.7:拒绝发布。
同时还可以给每个维度设置单独阈值:
- groundedness 不得低于 0.8;
- hallucinationRate 不得高于 0.15;
- relevanceScore 不得低于 0.75。
4)告警:让问题尽早暴露
当线上指标异常时,应触发告警,比如:
- 幻觉率突然升高;
- 某类问题准确率下降;
- 某版本回答长度异常变短;
- 某个知识域的相关性持续下滑。
AI 系统最怕“慢性恶化”,因为用户不会告诉你它何时变差,只会在某一天不再信任你。
十二、用流程图画出整体评估流程
下面这张图可以帮助读者建立全局认知。
相关示意图绘制如下,仅供参考:
图解
- 从用户问题开始,经过模型生成答案;
- 自动评估层负责快速筛查;
- 综合评分决定是否放行;
- 不达标则回到优化环节;
- 人工抽样复核用于校准自动指标。
这就形成了一个闭环。
十三、再看一张“指标关系图”
相关示意图绘制如下,仅供参考:
这张图可以帮助读者把抽象概念变成可记忆的知识结构。
十四、一个完整的评估闭环应该长什么样
完整的 AI 评估闭环可以概括为:
- 收集真实问题;
- 构建评测集;
- 定义维度与阈值;
- 自动评估批量跑分;
- 人工抽样复核;
- 发现失败模式;
- 调整提示词、检索、模型或后处理;
- 回到评测集重新验证;
- 形成版本化基线;
- 上线后持续监控。
这个闭环的本质不是“给 AI 打分”,而是让 AI 系统具备可持续改进能力。
十五、常见误区与最佳实践
误区一:只看平均分
平均分会掩盖极端样本。一个系统可能平均分不错,但在高风险样本上表现很差。
更好的做法是:按场景、风险等级、问题类型分别统计。
误区二:只看自动指标
自动指标会因为规则偏差而误导你。某些答案“看起来相似”,但其实关键事实错了。
更好的做法是:自动评估 + 人工复核。
误区三:评测集长期不更新
业务变化后,旧评测集会失去代表性。
更好的做法是:把线上失败样本持续回流到评测集。
误区四:只看答案,不看证据链
在 RAG 场景里,答案只是最终结果,真正决定质量的是证据链。
更好的做法是:同时评估答案和引用证据。
十六、从 Spring Boot 3.x 的角度看,为什么这套体系适合工程化
Spring Boot 3.x 的价值,不只是“能快速起项目”,还在于它非常适合把 AI 评估体系做成规范化平台。
1)模块边界清晰
- Controller 管接口;
- Service 管流程;
- Scorer 管评分;
- Repository 管存储;
- Scheduler 管定时任务。
2)生态成熟
你可以接入:
- Spring Web;
- Bean Validation;
- Spring Data;
- Actuator;
- 日志与指标系统;
- 测试框架。
3)便于扩展
后续你可以很自然地扩展:
- 接入向量数据库;
- 接入大模型 API;
- 接入人工标注平台;
- 接入 Prometheus 和 Grafana;
- 接入消息队列异步评测。
4)适合团队协作
当评测平台逐渐变成多人协作项目时,Spring Boot 3.x 提供的规范结构能让代码更稳定、更易维护。
十七、继续拓展延伸
如果把本文作为你学习路线中的一篇,那么后续你还继续要延伸学习:
方向一:LLM-as-a-Judge 的实现
讲清楚如何利用大模型作为裁判,但同时控制偏差。
方向二:RAG 评测进阶
深入讲解检索召回率、上下文命中率、答案可追溯性。
方向三:在线评估与离线评估
解释为什么离线分数高,不代表线上体验一定好。
方向四:评测数据治理
如何做样本去重、脱敏、版本管理、权限控制。
方向五:Spring Boot 3.x + 可观测性
将评测指标接入监控系统,做趋势图和告警。
十八、总结
AI 系统的评估,绝不只是“答没答出来”这么简单。
真正有价值的评估体系,应该同时回答下面几个问题:
- 答案是否准确;
- 是否与问题相关;
- 是否扎根于证据;
- 是否忠实于上下文;
- 是否存在幻觉;
- 是否能被持续回归验证。
在 Spring Boot 3 的工程体系中,我们完全可以把这些能力组织成一个清晰、可维护、可扩展的评估平台:
- 用统一的数据模型描述样本与结果;
- 用评分器拆分不同维度;
- 用 API 和批处理支撑在线离线两种场景;
- 用评测集与回归集保证迭代稳定;
- 用日志、版本和告警保证可追溯性。
这样做,AI 才不只是“会回答”,而是真正变成一个可被信任、可被验证、可被运营的系统。✨
…
ok,同学们,本节课就上到这儿,下课~
🧧 学习福利 · 限时开放 🧧
当然,无论你是计算机专业在读学生,还是对编程充满兴趣的入门者,都强烈建议系统学习SpringBoot全体系专栏:👉 「滚雪球学 Spring Boot」;涵盖SpringBoot所有教学内容。
该专栏以“循序渐进 + 实战驱动”为核心理念,从基础到进阶到就业到架构师逐层展开,帮助你快速建立完整的 Spring Boot 技术体系,带你玩转SpringBoot框架。
📌 学习承诺:
通过该专栏,你将能够:
- 快速掌握 Spring Boot 核心开发能力
- 构建完整的后端项目认知体系
- 实现从“入门”到“独立开发”的跃迁
就像“滚雪球”一样,知识不断积累、能力持续放大,实现指数级成长 🚀
最后,如果这篇文章对你有所帮助,帮忙给作者来个一键三连,关注、点赞、收藏,您的支持就是我坚持写作最大的动力。
同时欢迎大家关注技术号:「猿圈奇妙屋」 ,以便学习更多同类型的技术文章,免费白嫖最新BAT互联网公司面试题、4000G PDF编程电子书、简历模板、技术文章Markdown文档等海量资料。
ps:本文涉及所有源代码,均已上传至Gitee开源,供同学们直接对照学习 Gitee传送门,同时,原创开源不易,欢迎给个star🌟,想体验下被🌟的感jio,非常感谢❗
🫵 Who am I?
我是 bug菌,一名深耕 Java 后端领域数十年的一线研发老兵,曾担任独角兽企业后端技术经理、研发架构师等职位,长期专注于 Java 后端、分布式架构、微服务治理、高并发系统、工程效能与研发管理等方向。
目前活跃于多个主流技术社区,包括:
CSDN|稀土掘金|InfoQ|51CTO|华为云开发者社区|阿里云开发者社区|腾讯云开发者社区|开源中国|博客园|墨天轮 等平台。
曾获得:
- CSDN 博客之星 Top30
- 华为云多年度十佳博主 & 卓越贡献奖
- 掘金多年度人气作者 Top40
- CSDN、掘金、InfoQ、51CTO 等平台签约作者 / 优质作者
截至目前,全网技术内容累计影响读者众多,全网粉丝已超过 30w+。
如果你也关注 Java 后端、架构设计、技术成长、职场进阶与研发管理,欢迎关注我的技术内容合集入口:👉 点击查看 👈️
硬核技术号 「猿圈奇妙屋」 期待你的加入。
这里不仅分享技术干货,也记录一线研发人的成长、踩坑、思考与进阶路径。
愿我们一起打怪升级,在技术路上持续进阶。
- End -
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)