SpringAI[11]:模型评估(Model Evaluation)

内容分享2周前发布
0 0 0

SpringAI[11]:模型评估(Model Evaluation)

本章目标:掌握Spring AI提供的Evaluator框架,精准验证RAG应用中模型生成内容的实际性,解决模型幻觉(Hallucination)问题,确保AI输出严格基于检索到的可靠知识库。

11.1 RAG幻觉:为什么必须专项评估?

幻觉的典型场景(RAG应用常见问题)

问题类型

示例

严重性

虚构实际

“根据2023年财报,公司营收达500亿人民币(实际为320亿)”

⚠️⚠️⚠️

脱离检索结果

用户问“Qwen3发布日期”,模型回答“2024年1月”(检索结果为2024年3月)

⚠️⚠️

逻辑矛盾

“iPhone 15支持5G,但电池续航仅8小时(实际续航12小时)”

⚠️⚠️

关键结论:RAG应用的评估核心是实际一致性(Factual Consistency),而非泛泛的“准确性”。

11.2 Spring AI的Evaluator框架:解决幻觉的利器

Spring AI提供Evaluator接口(
org.springframework.ai.evaluation.Evaluator)是 Spring AI 评测框架的核心抽象,定义了 “输入评估数据→输出评估结果” 的标准化流程。其设计目标是:支持灵活扩展(自定义评估逻辑)、内置高频场景评估器(无需重复造轮子),完美适配 RAG 的 “检索 – 生成” 全链路评估需求。

11.2.1 Evaluator 接口核心定义

Evaluator接口位于
org.springframework.ai.evaluation包下,核心方法为evaluate,接收评估所需的核心数据(如问题、参考上下文、生成结果),返回包含 “得分”“评估结论”“错误信息” 的EvaluationResult对象。

// Spring AI Evaluator接口核心定义
public interface Evaluator {
    // 核心评估方法:输入评估请求,输出评估结果
    EvaluationResponse evaluate(EvaluationRequest evaluationRequest);
    
    default String doGetSupportingData(EvaluationRequest evaluationRequest) {
    List<Document> data = evaluationRequest.getDataList();
    return data.stream()
        .map(Document::getText)
        .filter(StringUtils::hasText)
        .collect(Collectors.joining(System.lineSeparator()));
    }
    
}

其中,EvaluationRequest 是 Spring AI 评估机制的核心数据载体,它的作用一句话概括就是:把“用户问了什么、AI 答了什么、参考了哪些资料”这三件事打包成一个对象,交给 Evaluator 去做评估。

字段

类型

含义

userText

String

用户原始输入的文本。可以是问题、指令或任何自然语言请求。在 RAG 场景下,它就是提问本身,不包含检索出来的文档。

dataList

List<Document>

提供给 AI 的上下文/知识库片段,一般来自 RAG 检索结果,也可以是手工组装的文档。Evaluator 会把它当作“证据”去判断 AI 的回答是否靠谱。

responseContent

String

AI 模型实际给出的回答内容。Evaluator 的任务就是衡量这段回答相对于 userText和 dataList的质量(相关性、实际性等)。

假设你在做 RAG 问答:

  1. 用户问:“ChatGLM3 是哪个公司发布的?”
    → userText = “ChatGLM3 是哪个公司发布的?”
  2. 向量库返回两条相关段落放入 dataList:
    → dataList = [doc1, doc2]
    doc1 文本:“ChatGLM3 由智谱华章和清华 KEG 联合发布。”
    doc2 文本:“智谱华章是一家中国大模型公司。”
  3. 大模型回答:“ChatGLM3 是北京智谱华章科技有限公司和清华大学 KEG 实验室联合发布的。”
    → responseContent = “ChatGLM3 是北京智谱华章科技有限公司和清华大学 KEG 实验室联合发布的。”

把这三样装进 EvaluationRequest,扔进 RelevancyEvaluator 或 FactCheckingEvaluator,就能自动得到一次“回答是否相关/属实”的评估结果。

11.2.2 Evaluator 实现

