检索增强生成:从向量库到答案的 RAG 核心闭环

检索增强生成:从向量库到答案的 RAG 核心闭环 前面的文章完成了知识库构建的整条链路——从文档读取、解析、分段、向量化到写入向量数据库。接下来进入 RAG 的核心环节检索增强生成Retrieval-Augmented Generation。增强二字的含义是——先通过向量相似度检索从知识库中筛选出与用户问题最相关的内容再将检索结果注入大模型上下文让模型基于外部知识而不是自身参数记忆来回答。这是解决大模型幻觉问题最关键的一步。一、手动拼接 Prompt 方式手动方式将 RAG 的每一步都暴露给开发者适合需要精细控制的场景。核心流程封装在RagService中作为可复用的服务层1.1 RagService可复用的 RAG 服务Servicepublic class RagService { private static final String DEFAULT_PROMPT 请基于以下提供的参考文档内容回答用户的问题。 如果参考文档中没有相关信息请直接说明没有找到相关信息不要编造内容。 参考文档: {documents} 用户问题: {question} ; private final ChatModel chatModel; private final VectorStore vectorStore; public RagService(ChatModel chatModel, VectorStore vectorStore) { this.chatModel chatModel; this.vectorStore vectorStore; } /** * 手动拼接 Prompt 的 RAG 问答。 * return QaResponse 包含答案和引用来源 */ public QaResponse searchAndAnswer(String query, int topK, double threshold) { return searchAndAnswer(query, topK, threshold, null, DEFAULT_PROMPT); } /** * 手动拼接 Prompt 的 RAG 问答支持元数据过滤和自定义 Prompt 模板。 */ public QaResponse searchAndAnswer(String query, int topK, double threshold, String filterExpression, String promptTemplate) { // Step 1: 相似度检索 SearchRequest.Builder builder SearchRequest.builder() .query(query) .topK(topK) .similarityThreshold(threshold); if (filterExpression ! null !filterExpression.isBlank()) { builder.filterExpression(filterExpression); } ListDocument documents vectorStore.similaritySearch(builder.build()); // Step 2: 拼接文档内容 String documentContent documents.stream() .map(Document::getText) .collect(Collectors.joining( \n\n文档分隔线\n\n)); // Step 3: 构建 Prompt PromptTemplate template new PromptTemplate(promptTemplate); Prompt prompt template.create(Map.of( documents, documentContent, question, query)); // Step 4: 调用大模型 var response chatModel.call(prompt); String answer response ! null response.getResult() ! null response.getResult().getOutput() ! null ? response.getResult().getOutput().getText() : ; // Step 5: 构建来源文档列表含相似度分数 ListSourceDoc sources documents.stream() .map(doc - { double score extractScore(doc); return new SourceDoc( (String) doc.getMetadata().get(chunkId), truncate(doc.getText(), 300), score, doc.getMetadata()); }) .toList(); return new QaResponse(query, answer, sources); }}RagService完成五个步骤相似度检索 → 拼接文档 → 构建 Prompt → 调用大模型 → 组装来源信息。返回值QaResponse是一个结构化的 DTO包含原始问题、模型回答和检索来源public record QaResponse( String query, String answer, ListSourceDoc sources) {}public record SourceDoc( String chunkId, String content, // 截取前 300 字符的预览 double score, // 相似度分数 MapString, Object metadata) {}1.2 Controller 层调用Controller 只需注入RagService一行调用即可完成 RAGRestControllerRequestMapping(/rag)public class RagController { private final RagService ragService; // ... 构造器注入 /** 手动拼接 Prompt 的 RAG 问答 */ GetMapping(/xianni) public QaResponse ragXianni( RequestParam(query) String query, RequestParam(value threshold, defaultValue 0.7) double threshold, RequestParam(value topK, defaultValue 5) int topK) { if (query null || query.isBlank()) { throw new IllegalArgumentException(query is empty); } return ragService.searchAndAnswer(query, topK, threshold); }}1.3 检索参数调优指南参数含义建议范围说明query查询语句—用户原话即可会在内部自动 Embedding 向量化topK返回条数3~10太小可能漏掉关键信息太大可能引入噪音分散模型注意力similarityThreshold相似度阈值0~10.6~0.8越高越严格返回的都是高度相关的但也可能漏掉边缘相关内容需要反复调测检索效果以《仙逆》为例——用户问王林为什么不让王平修仙检索命中 3 条结果覆盖怨婴体质 → 不能修仙 → 被雷道子看穿的完整因果链足以让大模型给出靠谱回答。1.4 调用效果场景一问知识库外的问题GET /rag/xianni?query叶凡在哪threshold0.7响应JSON 格式Prompt 中明确要求没有找到不要编造避免了模型的自由发挥。场景二问知识库内的问题GET /rag/xianni?query王林为什么不让王平修仙threshold0.7检索命中 3 条相关文档后模型综合生成根据参考文档王林不让王平修仙的原因是王平天生具有怨婴体质。怨婴体质的人一旦修仙体内的怨气就会爆发导致魂飞魄散连转世重生都做不到。这不是不爱恰恰是最深沉的爱——王林宁愿儿子怨他一辈子也不愿儿子魂飞魄散。从十二岁到七十八岁王平三次询问能不能修道王林次次拒绝。直到王平临终前王林才含泪说出真相。同时返回 3 条检索来源每条包含chunkId、score和content预览方便追溯回答依据。1.5 手动拼接方式的定位优点完全掌控每一个步骤——检索参数、文档拼接格式、Prompt 措辞、调用链路都可以自由定制。返回值包含结构化的来源追溯信息。缺点多处代码需要重复编写topK和similarityThreshold改动需要重新发版多轮对话时上下文管理复杂。二、RetrievalAugmentationAdvisorSpring AI 的自动化方案Spring AI 提供了RetrievalAugmentationAdvisor将检索 → 拼接 Prompt → 调用模型的流程固化为一个可复用的 Advisor挂在ChatClient的 Advisor 链上。2.1 Maven 依赖dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-advisors-vector-store/artifactId/dependency2.2 最简配置RestControllerRequestMapping(/rag/retriever)public class RetrieverRagController implements InitializingBean { privatefinal ChatModel chatModel; privatefinal VectorStore vectorStore; private ChatClient chatClient; public RetrieverRagController(ChatModel chatModel, VectorStore vectorStore) { this.chatModel chatModel; this.vectorStore vectorStore; } Override public void afterPropertiesSet() { // RetrievalAugmentationAdvisor 使用 VectorStoreDocumentRetriever 封装检索逻辑 this.chatClient ChatClient.builder(chatModel) .defaultAdvisors( RetrievalAugmentationAdvisor.builder() .documentRetriever( VectorStoreDocumentRetriever.builder() .vectorStore(vectorStore) .similarityThreshold(0.7) .topK(5) .build() ) .build() ) .build(); } GetMapping(/retrieveAdvisor) public String retrieveAdvisor(RequestParam(query) String query) { // 一行调用——Advisor 自动完成检索 → 拼接 Prompt → 调用模型 return chatClient.prompt(query).call().content(); }}RetrievalAugmentationAdvisor通过 builder 模式的documentRetriever()方法注入VectorStoreDocumentRetriever后者封装了VectorStore和检索参数。Advisor 内部默认 Prompt 模板与手动版的含义相近——将检索到的文档作为上下文引导模型基于上下文回答。2.3 自定义 Prompt 模板当默认 Prompt 不满足需求时可以指定自定义模板必须包含{query}和{question_answer_context}占位符Overridepublic void afterPropertiesSet() { // 自定义 Prompt 模板——与仙逆知识库更匹配 PromptTemplate customTemplate new PromptTemplate( 你是《仙逆》小说的知识助手。请基于以下提供的原文片段回答读者的提问。 回答要求 1. 如果原文片段中有相关信息请基于原文回答不要凭空编造。 2. 如果原文片段中没有相关信息请明确告知这个问题暂时没有收录相关的仙逆原文。 3. 回答时请尽可能引用原文的关键描述。 相关原文片段: {question_answer_context} 读者提问: {query} ); this.chatClient ChatClient.builder(chatModel) .defaultAdvisors( RetrievalAugmentationAdvisor.builder() .documentRetriever( VectorStoreDocumentRetriever.builder() .vectorStore(vectorStore) .similarityThreshold(0.7) .topK(5) .build() ) .build() ) .build();}2.4 运行时动态过滤Advisor 支持在每次调用时动态传入过滤条件同一实例对不同请求使用不同的过滤表达式GetMapping(/byChapter)public String retrieveByChapter( RequestParam(query) String query, RequestParam(chapter) String chapter) { return chatClient.prompt(query) .advisors(a - a.param( VectorStoreDocumentRetriever.FILTER_EXPRESSION, chapter chapter )) // 运行时动态过滤 .call() .content();}// 调用示例// GET /rag/retriever/byChapter?query王林怎么悟出因果意境chapter二次化凡// → 仅检索二次化凡章节中的相关内容FILTER_EXPRESSION常量定义在VectorStoreDocumentRetriever上运行时通过advisors(a - a.param(...))传入实现对同一 ChatClient 实例的不同过滤策略。2.5 手动拼接 vs Advisor 对比维度手动拼接 PromptRetrievalAugmentationAdvisor代码量多检索拼接调用三步少声明式配置灵活性极高——每一步都可控制中——自定义 Prompt 运行时过滤复用性低——每处需重写高——Advisor 可全局共享多 Advisor 组合自行管理链式组合RAG ChatMemory 日志…适用场景精细调优阶段快速构建 标准化流程三、RetrievalAugmentationAdvisor 源码原理理解 Advisor 的运作机制有助于在使用中做更精细的控制。RetrievalAugmentationAdvisor通过实现CallAdvisor接口在模型调用前后插入逻辑3.1 初始化// RetrievalAugmentationAdvisor 初始化阶段通过 VectorStoreDocumentRetriever 组装检索依赖RetrievalAugmentationAdvisor.builder() .documentRetriever( VectorStoreDocumentRetriever.builder() .vectorStore(vectorStore) // 向量存储 .similarityThreshold(0.7) // 相似度阈值 .topK(5) // 返回条数 .build() ) .build();VectorStoreDocumentRetriever作为检索器封装了VectorStore和检索参数与 Advisor 解耦——检索逻辑可以独立替换例如替换为混合检索器而不影响 Advisor 的其他部分。3.2 前置处理adviseCall在模型调用前before阶段执行核心的 RAG 逻辑——与手动方式完全一致用户请求进入 │ ▼Step 1: 获取用户问题query │ ▼Step 2: VectorStoreDocumentRetriever 调用 VectorStore.similaritySearch(query) │ 王林为何炼化射神车 → {doc1, doc2, doc3} │ ▼Step 3: 将检索文档拼入 {question_answer_context} │ Prompt Context: {doc1}\n{doc2}\n{doc3}\n\nQuestion: 王林为何炼化射神车 │ ▼Step 4: 构造增强后的 ChatClientRequest → 传给 ChatModel │ ▼ChatModel.call(request) → 生成回答3.3 后置处理adviseAfter模型返回回答后after阶段只做收尾工作——将检索到的文档信息内容、元数据、相似度分数附加到响应的metadata中不修改模型生成的文本。这方便上层做调试和审计比如追溯回答是基于哪些文档生成的。四、RetrievalAugmentationAdvisor 的高级管道能力RetrievalAugmentationAdvisor基于 Modular RAG 架构除了基础的检索增强还支持将检索增强拆分为更细粒度的管道阶段适合查询重写、多路召回、后处理排序等高级场景Advisor ragAdvisor RetrievalAugmentationAdvisor.builder() // Pre-Retrieval查询预处理 .queryAugmenter(ContextualQueryAugmenter.builder() .allowEmptyContext(true) // 允许上下文为空时仍继续 .build()) // .queryTransformer(new RewriteQueryTransformer(chatModel)) // 模糊查询重写 // Retrieval文档检索 .documentRetriever(VectorStoreDocumentRetriever.builder() .similarityThreshold(0.7) .topK(5) .vectorStore(vectorStore) .build()) // Post-Retrieval文档后处理可选 // .documentPostProcessor(...) // 去重、重排序、压缩 .build();管道阶段组件功能仙逆示例查询预处理RewriteQueryTransformer模糊查询改为检索友好的表述“落月村那部分” → “王林在落月村的经历”查询扩展MultiQueryExpander生成多个检索查询同时搜王平“怨婴”“化凡”文档检索VectorStoreDocumentRetriever向量库相似度搜索核心检索步骤文档后处理DocumentPostProcessor去重、重排序、压缩按全文连贯性重排检索结果上下文增强ContextualQueryAugmenter将文档注入上下文拼接为最终 Prompt五、LangChain4j 的检索增强生成作为对照LangChain4j 的检索增强生成通过AiServicesContentRetriever实现// 方式一AiServices 声明式 RAGpublic interface XianniAssistant { SystemMessage(你是《仙逆》小说的知识助手。基于提供的资料回答问题不要编造。) String chat(MemoryId int memoryId, UserMessage String question);}// 配置 ContentRetrieverContentRetriever contentRetriever EmbeddingStoreContentRetriever.builder() .embeddingModel(embeddingModel) .embeddingStore(embeddingStore) // 向量存储 .maxResults(5) // 等同 topK .minScore(0.7) // 等同 similarityThreshold .build();// 创建 AiServices 实例XianniAssistant assistant AiServices.builder(XianniAssistant.class) .chatLanguageModel(chatModel) .contentRetriever(contentRetriever) // 注入检索器 .chatMemory(MessageWindowChatMemory.withMaxMessages(10)) // 对话记忆 .build();// 使用——与调用普通 Service 接口完全一致String answer assistant.chat(1, 王林的因果意境是怎么悟出来的);两框架检索增强生成对比维度Spring AILangChain4j简单 RAGRetrievalAugmentationAdvisorAiServicesContentRetriever多 Advisor 链defaultAdvisors(RAG, ChatMemory, Logging...)AiServices逐项builder().xxx()配置自定义 PromptpromptTemplate()SystemMessage注解元数据过滤运行时VectorStoreDocumentRetriever.FILTER_EXPRESSION参数EmbeddingSearchRequest.filter()对话记忆MessageChatMemoryAdvisorChatMemoryMemoryId模块化管道RetrievalAugmentationAdvisor查询重写/扩展/后处理需自行组合六、《仙逆》知识库的完整检索增强示例将前面所有知识串联构建一个完整的仙逆问答 Controller。XianniQAController使用RagService作为 RAG 引擎通过自定义的XIANNI_PROMPT模板注入仙逆知识库的专属角色设定RestControllerRequestMapping(/xianni)public class XianniQAController { private static final String XIANNI_PROMPT 你是《仙逆》的知识助手耳根所著仙侠小说的专家。 请基于以下原文片段详细回答读者的问题。 注意 - 如果原文中有答案务必基于原文回答 - 如果涉及多个片段请综合分析 - 如果原文中没有直接说明这个问题暂时没有收录相关的仙逆原文 原文片段: {documents} 读者问题: {question} ; privatefinal RagService ragService; publicXianniQAController(RagService ragService) { this.ragService ragService; } /** 仙逆知识库问答 */ GetMapping(/ask) public QaResponse ask(RequestParam(query) String query) { if (query null || query.isBlank()) { throw new IllegalArgumentException(query is empty); } return ragService.searchAndAnswer(query, 5, 0.7, null, XIANNI_PROMPT); } /** 仙逆知识库问答——按章节过滤 */ GetMapping(/ask/chapter) public QaResponse askInChapter( RequestParam(query) String query, RequestParam(chapter) String chapter) { if (query null || query.isBlank()) { throw new IllegalArgumentException(query is empty); } if (chapter null || chapter.isBlank()) { throw new IllegalArgumentException(chapter is empty); } return ragService.searchAndAnswer(query, 5, 0.7, chapter chapter , XIANNI_PROMPT); }}与第二节的 Advisor 方式不同这里选择手动方式的原因在于XianniQAController返回的是结构化QaResponse含query、answer、sources而 Advisor 方式返回纯文本。对于需要前端展示引用来源的场景RagServiceQaResponse的组合更合适。调用效果请求预期回答GET /xianni/ask?query化神先化凡是什么意思从检索到的原文解释化神期必须先经历凡人生活、感悟生死方能突破GET /xianni/ask?query什么是微服务“这个问题暂时没有收录相关的仙逆原文”——知识库中没有相关内容GET /xianni/ask/chapter?query雷道子是谁chapter二次化凡仅检索二次化凡章节返回雷道子作为阴虚境强者的信息七、小结要点说明RAG 三步检索文档 → 拼接 Prompt → 调用模型生成检索参数topK控制数量310similarityThreshold控制质量0.60.8手动 vs Advisor手动灵活适合调优返回结构化结果Advisor 简洁适合标准化复用RetrievalAugmentationAdvisor 原理before 阶段检索拼接after 阶段追加元数据高级管道支持查询重写/多路召回/后处理通过 builder 按需启用两框架对照Spring AI 靠 Advisor 链LangChain4j 靠AiServices声明至此从索引构建ETL Pipeline到检索增强生成RAG的完整链路已经打通。这是 RAG 系统的最小闭环——从文档到答案。实际生产中的高级优化查询重写、混合检索、重排序等将在此基础上逐层叠加。学AI大模型的正确顺序千万不要搞错了2026年AI风口已来各行各业的AI渗透肉眼可见超多公司要么转型做AI相关产品要么高薪挖AI技术人才机遇直接摆在眼前有往AI方向发展或者本身有后端编程基础的朋友直接冲AI大模型应用开发转岗超合适就算暂时不打算转岗了解大模型、RAG、Prompt、Agent这些热门概念能上手做简单项目也绝对是求职加分王给大家整理了超全最新的AI大模型应用开发学习清单和资料手把手帮你快速入门学习路线:✅大模型基础认知—大模型核心原理、发展历程、主流模型GPT、文心一言等特点解析✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑✅开发基础能力—Python进阶、API接口调用、大模型开发框架LangChain等实操✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经以上6大模块看似清晰好上手实则每个部分都有扎实的核心内容需要吃透我把大模型的学习全流程已经整理好了抓住AI时代风口轻松解锁职业新可能希望大家都能把握机遇实现薪资/职业跃迁这份完整版的大模型 AI 学习资料已经上传CSDN朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】