基于SpringAI框架,学习RAG知识库应用开发的核心特性和高级知识点,掌握RAG最佳时间和调优技巧。
具体包括:
- RAG核心特性
- 文档手机和切割ETL
- 向量转换和存储(向量数据库)
- 文档过滤和检索(文档检索器)
- 查询增强和关联(上下文查询增强器)
- RAG 最佳实践和调优
- RAG高级知识
- 检索策略
- 大模型幻觉
- 高级RAG架构
文档收集和切割 ETL
Spring AI 中的文档是一个Document对象。
Extract 提取文档
SpringAI是基于DocumentReader来读取文档的,内置了很多DocumentReader的实现类,例如JSON,Text,HTML,Markdown,PDF。HTML的读取底层是基于JSoup来实现的, JSoup是一个读取、点击HTML的库。Tika是一个apache的多格式转换库。
以JSONReader为例,支持Pointer特性,即快速从文档中读取嵌套的特定属性的值。
不需要一层层解析出来然后取值。
此外,Sprin AI Alibaba官方社区提供了很多的文档读取器,比如加载飞书文档,提取B站视频信息和字幕,加载邮件,加载GitHub官方文档等等。
官方社区文档地址:https://java2ai.com/integration/rag/document-readers
tips: 在github仓库页面,按句号,可以以网页VS编辑器的形式查看项目。
Transform 文档转换
Spring AI 文档的转换是通过Transformer 来实现的。
- TextSplitter 文档分割器
- MetaDataEnricher 元数据增强器
- ContentFormatter 内容格式化
TokenTextSplitter 来分割文档。
SummaryMetaDataEnricher 添加元数据标签。
最后写入向量数据库 DocumentWriter。
将三者组合起来即可完成文档ETL流程。
也可以使用链式调用:
java
vectorStore.write(enricher.apply(spliter.apply(pdfReader.read())))
向量转换和存储
Spring AI 官方提供了向量数据库接口VectorStore 和向量存储整合包,帮助开发者快速集成各种第三方向量存储,比如Milvus, Redis, PGVector, Elasticsearch 等。
VectorStore 接口介绍
VectorStore 继承自 DocumentWriter,提供以下功能:
接口提供了向量存储的基本操作,即增删改查,加上返回数据库客户端,以便更灵活地自定义地处理数据。
- 添加文件到向量库
- 从向量库删除文档
- 基于查询进行相似度检索
- 获取原生客户端(用于特定的高级操作)
搜索请求构建
SearchRequest类,用来构建相似度检索。
java
SearchRequest.builder().query("yupi最好的教程是").topK(5).similarThreshold(0.7).filerExpression("category = 'web' and date > '2025-05-20' ").build();
向量存储的工作原理:
文档切块 - Embedding模型 向量化 - 存入向量库;
用户问题 - 问题改写 - Embedding模型 向量化 - 相似度检索(欧氏距离等算法) - 取 top k 条 - 精排模型重排序 - 问题 + 检索到的词条 结合来回答。
Spring AI支持很多向量库。
对于每种Vector Store 实现,我们都可以参考对应的官方文档进行整合,开发方法基本一致:先准备好数据源 => 引入不同的整合包 => 编写对应的配置 => 使用自动注入的VectorStore即可。
另外,Spring AI Alibaba 已经继承了阿里云百炼平台,可以直接使用阿里云百炼平台提供的Vector Store API,无需自己搭建向量数据库了。
参考官方文档,主要是提供了DashScopeCloudStore类:
基于PGVector 实现向量存储
PGVector 是postgreSQL的扩展插件,为postgreSQL提供了存储和检索高维向量数据的能力。
为什么选择它来实现向量存储呢?因为很多传统业务都会把数据存储在这种关系型数据库中,直接给原有的数据库安装扩展就能实现向量相似度检索,而不需要额外搞一套向量数据库,人力物力成本很低,这种方案很受青睐,也是目前实现RAG的主流方案之一。
阿里云的数据库创建:
1、创建高权限账号;
2、创建数据库;
3、申请公网访问链接;
4、将本机IP设置为白名单,或者设置所有IP都可访问(在白名单中添加"0.0.0.0/0")。
引入依赖
xml
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
<version>1.1.2</version>
</dependency>
根据SpringAI的官方文档教程,引入配置项,用来配置数据库连接信息:
yaml
spring:
datasource:
url: jdbc:postgresql://localhost:5432/postgres
username: postgres
password: postgres
ai:
vectorstore:
pgvector:
index-type: HNSW
distance-type: COSINE_DISTANCE
dimensions: 1536
max-document-batch-size: 10000 # Optional: Maximum number of documents per batch
官方文档中提供了自动注入依赖的代码,但是这种方式不适合当前项目,因为VectorStore依赖EmbeddingModel对象,咱们之前的学习中同时引入了Ollama和阿里云DashScope的依赖,又两个EmbeddingModel的Bean,Spring不知道注入哪个,就会报错。
鱼皮的报错:
他在项目中使用手动的方式来做PGVectorStore,但是依然是报错,发现了两个EmbeddingModel:ollamaEmbeddingModel, dashScopeEmbeddingModel.
他发现之前的LoveAppVectorStore类都可以正常运行,但是新建的PGVectorStore类就报错,于是检查到报错信息,果然发现了一行,autoConfiguration自动注入配置,解决办法:在启动类上的注解中排除掉这个类的自动注入。
java
@SpringBootApplication(exclude = PgVectorStoreAutoConfiguration.class)
问题排查思路:
1、检查pom.xml文件中的依赖是否正确,已注释掉spring-ai-starter-vector-store-pgvector 自动整合PGVectorStore的依赖项;
2、在maven仓库的依赖项中点击查看,是否依赖对了pgvector依赖项,并检查其中应该没有自动注入的代码。
3、仔细检查报错的位置,就是在自己新写的代码中报错。发现原来的loveAppVectorStore没报错,于是怀疑存在有自动注入的类。
4、仔细查看报错行信息,果然发现是在自动注入类的代码的执行中报错的,无法选定EmbeddingModel。
5、解决,在启动类的注解中排除掉该类的自动注入即可。