Spring AI 针对 RAG 的 “相关性” 和 “实际性” 两大核心评估需求,提供了开箱即用的内置评估器,无需开发者从零实现评估逻辑。

  • Spring AI 内置实现

内置评估器

核心作用

评估逻辑

RelevanceEvaluator

看“答”是否跟“问+上下文”相关。

把 userText + dataList 拼成“期望范围”,让模型判断 responseContent 是否落在该范围内;返回布尔/得分。

FactualityEvaluator

看“答”里每个可验证陈述是否被上下文支持。

先让模型把 responseContent 拆成若干“原子声明”,再逐条问:“该声明是否能从 dataList 推出?” 全部通过才算过。

  • Spring AI Alibaba 提供实现

内置评估器

核心作用

评估逻辑

AnswerCorrectnessEvaluator

综合“相关性 + 实际性”给出整体正确性得分。

内部把 Relevancy + FactChecking 两步合并,并输出 0-1 分及细化理由;可自定义权重。

AnswerFaithfulnessEvaluator

专盯“幻觉”——只要回答里出现上下文没提到的信息就判负。

让模型把 responseContent 与 dataList 做“蕴含判断”,发现任何额外臆测即扣分;可配置容忍度。

AnswerRelevancyEvaluator

只看“答”对“问”的贴合度,不思考上下文。

去掉 dataList,仅用 userText 与 responseContent 做语义类似度/问答对打分;可换用 Embedding 或 LLM 判断。

Spring Al Alibaba 是“组合套装 + 细分螺丝刀”,按业务对“相关性/实际性/幻觉容忍度”的敏感程度自由搭配。

SpringAI[11]:模型评估(Model Evaluation)

11.2.3 评估流程与得分

基于 Spring AI Evaluator 的 RAG 评估流程遵循 “数据准备→评估执行→结果解读→优化迭代” 四步走,核心是通过量化得分定位问题:

  1. 数据准备:收集RagEvaluationRequest所需的 3 类数据(userText, dataList,responseContent)。
  2. 评估执行:注入内置评估器(如FactualityEvaluator),调用evaluate方法执行评估。
  3. 结果解读: 得分≥0.8:优秀(无幻觉风险,符合需求); 0.5≤得分 < 0.8:待优化(存在轻微偏差,需微调 RAG 链路); 得分 < 0.5:不合格(存在明显幻觉或答非所问,需重构检索 / 生成逻辑)。
  4. 优化迭代:根据评估结果定位问题(如得分低是检索上下文无关,还是生成偏离上下文),调整 RAG 策略后重新评估。

11.3 实战:基于 Spring AI Evaluator 验证 RAG 的实际性与相关性

本实战以 “电商商品知识问答” RAG 场景为例,基于 Spring AI 集成阿里百炼qwen-plus-latest模型,结合Milvus 向量数据库构建 RAG 链路,通过Evaluator接口自动化评估模型生成内容,解决幻觉问题。

11.3.1 准备数据集

数据集放入到json文件中,程序读入后解析

// chapter11/src/test/resources/notebook-qa.json
[
  {
    "question": "这款笔记本电脑的电池容量是多少?",
    "docs": [
      "商品名称:XX 笔记本 Pro;电池容量:72Wh;续航时间:12 小时;处理器:Intel i7",
      "XX 笔记本 Pro 电池采用锂聚合物材质,支持 65W 快充"
    ],
    "answer": "这款 XX 笔记本 Pro 的电池容量为 72Wh。"
  },
  {
    "question": "该笔记本支持快充吗?充电功率是多少?",
    "docs": [
      "XX 笔记本 Pro 电池支持 65W 快充,30 分钟可充至 60% 电量;充电接口为 Type-C"
    ],
    "answer": "该 XX 笔记本 Pro 支持快充,充电功率为 65W。"
  },
  {
    "question": "这款笔记本的处理器型号是什么?",
    "docs": [
      "商品参数:处理器型号:AMD Ryzen 7 7840HS;内存:16GB DDR5;硬盘:1TB SSD"
    ],
    "answer": "这款笔记本的处理器型号是 AMD Ryzen 7 7840HS。"
  }
]

定义一个 record ,对应数据聚焦的每条记录

