![SpringAI[11]:模型评估(Model Evaluation)](https://img.dunling.com/blogimg/20251126/3e957f10d7d74036b5af6916f8039f38.jpg)
本章目标:掌握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 问答:
- 用户问:“ChatGLM3 是哪个公司发布的?”
→ userText = “ChatGLM3 是哪个公司发布的?” - 向量库返回两条相关段落放入 dataList:
→ dataList = [doc1, doc2]
doc1 文本:“ChatGLM3 由智谱华章和清华 KEG 联合发布。”
doc2 文本:“智谱华章是一家中国大模型公司。” - 大模型回答:“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)](https://img.dunling.com/blogimg/20251126/c7e0b12acacd4a34847a0f1e65840b1a.jpg)
11.2.3 评估流程与得分
基于 Spring AI Evaluator 的 RAG 评估流程遵循 “数据准备→评估执行→结果解读→优化迭代” 四步走,核心是通过量化得分定位问题:
- 数据准备:收集RagEvaluationRequest所需的 3 类数据(userText, dataList,responseContent)。
- 评估执行:注入内置评估器(如FactualityEvaluator),调用evaluate方法执行评估。
- 结果解读: 得分≥0.8:优秀(无幻觉风险,符合需求); 0.5≤得分 < 0.8:待优化(存在轻微偏差,需微调 RAG 链路); 得分 < 0.5:不合格(存在明显幻觉或答非所问,需重构检索 / 生成逻辑)。
- 优化迭代:根据评估结果定位问题(如得分低是检索上下文无关,还是生成偏离上下文),调整 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 |
|
模型格式 |
回答正确但格式不符(如单位缺失) |
① 调 Prompt 模板 ② 加 OutputConverter |
|
模型幻觉 |
faithful<0.6 |
① 提高 temperature=0.1 |
|
评估器过严 |
人工确认答案无误但得分低 |
① 调阈值 ② 换轻量评估小模型 ③ 自定义 Evaluator |
源代码地址:
https://github.com/kaiwill/kaifamiao


