
以 knowledge-base.md 为语料的 Spring AI RAG 集成测试实战指南【免费下载链接】spring-aiAn Application Framework for AI Engineering项目地址: https://gitcode.com/GitHub_Trending/spr/spring-ai本指南以 Spring AI 仓库中 knowledge-base.md 为切入点剖析这份测试知识库在QuestionAnswerAdvisor、RetrievalAugmentationAdvisor等检索增强生成RAG集成测试中的完整生命周期——从 Markdown 文档读取、向量化入库、相似度检索到上下文增强问答并给出可直接复用的代码示例与断言思路。读完本文你将掌握如何在 Spring AI 项目中构建一套文档 → 向量库 → Advisor → 问答的端到端集成测试。一、这份文档是什么一份会讲故事的测试语料knowledge-base.md位于 spring-ai-integration-tests/src/test/resources/documents/是 Spring AI 集成测试模块spring-ai-integration-tests内置的知识库样本。它的正文并非技术说明而是一篇完整的虚构故事《Anacletus and Birbas Quest for the Loch of the Stars》《阿纳克莱图斯与比尔巴的星之湖寻宝记》共 10 个章节讲述了主角一只谨慎聪明的猫头鹰Anacletus和一只活泼好奇的猫Birba冒险地点苏格兰高地Scottish Highlands最终目的地是传说中夜晚比任何湖泊都明亮的星之湖Loch of the Stars关键情节跨越小溪、遇到长毛高地牛Fergus指路沿大石头旁的小路走、穿过神秘森林、与鹿群相遇、在湖畔过夜后返程回家。这份语料的设计初衷值得玩味它包含精确可验证的事实点如冒险发生在苏格兰高地、专有名词Anacletus、Birba、Fergus、Loch of the Stars以及前后呼应的叙事线索非常适合用来校验 RAG 链路是否真的检索到了正确上下文。同时它没有任何版权与合规负担可以被安全地提交进仓库并反复运行。在正文中文章提供了知识库文件本身的完整内容你可以在 knowledge-base.md 中直接查看全部 10 个章节的原始文本。集成测试通过Value(${classpath:documents/knowledge-base.md})将该资源注入为Resource例如 QuestionAnswerAdvisorIT.java 与 RetrievalAugmentationAdvisorIT.java 中的用法完全一致。二、知识库的完整生命周期从 Markdown 到向量存储在任一集成测试中这份知识库都要经历一条标准化的数据处理管线其核心代码在每个测试类的setUp()中保持一致见 QuestionAnswerAdvisorIT.javaBeforeEach void setUp() { DocumentReader markdownReader new MarkdownDocumentReader(this.knowledgeBaseResource, MarkdownDocumentReaderConfig.defaultConfig()); this.knowledgeBaseDocuments markdownReader.read(); this.pgVectorStore.add(this.knowledgeBaseDocuments); }2.1 读取MarkdownDocumentReaderMarkdownDocumentReader是 Spring AI 提供的 Markdown 专用读取器实现类位于 spring-ai-markdown-document-reader 模块。构造函数接收Resource此处为 classpath 下的documents/knowledge-base.md和MarkdownDocumentReaderConfig。defaultConfig()使用默认配置MarkdownDocumentReaderConfig支持按需调整分块chunk尺寸、是否按标题切分等参数详见 MarkdownDocumentReaderConfig 源码。read()返回ListDocument——一个 10 章故事会被切分为多个Document每个文档即一个可检索的知识单元。2.2 入库PgVectorStorePgVectorStore是 PostgreSQL pgvector 扩展的向量存储实现位于 spring-ai-pgvector-store。pgVectorStore.add(documents)会为每个Document生成嵌入向量并写入数据库同时保留其文本与元数据。测试结束后tearDown()通过pgVectorStore.delete(ids)清理数据保证测试可重复运行AfterEach void tearDown() { this.pgVectorStore.delete(this.knowledgeBaseDocuments.stream().map(Document::getId).toList()); }提示在RetrievalAugmentationAdvisorIT与QuestionAnswerAdvisorIT中PgVectorStore均通过Autowired注入并由TestApplicationspring-ai-integration-tests模块提供自动配置。整个链路依赖真实的 OpenAI API测试类标注EnabledIfEnvironmentVariable(named OPENAI_API_KEY, matches .)与可用的 PostgreSQL/pgvector 实例。三、QuestionAnswerAdvisor基于向量检索的问答3.1 基础用法QuestionAnswerAdvisorIT.java 中最基础的一条测试如下Test void qaBasic() { String question Where does the adventure of Anacletus and Birba take place?; QuestionAnswerAdvisor qaAdvisor QuestionAnswerAdvisor.builder(this.pgVectorStore).build(); ChatResponse chatResponse ChatClient.builder(this.openAiChatModel) .build() .prompt(question) .advisors(qaAdvisor) .call() .chatResponse(); assertThat(chatResponse).isNotNull(); String response chatResponse.getResult().getOutput().getText(); assertThat(response).containsIgnoringCase(Highlands); evaluateRelevancy(question, chatResponse); }关键点QuestionAnswerAdvisor.builder(vectorStore).build()一条链式调用即完成 Advisor 装配通过ChatClient.advisors(qaAdvisor)挂载后用户的原始问题会先被拿去向量库检索检索到的文档作为上下文与问题一起交给模型断言response.containsIgnoringCase(Highlands)直接验证了模型确实基于检索到的知识库回答而非依赖自身先验知识——这正是本题选材的巧妙之处Anacletus 与 Birba 是虚构角色模型不可能知道这个故事。3.2 用 RelevancyEvaluator 做相关性评测测试末尾的evaluateRelevancy使用了 Spring AI 的评测能力QuestionAnswerAdvisorIT.javaprivate void evaluateRelevancy(String question, ChatResponse chatResponse) { EvaluationRequest evaluationRequest new EvaluationRequest(question, chatResponse.getMetadata().get(QuestionAnswerAdvisor.RETRIEVED_DOCUMENTS), chatResponse.getResult().getOutput().getText()); RelevancyEvaluator evaluator new RelevancyEvaluator(ChatClient.builder(this.openAiChatModel)); EvaluationResponse evaluationResponse evaluator.evaluate(evaluationRequest); assertThat(evaluationResponse.isPass()).isTrue(); }QuestionAnswerAdvisor.RETRIEVED_DOCUMENTS是 Advisor 写入响应元数据中的键携带本次实际检索到的文档可用来追溯检索质量RelevancyEvaluator让模型充当裁判判断回答与问题的相关性将结果封装为EvaluationResponse并断言isPass()。这样就把有没有答对从人工判断升级为自动化断言。3.3 流式场景QuestionAnswerAdvisorStreamIT.java 验证了 Advisor 在流式streaming上下文中的行为它使用OpenAiChatOptions.builder().streamUsage(true)开启流式用量统计并通过FluxString订阅逐块输出FluxString responseFlux ChatClient.builder(this.openAiChatModel) .build() .prompt(question) .advisors(qaAdvisor) .options(OpenAiChatOptions.builder().streamUsage(true)) .stream() .content(); String response responseFlux.collectList().block().stream().collect(Collectors.joining()); assertThat(response).isNotEmpty(); assertThat(response).containsIgnoringCase(Highlands);这保证了对同一个知识库流式与非流式问答能给出同样准确的检索增强结果。3.4 自定义提示模板QuestionAnswerAdvisor默认用一套内置模板把用户问题 检索上下文拼接后交给模型但允许通过.promptTemplate()完全替换。官方文档 retrieval-augmented-generation.adoc 与集成测试 qaCustomPromptTemplate 展示了自定义模板的两个硬性要求——必须包含$query$用户问题与$question_answer_context$检索上下文两个占位符PromptTemplate customPromptTemplate PromptTemplate.builder() .renderer(StTemplateRenderer.builder().startDelimiterToken($).endDelimiterToken($).build()) .template( $query$ Context information is below, surrounded by --------------------- --------------------- $question_answer_context$ --------------------- Given the context and provided history information and not prior knowledge, reply to the user comment. If the answer is not in the context, inform the user that you cant answer the question. ) .build(); QuestionAnswerAdvisor qaAdvisor QuestionAnswerAdvisor.builder(this.pgVectorStore) .promptTemplate(customPromptTemplate) .build();两种常见的占位符定界符用法定界符配置方式场景query/question_answer_contextStTemplateRenderer配置、作为起止符避免与模板内其他尖括号内容冲突$query$/$question_answer_context$StTemplateRenderer配置$、$作为起止符默认风格语义清晰3.5 自定义模板渲染器qaCustomTemplateRenderer 展示了另一种定制维度在ChatClient层配置TemplateRenderer使带占位符的用户消息在进入 Advisor 前先被渲染ChatResponse chatResponse ChatClient.builder(this.openAiChatModel) .build() .prompt() .user(user - user.text(Where does the adventure of character1 and character2 take place?) .param(character1, Anacletus) .param(character2, Birba)) .advisors(qaAdvisor) .templateRenderer(StTemplateRenderer.builder().startDelimiterToken().endDelimiterToken().build()) .call() .chatResponse();注意区分两层渲染ChatClient.templateRenderer()渲染的是进入 Advisor 之前的初始用户消息QuestionAnswerAdvisor.Builder.promptTemplate()定制的是Advisor 合并检索上下文时的提示模板。二者职责不同可独立配置。四、RetrievalAugmentationAdvisor模块化 RAG 流水线与开箱即用的QuestionAnswerAdvisor不同RetrievalAugmentationAdvisor采用模块化架构让你显式组装查询处理 → 检索 → 上下文增强 → 生成的每一环。RetrievalAugmentationAdvisorIT.java 以同一份knowledge-base.md语料完整演示了六种典型装配方式。4.1 朴素 RAGNaive RAGRetrievalAugmentationAdvisor ragAdvisor RetrievalAugmentationAdvisor.builder() .documentRetriever(VectorStoreDocumentRetriever.builder().vectorStore(this.pgVectorStore).build()) .build(); ChatResponse chatResponse ChatClient.builder(this.openAiChatModel) .build() .prompt(Where does the adventure of Anacletus and Birba take place?) .advisors(ragAdvisor) .call() .chatResponse();VectorStoreDocumentRetriever负责把问题转成向量并查询PgVectorStore。官方文档 retrieval-augmented-generation.adoc 还展示了通过similarityThreshold(0.50)设置相似度阈值等参数。4.2 请求级过滤FILTER_EXPRESSION通过VectorStoreDocumentRetriever.FILTER_EXPRESSION上下文参数可以在不修改 Advisor 定义的情况下按元数据动态过滤检索范围RetrievalAugmentationAdvisorIT.javaChatResponse chatResponse ChatClient.builder(this.openAiChatModel) .build() .prompt(Where does the adventure of Anacletus and Birba take place?) .advisors(ragAdvisor) .advisors(a - a.param(VectorStoreDocumentRetriever.FILTER_EXPRESSION, location Italy)) .call() .chatResponse(); // 由于知识库中没有任何文档的 location 元数据等于 Italy // 检索结果为空DOCUMENT_CONTEXT 元数据为 null assertThat((String) chatResponse.getResult().getMetadata() .get(RetrievalAugmentationAdvisor.DOCUMENT_CONTEXT)).isNull();这个断言非常巧妙它验证了过滤表达式确实生效——Italy 与故事语料完全无关因此检索不到任何文档Advisor 的DOCUMENT_CONTEXT元数据为空。同样的机制在 VectorStoreDocumentRetrieverIT.java 中有更细粒度的验证四个带location元数据Whispering Woods、Alfea等的文档在location Whispering Woods过滤下只命中 2 篇。4.3 高级 RAG查询变换与扩展同一个知识库还驱动了三种查询端优化策略的测试策略作用测试示例关键配置CompressionQueryTransformer结合对话历史压缩查询去除冗余指代ragWithCompression结合MessageChatMemoryAdvisor与MessageWindowChatMemory追问 Did they meet any cow? 断言答出 Fergus.chatClientBuilder(ChatClient.builder(openAiChatModel))RewriteQueryTransformer把口语化/模糊问题重写为适合向量检索的表述ragWithRewrite问题 Where are the main characters going? 断言答出 Loch of the Stars.targetSearchSystem(vector store)TranslationQueryTransformer跨语言检索先翻译再检索ragWithTranslation丹麦语问题 Hvor finder Anacletus og Birbas eventyr sted? 断言答案含 highlands 或 højland.targetLanguage(english)MultiQueryExpander把一个问题扩展为多个变体查询提升召回ragWithMultiQuery.numberOfQueries(2)以重写为例RetrievalAugmentationAdvisor ragAdvisor RetrievalAugmentationAdvisor.builder() .queryTransformers(RewriteQueryTransformer.builder() .chatClientBuilder(ChatClient.builder(this.openAiChatModel)) .targetSearchSystem(vector store) .build()) .documentRetriever(VectorStoreDocumentRetriever.builder().vectorStore(this.pgVectorStore).build()) .build();这些测试共同说明语料本身的一致性人名、地名、情节在全文重复出现是检验查询优化后仍能命中正确上下文的前提。4.4 文档后处理替换检索结果ragWithDocumentPostProcessor展示了documentPostProcessors回调——可以在检索之后、送入模型之前对文档列表做任意变换RetrievalAugmentationAdvisorIT.javaRetrievalAugmentationAdvisor ragAdvisor RetrievalAugmentationAdvisor.builder() .documentRetriever(VectorStoreDocumentRetriever.builder().vectorStore(this.pgVectorStore).build()) .documentPostProcessors((query, documents) - List .of(Document.builder().text(The adventure of Anacletus and Birba takes place in Molise).build())) .build();这里直接把检索结果替换为一段新文本随后断言模型回答包含 Molise用于验证后处理管线确实接管了文档流。五、从语料设计反推测试要点如何复制这套测试方案综合以上测试knowledge-base.md之所以能同时服务多种 RAG 场景是因为它天然满足了几条测试语料设计原则这也正是你在自己项目中编写测试知识库时可借鉴的事实可断言故事中的地点Highlands、角色Anacletus、Birba、Fergus、目标Loch of the Stars在文中反复出现任意一次成功检索都可用containsIgnoringCase精确断言虚构无污染角色与情节均为原创模型不具备先验知识回答必须来自检索上下文杜绝蒙对跨语言/跨形态可测适合验证翻译、重写、压缩等查询变换器的效果元数据可扩展配合FILTER_EXPRESSION可模拟空结果、精确过滤等边界场景。若要在自己的 Spring AI 项目中复刻这套方案最小步骤为准备一份 Markdown 知识库并放入src/test/resources/documents/用MarkdownDocumentReader读取并add进PgVectorStore或任意 VectorStore在ChatClient上挂载QuestionAnswerAdvisor或RetrievalAugmentationAdvisor用断言 RelevancyEvaluator双保险验证答案正确性与相关性通过EnabledIfEnvironmentVariable控制需要真实模型 API 的测试执行。六、小结knowledge-base.md表面上是一篇童话故事实质上是 Spring AI 集成测试体系中设计精良的事实固定语料它以 10 个章节构建了一个可检索、可断言、可变换的知识空间被QuestionAnswerAdvisor、RetrievalAugmentationAdvisor、VectorStoreDocumentRetriever以及流式问答等多个集成测试共同引用覆盖了 RAG 链路中的读取、入库、检索、过滤、查询优化、后处理、评测与流式输出等全部关键环节。阅读其对应源码QuestionAnswerAdvisorIT.java、RetrievalAugmentationAdvisorIT.java、VectorStoreDocumentRetrieverIT.java与官方 RAG 文档retrieval-augmented-generation.adoc即可在自有项目中复现整套文档 → 向量库 → Advisor → 问答的工程实践。【免费下载链接】spring-aiAn Application Framework for AI Engineering项目地址: https://gitcode.com/GitHub_Trending/spr/spring-ai创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考