十一、评估测试

说明:文中类名 / 方法签名均在本地 spring-ai 1.1.3 的 jar 中核对过;文档里几处 1.0 时代的旧写法已在「踩坑清单」标出。

1. 什么叫评估测试

普通测试断言确定值(assertEquals(2, 1 + 1)),但 AI 的输出是自然语言、每次还不一样,没法硬断言。

评估测试 = 再找一个"裁判模型",按规则对 AI 的输出打分 / 判定,把不确定的生成结果变成可断言的 true / false(或分数)。

  • 目的:发现幻觉(hallucination)、答非所问、回答脱离给定上下文。
  • 定位:属于集成测试,会真实调用模型(有费用、有延迟、结果不完全稳定)。
  • 常见评估维度:相关性(relevancy)、事实一致性 / groundedness、答案正确性。

Spring AI 的抽象就是一个函数式接口:

java 复制代码
@FunctionalInterface
public interface Evaluator {
    EvaluationResponse evaluate(EvaluationRequest evaluationRequest);
}

2. 两个数据类

java 复制代码
public class EvaluationRequest {
    private final String userText;          // 用户原始输入
    private final List<Content> dataList;   // 上下文数据,例如 RAG 检索到的文档
    private final String responseContent;   // AI 模型的回答内容
}
java 复制代码
public class EvaluationResponse {
    boolean isPass();                            // 核心:是否通过评估
    float getScore();                            // 分数
    String getFeedback();                        // 裁判模型的反馈
    Map<String, Object> getMetadata();           // 元数据
}

1.1.3 实测构造器重载有三个:

java 复制代码
new EvaluationRequest(String userText, String responseContent);
new EvaluationRequest(List<Document> dataList, String responseContent);
new EvaluationRequest(String userText, List<Document> dataList, String responseContent);

3. 相关性评估器 RelevancyEvaluator

判什么:回答是否"贴合用户问题 + 检索到的上下文"。主要用于验证 RAG 流程质量(上下文有没有真被用上)。

框架内置的默认提示模板是英文的(模型只回答 YES / NO),含义如下:

复制代码
Your task is to evaluate if the response for the query
is in line with the context information provided.
You have two options to answer. Either YES or NO.
Answer YES, if the response for the query
is in line with context information otherwise NO.
Query:   {query}
Response:{response}
Context: {context}
Answer:

想让裁判模型用中文理解(或统一团队语言),可以换成中文版模板(占位符必须是 query / response / context 三个):

复制代码
你的任务是判断下面的回答是否与提供的上下文信息一致。
你只有两个选择:YES 或 NO。
如果回答与上下文信息一致,回答 YES;否则回答 NO。

问题:
{query}

回答:
{response}

上下文:
{context}

答案:

集成测试用法(验证 RAG 流程)

java 复制代码
@Test
void evaluateRelevancy() {
    String question = "阿纳克莱图斯和比尔巴的冒险发生在什么地方?";

    RetrievalAugmentationAdvisor ragAdvisor = RetrievalAugmentationAdvisor.builder()
        .documentRetriever(VectorStoreDocumentRetriever.builder()
            .vectorStore(pgVectorStore)
            .build())
        .build();

    ChatResponse chatResponse = ChatClient.builder(chatModel).build()
        .prompt(question)
        .advisors(ragAdvisor)
        .call()
        .chatResponse();

    EvaluationRequest evaluationRequest = new EvaluationRequest(
        question,                                                               // 原始问题
        chatResponse.getMetadata().get(RetrievalAugmentationAdvisor.DOCUMENT_CONTEXT), // RAG 检索到的上下文
        chatResponse.getResult().getOutput().getText()                          // 模型回答
    );

    RelevancyEvaluator evaluator = new RelevancyEvaluator(ChatClient.builder(chatModel));
    EvaluationResponse evaluationResponse = evaluator.evaluate(evaluationRequest);

    assertThat(evaluationResponse.isPass()).isTrue();
}

关键点:dataList 不能随便传,必须是这一次 RAG 真正检索到的上下文 (从 RetrievalAugmentationAdvisor.DOCUMENT_CONTEXT 取),否则评估没有意义。

4. 自定义模板

通过 .promptTemplate() 传入自己的 PromptTemplate;可以用任意 TemplateRenderer(默认基于 StringTemplate 的 StPromptTemplate)。

硬性要求:模板必须包含三个占位符 ------ query、response、context。

java 复制代码
RelevancyEvaluator evaluator = RelevancyEvaluator.builder()
        .chatClientBuilder(ChatClient.builder(chatModel))
        .promptTemplate(PromptTemplate.builder()
                .renderer(StTemplateRenderer.builder()
                        .startDelimiterToken('<').endDelimiterToken('>').build())
                .template("""
                        问题:<query>
                        回答:<response>
                        上下文:<context>
                        回答是否由上下文支持?只输出 YES 或 NO。
                        """)
                .build())
        .build();

