SpringAI RAG 智能问答实战:用自然语言查询知识库

1. 引言

在之前的文章中,我们体验了 SpringAI 的基本对话能力。但在实际业务中,大模型往往需要结合企业内部的私有知识库来回答问题,这就是 RAG(检索增强生成)的核心场景。

本文将带你从零开始,使用 SpringAI 构建一个完整的 RAG 智能问答系统。我们将以"美团问答"知识库为例,逐步实现知识库的索引、检索,并与大模型整合,最终让 AI 能够基于我们提供的知识准确回答用户问题。

2. RAG 基本流程回顾

RAG 全称 Retrieval-Augmented Generation(检索增强生成),其核心思想是:在让大模型回答问题之前,先从知识库中检索出与问题相关的信息,然后将这些信息作为上下文提供给大模型,从而生成更准确、更可靠的答案。

RAG 整体分为两个阶段:

2.1 Index(索引阶段)

这一阶段主要是对已有知识库进行处理:

  1. 加载知识库文件:读取本地的文本、PDF、JSON 等文件。
  2. 拆分知识条目:将长文本拆分成一个个独立的知识单元(如问答对)。
  3. 向量化并存入向量数据库:将每个知识条目通过 Embedding 模型转换为向量,并存入向量数据库(如 Redis、Pgvector 等)。

2.2 Retrieval(检索阶段)

当用户提出问题时:

  1. 问题向量化:将用户的问题同样进行向量化处理。
  2. 相似度检索:到向量数据库中检索出与问题最相关的 Top-K 条知识。
  3. 组装 Prompt:将用户问题 + 检索到的知识 + 系统提示词组装成一个完整的 Prompt。
  4. 生成回答:将 Prompt 发送给大模型,由大模型分析整合后给出最终答案。

3. 环境准备

3.1 依赖配置

首先,在 pom.xml 中引入 SpringAI 的核心依赖以及 Redis 向量数据库支持:

xml 复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-redis-store-spring-boot-starter</artifactId>
</dependency>

3.2 配置文件

application.yml 中配置 Redis 连接和向量数据库索引信息:

yaml 复制代码
spring:
  ai:
    vectorstore:
      redis:
        index: meituan-rag          # Redis 中的索引名称
        initialize-schema: true     # 是否初始化 Redis 中的索引,默认 false
  data:
    redis:
      host: 127.0.0.1
      # password: 123qweasd
      # port: 6379

注意 :Redis 需要安装 redisSearchredisJSON 插件才能支持向量检索功能。

4. Index 索引阶段实战

4.1 加载知识库文件

SpringAI 提供了 DocumentReader 接口来读取知识库文件。目前内置了 JsonReaderTextReader 两个实现类。

我们将 meituan-questions.txt 文件放到项目的 classpath 目录下,然后使用 TextReader 读取:

java 复制代码
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.TextReader;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class DocumentService {

    @Value("classpath:meituan-questions.txt")
    private Resource resource;

    public List<Document> loadDocuments() {
        TextReader textReader = new TextReader(resource);
        textReader.getCustomMetadata().put("filename", "meituan-questions.txt");
        List<Document> documents = textReader.get();
        return documents;
    }
}

4.2 拆分知识条目

加载出来的文件内容是一个整体,我们需要将其拆分成一个个独立的问答条目。SpringAI 提供了 DocumentTransformer 接口,但默认的 TokenTextSplitter 往往不能满足业务需求,因此我们需要自定义拆分逻辑。

这里我们借助 TextSplitter 抽象类,实现一个按双换行符拆分的工具类:

java 复制代码
import org.springframework.ai.transformer.splitter.TextSplitter;

import java.util.List;

public class MeituanTextSplitter extends TextSplitter {

    @Override
    protected List<String> splitText(String text) {
        return List.of(text.split("\\s*\\R\\s*\\R\\s*"));
    }
}

然后在 DocumentService 中应用这个拆分器:

java 复制代码
@Service
public class DocumentService {

    @Value("classpath:meituan-questions.txt")
    private Resource resource;

    public List<Document> loadDocuments() {
        TextReader textReader = new TextReader(resource);
        textReader.getCustomMetadata().put("filename", "meituan-questions.txt");
        List<Document> documents = textReader.get();

        // 拆分文件
        DocumentTransformer transformer = new MeituanTextSplitter();
        List<Document> list = transformer.apply(documents);
        return list;
    }
}

4.3 向量化并存入向量数据库

SpringAI 通过 VectorStore 接口统一操作各种向量数据库。我们使用 Redis 作为向量数据库,将拆分后的知识条目向量化并存储:

java 复制代码
import jakarta.annotation.Resource;
import org.springframework.ai.document.Document;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class MeituanRagController {

    @Resource
    private DocumentService documentService;

    @Resource
    private VectorStore vectorStore;

    @GetMapping("/loadDocument")
    public List<Document> loadDocument() {
        // 解析知识库文件
        List<Document> documents = documentService.loadDocuments();
        // 保存到向量数据库
        vectorStore.add(documents);
        return documents;
    }
}

