首页 / Spring AI 入门教程 / 检索增强问答与评估

Spring AI 入门教程

检索增强问答与评估

本教程共 45 篇 · 第 37 篇 · 更新于 2026-08-16 · 约 9 分钟阅读

Spring AIRAGQuestionAnswerAdvisorLLM-as-a-Judge评估检索优化RelevancyEvaluatorFactCheckingEvaluator

本节目标:用 QuestionAnswerAdvisor 完成知识问答,掌握检索质量的调优手段,学会用相关性评估、事实核查和 LLM-as-a-Judge 三种方式衡量 RAG 效果。

37.1 QuestionAnswerAdvisor:开箱即用的问答

第 32 章的流程,QuestionAnswerAdvisor 一步帮你做完。它接到用户问题,先对向量库做相似度检索,再把结果拼进提示词,最后让模型回答。

使用它需要引入独立依赖:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-vector-store-advisor</artifactId>
</dependency>

假设数据已经入库,配置 Advisor 后接入 ChatClient:

QuestionAnswerAdvisor qaAdvisor = QuestionAnswerAdvisor.builder(vectorStore)
    .searchRequest(SearchRequest.builder()
        .similarityThreshold(0.8)  // 相似度低于 0.8 的文档不采用
        .topK(6)                   // 最多取 6 条
        .build())
    .build();

ChatClient chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(qaAdvisor)
    .build();

String answer = chatClient.prompt("我们的退款政策是什么?").call().content();

searchRequest 在构造时配置,会对所有请求生效。上一章的 topK、相似度阈值、过滤表达式,在这里就是问答质量的三个旋钮。

动态过滤

多租户场景下,不同用户只能检索自己的文档。过滤条件可以按请求动态设置:

String answer = chatClient.prompt()
    .user("我的订单什么时候发货?")
    .advisors(a -> a.param(QuestionAnswerAdvisor.FILTER_EXPRESSION,
        "tenant == 'user-123'"))
    .call()
    .content();

FILTER_EXPRESSION 参数在运行时覆盖默认过滤条件。租户 ID 从上下文取,一套代码服务所有租户。

自定义提示词模板

默认模板把检索结果和问题拼在一起。模板内容可以换成自己的,要求更严格的回答规则:

PromptTemplate customTemplate = PromptTemplate.builder()
    .template("""
        <query>

        以下是上下文信息。
        ---------------------
        <question_answer_context>
        ---------------------
        只依据上下文信息回答,不要使用先验知识。

        遵循以下规则:
        1. 如果答案不在上下文中,直接说不知道。
        2. 避免"根据上下文……""以上信息表明……"这类表述。
        """)
    .build();

QuestionAnswerAdvisor qaAdvisor = QuestionAnswerAdvisor.builder(vectorStore)
    .promptTemplate(customTemplate)
    .build();

自定义模板必须保留两个占位符:query(用户问题)和 question_answer_context(检索结果)。模板里的规则可以自由发挥,比如强制”不知道就说不知道”。

Note

2.0 里 QuestionAnswerAdvisor.Builder.userTextAdvise() 已废弃,统一用 .promptTemplate() 定制。

37.2 RetrievalAugmentationAdvisor:模块化编排

问答简单,复杂 RAG 流程要精细控制。RetrievalAugmentationAdvisor 是模块化版本,把第 32 章讲的四个阶段都做成可配置项。它需要 spring-ai-rag 依赖。

最朴素的用法,只配检索器:

import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.rag.generation.augmentation.ContextualQueryAugmenter;

Advisor ragAdvisor = RetrievalAugmentationAdvisor.builder()
    .documentRetriever(VectorStoreDocumentRetriever.builder()
        .vectorStore(vectorStore)
        .similarityThreshold(0.50)
        .topK(3)
        .build())
    .build();

String answer = chatClient.prompt()
    .advisors(ragAdvisor)
    .user(question)
    .call()
    .content();

VectorStoreDocumentRetriever 是检索阶段的实现,支持动态过滤表达式。默认情况下,检索结果为空时,Advisor 会指示模型不回答。想放开,让模型自由发挥,用 ContextualQueryAugmenter 打开开关:

Advisor ragAdvisor = RetrievalAugmentationAdvisor.builder()
    .documentRetriever(VectorStoreDocumentRetriever.builder()
        .vectorStore(vectorStore)
        .similarityThreshold(0.50)
        .build())
    .queryAugmenter(ContextualQueryAugmenter.builder()
        .allowEmptyContext(true)
        .build())
    .build();

知识库问答建议保持默认:查不到就说不知道,别让模型瞎编。

检索前的查询加工

检索质量差,先别怪向量库,可能是问题本身不适合检索。检索前阶段(Pre-Retrieval)负责加工问题。

RewriteQueryTransformer 用模型改写问题,去噪、补全语义:

Advisor ragAdvisor = RetrievalAugmentationAdvisor.builder()
    .queryTransformers(RewriteQueryTransformer.builder()
        .chatClientBuilder(chatClientBuilder.build().mutate())
        .build())
    .documentRetriever(VectorStoreDocumentRetriever.builder()
        .vectorStore(vectorStore)
        .build())
    .build();

对话场景用 CompressionQueryTransformer,把聊天历史和追问压缩成独立问题。“它第二大城市是哪”会被还原成”丹麦第二大城市是哪个”,否则向量检索根本匹配不上。

