RAG 入门到实战:Spring AI 搭建旅游问答知识库(本地 + 阿里云百炼双方案)

RAG入门

什么是RAG

RAG(检索增强生成) 是一种提升大模型问答准确性与实用性的技术框架,核心是 **「先检索、再生成」**,解决大模型幻觉、知识过时、领域知识不足的痛点。

简单来说,RAG 不依赖重新训练大模型,而是先从外部专属知识库(如文档、数据库)中检索与用户问题最相关的信息片段,再将这些片段作为参考资料,让大模型基于这些真实数据生成答案,既保证回答的精准性,又能实现知识的灵活更新。

RAG与传统AI模型的区别:

对比维度 传统大模型 RAG(检索增强生成)
知识来源 来源于预训练数据,知识固化在模型参数中 来源于外部专属知识库+ 模型参数知识,可灵活扩展
知识更新 需重新训练 / 微调模型,成本高、周期长 无需改模型,直接更新知识库即可,实时性强
回答准确性 易产生幻觉(编造不存在的信息),无法溯源 基于检索到的真实数据生成,答案可溯源,大幅降低幻觉
领域适配成本 适配垂直领域需海量标注数据 + 高额算力微调 只需构建领域知识库,低成本快速适配(如旅行、法律、医疗)
上下文依赖 受模型上下文窗口长度限制,无法处理超长文本 可通过检索突破窗口限制,调用超长领域文档的关键片段

RAG工作流程

RAG技术实现主要包含以下4个核心步骤:

  1. 文档收集和切割
  2. 向量转换和存储
  3. 文档过滤和清除
  4. 查询增强和关联

文档收集和切割

获取领域专属数据源,并拆分为语义完整、长度适中的文本块(Chunk),避免大模型上下文窗口超限,同时提升检索精准度。

  1. 文档收集
    • 来源:公开领域数据(如文旅局官网攻略、景点官方介绍)、自有数据(如用户整理的旅行笔记);
    • 类型:文本、PDF、Markdown、网页等均可。
  2. 文档切割(核心)
    • 粒度选择 :中文场景推荐 500-1000 字 / 块(可根据模型窗口调整,如 GPT-3.5 窗口 4k,块长度不超过 1k);
    • 切割原则 :按语义边界拆分,而非纯字符数(如一篇攻略拆分为「开放时间」「门票政策」「游玩路线」3 个块,而非生硬截断);
    • 辅助策略 :给每个块添加元数据标签 (如景点=故宫类型=门票),方便后续精准检索。

向量转换和存储

  • **向量转换:**是使用Embedding模型将文本数据转换为高维向量的过程,可用捕获到文本的语义特征,该过程会直接影响到后续检索的效果
  • **向量存储:**将生成的向量和对应文本存入向量数据库,支持高效的相似性搜索

文档过滤和搜索

  • 查询处理:将用户问题也转换为向量表示
  • 过滤机制:基于元数据、关键词或自定义规则进行过滤
  • 相似度搜索⁠:在向量数据库中查‌找与问题向量最相似的文档块,常用的相‎似度搜索算法有余弦‌相似度、欧氏距离等
  • 上下文组装:将检索到的多个文档块组装成连贯上下文

查询增强和关联

  • 提示词组装:将检索到的相关文档与用户问题组合成增强提示
  • 上下文融合:大模型基于增强提示生成回答
  • 源引用:在回答中添加信息来源引用
  • 后处理:格式化、摘要或其他处理以优化最终输出

RAG 相关技术

Embedding 和 Embedding 模型

Embeddin⁠g 嵌入是将高维离散数据(如文‌字、图片)转换为低维连续向量的​过程。这些向量能在数学空间中表‎示原始数据的语义特征,使计算机‌能够理解数据间的相似性。

Embedding 模型是⁠执行这种转换算法的机器学习模型,如 Word2Ve‌c(文本)、ResNet(图像)等。不同的 Emb​edding 模型产生的向量表示和维度数不同,一般‎维度越高表达能力更强,可以捕获更丰富的语义信息和更‌细微的差别,但同样占用更多存储空间。

向量数据库

