一、Spring AI & RAG
RAG检索增强,最常见的实现方式便是向量检索增强,通过分析读取本地私有化知识内容,Embedding后存储至VectorStore中做为知识库。当用户发起提问时,通过向量余弦距离相似度等方式,召回知识库中的相似内容作为上下文背景组装至Prompt中,达到动态扩充大模型对私有知识感知的能力,减少大模型的幻觉。
在本专栏前面文章,介绍过LangChain构建本地知识库问答应用,实现过程也是遵循了上述的流程,整体是基于Python实现的方案。文章地址如下:
LangChain 基于 ChatGPT 构建本地知识库问答应用
在JAVA体系下Spring AI针对RAG的实现,也类似LangChain做了很多的封装和支持,基本上做到使用较少的代码即可实现RAG的过程。
例如:官方提供了多种格式的DocumentReader封装,包括:JSON、Text、HTML、Markdown、PDF、以及万能文件解析神器Tika。针对文本内容拆分,Spring AI也提供了TextSplitter和TokenTextSplitter两种方式。对于向量库的支持,也是几乎涵盖了市面上所有类型的向量库可供选择。
更多的细节可以参考官方的文档:
https://docs.spring.io/spring-ai/reference/api/retrieval-augmented-generation.html
本次实现的RAG流程如下所示:
模型使用OpenAI的GPT-4.1,Embedding模型采用OpenAI的text-embedding-3-small,向量库使用Milvus,实验前请确保安装好Milvus环境。
二、私有知识准备
对于实验的私有知识,我这里随便制造了一些内容,包括,xlsx、pdf、docx格式,你可以使用真实的私有知识进行实验,内容示例如下:
Excel:
PDF:
Docx:
三、Spring AI RAG 流程搭建
新建SpringBoot项目,在pom中修改如下依赖:
<?xml version="1.0" encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.example</groupId><artifactId>rag</artifactId><version>0.0.1-SNAPSHOT</version><name>rag</name><description>rag</description><properties><java.version>17</java.version><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><spring-boot.version>3.3.0</spring-boot.version><spring-ai.version>1.0.0-SNAPSHOT</spring-ai.version></properties><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-model-openai</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-starter-vector-store-milvus</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-advisors-vector-store</artifactId></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-tika-document-reader</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency></dependencies><dependencyManagement><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-bom</artifactId><version>${spring-ai.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><repositories><repository><name>Central Portal Snapshots</name><id>central-portal-snapshots</id><url>https://central.sonatype.com/repository/maven-snapshots/</url><releases><enabled>false</enabled></releases><snapshots><enabled>true</enabled></snapshots></repository><repository><id>spring-milestones</id><name>Spring Milestones</name><url>https://repo.spring.io/milestone</url><snapshots><enabled>false</enabled></snapshots></repository><repository><id>spring-snapshots</id><name>Spring Snapshots</name><url>https://repo.spring.io/snapshot</url><releases><enabled>false</enabled></releases></repository></repositories><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.8.1</version><configuration><source>17</source><target>17</target><encoding>UTF-8</encoding></configuration></plugin><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId><version>${spring-boot.version}</version><configuration><mainClass>com.example.rag.RagApplication</mainClass></configuration><executions><execution><id>repackage</id><goals><goal>repackage</goal></goals></execution></executions></plugin></plugins></build></project>修改application.yml文件,加入如下配置:
server:port:8080spring:ai:openai:base-url:https://api.openai.comapi-key:{your_openai_key}chat:options:model:gpt-4.1embedding:options:model:text-embedding-3-smallvectorstore:milvus:client:host:127.0.0.1port:19530username:"root"password:"milvus"databaseName:"default"collectionName:"vector_rag"embeddingDimension:1536# text-embedding-3-small 模型向量维度的大小indexType:IVF_FLATmetricType:COSINEinitialize-schema:true实现文档解析、Embedding、持久化至向量库逻辑,其中文档解析这里使用Tika来实现,这里为了演示效果直接递归读取了本机目录下的文件,对于文件的管理你在实际应用时应该考虑使用文件系统进行统一管理:
publicinterfacePersistenceVectorService{// 加载解析文件知识,并存储至向量库中voidload(Stringpath);}@Slf4j@ServicepublicclassPersistenceVectorServiceImplimplementsPersistenceVectorService{privatefinalVectorStorevectorStore;publicPersistenceVectorServiceImpl(VectorStorevectorStore){this.vectorStore=vectorStore;}@Overridepublicvoidload(Stringpath){List<Resource>list=newArrayList<>();recursionAllFiles(list,newFile(path));log.info("找到{}个文件。",list.size());list.stream().filter(Objects::nonNull).map(r->newTikaDocumentReader(r).get()).map(d->newTokenTextSplitter().apply(d)).forEach(vectorStore::add);log.info("持久化至vectorStore完成。");}privatevoidrecursionAllFiles(List<Resource>list,Filefile){if(file.isDirectory()){Arrays.stream(Objects.requireNonNull(file.listFiles())).filter(Objects::nonNull).forEach(f->{recursionAllFiles(list,f);});}else{list.add(newFileSystemResource(file));}}}执行逻辑,处理上面准备的文档:
@SpringBootTestclassRagApplicationTests{@ResourceprivatePersistenceVectorServiceetlService;@TestvoidcontextLoads(){etlService.load("D:/rag");}}运行后,可以在 Milvus 中看到自动创建的Collection:
接着实现问答的过程:
publicinterfaceQAService{// 知识问答Stringqa(Stringquestion);}@Slf4j@ServicepublicclassQAServiceImplimplementsQAService{privatefinalChatClientchatClient;publicQAServiceImpl(ChatClient.BuilderchatClientBuilder,VectorStorevectorStore){this.chatClient=chatClientBuilder.defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore).searchRequest(SearchRequest.builder().similarityThreshold(0.5d).topK(4).build()).build()).build();}@OverridepublicStringqa(Stringquestion){returnchatClient.prompt(question).call().content();}}实现一个测试Controller作为入口:
@RestController@RequestMapping("/test")publicclassTestController{privatefinalQAServiceqaService;publicTestController(QAServiceqaService){this.qaService=qaService;}@GetMapping("/qa")publicStringqa(@RequestParam(name="question",required=true)Stringquestion){returnqaService.qa(question);}}到此,RAG的过程就实现完成了。
启动服务。