MultiQueryExpander 把一个问法扩成多个,提高召回:

MultiQueryExpander queryExpander = MultiQueryExpander.builder()
    .chatClientBuilder(chatClientBuilder)
    .numberOfQueries(3)   // 生成 3 个变体
    .includeOriginal(true) // 保留原始问题
    .build();
Tip

QueryTransformer 时,把 ChatModel 的 temperature 调到 0。改写结果越稳定,检索质量越高,默认温度对改写任务通常偏高。

37.3 检索优化三板斧

RAG 效果不好,按这个顺序排查。

topK 调数量。 返回太少,上下文不全;返回太多,噪声稀释重点,token 成本也涨。从 4-6 起步,看答案质量调整。

相似度阈值调纯度。 检索结果像但不对,是幻觉重灾区。阈值设 0.7-0.8,宁缺毋滥。

过滤表达式收范围。 多租户、多分类数据必须过滤,否则跨域文档会污染上下文。

三个参数组合实验,每次只动一个,记录答案质量对比。这比拍脑袋改提示词有效得多。

37.4 评估:RAG 效果怎么量

RAG 上线前必须回答一个问题:效果到底好不好?人工抽查太慢,传统指标(BLEU、ROUGE)衡量不了语义。Spring AI 的思路是用模型评估模型

核心接口是 Evaluator

@FunctionalInterface
public interface Evaluator {
    EvaluationResponse evaluate(EvaluationRequest evaluationRequest);
}

EvaluationRequest 装三样东西:用户原始问题、检索到的上下文、模型生成的回答。评估就是拿这三样去问评估模型:答得对不对、贴不贴上下文。

RelevancyEvaluator:相关性评估

RelevancyEvaluator 判断回答是否与上下文相关,专门用来检验 RAG 流程。典型用法是在集成测试里跑完问答后立刻评估:

@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),  // 检索上下文
        chatResponse.getResult().getOutput().getText());     // 模型回答

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

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

回答可以从 ChatResponse 的元数据里拿到检索上下文(DOCUMENT_CONTEXT 键),正好构成评估三要素。把这种测试挂进 CI,每次改检索参数、换模型都能自动回归。

FactCheckingEvaluator:事实核查

FactCheckingEvaluator 专门查幻觉:把”上下文”和”回答中的论断”分别作为 document 和 claim 交给评估模型,判断论断是否被上下文支持。

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

EvaluationRequest request = new EvaluationRequest(
    "地球是离太阳第三近的行星……",  // 上下文
    Collections.emptyList(),
    "地球是离太阳第四近的行星。");  // 待核查的论断

EvaluationResponse response = evaluator.evaluate(request);
assertFalse(response.isPass());  // 论断不被支持,评估不通过

有个实用的成本技巧:事实核查用小型专用模型。Bespoke 的 Minicheck 就是为这事训练的,跑在 Ollama 上,成本远低于旗舰模型。核查是高频动作,模型选型值得单独考虑。

LLM-as-a-Judge:让模型当裁判

更系统的评估方案是 LLM-as-a-Judge:用专门的评估模型给回答打分。研究显示,好的裁判模型和人类判断的一致性可达 85%,高于人类之间的一致性。

两种主流模式。直接打分:裁判按 1-4 分评估单个回答的质量。成对比较:裁判从两个候选回答里挑更好的,常用于 A/B 测试。

Spring AI 用 Recursive Advisor 实现自动打分-改进循环:生成回答 → 裁判打分 → 不及格就把反馈拼回提示词重试 → 直到达标或达到次数上限。完整示例见第 38 章的”评估流程”一节:生成用 Claude,评估用 Ollama 上的专用裁判模型,两个模型分离能减少偏误;打分结果通过结构化输出解析成 EvaluationResponse 记录(rating、evaluation、feedback)。

Note

Recursive Advisor 是实验特性:仅支持非流式调用,多次重试会放大模型调用成本,必须设置重试上限防止死循环。

LLM-as-a-Judge 的几条最佳实践:

  • 用专门的裁判模型,而不是随手拿主力模型
  • 生成和评估用不同模型,避免”自己评自己”
  • temperature 设为 0,保证打分稳定
  • 提示词用整数分制、给示例
  • 高风险场景保留人工复核

37.5 评估体系怎么搭

生产级 RAG 的评估分三层。

回归测试层。 准备一批”问题-标准答案”的测试集,跑 RelevancyEvaluator 自动判断,挂 CI。检索参数改动、模型升级都能快速发现效果回退。

事实核查层。 对高风险的生成内容(医疗、法律、金融)用 FactCheckingEvaluator 逐条核查,不通过就不返回给用户。

人工抽检层。 LLM-as-a-Judge 跑批量评分,人工抽看低分样本,沉淀成新的测试用例。

三层配合,RAG 系统的质量才可控。

37.6 小结

QuestionAnswerAdvisor 让知识问答开箱即用,RetrievalAugmentationAdvisor 提供模块化编排,查询改写和扩展解决”问题不适合检索”。检索优化三板斧是 topK、相似度阈值、过滤表达式。评估用 Evaluator 体系:RelevancyEvaluator 看相关性,FactCheckingEvaluator 查事实,LLM-as-a-Judge 做批量打分,三层评估守住质量底线。

到这里,RAG 从原理、数据管道、向量库到问答评估的完整链路就通了。