向量数据库⁠是专门存储和检索向量‌数据的数据库系统。通​过高效索引算法实现快‎速相似性搜索,支持 ‌K 近邻查询等操作。

注意,并不⁠是只有向量数据库才‌能存储向量数据,只​不过与传统数据库不‎同,向量数据库优化‌了高维向量的存储和检索。

AI 的流行带火了一波⁠向量数据库和向量存储,比如 Milvus、‌Pinecone 等。此外,一些传统数据库​也可以通过安装插件实现向量存储和检索,比如‎ PGVector、Redis Stack‌ 的 RediSearch 等。

RAG实战:SpringAI + 本地知识库

Spring AI 框架为我们实现 RAG 提供了全流程的支持,参考 Spring AISpring AI Alibaba 的官方文档。

文档准备

可用通过AI去生成对应知识库文档,提供一段提示词作为参考:

帮我生成两篇Markdown文章(注意输出格式),主题是【旅游常见的问题和回答】,两篇文章的问题是针对自驾游,跟团的旅游方式,内容形式为1问1答的形式,每个问题标题使用4级标题,每篇内容要有至少10个问题,要求每个回答最后注明此回答的出处(包含来源标题和真实网址)。注意每个内容要贴近旅游方式回答内容尽量详细清晰,若多篇文章中出现冲突内容,选择最合理的建议,每个问题尽量不要有相同的答案并在500字左右

在resoures目录下新建doc文件夹,将文档粘贴过来即可

文档读取

首先,我们要对自己准备好的知识库文档进行处理,然后保存到向量数据库中。这个过程俗称 ETL(抽取、转换、加载),Spring AI 提供了对 ETL 的支持,参考 官方文档

ETL 的 3 大核心组件,按照顺序执行:

  • DocumentReader:读取文档,得到文档列表
  • DocumentTransformer:转换文档,得到处理后的文档列表
  • DocumentWriter:将文档列表保存到存储中(可以是向量数据库,也可以是其他存储)

刚开始学习 RAG⁠,我们不需要关注太多 ETL 的细‌节、也不用对文档进行特殊处理,下面​我们就先用 Spring AI 读‎取准备好的 Markdown 文档‌,为写入到向量数据库做准备。

引入依赖

SpringAI通过DocumentReader组件实现文档抽取,也就是把文档加载到内存中

Sprin⁠g AI 提供了很‌多种 Docume​ntReaders‎,用于加载不同类‌型的文件。

我们可以使用 MarkdownDocumentReader 来读取 Markdown 文档。需要先引入依赖,可以在 Maven 中央仓库 找到。

首先在pom.xml引入依赖:

XML 复制代码
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-markdown-document-reader</artifactId>
            <version>1.0.0-M6</version>
        </dependency>

编写文档读取器

新建rag包,存放RAG相关类型。我们先编自己读取旅游问答的DocumentReader实现

java 复制代码
public class TravelMarkdownDocumentReader implements DocumentReader {

    @Autowired
    private ResourcePatternResolver resourcePatternResolver;

    // 指定Markdown文档的路径模式,默认读取classpath*/doc/*.md
    @Value("${travel.document.markdown.path.pattern:classpath*:/doc/*.md}")
    private String pathPattern;