5. 事实核查评估器 FactCheckingEvaluator

判什么:回答中的"陈述(claim)"是否被给定"文档(document)"逻辑支持 ------ 用来检测幻觉。

框架内置提示模板(英文):

复制代码
Document: {document}
Claim: {claim}

中文含义 / 想中文化时可用:

复制代码
文档:{document}
陈述:{claim}

模型建议用更小更省的专用模型,例如 Bespoke-Minicheck(可通过 Ollama 跑),比 GPT-4 便宜得多。

java 复制代码
@Test
void testFactChecking() {
  OllamaApi ollamaApi = new OllamaApi("http://localhost:11434");

  ChatModel chatModel = new OllamaChatModel(ollamaApi,
        OllamaChatOptions.builder().model(BESPOKE_MINICHECK).numPredict(2).temperature(0.0d).build());

  var factCheckingEvaluator = new FactCheckingEvaluator(ChatClient.builder(chatModel));

  String context = "地球是距离太阳第三近的行星,也是目前已知唯一存在生命的天体。";
  String claim   = "地球是距离太阳第四近的行星。";

  EvaluationRequest evaluationRequest = new EvaluationRequest(context, Collections.emptyList(), claim);
  EvaluationResponse evaluationResponse = factCheckingEvaluator.evaluate(evaluationRequest);

  assertFalse(evaluationResponse.isPass(), "该陈述与上下文矛盾,应当判为不通过");
}

6. ⚠ 1.1.3 踩坑清单(文档 vs 实际)

文档 / 教程写法 1.1.3 实际
org.springframework.ai.evaluation.RelevancyEvaluator org.springframework.ai.chat.evaluation.RelevancyEvaluator
org.springframework.ai.evaluation.FactCheckingEvaluator org.springframework.ai.chat.evaluation.FactCheckingEvaluator
EvaluationRequest(List<Content> dataList, ...) 实际是 List<Document>;EvaluationRequest / EvaluationResponse / Evaluator 在 org.springframework.ai.evaluation(spring-ai-commons)
new FactCheckingEvaluator(chatClientBuilder) 该构造器是 protected,只能用 FactCheckingEvaluator.builder(builder) 或 FactCheckingEvaluator.forBespokeMinicheck(builder)

即导入应该是:

java 复制代码
import org.springframework.ai.chat.evaluation.RelevancyEvaluator;      // spring-ai-client-chat
import org.springframework.ai.chat.evaluation.FactCheckingEvaluator;   // spring-ai-client-chat
import org.springframework.ai.evaluation.EvaluationRequest;           // spring-ai-commons
import org.springframework.ai.evaluation.EvaluationResponse;          // spring-ai-commons
import org.springframework.ai.evaluation.Evaluator;                   // spring-ai-commons

照抄旧文档会直接 Cannot resolve symbol / 构造器不可见。

7. 落地建议

  • 评估测试会真实烧 token ,建议用 @Disabled 或 @Tag("evaluation") 隔离,别让 mvn test 每次都跑。
  • 评估模型可以和生成模型不同("强模型生成、专/小模型评判"是常见省钱姿势)。
  • 裁判模型本身也有偏差:isPass() == false 只能当信号,不能直接当结论。
  • 写 JUnit 5 + assertThat 需要 spring-boot-starter-test;只引入 junit:junit:3.8.1 的模块跑不了。
  • 本模块用的是内存 SimpleVectorStore,评估前必须先灌库,否则检索上下文为空、评估必然失败。
相关推荐
Metaphor69236 分钟前
Python 自动化办公:如何轻松在 PDF 中导入与导出表单数据
python·pdf·自动化
lzhdim4 小时前
AI的分类
人工智能·算法·机器学习·分类·数据挖掘
程序员海军6 小时前
用了两年多 GPT,我最近开始换模型了,还折腾了个求职避坑 Agent
前端·人工智能·后端
AIsoft_86896 小时前
人才评估中的关键信息如何沉淀?
人工智能·iphone
紧跟先前的步伐6 小时前
Go AI Agent 实战 06|JSON 结构化输出与 Go 后端校验
人工智能·ai·golang
H.莓飛7 小时前
【数据结构】排序_OJ题(一):计数、桶与排序后的处理
java·开发语言·数据结构
vibecoding778 小时前
Codex 分层用模型完整教程:让 GPT-6.1 Sol 拍板、GPT-6 Luna 跑侦察
人工智能·gpt
wangchunyu1148 小时前
JAVA AQS与Lock实现原理(含CAS)
java·开发语言·word
新知图书8 小时前
1.3 启动第一个机器人
人工智能·机器人
明志数科8 小时前
自主探索范式下机器人示教数据的工程定位:少量示范为何对质量更敏感
人工智能·机器学习·机器人