一、Spring AI & RAG
RAG
检索增强,最常见的实现方式便是向量检索增强,通过分析读取本地私有化知识内容,Embedding
后存储至VectorStore
中做为知识库。当用户发起提问时,通过向量余弦距离相似度等方式,召回知识库中的相似内容作为上下文背景组装至 Prompt
中,达到动态扩充大模型对私有知识感知的能力,减少大模型的幻觉。
在 JAVA
体系下 Spring AI
针对RAG
的实现,也类似 LangChain
做了很多的封装和支持,基本上做到使用较少的代码即可实现 RAG
的过程。
例如:官方提供了多种格式的 DocumentReader
封装,包括:JSON
、Text
、HTML
、Markdown
、PDF
、以及万能文件解析神器 Tika
。针对文本内容拆分,Spring AI
也提供了 TextSplitter
和 TokenTextSplitter
两种方式。对于向量库的支持,也是几乎涵盖了市面上所有类型的向量库可供选择。
更多的细节可以参考官方的文档:
本次实现的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"?><project xmlns="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.com api-key: {your_openai_key} chat: options: model:gpt-4.1 embedding: options: model:text-embedding-3-small vectorstore: milvus: client: host:127.0.0.1 port:19530 username:"root" password:"milvus" databaseName:"default" collectionName:"vector_rag" embeddingDimension:1536# text-embedding-3-small 模型向量维度的大小 indexType:IVF_FLAT metricType:COSINE initialize-schema: true
实现文档解析、Embedding
、持久化至向量库逻辑,其中文档解析这里使用 Tika
来实现,这里为了演示效果直接递归读取了本机目录下的文件,对于文件的管理你在实际应用时应该考虑使用文件系统进行统一管理:
public interface PersistenceVectorService { // 加载解析文件知识,并存储至向量库中 void load(String path);}
@Slf4j@ServicepublicclassPersistenceVectorServiceImplimplementsPersistenceVectorService { privatefinal VectorStore vectorStore; publicPersistenceVectorServiceImpl(VectorStore vectorStore) { this.vectorStore = vectorStore; } @Override publicvoidload(String path) { 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, File file) { if (file.isDirectory()) { Arrays.stream(Objects.requireNonNull(file.listFiles())).filter(Objects::nonNull).forEach(f -> { recursionAllFiles(list, f); }); } else { list.add(newFileSystemResource(file)); } }}
执行逻辑,处理上面准备的文档:
@SpringBootTestclass RagApplicationTests { @Resource private PersistenceVectorService etlService; @Test void contextLoads() { etlService.load("D:/rag"); }}
运行后,可以在 Milvus 中看到自动创建的 Collection
:
接着实现问答的过程:
public interface QAService { // 知识问答 String qa(String question);}
@Slf4j@ServicepublicclassQAServiceImplimplementsQAService { privatefinal ChatClient chatClient; publicQAServiceImpl(ChatClient.Builder chatClientBuilder, VectorStore vectorStore) { this.chatClient = chatClientBuilder .defaultAdvisors( QuestionAnswerAdvisor.builder(vectorStore) .searchRequest(SearchRequest.builder().similarityThreshold(0.5d).topK(4).build()) .build()) .build(); } @Override public String qa(String question) { return chatClient.prompt(question).call().content(); }}
实现一个测试Controller
作为入口:
@RestController@RequestMapping("/test")publicclassTestController { private final QAService qaService; public TestController(QAService qaService) { this.qaService = qaService; } @GetMapping("/qa") public String qa(@RequestParam(name = "question", required = true) String question){ return qaService.qa(question); }}
到此,RAG
的过程就实现完成了。
启动服务。
四、效果测试