RAG知识库进阶-1

基于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的主流方案之一。

阿里云的PG官网地址。

阿里云的数据库创建:

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();
相关推荐
必须会一定会1 小时前
大模型手搓文件对比工具(6):差异不用再手选
java·人工智能·ai编程
yio_yin1 小时前
MyBatis
java·前端·mybatis
程序员-Benothing1 小时前
Java ForkJoinPool 详解:从分治思想到高性能并行计算
java·开发语言·后端·面试·职场和发展
手写码匠1 小时前
华为云征文|DeepSeek-R1 智能问数 Agent 实战:Flexus X 实例 + Dify 构建企业级 Text-to-SQL 查询助手
人工智能·深度学习·算法·aigc
上海安当技术2 小时前
单点登录 SSO 怎么选协议?SAML 2.0 / OAuth 2.0 / OIDC / CAS 对比与 ERP、OA、CRM 接入实战
java·开发语言
mifengxing2 小时前
Java集合与泛型
java·算法·复习笔记
donoot2 小时前
《大话文渊慧典》:六
人工智能·深度学习·aigc·ppstructure·文渊慧典·大话系列
不才不才不不才2 小时前
Spring 源码系列(14): JDK 动态代理 vs CGLIB,以及拦截器责任链
java·开发语言·spring
流云鹤2 小时前
05Java学习day(5)
java·学习