访问 /loadDocument 接口后,SpringAI 会自动调用容器中的 EmbeddingModel 组件对知识条目进行向量化,并将结果存入 Redis。至此,Index 索引阶段完成。

5. Retrieval 检索阶段实战

5.1 基础检索

有了向量数据库后,检索就变得非常简单。直接使用 VectorStoresimilaritySearch 方法即可:

java 复制代码
@Resource
private VectorStore vectorStore;

public List<Document> search(String query) {
    return vectorStore.similaritySearch(
        SearchRequest.query(query).withTopK(5)
    );
}

5.2 组装 Prompt 并调用大模型

检索到相关知识后,我们需要将用户问题、检索结果和系统提示词组装成一个完整的 Prompt,再发送给大模型。

首先,在 classpath 下创建提示词模板文件 rag-prompt-template.st

复制代码
{question}

Context information is below.
---------------------
{question_answer_context}
---------------------
Given the context and provided history information and not prior knowledge,
reply to the user comment. If the answer is not in the context, inform
the user that you can't answer the question.

然后编写检索并回答的接口:

java 复制代码
@Value("classpath:/rag-prompt-template.st")
private Resource ragPromptResource;

@Resource
private ChatClient.Builder builder;

@Resource
private VectorStore vectorStore;

@GetMapping("/ragask")
public String vectorSearch(@RequestParam("message") String message) {
    // 查询相关知识条目
    List<Document> documents = vectorStore.similaritySearch(
        SearchRequest.query(message).withTopK(5)
    );

    // 请求 AI 大模型
    ChatClient client = builder.build();
    String result = client.prompt()
        .user(promptUserSpec -> promptUserSpec
            .text(ragPromptResource)
            .param("question", message)
            .param("question_answer_context", documents)
        )
        .call()
        .content();
    return result;
}

现在,访问 /ragask?message=如何退款,AI 就会基于知识库中的内容给出准确回答。

6. 使用 Advisor 简化检索流程

SpringAI 提供了一个更简洁的方式------QuestionAnswerAdvisor。它是一个 Advisor 切片,可以在与大模型交互之前自动完成检索和消息组装:

java 复制代码
@Resource
private ChatClient.Builder builder;

@Resource
private VectorStore vectorStore;

@GetMapping("/ragdemo")
public String rag(@RequestParam("message") String message) {
    ChatClient client = builder.build();
    return client.prompt(message)
        .advisors(new QuestionAnswerAdvisor(vectorStore))
        .call()
        .content();
}

QuestionAnswerAdvisor 内部使用的默认消息模板与我们之前手动编写的模板类似:

复制代码
Context information is below.
---------------------
{question_answer_context}
---------------------
Given the context and provided history information and not prior knowledge,
reply to the user comment. If the answer is not in the context, inform
the user that you can't answer the question.

使用 Advisor 后,代码量大幅减少,逻辑也更加清晰。

7. 总结

本文我们完整地体验了使用 SpringAI 构建 RAG 智能问答系统的全过程:

阶段 核心操作 SpringAI 组件
Index 加载文件 → 拆分条目 → 向量化存储 DocumentReaderDocumentTransformerVectorStore
Retrieval 问题向量化 → 相似度检索 → 组装 Prompt → 调用大模型 VectorStoreChatClientQuestionAnswerAdvisor

SpringAI 目前仍处于快速迭代阶段,API 可能会有较大变化,但 RAG 的核心思想是通用的。掌握了本文的思路,无论未来框架如何演进,你都能快速上手。

下一步,你可以尝试:

  • 替换为其他向量数据库(如 Pgvector、Milvus)
  • 使用不同的 Embedding 模型
  • 优化知识库的拆分策略
  • 结合多轮对话历史提升问答体验
相关推荐
Flynt5 分钟前
从Spring Boot 4.0升到4.1,我在Maven和gRPC上栽了跟头
java·spring boot·后端
雨落Re1 小时前
如何设计一个高质量Skill
人工智能
plainGeekDev1 小时前
Activity 间传值 → Navigation 参数
android·java·kotlin
Token炼金师1 小时前
大模型权重文件全指南:从格式选择到优化实战
人工智能
plainGeekDev1 小时前
onActivityResult → ActivityResult API
android·java·kotlin
Sunia1 小时前
《AgentX 专栏》10-生产部署:3台2C4G云服务器把企业级Agent真正跑起来的完整方案
java·架构
阿牛哥_GX1 小时前
CDP 浏览器操控原理:让脚本接管你的浏览器
人工智能
ThreeS1 小时前
手搓MiniVLA全实战教程-一步一步用pytorch解释原理与思路
人工智能·python
米小虾2 小时前
Loop Engineering —— 循环的设计与自主执行
人工智能·agent
ZhengEnCi2 小时前
J7A-高级Java工程师面试三道灵魂拷问-深度广度与工程素养的终极检验
java·后端