spring-ai 1.0.0 学习(十五)——RAG
spring-ai学习逐渐进入深水区,今天聊一下RAG——检索增强生成
顾名思义,RAG其实就是通过外挂知识库,来解决大模型无法获取最新知识或某专业领域知识的问题,也就是通过检索外部数据源,来增强大模型生成的回答
作用原理
具体来说,RAG分为三步:
1)数据预处理:
准备外部数据源,可以使用向量数据库,也可以使用传统数据库甚至搜索引擎作为外部数据源。由于向量数据库相似性查找的特性,一般更多使用向量数据库作为外部数据源。
加载:首先将docx、pdf等格式的规章制度、专业领域文件等文档加载到系统中
切分:为了方便查找和减少token消耗,一般将各文档按一定大小进行切分
向量化:将切分后的文档片段使用嵌入式大模型转换为向量
保存:然后将向量与文档片段一起存入向量数据库
2)检索:
向量化:将用户的输入转换为向量
相似性查找:在向量数据库中进行相似性查找,得到与用户输入相关的文档片段。
3)生成:将用户输入与检索到的文档片段一起发送给大模型,大模型会根据其自身知识和文档片段,生成最终的回答。
样例
首先将RAG相关包引入
<dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-rag</artifactId></dependency>
数据预处理
TextReader textReader = new TextReader("src/main/resources/data/rag.txt");TokenTextSplitter tokenTextSplitter = TokenTextSplitter.builder().build();vectorStore.write(tokenTextSplitter.split(textReader.read()));
上述样例中,reader负责加载,splitter负责切分,vectorStore中的EmbeddingModel负责向量化,最终存入vectorStore中
问答RAG
spring-ai中有一个简单的问答系统的实现,QuestionAnsweiAdvisor,就是一个封装好的简单易用的RAG(此类位于spring-ai-advisors-vector-store包中)
@AutowiredVectorStore vectorStore;@GetMapping("/ai/rag")String generation(String userInput){return this.chatClient.prompt().user(userInput).advisors(new QuestionAnswerAdvisor(vectorStore)).call().content();}
只需提前准备好VectorStore,并在VectorStore中保存好相关领域的知识,将其传入QuestionAnswerAdvisor,即可通过QuestionAnswerAdvisor进行相关知识的问答
QuestionAnswerAdvisor会在调用大模型前,在VectorStore中查找相关文档,并将其融入提示词中,最终一起发送给大模型
进阶知识
数据预处理相关接口及实现类
接口 | 实现类 | 作用 |
DocumentReader | JsonReader TextReader 其他扩展包如pdf、markdown | 加载文档生成Document |
DocumentTransformer | TokenTextSplitter KeywordMetadataEnricher SummaryMetadataEnricher | 切分文档 提取文档关键词 生成文档摘要 |
DocumentWriter | FileDocumentWriter SimpleVectorStore 其他VectorStore扩展包 | 文档存储 |
检索增强
QuestionAnswerAdvisor能够使我们快速上手RAG,即开即用,但是其缺点也源于此,由于其对RAG内部各环节进行了封装,导致其扩展性并不好,不方便针对各个环节进行优化
Spring-ai提供了另一个Advisor,RetrievalAugmentationAdvisor,可以对检索和生成阶段的环节进行优化
Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder().queryTransformers(...).queryExpander(...).documentRetriever(...).documentJoiner(...).documentPostProcessors(...).queryAugmenter(...).build();String answer = chatClient.prompt().advisors(retrievalAugmentationAdvisor).user(question).call().content();
RetrievalAugmentationAdvisor相关接口及实现类
接口 | 实现类 | 作用 | 是否需要大模型 |
QueryTransformer | CompressionQueryTransformer RewriteQueryTransformer | 查询转换,如压缩查询、重写查询等 | 是 |
QueryExpander | MultiQueryExpander | 一个查询扩展为多个,如将一个复杂问题扩展为多个简单问题 | 是 |
DocumentRetriever | VectorStoreDocumentRetriever | 检索相关文档片段 | 否 |
DocumentJoiner | ConcatenationDocumentJoiner | 将查找到的多个文档片段合并 | 否 |
DocumentPostProcessor | 无 | 进行检索后处理 | 否 |
QueryAugmenter | ContextualQueryAugmenter | 将查询到的文档融合进提示词等 | 否 |
在调用大模型前,RetrievalAugmentationAdvisor会按照上述接口顺序依次调用相关功能,对相应环节进行优化
其中,使用QueryTransformer和QueryExpander时会调用大模型进行转换或扩展查询,此时建议将大模型温度设为0,减少大模型的自由发挥
优化方案
针对不同的具体环节,有许多不同的优化方案,来进一步提高回答的质量
文档切分环节:尽量保持语义的完整性
1)递归按字符切分,切分字符为【换行符,句号,问号,叹号,分号,逗号等】,首先按第一个字符进行切分,若仍然有文档片段大于最大长度时,继续用第二个字符切分,直到所有文档片段长度都满足要求。
2)允许切分时文档片段有部分重叠
3)切分后的文档最前面添加文章标题或段落标题
向量化环节:建议对多种嵌入式大模型进行测试,需要关注的指标有是否支持中文、向量维度、最大token数、词汇表大小等。
文档入库环节:
1)2层检索:对所有文档总结摘要信息,每次检索时先检索摘要,找到相关文档,然后再检索该文档切分的文档片段
检索环节:
1)检索到相似文档片段时,不仅返回命中的结果片段,同时返回其相邻上下文片段
2)结果压缩,去除结果片段中与用户查询不相关的内容
3)自适应检索策略:根据问题类型不同(事实性、分析性、观点性、上下文性)采用不同的检索策略
4)根据用户查询来决定是否需要进行检索
5)融合检索:采用相似度检索和其他检索方式(如全文检索)相融合的方式进行检索
6)重排序:将检索到的文档片段按相关性大小排序,优先返回最相关的文档片段
大模型调用环节:
1)查询转换:将用户冗长、含歧义、包含无关信息、需要结合聊天历史理解的查询进行重写
2)查询扩展:将单个复杂查询扩展为多个简单查询