// chapter11/src/test/java/com/kaifamiao/chapter11/QaJson.java
//保存json文件中的数据
public record QaJson(String question, List<String> docs, String answer) {
}

11.3.2 启动milvus向量数据库

请参考《第7章:嵌入模型(Embedding Models)与向量化存储》启动 milvus 向量数据库提供向量存储服务。

前面章节中将milvus 安装在了docker中,可以使用命令查看容器是否正常启动:

# 中间省略了其它字段
docker ps
CONTAINER ID   IMAGE                                      NAMES
c75858812635   milvusdb/milvus:v2.6.0                     milvus-standalone
c00c2f9c67c6   minio/minio:RELEASE.2024-12-18T13-15-44Z   milvus-minio
423602c27819   quay.io/coreos/etcd:v3.5.18                milvus-etcd

11.3.3 引入maven依赖

<!-- chapter11/pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-milvus-store</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-milvus</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/io.milvus/milvus-sdk-java -->
<dependency>
    <groupId>io.milvus</groupId>
    <artifactId>milvus-sdk-java</artifactId>
    <version>2.6.4</version>
</dependency>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-rag</artifactId>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

11.3.4 bean配置

# chapter11/src/main/resources/application.yml
spring:
  ai:
    dashscope:
      api-key: ${AI_BAI_LIAN_API_KEY} # 必填,在操作系统环境变量中设置这个变量后,重启IDEA才能生效。由于IDEA启动的时候会缓存这个变量
      chat:
        options:
          model: qwen-plus
          # 这个值0~1,值越大代表生成的结果随机性越强。如果是一个聊天,这个值可以大一点。如果是一些严谨的规划,则这个值可以设置小一些
          temperature: 0.7
logging:
  level:
    org.springframework.ai: DEBUG
    org.springframework.web: DEBUG
// chapter11/src/main/java/com/kaifamiao/chapter11/configuration/VectorStoreConfig.java
@Configuration
public class VectorStoreConfig {
    @Bean
    public VectorStore vectorStore(MilvusServiceClient milvusClient, EmbeddingModel embeddingModel) {
        return MilvusVectorStore.builder(milvusClient, embeddingModel)
                .collectionName("default")
                .databaseName("default")
                .indexType(IndexType.IVF_FLAT)
                .metricType(MetricType.COSINE)
                .initializeSchema(true)
                .build();
    }

    @Bean
    public MilvusServiceClient milvusClient() {
        return new MilvusServiceClient(ConnectParam.newBuilder()
                .withHost("192.168.31.254")
                .withPort(19530)
                .build());
    }
    
}

11.3.5 保存到向量数据库

当用户提出问题的时候,希望到向量数据库中检索信息,所以文档数据要事先保存到向量数据库中。为此,我们编写测试用例,从json文件中读取要保存到milvus中的数据,然后保存到milvus中。

// chapter11/src/test/java/com/kaifamiao/chapter11/VectorStoreTest.java
@SpringBootTest
@Slf4j
public class VectorStoreTest {

    @Test
    void loadDataToMilvus(@Autowired VectorStore vectorStore) throws IOException {
        log.info("vectorStore:{}", vectorStore.getClass());
        List<QaJson> list = loadData();
        for (QaJson qaJson : list) {
            log.info("qaJson->docs:{}", qaJson.docs());
            for (String doc : qaJson.docs()) {
                Document phoneDoc = Document.builder()
                        .text(doc) // 文本内容
                        .build();
                vectorStore.add(List.of(phoneDoc));
            }
        }

    }

    private List<QaJson> loadData() throws IOException {
        ObjectMapper      mapper   = new ObjectMapper();
        ClassPathResource resource = new ClassPathResource("notebook-qa.json");
        List<QaJson> list = mapper.readValue(resource.getInputStream(), new TypeReference<>() {
        });
        return list;
    }

11.3.6 评估测试

目前进行评估测试

