简单的 RAG 开发步骤

  1. 文档准备

  2. 文档读取

  3. 向量转换和存储

  4. 查询增强

Spring AI + 本地知识库实现

准备好知识库文档——用ai结构化一下,使其条理更清晰

准备好知识库文档——用ai结构化一下,使其条理更清晰

文档读取

对自己准备好的知识库文档进行处理,然后保存到向量数据库中。这个过程俗称 ETL(抽取、转换、加载)

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

  • DocumentReader:读取文档,得到文档列表

  • DocumentTransformer:转换文档,得到处理后的文档列表

  • DocumentWriter:将文档列表保存到存储中(可以是向量数据库,也可以是其他存储)

Spring AI 提供了很多种 DocumentReaders(JSON Text HTML (JSoup) Markdown PDF Page PDF Paragraph Tika(DOCX,PPTX, HTML...)) 用于加载不同类型的文件。使用不同的 DocumentReaders要引入对应的依赖

例如:以读取markdown文档为例——可以使用 MarkdownDocumentReader 来读取 Markdown 文档

引入依赖

引入处理对应文档的阅读器依赖

 <dependency>
     <groupId>org.springframework.ai</groupId>
     <artifactId>spring-ai-markdown-document-reader</artifactId>
     <version>1.0.0-M6</version>
 </dependency>

读取 Markdown 文档并转换为 Document 列表

在根目录下新建包,编写文档加载器类 ,读取所有 Markdown 文档并转换为 Document 列表。

 package com.yupi.aiagent.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;
 ​
 /**
  * 应用文档加载器
  */
 @Slf4j
 @Component
 public class LoveAppDocumentLoader {
     //用spring内置的资源解析类——用构造函数的方法注入
     private final ResourcePatternResolver resourcePatternResolver;//这是 Spring 提供的资源解析器,用于加载类路径下的资源
 ​
     //通过构造函数注入ResourcePatternResolver对象
     public LoveAppDocumentLoader(ResourcePatternResolver resourcePatternResolver) {
         this.resourcePatternResolver = resourcePatternResolver;
     }
 ​
     //Document Spring AI 框架 中定义的一个特定类,。在 Spring AI中,各种文档读取器都会将文件内容解析为 List<Document> 格式,以便统一处理不同类型的文档。
     public List<Document> loadMarkdowns(){
         List<Document> allDocuments =new ArrayList<>();
     //加载多篇markDown文档
         try {
             //通过resourcePatternResolver.getResources()方法加载资源
             Resource[] resources = resourcePatternResolver.getResources("classpath:document/*.md");//classPath:为设置从类路径下加载文档
 ​
             for (Resource resource : resources) {
                 String filename = resource.getFilename(); //得到文件名
                 //指定了如何加载读取Markdown文件
                 MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder()//创建MarkdownDocumentReaderConfig配置对象:
                         .withHorizontalRuleCreateDocument(true)             //withHorizontalRuleCreateDocument(true):设置通过水平分隔线 (---) 分割文档
                         .withIncludeCodeBlock(false)//是否包含代码块
                         .withIncludeBlockquote(false)//是否包含引用格式
                         .withAdditionalMetadata("filename", filename)//为文档添加元数据-————给文档内容附加一些额外的描述性信息(不属于文档的内容),但能帮助后续处理(如检索、过滤、溯源等)更高效地进行。
                         .build();
                 MarkdownDocumentReader reader = new MarkdownDocumentReader(resource, config);
                 allDocuments.addAll(reader.get());
                 //reader.get():读取并解析 Markdown 文件,返回Document对象列表
                 //allDocuments.addAll():将解析得到的文档添加到总文档列表中
             }
         } catch (IOException e) {
             log.error("MarkDowns error", e);
         }
         return allDocuments;
     }
 ​
 }
 ​

提取文档的文件名(fileName)作为文档的元信息,可以便于后续知识库实现更精确的检索。

向量转换和存储

为了实现方便,我们先使用 Spring AI 内置的基于内存读写的向量数据库 SimpleVectorStore 来保存文档。

SimpleVectorStore 实现了 VectorStore 接口,而 VectorStore 接口集成了 DocumentWriter,所以具备文档写入能力。

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

 package com.yupi.aiagent.rag;
 ​
 ​
 import jakarta.annotation.Resource;
 import org.springframework.ai.document.Document;
 import org.springframework.ai.embedding.EmbeddingModel;
 import org.springframework.ai.vectorstore.SimpleVectorStore;
 import org.springframework.ai.vectorstore.VectorStore;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 ​
 import java.util.List;
 ​
 /**
  * 向量数据库配置(初始化基于内存的向量数据库 Bean)
  */
 @Configuration//标识该类为 Spring 的配置类,Spring 会扫描并加载其中的配置
 public class LoveAppVectorStoreConfig {
 ​
     //引入刚刚的文档加载器
     @Resource
     private LoveAppDocumentLoader loveAppDocumentLoader;
 ​
     @Bean//创建一个 Bean 对象,并将其放入 Spring 容器中
     //VectorStore(向量存储接口)
     VectorStore loveAppVectorStore(EmbeddingModel dashscopeEmbeddingModel) {
         SimpleVectorStore simpleVectorStore = SimpleVectorStore.builder(dashscopeEmbeddingModel)
                 .build();//SimpleVectorStore.builder(dashscopeEmbeddingModel):SimpleVectorStore是基于内存的实现,传入嵌入模型(用于文本向量化)
         // 加载文档
         List<Document> documents = loveAppDocumentLoader.loadMarkdowns();//调用刚刚注入的loveAppDocumentLoader中的loadMarkdowns()方法,获取所有解析后的文档列表
         simpleVectorStore.add(documents);//将加载的文档添加到向量数据库中,内部会通过传入的EmbeddingModel将文档内容转换为向量
 ​
         return simpleVectorStore;
     }
 }
 ​

