Spring AI 中的 RAG 检索原理:Embedding 模型调用流程与检索实现
在 Spring AI 中,RAG(检索增强生成)的核心流程无非是两步:① 检索(Retrieval,查出相关文档)+② 生成(Generation,让大模型阅读文档并回答)。
如果你写了如下的similaritySearch方法:
publicList<Document>similaritySearch(Stringquery){returnvectorStore.similaritySearch(SearchRequest.builder().query(query).topK(5).similarityThreshold(0.7f).build());}这就意味着:你通过这个方法自己实现了 RAG 中的“检索”能力。如果你再写代码把这个方法查出来的文档拼到 Prompt 里发给大模型,你实际上就是“自己实现了一套简易版的 QuestionAnswerAdvisor”。
💡 核心澄清:基础语义检索 vs. 把问题转化成多种问法
在讨论如何实现检索前,我们需要区分 RAG 中的两种不同层次的语义优化:
向量层面的语义理解(通过
similaritySearch或QuestionAnswerAdvisor实现):
因为哪怕不改写问题,直接把用户的原话通过 Embedding 向量模型变成向量去搜,向量模型本身就能听懂同义词(比如“报销”和“补贴”在向量空间里本来就挨得很近)。这属于向量层面的语义理解。
你写的similaritySearch方法正是利用了这种能力。文本层面的同义扩写(把问题转化成多种问法 / Multi-Query Expansion):
如果用户的问题非常口语化、含糊不清、或者包含多个子问题,单靠把原话转成一次向量可能不够稳妥。这时才需要在检索前让大模型先把 1 个问题“改写/裂变成 3 种不同角度的问法”(需要使用更高级的RetrievalAugmentationAdvisor配合MultiQueryExpander),然后分别去向量库检索并合并去重。
一、 核心底层:调用 Embedding 模型的详细流程与代码
无论是你手动调similaritySearch,还是通过QuestionAnswerAdvisor间接去查,只要触发了向量检索,底层必定会执行以下调用 Embedding 模型的流程:
[ 用户输入的字符串 Query ] │ ▼ (1. 注入并调用 EmbeddingModel) [ 调用 embeddingModel.embed(query) ] │ ▼ (2. 模型计算并输出数学特征) [ 高维浮点数数组向量 (float[] 向量) ] │ ▼ (3. 携带 topK 和阈值参数) [ 访问向量数据库进行余弦相似度匹配并返回结果 ]简明代码示例:
在 Spring AI 中,你可以直接注入EmbeddingModel显式调用,也可以通过VectorStore隐式触发:
@AutowiredprivateEmbeddingModelembeddingModel;publicvoidtestEmbedding(Stringquery){// 1. 显式调用:将文本转为高维浮点数向量float[]vector=embeddingModel.embed(query).getVector();// 2. 向量库检索(内部会自动执行上述的 embed 流程)List<Document>docs=vectorStore.similaritySearch(SearchRequest.builder().query(query).build());}二、 对比:你是怎么处理similaritySearch的?
方式 A:【自己写方法】手动串联(类似于自己实现 Advisor)
如果在业务代码中是这样调用的:
@AutowiredprivateVectorStorevectorStore;@AutowiredprivateChatClientchatClient;publicStringmanualRag(StringuserQuery){// 1. 调用你自己写的语义检索方法(内部会自动触发上述的 Embedding 模型调用流程)List<Document>docs=this.similaritySearch(userQuery);// 2. 手动把查出来的文档拼成字符串Stringcontext=docs.stream().map(Document::getText).collect(Collectors.joining("\n---\n"));// 3. 手动塞给大模型生成回答returnchatClient.prompt().system("请根据以下参考资料回答:"+context).user(userQuery).call().content();}- 特点:逻辑完全由你掌控,每一步干了什么一清二楚。
方式 B:【使用 Spring AI 官方的】QuestionAnswerAdvisor(自动帮你调用similaritySearch)
如果不希望每次都手动写拼字符串的代码,可以直接用官方提供的QuestionAnswerAdvisor。它在底层自动帮你封装了调用similaritySearch的过程:
@OverridepublicvoidafterPropertiesSet()throwsException{// 官方的 QuestionAnswerAdvisor 内部会拿着配置的 SearchRequest 去自动执行类似你写的那套向量检索QuestionAnswerAdvisorquestionAnswerAdvisor=QuestionAnswerAdvisor.builder(vectorStore).searchRequest(SearchRequest.builder().similarityThreshold(0.7f).topK(5).build()).build();this.chatClient=ChatClient.builder(chatModel).defaultAdvisors(questionAnswerAdvisor)// 挂载后,发消息时自动触发检索.build();}