PgVectorStore的构建时,还需要指定维度,因为目前用的是text-embedding-v模型,其维度为1024,二者需要统一。
.dimensions(1024)
目前有个疑问?
项目每次启动时,都会注入依赖pgVectorVectorStore,重新构建PgVectorStore,加载文档oveAppDocumentLoader.loadDocuments(classPath);
然后分批次插入PG向量库PgVectorVectorStore.add(batch);
每次消耗的Token都比较多?
文档过滤和检索
预检索 - 检索时 - 检索后
三个阶段的一些处理。
预检索:优化用户查询
处理和优化用户的原始查询
查询转换-查询改写
RewriteQueryTransformer,使用LLM对原始查询改写,使其更加清晰和详细
java
Query query = new Query("I'm studying machine learning. What is an LLM?");
QueryTransformer queryTransformer = RewriteQueryTransformer.builder()
.chatClientBuilder(chatClientBuilder)
.build();
Query transformedQuery = queryTransformer.transform(query);
查询转换 - 查询翻译
将用户问题翻译成指定语言。写一个固定格式的prompt,丢给LLM翻译。
java
Query query = new Query("Hvad er Danmarks hovedstad?");
QueryTransformer queryTransformer = TranslationQueryTransformer.builder()
.chatClientBuilder(chatClientBuilder)
.targetLanguage("english")
.build();
Query transformedQuery = queryTransformer.transform(query);
查询转换 - 查询压缩
java
Query query = Query.builder()
.text("And what is its second largest city?")
.history(new UserMessage("What is the capital of Denmark?"),
new AssistantMessage("Copenhagen is the capital of Denmark."))
.build();
QueryTransformer queryTransformer = CompressionQueryTransformer.builder()
.chatClientBuilder(chatClientBuilder)
.build();
Query transformedQuery = queryTransformer.transform(query);
查询扩展
多扩展查询
将用户的问题,从多个角度改写为不同的问题,更换相近语义的关键词来查询。
目的:扩展搜索空间,提高发现相关信息的概率,
query: 谁是鱼皮?
expandQueryList:
请介绍一下鱼皮的基本情况
给出鱼皮的简历
鱼皮会什么技能
鱼皮取得过什么成就
java
MultiQueryExpander queryExpander = MultiQueryExpander.builder()
.chatClientBuilder(chatClientBuilder)
.numberOfQueries(3)
.build();
List<Query> queries = queryExpander.expand(new Query("How to run a Spring Boot app?"));
检索 提高查询相关性
基于向量数据库的文档检索器
java
DocumentRetriever retriever = VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.similarityThreshold(0.73)
.topK(5)
.filterExpression(new FilterExpressionBuilder()
.eq("genre", "fairytale")
.build())
.build();
List<Document> documents = retriever.retrieve(new Query("What is the main character of the story?"));
也可以构建一个Query对象
java
DocumentRetriever retriever = VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.filterExpression(() -> new FilterExpressionBuilder()
.eq("tenant", TenantContextHolder.getTenantIdentifier())
.build())
.build();
List<Document> documents = retriever.retrieve(new Query("What are the KPIs for the next semester?"));
文档合并
ConcatenationDocumentJoiner 会将根据多个查询从多个数据源检索到的文档合并为一个文档集合。若存在重复文档,则仅保留首次出现的文档。每个文档的得分保持不变。
java
Map<Query, List<List<Document>>> documentsForQuery = ...
DocumentJoiner documentJoiner = new ConcatenationDocumentJoiner();
List<Document> documents = documentJoiner.join(documentsForQuery);
源码
java
return new ArrayList(((Map)documentsForQuery.values().stream().flatMap(Collection::stream).flatMap(Collection::stream).collect(Collectors.toMap(Document::getId, Function.identity(), (existing, duplicate) -> existing))).values().stream().sorted(Comparator.comparingDouble((doc) -> doc.getScore() != null ? doc.getScore() : (double)0.0F).reversed()).toList());
检索后 优化文档处理
Document Post-Processing
一个用于根据查询对检索到的文档进行后处理的组件,旨在解决诸如"中间丢失"问题、模型造成的上下文长度限制,以及需要减少检索信息中的噪声和冗余等挑战。
例如,它可以根据文档与查询的相关性对文档进行排序,移除无关或冗余的文档,或者压缩每个文档的内容以减少噪声和冗余。
查询增强和关联
前面已经学习了SpringAI的两种实现RAG查询增强的Advisor,分别是QuestionAnswerAdvisor 和 RetrievalAugmentationAdvisor。
QuestionAnswerAdvisor
当用户问题发送给AI,Advisor会查询向量数据来获取与用户问题相关的文档,并将这些文档作为上下文附加到用户查询中。
向量数据库的响应将附加到用户文本之后,为 AI 模型生成响应提供上下文。
假设您已经将数据加载到 VectorStore 中,您可以通过向 ChatClient 提供一个 QuestionAnswerAdvisor 实例来执行检索增强生成(RAG)
java
ChatResponse response = ChatClient.builder(chatModel)
.build().prompt()
.advisors(QuestionAnswerAdvisor.builder(vectorStore).build())
.user(userText)
.call()
.chatResponse();
自定义检索的一些参数
java
var qaAdvisor = QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(SearchRequest.builder().similarityThreshold(0.8d).topK(6).build())
.build();
动态过滤器表达式
使用 FILTER_EXPRESSION 顾问上下文参数在运行时更新 SearchRequest 过滤器表达式:
java
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(SearchRequest.builder().build())
.build())
.build();
// Update filter expression at runtime
String content = this.chatClient.prompt()
.user("Please answer my question XYZ")
.advisors(a -> a.param(QuestionAnswerAdvisor.FILTER_EXPRESSION, "type == 'Spring'"))
.call()
.content();
RetrievalAugmentationAdvisor
Spring AI 包含一个 RAG 模块库,您可以利用它来构建自己的 RAG 流程。RetrievalAugmentationAdvisor 是一个顾问组件,基于模块化架构,为最常见的 RAG 流程提供了开箱即用的实现。
java
Advisor retrievalAugmentationAdvisor = RetrievalAugmentationAdvisor.builder()
.documentRetriever(VectorStoreDocumentRetriever.builder()
.similarityThreshold(0.50)
.vectorStore(vectorStore)
.build())
.build();
String answer = chatClient.prompt()
.advisors(retrievalAugmentationAdvisor)
.user(question)
.call()
.content();