1.RAG概念

1.什么是RAG

首先我们先理清楚什么是RAG(检索增强) 是一种信息检索技术结合AI进行内容生成的混合架构,可以有效解决大模型产生幻觉

简单说,当我们向大模型进行提问时,大模型可以从数据库中拿到相关性比较强的数据来进行结合回答,比如我们想要知道某个商品的某些详细数据,大模型不知道这些参数,就可以结合数据库中已经存在的数据进行结合回答。

2.RAG的作用

RAG技术的出现能够使得大模型问题会更加精确

1. 知识实时性增强

通过RAG改造后的大模型可以通过更新RAG的知识库来使得大模型能学习新的知识,例如回答“2023年世界杯冠军是谁?”时,RAG可直接检索最新结果,而非依赖训练数据(可能截止到更早时间)。

2. 减少幻觉(Hallucination)

  • 基于事实生成:传统生成模型可能编造不存在的信息(如虚假数据、事件)。RAG通过检索真实来源提供依据,生成内容更可信

3. 处理长尾/专业领域问题

  • 弥补训练数据不足:大模型在冷门或专业领域(如医学、法律)可能表现不佳。RAG通过检索领域特定知识库(如论文、法规),生成更专业的回答。

2.Spring AI  RAG应用开发

2.1 基于阿里百联的向量数据库

2.1.1创建知识库

在百练平台创建一个知识库

知识库名称 是我们后续所需要写到代码中的

创建知识库前如果我们并没有添加任何文件内容,则需要先进入下一步时

点击数据中心添加数据

先添加类目

然后往该目录中倒入数据

上传完成后可以对对应的文档进行打标签

这样能更精准的召回所需要的内容

此时回到创建知识库

选择对应的泪目

metaData抽取一定要现在开启,不然创建后就不能开启了

选择智能切分,会调用ai大模型进行文档的切割,最大长度是指切分后的文章大小,重叠程度就可以强化上下文的联系

meataData就是指分片之后的文件元信息,取值方法我推荐大模型取值,这样有助于大模型自己检索,配置实体描述之类的就按照你的想法写,ai也会根据你的描述进行取值作为value例如

创建完成后我们就可以测试命中率

可以测试我们的输入,所能召回的被切割文档有哪些

2.1.2 Spring AI 结合 云知识库

package com.xiaog.aiapp.RAG;

import com.alibaba.cloud.ai.dashscope.api.DashScopeApi;
import com.alibaba.cloud.ai.dashscope.rag.DashScopeDocumentCloudReader;
import com.alibaba.cloud.ai.dashscope.rag.DashScopeDocumentRetriever;
import com.alibaba.cloud.ai.dashscope.rag.DashScopeDocumentRetrieverOptions;
import org.springframework.ai.chat.client.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.chat.client.advisor.api.Advisor;
import org.springframework.ai.rag.retrieval.search.DocumentRetriever;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 基于阿里百联平台自己搭建的知识库
 */

@Configuration
public class LoveAppRagCloudAdvisorConfig{

    @Value("${spring.ai.dashscope.api-key}")
    private String apiKey;


    @Bean
    public Advisor loveAppRagCloudAdvisor() {
        DashScopeApi dashScopeApi = new DashScopeApi(apiKey);

        // 初始化基于阿里百联的向量数据库
        DocumentRetriever documentRetriever = new DashScopeDocumentRetriever(dashScopeApi
        , DashScopeDocumentRetrieverOptions.builder()
                .withIndexName("小G的知识库") //知识库的名称
                .build());

            //构建检索增强
            return RetrievalAugmentationAdvisor.builder()
                    .documentRetriever(documentRetriever)
                    .build();

    }


}
 //注入Advisor
    @Resource
    private Advisor loveAppRagCloudAdvisor;

    /**
     * 基于云知识库 的对话
     */
    public  void dochatRag(String message , String sessionId){
        ChatResponse chatResponse = chatClient.prompt()
                .user(message)
                .user(message)
                .advisors(advisorSpec -> advisorSpec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, sessionId) //根据会话id获取对话历史
                        .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))//获取历史消息的条数
                .advisors(loveAppRagCloudAdvisor)
                .call()
                .chatResponse();

        System.out.println(chatResponse.getResult().getOutput().getText());
    }

测试

    @Test
    void dochatRag() {
        app.dochatRag("小明现在是单身,想在网上交友谈恋爱,请给他一点建议,精简总结一下", "1");
    }

输出:

被召回的分片,通过自定义Advisor捕捉 到被一同传入

内容:

2.2 基于本地内存向量数据库

2.2.1 读取文档

创建一个读取文档的类,并且写一个读取文档的方法

我们此次所读取的文档是Markdown文档,所以编写一个读取markdown文档的方法

需要引入读取Markdown的依赖

<!--spring AI Markdown 文件读取-->
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-markdown-document-reader</artifactId>
    <version>1.0.0-M6</version>
</dependency>

编写读取的类和方法

