十一、评估测试

说明:文中类名 / 方法签名均在本地 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,评估前必须先灌库,否则检索上下文为空、评估必然失败。
相关推荐
huaqianzkh8 分钟前
WinForm C#:循环修改控件UI闪烁卡顿?SuspendLayout / ResumeLayout 与 BeginUpdate 优化
开发语言·ui·c#
小oo呆2 小时前
【自然语言处理与大模型】如何编写优质的Skill?
人工智能
非优秀程序员9 小时前
第12章 烧钱无底洞!!!不烧就死,烧了也不知道啥时候死|《互联网产品恩仇录》
人工智能
Zelman94510 小时前
第06章-交互与观察动作空间
人工智能
冬奇Lab10 小时前
开源项目第258期:Harness² —— Google Research 的「递归式 Agent Harness 优化」,模型权重不动,只改 Agent 的外壳
人工智能·开源
沐晓时光10 小时前
C语言入门,内存函数知识讲解
c语言·开发语言
字节跳动视频云技术团队12 小时前
每两家头部手机厂商就有一家选它:火山引擎如何成为 AI 手机助手的“隐形底座”?
人工智能
泯泷12 小时前
像项目经理一样指挥 AI 团队:管理者模式
人工智能·llm·agent
京东云开发者12 小时前
双11囤Token火了:京东搜索量暴涨7倍,AI生产力成新刚需
人工智能