🏆本文收录于《滚雪球学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 系统不能只看“能不能答出来”?

很多刚接触大模型的团队,第一反应往往是:只要模型能答出来就行。这个判断在 demo 阶段看似成立,但一旦进入真实业务,就会迅速失效。

原因很简单:“答出来”只是最弱的目标,“答得对、答得稳、答得可追溯”才是上线标准。

例如,你让 AI 回答“某个产品是否支持退款”,模型可能给出一段看起来非常流畅的答案,但其中可能混入了:

  • 过时的制度信息;
  • 与当前文档无关的“常识性补充”;
  • 把别的产品规则套到了当前产品上;
  • 甚至直接编造一个并不存在的条款。

这些问题在用户眼里不只是“答错了”,而是会直接破坏信任。AI 系统的风险不是“不会说话”,而是“会说得太像真的”。

所以,AI 评估不能只盯着最终输出文本是否通顺,而要看三个更关键的问题:

  1. 答案是否准确:和标准答案、事实依据是否一致?
  2. 答案是否相关:是否真正回答了用户问题?有没有跑题?
  3. 答案是否幻觉:是否引用了不存在的信息,或脱离上下文胡乱推断?

这三点,分别对应我们今天的核心主题:准确率、相关性、幻觉率

二、评估体系的核心目标:准确率、相关性、幻觉率

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;
  • 批处理任务;
  • 定时回归测试;
  • 指标上报;
  • 结果持久化;
  • 可观测性接入。

在工程上,我们可以把系统拆成以下几层:

  1. API 层:接收评估请求,返回评估结果;
  2. Service 层:组织评估流程,调用多个评分器;
  3. Scorer 层:负责单项评分,如相关性、groundedness;
  4. Repository 层:保存评测记录、样本、版本;
  5. Scheduler 层:定时跑回归;
  6. 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 评估闭环可以概括为:

  1. 收集真实问题;
  2. 构建评测集;
  3. 定义维度与阈值;
  4. 自动评估批量跑分;
  5. 人工抽样复核;
  6. 发现失败模式;
  7. 调整提示词、检索、模型或后处理;
  8. 回到评测集重新验证;
  9. 形成版本化基线;
  10. 上线后持续监控。

这个闭环的本质不是“给 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稀土掘金InfoQ51CTO华为云开发者社区阿里云开发者社区腾讯云开发者社区开源中国博客园墨天轮 等平台。

曾获得:

  • CSDN 博客之星 Top30
  • 华为云多年度十佳博主 & 卓越贡献奖
  • 掘金多年度人气作者 Top40
  • CSDN、掘金、InfoQ、51CTO 等平台签约作者 / 优质作者

截至目前,全网技术内容累计影响读者众多,全网粉丝已超过 30w+

如果你也关注 Java 后端、架构设计、技术成长、职场进阶与研发管理,欢迎关注我的技术内容合集入口:👉 点击查看 👈️

硬核技术号 「猿圈奇妙屋」 期待你的加入。

这里不仅分享技术干货,也记录一线研发人的成长、踩坑、思考与进阶路径。

愿我们一起打怪升级,在技术路上持续进阶。

- End -

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