package com.example.demo.RAG;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.markdown.MarkdownDocumentReader;
import org.springframework.ai.reader.markdown.config.MarkdownDocumentReaderConfig;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Component
@Slf4j
public class LoveAppDocumentLoader {

    //文件解析
    private final ResourcePatternResolver resourcePatternResolver;

    LoveAppDocumentLoader(ResourcePatternResolver resourcePatternResolver) {
        this.resourcePatternResolver = resourcePatternResolver;
    }


    public List<Document> loadMarkdownDocuments(){

        List<Document> documentList=new ArrayList<>();

        try {
            // 这里可以修改为你要加载的多个 Markdown 文件的路径模式 读取资源目录下的所有 Markdown 文件
            Resource[] resources = resourcePatternResolver.getResources("classpath:document/*.md");

            //加载多篇Markdown文件
            for (Resource resource : resources) {
                String fileName = resource.getFilename(); // 获取文件名
                //读取的配置设置
                MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder()
                        .withHorizontalRuleCreateDocument(true)//添加水平线
                        .withIncludeCodeBlock(false)//添加代码块
                        .withIncludeBlockquote(false)//添加引用
                        .withAdditionalMetadata("filename", fileName) //添加(元信息)而外信息 //比如文件名 其他标签等
                        .build();

                MarkdownDocumentReader reader = new MarkdownDocumentReader(resource, config);

                //添加到集合
                documentList.addAll(reader.get());
            }
        } catch (Exception e){
            log.error("loadMarkdownDocuments error 文件读取失败",e);
        }


        return documentList;

    }


}

2.2.2.加载文档到 基于内存的 向量数据库

将加载到的文档 进行 切割 、添加关键词 和摘要 再添加到向量数据库

所使用的 关键词 和文本摘要都需要涉及到大模型,所以也需要消耗对应的Token,在当前学习阶段用一次证明可以使用就能关掉了,否这token消耗大,且大模型回复较慢

package com.example.demo.RAG;

import jakarta.annotation.Resource;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.transformer.KeywordMetadataEnricher;
import org.springframework.ai.transformer.SummaryMetadataEnricher;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration
public class AppDocumentVectorStoreConfig {

    @Resource
    private LoveAppDocumentLoader loveAppDocumentLoader;

    @Autowired
    ChatModel chatModel;

    @Bean
    public VectorStore appVectorStore(EmbeddingModel dashScopeEmbeddingModel){

        //初始化一个基于内存的向量数据库
        SimpleVectorStore simpleVectorStore = SimpleVectorStore.builder(dashScopeEmbeddingModel).build();

        //读取文挡
        List<Document> documents = loveAppDocumentLoader.loadMarkdownDocuments();
     

        //切分文档 文档按照Token进行切分 可以配置token数量 上下文token交集
        TokenTextSplitter tokenTextSplitter = new TokenTextSplitter();
        List<Document> documentsSplitter = tokenTextSplitter.apply(documents);

        // 添加元数据 提取关键词生成器
        KeywordMetadataEnricher keywordMetadataEnricher = new KeywordMetadataEnricher(chatModel,3);
        List<Document> documentsMetaDataEnricher = keywordMetadataEnricher.apply(documentsSplitter);

        //摘要生成器 生产 摘要
        SummaryMetadataEnricher summaryMetadataEnricher = new SummaryMetadataEnricher(chatModel,
                List.of(SummaryMetadataEnricher.SummaryType.PREVIOUS, //结合之前的文档
                        SummaryMetadataEnricher.SummaryType.CURRENT,    //当前的文档
                        SummaryMetadataEnricher.SummaryType.NEXT));        //下一个文档
        List<Document> documentList = summaryMetadataEnricher.apply(documentsMetaDataEnricher);
        

        //添加到向量数据库
        simpleVectorStore.add(documentList);

        //返回向量数据库
        return simpleVectorStore;


    }




}

编写对话 接口

 @Resource
    private VectorStore appVectorStore;

  
    public  void dochatRag(String message , String sessionId){
        ChatResponse chatResponse = chatClient.prompt()
                .user(message)
                .advisors(advisorSpec -> advisorSpec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, sessionId) //根据会话id获取对话历史
                        .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))//获取历史消息的条数
                .advisors(new QuestionAnswerAdvisor(appVectorStore))
                .call()
                .chatResponse();

        System.out.println(chatResponse.getResult().getOutput().getText());
    }

编写测试

    @Test
    public void testLoveAppRag(){
        app.dochatRag("我是小明 我是单身线上交友有哪些注意事项能提高脱单成功率,简略的回答一下","1");
        app.dochatRag("有没有相应的课程推荐?","1");
    }

结果:

3.基于PostgreSql及PgVector插件 作为向量数据库

没有使用过pgsql数据库的同学可以根据以下链接指导安装数据库和插件

Windows 安装 PostgreSQL 并安装 vector 扩展_win10上安装postgresql的 vector扩展-CSDN博客

本文章采用手动配置

官方文档链接:PGvector :: Spring AI 参考