查询增强

向量数据库存储着 AI 模型本身不知道的数据,当用户问题发送给 AI 模型时,QuestionAnswerAdvisor 会查询向量数据库,获取与用户问题相关的文档。然后从向量数据库返回的响应会被附加到用户文本中,为 AI 模型提供上下文,帮助其生成回答。

引入依赖

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

完成查询的对应代码

新增和 RAG 知识库进行对话的方法。

 ​
     @Resource
     private VectorStore loveAppVectorStore;
 ​
     /**
      * RAG知识库进行对话
      * @param message
      * @param chatId
      * @return
      */
     public String doChatWithRage(String message, String chatId) {
         ChatResponse chatResponse = chatClient
                 .prompt()//prompt():开始构建一个对话请求(创建一个提示信息构建器)
                 .user(message)
                 .advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)//CHAT_MEMORY_CONVERSATTON_ID_KEY指定的为上下文关联的id
                         .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
                 //应用RAG知识库问答-要找到我们的知识库去调用响应的模型
                 .advisors(new QuestionAnswerAdvisor(loveAppVectorStore))//RAG 核心增强器QuestionAnswerAdvisor,并传入之前配置的向量数据库loveAppVectorStore
                 .call()
                 .chatResponse();
                 //.call():发送构建好的请求给 AI 模型
                // .chatResponse():获取 AI 返回的对话响应结果,封装为ChatResponse对象
 ​
 ​
         String content = chatResponse.getResult().getOutput().getText();//拿到返回结果中的输出信息,在从输出信息中拿到ai生成的文本
         log.info("content: {}",content);
         return content;
     }
 ​

QuestionAnswerAdvisor核心的作用:

1,自动将用户问题(message)转换为向量

2,在loveAppVectorStore中检索与问题最相关的知识库文档

3,将检索到的文档内容作为上下文传递给 AI 模型,让 AI 基于知识库回答

Spring AI + 云知识库服务实现

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

完成云知识库的配置

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

上传原始文档数据到平台

导入数据到知识库中

RAG 开发

可以参考 Spring AI Alibaba 的官方文档 来学习

Spring AI Alibaba 利用了 Spring AI 提供的文档检索特性(DocumentRetriever),自定义了一套文档检索的方法,使得程序会调用阿里灵积大模型 API 来从云知识库中检索文档

创建基于阿里云知识库服务的RAG增强Advisor

 /**
  * 自定义基于阿里云知识库服务的RAG增强顾问
  */
 @Configuration
 @Slf4j
 public class LoveAppRagCloudAdvisorConfig {
 ​
     //注入自己的阿里大模型的api——key
     @Value("${spring.ai.dashscope.api-key}")//从配置文件中读取spring.ai.dashscope.api-key属性的值
     private String dashScopeApiKey;
 ​
     @Bean//标识这是一个 Spring Bean,会被注册到 Spring 容器中
     public Advisor loveAppRagCloudAdvisor() {
         
         DashScopeApi dashScopeApi = new DashScopeApi(dashScopeApiKey);//创建阿里云 DashScope 服务API 客户端,传入我们的api
         
         final String KNOWLEDGE_INDEX = "你的知识库名";//要索引的知识库的名称
         
         //创建阿里云文档检索器实例,用于从知识库中检索相关文档
         DocumentRetriever documentRetriever = new DashScopeDocumentRetriever(dashScopeApi,
                 DashScopeDocumentRetrieverOptions.builder()//构建检索器的配置选项
                         .withIndexName(KNOWLEDGE_INDEX)//指定要检索的知识库索引名称
                         .build());//.build()完成配置选项的构建
 ​
         //文档检索器
         return RetrievalAugmentationAdvisor.builder()//创建 RAG 增强顾问的构建器
                 .documentRetriever(documentRetriever)//设置文档检索器,用于在回答前检索相关知识
                 .build();//完成顾问的构建并返回
     }
 }

使用该Advisor

 @Resource
 private Advisor loveAppRagCloudAdvisor;
 ​
 public String doChatWithRag(String message, String chatId) {
     ChatResponse chatResponse = chatClient
             .prompt()
             .user(message)
             .advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId)
                     .param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
             // 开启日志,便于观察效果
             .advisors(new MyLoggerAdvisor())
             // 应用增强检索服务(云知识库服务)
             .advisors(loveAppRagCloudAdvisor)
             .call()
             .chatResponse();
             
     String content = chatResponse.getResult().getOutput().getText();
     log.info("content: {}", content);
     return content;
 }
Logo

为武汉地区的开发者提供学习、交流和合作的平台。社区聚集了众多技术爱好者和专业人士,涵盖了多个领域,包括人工智能、大数据、云计算、区块链等。社区定期举办技术分享、培训和活动,为开发者提供更多的学习和交流机会。

更多推荐