    @Override
    public List<Document> get() {
        List<Document> documents = new ArrayList<>();

        try{
            // 获取指定路径下的所有Markdown文件
            Resource[] markdownResources = resourcePatternResolver.getResources(pathPattern);
            for (Resource markdownResource : markdownResources) {
                // 创建MarkdownDocumentReaderConfig对象, 并设置参数
                MarkdownDocumentReaderConfig readerConfig =
                        MarkdownDocumentReaderConfig.builder()
                                .withHorizontalRuleCreateDocument(true)
                                .withIncludeCodeBlock( false)
                                .withIncludeBlockquote( false)
                                .withAdditionalMetadata("filename",markdownResource.getFilename())
                                .build();
                // 创建读取器
                MarkdownDocumentReader reader =
                        new MarkdownDocumentReader(markdownResource, readerConfig);
                documents.addAll(reader.get());
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return documents;
    }
}

上述代码中:

@Autowired 自动装配**ResourcePatternResolver** (Spring 资源解析器),用于定位和读取资源文件。@Value 注解读取配置项**travel.document.markdown.path.pattern** ,若未配置则使用默认值**classpath*/doc/*.md**,指定要读取的 Markdown 文件路径模式。

核心的**get()** 方法实现了接口逻辑:首先初始化空的**Document** 列表,通过**resourcePatternResolver** 获取匹配路径模式的所有 Markdown 资源文件;遍历每个资源时,构建**MarkdownDocumentReaderConfig** 配置对象 ------ 开启 "按水平线分割文档"、排除代码块和块引用、为文档添加 "文件名" 元数据;接着创建**MarkdownDocumentReader** 读取器,解析当前 Markdown 资源并将结果合并到列表中。若过程中出现 IO 异常,会捕获并转为运行时异常抛出,最终返回解析后的**Document**列表。

测试旅游文档读取器

创建测试类:

java 复制代码
@SpringBootTest
class TravelMarkdownDocumentReaderTest {

    @Resource
    private DocumentReader travelMarkdownDocumentReader;

    @Test
    void  readDocuments(){
        List<Document> documents = travelMarkdownDocumentReader.get();
        for (int i = 0; i < documents.size() ; i++) {
            Document document = documents.get(i);
            //读取我的文档有唯一的ID
            String id = document.getId();
            // 读取我的文档内容
            String content = document.getText();
            // 读取我的文档元数据
            Map<String, Object> metadata = document.getMetadata();
            System.out.println("------------------------------------");
            System.out.println("文档ID:" + id);
            System.out.println("文档内容:" + content);
            System.out.println("文档元数据:" + metadata);
            System.out.println("------------------------------------");
        }
    }

}

运行测试效果如下,可见markdown文档读取器是以标题进行Doument进行拆分,每个Dounment都有唯一ID,元数据集合,文本内容:

向量转换和存储

  • 向量转换(Embedding):将非结构化的文本(如你的旅游问答 MD 内容)转为计算机可计算的数值向量(Embedding 向量),保留文本的语义信息。
  • 向量存储(VectorStore) :专门存储向量并支持相似性检索 的数据库(区别于普通数据库),Spring AI 内置了对 Chroma、Milvus、Redis、PGVector 等的适配,这里选Chroma(轻量、无依赖、适合测试 / 小体量场景)。

VectorStore接口介绍

VectorStore是 Spring AI 框架中统一向量存储操作的顶级接口,它的设计目标是:

  • 屏蔽不同向量数据库(Chroma、Milvus、Redis、PGVector 等)的底层差异;
  • 提供标准化的 "向量写入、删除、相似性检索"API;
  • 让业务代码无需关心具体用的是哪种向量库,只需面向接口编程。

简单来说,它就像 "向量数据库的操作门面"------ 无论你用 Chroma 还是 Redis,调用的都是VectorStore的同一套方法,切换向量库时只需替换实现类和配置,业务代码无需修改。

SimpleVectorStore介绍

SimpleVectorStore是 Spring AI 提供的极简内存型 VectorStore 实现类 ,它完全基于 JVM 内存存储向量数据,无需依赖任何外部向量数据库(如 Chroma、Redis),也无需配置文件、无需启动额外服务,是快速测试、原型开发的最佳选择。

配置向量数据库

在rag包下新建TravelVctorStoreConfig类,实现初始化向量数据库并保存文档的方法:

java 复制代码
public class TravelVectorStoreConfig {
    @Resource
    private DocumentReader travelMarkdownDocumentReader;

    /*
    * 构建向量数据库
    * @param dashscopeEmbeddingClient
    * */
    @Bean
    public VectorStore travelVectorStore(EmbeddingModel dashscopeEmbeddingModel){
        // 读取文档
        List<Document> documents = travelMarkdownDocumentReader.get();
        // 创建向量数据库
        SimpleVectorStore vectorStore = SimpleVectorStore.builder(dashscopeEmbeddingModel).build();
        // 加载文档到向量数据库
        vectorStore.doAdd(documents);
        return vectorStore;
    }
}
代码核心逻辑简述
  1. 依赖注入 :通过@Resource注入你之前实现的travelMarkdownDocumentReader(Markdown 文档读取器),用于读取指定路径的 MD 文档;
  2. Bean 定义@Bean注解标识该方法会向 Spring 容器注册一个VectorStore类型的 Bean(名称为travelVectorStore);
  3. 参数注入 :方法参数dashscopeEmbeddingModel(通义千问等 DashScope 系列的 Embedding 模型)会被 Spring 自动注入,作为向量转换的核心依赖;
  4. 核心执行流程
    • 调用travelMarkdownDocumentReader.get()读取 Markdown 文档,得到Document列表;
    • 基于传入的 Embedding 模型构建SimpleVectorStore(内存型向量库)实例;
    • 调用doAdd()方法将文档列表转换为向量并加载到内存向量库中;
    • 返回构建好的SimpleVectorStore实例,使其成为 Spring 容器中可注入使用的 Bean。

查询增强和关联

查询增强(Query Enhancement)关联检索(Relevance Retrieval) 的核心概念、实现逻辑,以及如何结合你之前的SimpleVectorStore代码落地这两个关键环节 ------ 这两个环节是提升 RAG 检索精准度的核心,能解决 "用户查询模糊 / 简短导致检索结果不相关" 的问题。

SpringAI通过Advisor特性提供了开箱即用的RAG功能,主要是QuestionAnswerAdvisor问答拦截器和RetrievalAugmentationAdvisor检索增强拦截器,前者更简单易用,后者更灵活更强大

TravelApp实现RAG对话

了解QuestionAnswerAdvisor问答顾问

QuestionAnswerAdvisor是 RAG(检索增强生成)架构中 **"问答生成" 环节的核心组件 **,核心目标是:接收用户的自然语言问题 → 从向量库中检索相关的文档依据 → 结合大语言模型(LLM)生成有依据、不胡编、贴合你的旅游文档内容的精准回答。它区别于纯大模型问答的关键是:回答不是 "凭空生成",而是基于你提前存入向量库的旅游文档(如自驾篇 MD),确保回答的准确性和溯源性。

修改TravelApp构造函数代码:

其实要修改的就是上述框中的代码,将我们定义的VectorStore对象通过参数注入,用于创建QuestionAnswerAdvistor知识库问答顾问,并添加到ChatClient的默认顾问集合中这样每次用户提问就会先查询知识库,再进行精确回答

测试RAG对话

编写单元测试增加测试方法,testRAG:

java 复制代码
    @Test
    void testRAG(){
        String chatId = UUID.randomUUID().toString();
        // 写一个与知识库相关的问题
        String result = travelApp.chat(chatId,"自驾游前车辆必做哪些检查?");
        System.out.println(result);
    }

运行上述代码得到的结果

知识库md

从结果可看出大模型的输出中包含科泰养车店,这是本地知识库中的内容,只是为了测试虚构的,证明了大模型确实参考了提供的知识库数据进行回答

RAG实战:SpringAI+云知识库

在上一节,我们学习了文档读取,文档加载,向量数据库是在本地通过编程的方式实现的,其实还要另一种方式,直接使用别人的云知识库来简化RAG开发。但是缺点是额外的费用,以及数据隐私问题

很多AI大模型应用开发平台都提供了云知识库服务,这里我们还是选择阿里云百炼,因为Spring AI Alibaba 可以轻松集成,简化RAG开发

准备云知识库

首先我们可以利用云知识库完成文档读取,文档处理,文档加载,保存到向量数据库,知识库管理等操作。

上传文档数据

在应用数据模块中,上传原始文档数据到平台,由平台进行解析内容和结构:

首先推荐大家新建一个"旅游知识"类目,不要把所有的文档放入默认类目,方便管理。点击【类目管理】旁边的+号,新增类目:

新增后,点击对应的旅游知识类目,然后导入数据按钮

然后将旅游相关的md文档导入进去

点击确定,导入完成

创建云知识库

点击阿里云百炼平台的知识库菜单,创建一个知识库:

现在的知识库是需要收费的我们可以使用免费30天的知识库作为学习

创建知识库名称使用旅游大师,其他默认即可

进入到选择数据步骤这里选择推荐类目方式,并开启自动同步知识索引选项。然后选择旅游知识类中的文档

索引设置保持默认即可

基于云知识库的RAG开发

配置云知识库知识线索增强顾问

由于本地知识库使用的是QuestionAnswerAdvisor(知识问答顾问),云知识库使用的是RetrievalAugmentationAdvisor(检索增强顾问),都是顾问类的实例,我们可以都定义为Spring 中的Bean对象,注入TravelApp应用,这样就可以实现本地知识库和云知识库的切换了

我们将顾问对象定义到TravelVectorStoreConfig配置类中

java 复制代码
@Configuration
public class TravelVectorStoreConfig {
    @Resource
    private DocumentReader travelMarkdownDocumentReader;

