Spring AI 基于 RAG 构建本地知识库问答助手

791 阅读3分钟

 

一、Spring AI & RAG

RAG 检索增强,最常见的实现方式便是向量检索增强,通过分析读取本地私有化知识内容,Embedding 后存储至VectorStore 中做为知识库。当用户发起提问时,通过向量余弦距离相似度等方式,召回知识库中的相似内容作为上下文背景组装至 Prompt 中,达到动态扩充大模型对私有知识感知的能力,减少大模型的幻觉。

在 JAVA 体系下 Spring AI 针对RAG的实现,也类似 LangChain 做了很多的封装和支持,基本上做到使用较少的代码即可实现 RAG 的过程。

例如:官方提供了多种格式的 DocumentReader 封装,包括:JSONTextHTMLMarkdownPDF、以及万能文件解析神器 Tika 。针对文本内容拆分,Spring AI 也提供了 TextSplitter 和 TokenTextSplitter 两种方式。对于向量库的支持,也是几乎涵盖了市面上所有类型的向量库可供选择。

更多的细节可以参考官方的文档:

docs.spring.io/spring-ai/r…

本次实现的RAG流程如下所示:

模型使用 OpenAI 的 GPT-4.1, Embedding模型采用 OpenAI 的 text-embedding-3-small ,向量库使用 Milvus ,实验前请确保安装好 Milvus 环境。

二、私有知识准备

对于实验的私有知识,我这里随便制造了一些内容,包括,xlsxpdfdocx 格式,你可以使用真实的私有知识进行实验,内容示例如下:

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:8080

spring:
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
@Service
publicclassPersistenceVectorServiceImplimplementsPersistenceVectorService {

    privatefinal VectorStore vectorStore;

    publicPersistenceVectorServiceImpl(VectorStore vectorStore) {
        this.vectorStore = vectorStore;
    }

    @Override
    publicvoidload(String path) {
        List<Resource> listnewArrayList<>();
        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));
        }
    }
}

执行逻辑,处理上面准备的文档:

@SpringBootTest
class RagApplicationTests {

    @Resource
    private PersistenceVectorService etlService;

    @Test
    void contextLoads() {
        etlService.load("D:/rag");
    }
}

运行后,可以在 Milvus 中看到自动创建的 Collection :

接着实现问答的过程:

public interface QAService {
    // 知识问答
    String qa(String question);

}
@Slf4j
@Service
publicclassQAServiceImplimplementsQAService {

    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 的过程就实现完成了。

启动服务。

四、效果测试