  • 为ChatClient配置一个 QuestionAnswerAdvisor,用于向量数据库召回
  • 加载 notebook-qa.json文件中的数据,向大模型发出请求
  • 发出请求前先到向量数据库中召回一些上下文数据
  • 问题和召回的数据一起发送给大模型
  • 获取大模型的回答
  • 使用 AnswerCorrectnessEvaluator和 AnswerFaithfulnessEvaluator进行评估
@SpringBootTest
@Slf4j
public class AnswerCorrectnessEvaluatorTest {
    @Test
    void testAnswerCorrectnessEvaluator(
            @Autowired ChatClient.Builder chatClientBuilder,
            @Autowired VectorStore vectorStore) throws IOException {

        // 向量数据库 advisor, 于向量数据库召回
        QuestionAnswerAdvisor questionAdvisor = QuestionAnswerAdvisor
                .builder(vectorStore)
                .searchRequest(SearchRequest.builder()
                        .similarityThreshold(0.2) //类似度阈值,只有大于等于该值才会被返回(取值范围:0-1),默认是0(没有类似度排除)
                        .topK(5) //返回类似度排名前2的文档,默认是4
                        .build())
                .build();

        ChatClient chatClient = chatClientBuilder
                .defaultAdvisors(questionAdvisor) // 添加向量数据库advisor
                .build();

        AnswerCorrectnessEvaluator  answerCorrectnessEvaluator = new AnswerCorrectnessEvaluator(chatClientBuilder);
        AnswerFaithfulnessEvaluator faithfulnessEvaluator      = new AnswerFaithfulnessEvaluator(chatClientBuilder, new ObjectMapper());

        // 1. 读取JSON
        List<QaJson> list = loadData();
        // 2. 逐条评估
        list.forEach(qa -> {
            List<Document> docs = qa.docs().stream().map(Document::new).toList();

            // 向AI 获取答案
            String aiAnswer = chatClient.prompt()
                    .user(qa.question())
                    .call()
                    .content();

            EvaluationRequest request = new EvaluationRequest(qa.question(), docs, aiAnswer);

            EvaluationResponse correct  = answerCorrectnessEvaluator.evaluate(request);
            EvaluationResponse faithful = faithfulnessEvaluator.evaluate(request);

            System.out.printf("Q: %s%nA: %s%n", qa.question(), aiAnswer);
            System.out.printf("Correctness = %.2f , Faithfulness = %.2f%n%n",
                    correct.getScore(), faithful.getScore());
        });
    }

    private List<QaJson> loadData() throws IOException {
        ObjectMapper      mapper   = new ObjectMapper();
        ClassPathResource resource = new ClassPathResource("notebook-qa.json");
        List<QaJson> list = mapper.readValue(resource.getInputStream(), new TypeReference<>() {
        });
        return list;
    }
}

执行结果:

Q: 这款笔记本电脑的电池容量是多少?
A: 这款笔记本电脑的电池容量是 72Wh。
Correctness = 1.00 , Faithfulness = 1.00

Q: 该笔记本支持快充吗?充电功率是多少?
A: 该笔记本支持快充,充电功率为65W。
Correctness = 1.00 , Faithfulness = 1.00

Q: 这款笔记本的处理器型号是什么?
A: 这款笔记本的处理器型号是 AMD Ryzen 7 7840HS。

所有断言通过后,即可把 JSON 换成更大批量数据集进行评估。

对于评分不足的,我们有如下解决方案:

标签

判断规则

常见动作

召回不足

faithful≈1.0 且 correct<0.9 且 上下文不含答案

① 调低 SearchRequest.threshold
② 扩充知识库

召回冗余

faithful<0.9 且 回答出现上下文未提及信息

① 调高 threshold ② 给 QuestionAnswerAdvisor
加 top-k=1

模型格式

回答正确但格式不符(如单位缺失)

① 调 Prompt 模板 ② 加 OutputConverter

模型幻觉

faithful<0.6

① 提高 temperature=0.1
② 加 Seed
③ 换更大模型

评估器过严

人工确认答案无误但得分低

① 调阈值 ② 换轻量评估小模型 ③ 自定义 Evaluator

源代码地址:
https://github.com/kaiwill/kaifamiao

© 版权声明

相关文章

暂无评论

none
暂无评论...