    /*
    * 构建向量数据库
    * @param dashscopeEmbeddingClient
    * */
    @Bean
    public VectorStore travelVectorStore(EmbeddingModel dashscopeEmbeddingModel){
        // 读取文档
        List<Document> documents = travelMarkdownDocumentReader.get();
        // 创建向量数据库
        SimpleVectorStore vectorStore = SimpleVectorStore.builder(dashscopeEmbeddingModel).build();
        // 加载文档到向量数据库
        vectorStore.doAdd(documents);
        return vectorStore;
    }


    // 定义基于本地知识库的RAG Advisor
    @Bean
    public Advisor localRagAdvisor(VectorStore vectorStore){
        return new QuestionAnswerAdvisor(vectorStore);
    }

    // 注入apikey
    @Value("${spring.ai.dashscope.api-key}")
    private String apikey;

    @Bean
    public Advisor cloudRagAdvisor(VectorStore vectorStore){
        DashScopeApi dashScopeApi = new DashScopeApi(apikey);
        // 定义知识库的名称
        final String KNOWLEDGE_index = "旅游大师";
        // 创建知识库检索器
        DashScopeDocumentRetrieverOptions options = DashScopeDocumentRetrieverOptions.builder()
                .withIndexName(KNOWLEDGE_index)
                .build();
        // 构建检索器
        DocumentRetriever documentRetriever = new DashScopeDocumentRetriever(dashScopeApi, options);
        // 构建增强顾问
        return RetrievalAugmentationAdvisor.builder()
                .documentRetriever(documentRetriever)
                .build();
    }
}

注意:上述代码中指定的是知识库名称(非ID),可以在平台查看知识库列表获得。

修改TravelApp添加指定的RAG顾问

改造TreavelApp类的构造函数:

构造函数中不再注入VctorStore而是直接注入Advisor对象,通过@Qualifier注解指定为实例7中的cloudRagAdvisor云知识库检索增强顾问对象

如果想要改回本地知识库只需要改回@Qualifier("localRagAdvisor")即可

我们继续运行之前的测试方法:

可以看到大模型从我们云知识库获取信息并给出对应的回答

至此我们就已经学习了RAG知识库的基本内容开发

相关推荐
chilavert3181 小时前
技术演进中的开发沉思-329 JVM:垃圾回收(中)
java·jvm·算法
云雾J视界1 小时前
AI服务器供电革命:为何交错并联Buck成为算力时代的必然选择
服务器·人工智能·nvidia·算力·buck·dgx·交错并联
難釋懷2 小时前
隐藏用户敏感信息
java·spring boot
阳艳讲ai2 小时前
九尾狐AI:重构企业AI生产力的实战革命
大数据·人工智能
wangmengxxw2 小时前
SpringAi-MCP技术
java·大模型·springai·mcp
@老蝴2 小时前
MySQL数据库 - 事务
java·数据库·mysql
大势智慧2 小时前
大势智慧与土耳其合作发展中心、蕾奥规划签署土耳其智慧城市项目战略合作协议
人工智能·ai·智慧城市·三维建模·实景三维·发展趋势·创新
木井巳2 小时前
【Java】深入理解Java语言的重要概念
java·开发语言
yangminlei2 小时前
MyBatis插件开发-实现SQL执行耗时监控
java·开发语言·tomcat