需要将 PostgreSQL 连接和自动配置依赖项添加到您的项目中:PgVectorStore及JdbcTemplate

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <scope>runtime</scope>
</dependency>

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-pgvector-store</artifactId>
</dependency>

在application.yml中配置数据连接

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/postgres
    username: postgres
    password: root
    driver-class-name: org.postgresql.Driver
  ai:
    vectorstore:   # 向量数据库选择
      pgvector:   # pgvector
        index-type: hnsw # 向量索引类型
        dimensions: 1536  # 向量维度
        dimension-type: COSINE_DISTANCE  # 使用哪种方式进行相似度检索 # 可选值:COSINE_DISTANCE、EUCLIDEAN_DISTANCE
        max-document-batch-size: 10000    #单次检索的文档数量

要在应用程序中配置 PgVector数据库

package com.xiaog.aiapp.RAG;

import jakarta.annotation.Resource;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.pgvector.PgVectorStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;

import static org.springframework.ai.vectorstore.pgvector.PgVectorStore.PgDistanceType.COSINE_DISTANCE;
import static org.springframework.ai.vectorstore.pgvector.PgVectorStore.PgIndexType.HNSW;

@Configuration
public class PgVectorVectorStoreConfig {
    //自动创建向量 库
    @Bean
    public VectorStore pgVectorVectorStore(JdbcTemplate jdbcTemplate, EmbeddingModel dashScopeEmbeddingModel) {

        PgVectorStore vectoStore = PgVectorStore.builder(jdbcTemplate, dashScopeEmbeddingModel)
                .dimensions(1536)                    // Optional: defaults to model dimensions or 1536
                .distanceType(COSINE_DISTANCE)       // Optional: defaults to COSINE_DISTANCE // 使用哪一种方式进行相似度检索
                .indexType(HNSW)                     // Optional: defaults to HNSW  // 向量索引类型
                .initializeSchema(true)              // Optional: defaults to false // 是否初始化数据库
                .schemaName("public")                // Optional: defaults to "public" // 模式名
                .vectorTableName("vector_store")     // Optional: defaults to "vector_store" //表 名
                .maxDocumentBatchSize(10000)         // Optional: defaults to 10000
                .build();

//        vectoStore.add(loveAppDocumentLoader.loadMarkdowns());

        return vectoStore;
    }


}

编写对话方法,advisor中配置向量数据库和检索的关联度和所读取的文章


    @Resource
     VectorStore pgVectorVectorStore;

    /**
     * 基于RAG问答功能
     */
    public String dochatRag(String message,String id){


        ChatResponse chatResponse = chatClient
                .prompt()
                .user(message)
                .advisors(advisorSpec -> advisorSpec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, id) //根据会话id获取对话历史
                        .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))//获取历史消息的条数
//                .advisors(new MyLogAdvisor())

                //应用RAG(知识库问答)(自定义知识库) QuestionAnswerAdvisor 是问答(Q&A)助手,专门用于 RAG(检索增强生成)
//                .advisors(new QuestionAnswerAdvisor(loveAppVectorStore))

                //基于云知识库
                //应用RAG知识库(检索增强服务) 基于阿里百练知识库 且使用RetrievalAugmentedGenerationAssistant 功能更强大
//                .advisors(loveAppRagCloudAdvisor)

                //基于PGVector知识库
                    .advisors( QuestionAnswerAdvisor.builder(pgVectorVectorStore)
                            .searchRequest(SearchRequest.builder().similarityThreshold(0.4d).topK(6).build()) //设置搜索参数 关联度达到0.4的 取6条数据
                            .build())

                .call()
                .chatResponse();

        return chatResponse.getResult().getOutput().getText();
    }

编写测试方法

package com.xiaog.aiapp;

import com.xiaog.aiapp.RAG.LoveAppDocumentLoader;
import com.xiaog.aiapp.app.LoveApp;
import jakarta.annotation.Resource;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.document.Document;
import org.springframework.ai.transformer.KeywordMetadataEnricher;
import org.springframework.ai.transformer.SummaryMetadataEnricher;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@SpringBootTest
public class PgVectorStoreTest {


    @Resource
    VectorStore pgVectorVectorStore;

    @Resource
    LoveAppDocumentLoader loveAppDocumentLoader;


    @Autowired
    LoveApp app;

    @Test
    public void testPgVectorStore() throws Exception {

        List<Document> documents = new ArrayList<>();
        documents.add(new Document("Spring AI 的官方文档就算一坨大便", Map.of("meata1", "meata1")));
        //读取文档 本地的文档
        documents.addAll(loveAppDocumentLoader.loadMarkdowns());

        //添加到向量数据库
        pgVectorVectorStore.add(documents);
        //向ai提问 
        String systemMessage = app.dochatRag("我还是单身,应该怎么提高脱单的概率", "123");

        System.out.println(systemMessage);

    }

}

查看数据库,发现我们的3份文档被拆分成为了15条数据,加上我们新添加的一条对spring 官方文档的抱怨共16条

响应内容:

明显使用到了我们向量数据库的内容

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