RAG(Retrieval Augmented Generation)

目录

[1. ETL(Extract Transform Load)](#1. ETL(Extract Transform Load))

[2. RAG概述](#2. RAG概述)

[2. 加入依赖 和 配置](#2. 加入依赖 和 配置)

[3. 文本向量化](#3. 文本向量化)

[4. 向量存储(向量数据库)](#4. 向量存储(向量数据库))

[4.1 向量数据库介绍](#4.1 向量数据库介绍)

[4.2 SimpleVectorStore](#4.2 SimpleVectorStore)

[4.2.1 把Embedding模型传给向量数据库](#4.2.1 把Embedding模型传给向量数据库)

[4.2.2 运行 和 调试](#4.2.2 运行 和 调试)

5.相似性检索

[5.1 返回所有检索后的文档(不推荐)](#5.1 返回所有检索后的文档(不推荐))

[5.2 筛选检索后的文档(推荐)](#5.2 筛选检索后的文档(推荐))

[5.3 通过表达式构建逻辑复杂的过滤条件](#5.3 通过表达式构建逻辑复杂的过滤条件)

[5.4 相似性检索底层实现逻辑:](#5.4 相似性检索底层实现逻辑:)

[6. Advisor](#6. Advisor)

[6.1 QuestionAA 和 RetrievalAA相关介绍](#6.1 QuestionAA 和 RetrievalAA相关介绍)

[6.1.1 这两个Advisor如何工作的:](#6.1.1 这两个Advisor如何工作的:)

[6.1.2 它们两个的区别:](#6.1.2 它们两个的区别:)

[6.2 模块化RAG架构 (深度拆解)](#6.2 模块化RAG架构 (深度拆解))

阶段一:Pre-Retrieval(检索前------优化查询)

阶段二:Retrieval(检索------从数据源获取文档)

[阶段三:Document Join(文档合并------整合多路检索结果)](#阶段三:Document Join(文档合并——整合多路检索结果))

阶段四:Post-Retrieval(检索后------净化与精炼文档)

阶段五:Generation(生成------最终答案合成)

[6.3 QuestionAnswerAdvisor ---RAG 实战](#6.3 QuestionAnswerAdvisor —RAG 实战)

[6.3.1 加入依赖](#6.3.1 加入依赖)

[6.3.2 实战](#6.3.2 实战)

[7. Algorithm](#7. Algorithm)


1. ETL(Extract Transform Load)

ETL(Extract(提取) Transform(转换) Load(加载)),它是RAG最重要的战略合作伙伴!非常重要。

ETL包含三步:

1.Extract(提取)

从数据源提取数据,并转化为纯文本或基础结构化数据。(现在知道提取成文本就行)

  • 常见提取的数据源:PDF、Word、PPT文档;数据库记录、网页内容等

2.Transform(转换)

对提取的数据进行清洗 、标准化 和适配目标系统。目的是让每块数据变得更精炼。

  • 文本清洗:比如去除乱码、广告、页眉页脚和统一编码(UTF-8)
  • 分块:比如将长文本按语义或长度分割为固定大小块;例,数据源是公司规章制,由于这个规章制度包含"入职流程"、"报销申请"和"离职规则"板块,转换器就会将"入职流程"分割成一个单独的文档,并去除多余空格、换行符等。
  • 元数据标注:比如给文本打上标签,比如来源、创建时间和业务分类等。

3.Load(加载)

将转换 后的数据持久化存储指定位置,典型操作有:

  • 存入向量数据库(如 Redis、ES等)

形象比喻:E------》买菜,T------》洗菜、切菜、去皮,L------》把处理好的蔬菜放到备菜区,供厨师使用(存入向量数据库,供检索使用)。

(上面讲的是大多数框架ETL部分的固有流程,在下面针对Spring AI框架中还会讲解到)

2. RAG概述

RAG(Retrieval Augmented Generation,检索增强生成) 是一种帮助LLM(大语言模型)克服在 长格式内容、事实准确性和上下文感知方面的局限性的技术。(下图详讲)

看下图了解RAG含义:
​

ETL是RAG的战略合作伙伴!RAG检索的数据需要它存入(第1-4)

  1. 传入数据,可以是文档、视频、图片和PPT等类型数据。
  2. 阅读器 (Reader)读取完数据之后,将数据转换成 Document类型,并将文档传下去。
  3. 文档(documents)给到 转换器 ,它会把文档进行清洗、分割 ,比如 你传入公司的规章制度被转化成Document文档后,由于这个规章制度包含"入职流程"、"报销申请"和"离职规则"板块,转换器就会将"入职流程"分割成一个单独的文档,并去除多余空格、换行符等。分割的目的是保证后续RAG检索的准确性和检索效率。
  4. Writer 把文档存入指定 文档 或 向量数据库。如果你存入向量数据库,那么库会自动根据每个文档的内容生成一组固定维度的向量与文档关联。

RAG闪亮登场:

5. 用户发来请求,框架把用户输入封装成一个 ChatClientRequest类型的数据,它里面包含Prompt类型字段,该字段作为"消息载体",承载了用户的输入。

6. RAG把 prompt 提取给向量数据库,++向数把信息转化为有含义的**向量,**再根据这个向量在 数据库 中寻到与该用户向量距离最近的一批向量("向量距离"看最后一节),最后返回各向量对应的文档。(<<检索>>)++

7. 这步什么也没做。

8. ++从向量数据库中查询 出来的文档文本追加到 ChatClientRequest中的prompt++ ,这样就起到增强的效果---即不只发用户问题给AI模型,还加入与该问题强相关的数据。(<<增强>>)

8.++AI模型再根据自己已有的知识对prompt的数据进行++ **++润色和生成++,**最后作为响应返回。

(<<生成>>)

  • 总结:RAG就像给AI装了一个"外挂大脑 ",实际上的AI脑子里只有训练的数据,虽然使用 工具(Tool) 进行联网操作可让它的知识库更庞大,但有些不对外公开的数据它是拿不到的,如你公司的 某某数据、规章制度等,用户通过AI是查不到的或不准确。这时你可以把公司的文档、视频、图片和PPT等 各种数据,利用RAG将数据转换并存入向量数据库 (AI无法获得这些存入的数据)**,**后续用户可精准检索这些数据。

2. 加入依赖 和 配置

依赖:

现在2026-11-4,由于2.0.0-M1.1 存在缺陷,下面使用 1.1.2.3作为演示。(搭配spring boot 3.5.x)

复制代码
<dependency>
    <groupId>com.alibaba.cloud.ai</groupId>
    <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
    <version>1.1.2.3</version>
    <scope>compile</scope>
</dependency>
复制代码
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-bom</artifactId>
            <!-- 必须与 spring-ai-alibaba 1.1.2.3 所依赖的 1.1.2 对齐,否则缺少 audio/tts 等新类 -->
            <version>1.1.2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

配置:

具体更多的配置选项可点击进入 DashScopeEmbeddingProperties.class,并参考 同步接口API详情 - 阿里云百炼

复制代码
spring:
  ai:
    dashscope:
      api-key: ${DASHSCOPE_API_KEY} #阿里百炼申请的api-key
      embedding:
        options: #配置
          model: qwen3.7-text-embedding-flash #配置模型
          dimensions: 1024 #设置维度

3. 文本向量化

正经使用RAG全流程:

1.文档加载

2.文档拆分

3.向量转换

4.向量存储

5.相似性检索

6.查询增强

1、2、3、4点属于ETL,本章重点在于RAG,因此从4-6讲解。ETL在下章讲解。先看完这或ETL都可以,它们搭配使用,但不扣死。

代码:巨蛇神/JavaEE进阶 - 码云 - 开源中国

1.直接展示
​

2.运行(其实qwen3.7-text-embedding-flash默认就是1024维)
​

4. 向量存储(向量数据库)

4.1 向量数据库介绍

文本转为向量之后,需要把向量存储到向量数据库中。

Spring AI支持非常多的 向量数据库,其中包括一个"内存版 "向量数据库(SimpleVectorStore ),只需加入下方依赖便可使用,很适合本地测试和快速原型开发。Spring AI支持的数据库:Vector Databases :: Spring AI Reference

复制代码
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-vector-store</artifactId>
</dependency>

SimpleVectorStore 实现了VectorStore,VectorStore 继承了DocumentWriter( 文档写入接口,后续会讲**),**所以具备文档写入的能力。


Spring AI提供了一套统一的API :给 向量数据库 传入一个 Embedding模型 ,让向量数据库拥有将文本转化为向量的能力, 就无需手动转化了,然后直自动把向量存入向量数据库。

Spring AI支持的向量数据库流程:

  1. 文本转向量(调用Embedding模型)
  2. 存入向量数据库
  3. 接收用户问题------》转向量------》搜索相似内容------》返回给大模型做回答

(原本大多数向量数据库是没有文本转向量能力的)


4.2 SimpleVectorStore

4.2.1 把Embedding模型 传给向量数据库

注意!!下面这种编程方式只适合测试,它在每次启动程序都会加载一次数据,在实际开发中应该只加载一次数据,但在此处每次都得加载数据,因为SimpleVectorStore把数据存在内存中,测试一结束,该数据库就不复存在了。
​

使用什么方式往向数传Embedding等模型都行,@BeforeEach只是其中一种。

4.2.2 运行 和 调试

​

点进SimpleVectorStore.class 可看到它使用Map存数据。


这里顺便介绍**add()与doAdd()**的关系:

​


5.相似性检索

5.1 返回所有检索后的文档(不推荐)

​

5.2 筛选检索后的文档(推荐)

​

5.3 通过表达式 构建逻辑复杂的过滤条件

​

这个表达式可以写SQL语句、支持丰富的操作符,例如 eq(等于)、ne(不等于)、gt / ge / lt / le(大于/大于等于/小于/小于等于)、in(在集合中)、and / or(逻辑与/或)等,可以灵活组合出复杂的过滤逻辑。这个参数是在向量检索前对文档元数据进行的过滤条件,如果文档不满足则不参与检索。

例如:

java 复制代码
// 1. 使用 Builder 构建 SearchRequest,直接传入字符串过滤表达式
SearchRequest request = SearchRequest.builder()
    .query("What is the main plot?")
    .topK(5)
    .similarityThreshold(0.7)
    .filterExpression("genre == 'sci-fi' AND year >= 2020") // 类SQL的过滤字符串
    .build();

//------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

// 1. 使用 FilterExpressionBuilder 构建过滤表达式
Filter.Expression filterExpression = new FilterExpressionBuilder()
    .eq("projectId", "project-123")    // 条件1:projectId 等于 'project-123'
    .and()
    .ge("year", 2020)                  // 条件2:year 大于等于 2020
    .build();

// 2. 将构建好的表达式传入 SearchRequest
SearchRequest request = SearchRequest.builder()
    .query("Spring AI 的使用方法")
    .topK(10)
    .filterExpression(filterExpression) // 传入 Filter.Expression 对象
    .build();

不要以为这个表达式很难使用,它很简单!框架只是支持支持解析的语言多而已,比如你输入SQL**"genre == 'sci-fi' AND year >= 2020"** 和 eq("genre", "sci-fi" )其实等价,因为元数据使用Map键值对存储,SQL会被解析、拆分为"genre" 和"sci-fi"分别去匹配Map的键和值。


5.4 相似性检索底层实现逻辑:

6. Advisor

6.1 QuestionAA 和 RetrievalAA相关介绍

其实向量数据库中的数据AI模型是无法感知 到的,因此生成 (Generation)操作就无法完成,那么RAG该怎么实现呢?

Spring AI基于Advisor API提供了一个开箱即用 的和一个可定制的支持,用于实现RAG (检索增强生成):QuestionAnswerAdvisor 和 RetrievalAugmentationAdvisor。它们是Spring AI中实现RAG的两个重要组件,分别适用于不同层次和复杂度的场景。

下面详讲


6.1.1 这两个Advisor如何工作的:

  1. 我们需要给 Advisor实现 传一个含有数据的向量数据库,后续可加入新数据。(注意要传入已包含 向量转化模型 的向数)

  2. Advisor实现 拥有向量数据库后,它就可以把用户提问转化成向量(通过向量转化模型),然后给到向量数据库进行检索。QuestionAnswerAdvisor 和 RetrievalAugmentationAdvisor的整个工作流程是"2.RAG概述"的RAG工作部分。

  3. Advisor、向量数据库和向量转化模型的关系:

6.1.2 它们两个的区别:

  1. QuestionAnswerAdvisor 是 最简RAG,官方给它的定位是"快速验证、简单回答",是专门为 问答系统 设计的 Advisor实现。当你希望快速构建一个基于知识库的智能客服或FAQ(常见问题(frequently asked questions))助手时,它是首选。
  2. RetrievalAugmentationAdvisor是 **模块化 RAG 入口,**它将RAG流程拆解为可替换的组件(查询转化、检索、后处理、生成等),提供必QAA更灵活的编排能力。
  3. 使用时机:当基础 RAG 效果不佳,需要引入查询重写、多查询扩展、文档重排序等高级技术时,用RAA。

6.2 模块化RAG架构 (深度拆解)

以下均为Spring AI 2.x 版本文档包含功能,但1.x版本能否全部支持------不知道。

RetrievalAugmentationAdvisor的强大源于其背后的模块化架构,文档将其分为了五个阶段。

阶段一:Pre-Retrieval(检索前------优化查询)

  • **核心作用:**在查询向量数据库之前,对 用户原始查询(用户发送的问题) 进行加工,提高检索命中率。
  • **关键模块:**先总的概括,下面再具体说明

|-----------------------------|-------------------------------------------------------|--------------------------|-----------------------------------------------------------|
| 模块 | 作用 | 使用场景 | 示例 |
| CompressionQueryTransformer | 将多轮对话历史+后续问题,压缩成一个独立的、包含完整语境的查询 | 长对话中,使用指代词(如"它的第二大城市是?") | 历史:丹麦首都?--->哥本哈根。后续:"它他的第二大城市是?"------>压缩为:"丹麦的第二大城市是?" |
| RewriteQueryTransformer | 用LLM 重写:它针对的是单个查询本身,把表述不清、冗长或术语不规范的句子改写成更适合检索的形式。 | 用户提问罗嗦,夹杂大量背景描述 | "我正在学机器语言,LLM是什么?"------>重写为更聚焦的查询 |
| TranslationQueryTransformer | 将查询翻译成嵌入模型支持的目标语言 | 例如嵌入模型仅支持英文,但用户用"扣你吉瓦"提问 | 日语查询------>翻译为英文后再检索 |
| MultiQueryExpander | 将单一查询扩展为相同语义的多个变体,从不同角度检索 | 希望提高召回率,覆盖更多相关文档 | "如何运行Spring Boot应用?"------>扩展为3个不同表述的查询 |

1.CQT 未压缩

当前查询:"它的第二大城市?"

如果直接把这句话"它的第二大城市?"丢给向数,检索系统会完全懵掉:"它"是谁?检索出来的结果可能完全是关于其他国家或无关事物的。向数不是AI模型,它只会根据向量查信息。

CQT 压缩

CQT的"压缩"本质上是一次 "语义提炼":它把蕴含在多轮对话中的上下文信息,压缩并注入到单个查询中,使其成为一个完全独立、无歧义的检索指令。

  • 流程
  • **输入:**它接收完整的对话历史(ChatHistory) 和当前的模糊追问(follow-up query)。
  • **调用LLM进行压缩:**它将这两部分一起发送给一个指定的聊天模型(通过ChatClient.Builder 配置)。这个模型的任务是执行"指代消解"和"上下文融合"------理解追问中的代词("它"、"那个")在历史中实际指代什么,并将这个指代对象直接代入追问中。
  • **输出:**模型返回一个独立的、包含完整语境的查询。这个查询可以直接被向数理解并执行检索。
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);

2.RQT(重写)

|----------|----------------------------|
| 核心目标 | 优化表述,提升检索效果 |
| 输入 | 单个用户查询(不用历史记录) |
| 解决问题 | 单论查询中的表述不佳、术语模糊、口语化等问题 |
| 类比 | 一位编辑,把冗长的段落改写成精炼的检索关键词 |

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);

3.TQT(翻译)

TranslationQueryTransformer使用大型语言模型将查询翻译为目标语言,该目标语言由嵌入模型要支持才行。如果查询已经是目标语言,它将原样返回。如果查询的语言未知,它也会原样返回。

java 复制代码
Query query = new Query("Hvad er Danmarks hovedstad?");

QueryTransformer queryTransformer = TranslationQueryTransformer.builder()
        .chatClientBuilder(chatClientBuilder)
        .targetLanguage("english")
        .build();

Query transformedQuery = queryTransformer.transform(query);

4.MQE()

java 复制代码
MultiQueryExpander queryExpander = MultiQueryExpander.builder()
    .chatClientBuilder(chatClientBuilder)
    .numberOfQueries(3)
    .build();
List<Query> queries = queryExpander.expand(new Query("How to run a Spring Boot app?"));

默认情况下,MultiQueryExpander在扩展查询列表中包含原始查询。您可以通过构建器中的includeOriginal方法禁用此行为。

java 复制代码
MultiQueryExpander queryExpander = MultiQueryExpander.builder()
    .chatClientBuilder(chatClientBuilder)
    .includeOriginal(false)
    .build();

以上四个都支持使用的提示符可以通过构建器中提供的promptTemplate()方法进行输出格式定制。

阶段二:Retrieval(检索------从数据源获取文档)

  • **核心作用:**真正执行查询,从向数库或其他数据源中获取最相关的文档片段。
  • **核心组件:**VectorStoreDocumentRetriever

|---------------------|-----------------------------------|-------------------------------------------------------------------------------------------|
| 核心配置项 | 作用 | 示例 |
| similarityThreshoId | 相似度阈值,低于此值的文档被丢弃 | 0.73 |
| topK | 最多返回的文档数量 | 5 |
| filterExpression | 基于元数据的过滤条件(支持静态、动态Supplier或请求级参数) | eq("genre","fairytale");动态:() -> eq(''tenant",TenantContextHolder.getTenantIdentifier()) |

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 的 FILTER_EXPRESSION 参数)和 Retriever 级过滤,请求级过滤优先。

使用时机 :当你需要根据业务规则(如租户隔离、内容分类)精准缩小检索范围时,务必使用 filterExpression。

更详细:Retrieval Augmented Generation :: Spring AI Reference

阶段三:Document Join(文档合并------整合多路检索结果)

  • **核心作用:**当通过多个查询或多个数据源检索时,将结果集合合并作为一个统一的文档集合(List<Document>)。
  • **核心组件:**ConcatenationDocumentJoiner

若遇到重复文档,仅保留第一份。

阶段四:Post-Retrieval(检索后------净化与精炼文档)

  • **核心作用:**对检索到的文档进行再加工,解决"中间信息丢失"、上下文长度限制、信息冗余与噪音(干扰)等问题。
  • 典型操作:
  1. 重排序(Re-ranking):基于与查询的相关性,对文档重排序,将最相关的放在前面。
  2. 去重与剔除:移除无关或重复的文档。
  3. 压缩:精简每一篇文档的内容,减少干扰(噪音)。
  • 使用时机:当检索结果质量不稳定,或模型因上下文过长而忽略关键信息时,引入后处理环节。

阶段五:Generation(生成------最终答案合成)

  • **核心作用:**将用户查询与(经过处理的)结果合并,构造最后发给LLM的提示词。
  • **核心组件:**ContextualQueryAugmenter
  1. 将文档内容作为上下文,增强用户查询。
  2. 关键行为: 默认不允许空上下文。若无检索结果,会指令模型拒绝回答。可通过allowEmptyContext(true) 允许模型在无上下文时仍生成回答。
java 复制代码
QueryAugmenter queryAugmenter = ContextualQueryAugmenter.builder()
        .allowEmptyContext(true)
        .build();

这是RAG的最后一环。

6.3 QuestionAnswerAdvisor ---RAG 实战

6.3.1 加入依赖

复制代码
// Spring AI 2.x版本已经不是这个了,而是Spring AI Reference
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>

6.3.2 实战

代码所在:巨蛇神/JavaEE进阶 - 码云 - 开源中国

上面我们也看到了,AI会帮我们指出不合理的地方,但是如果我要查询的信息是公司内部使用,而外部没有或不全面的,我们不想让AI插手,就可以自定义提示词模板(promptTemplate)。


1.默认提示词模板

2.自定义提示词模板

3.检索

根据上下文,就算25年没有奥运会,AI也会说25年有奥运会。

7. Algorithm

我们学过如何计算 二维 或三维 空间的两个向量在方向上的相似程度 和 两个向量之间的距离。 更高维的向量(1,2,0.6,-0.1,....)的向量同样适用。

  • 余弦相似度:(cos等于1,说明两向量方向完全相同;0,则正交;-1,方向完全相反)

​

  • 欧氏距离: (两个向量之间的距离)

​

相关推荐
冬奇Lab1 小时前
一天一个开源项目(第259篇):Modern Web Guidance —— Chrome 团队给 AI 编码 Agent 的「现代 Web 最佳实践」外挂
人工智能·开源
飞哥数智坊2 小时前
打造自己的 Personal Agent,从抄大肥鱼作业开始
人工智能·deepseek
对你无可奈何3 小时前
Magpie 实战:把 WorkBuddy、Qoder 的订阅模型接进一个本地池子
人工智能
Csvn3 小时前
每请求到底多少钱?把 Token 成本算成可观测指标(C01)
人工智能·aigc·agent
吴名氏.3 小时前
Java转AI Agent教程(第十三周)混合检索与重排
java·数据库·人工智能·ai·ai agent
老金带你玩AI4 小时前
惊人的一致性!Seedream 5.5实测!
人工智能
数据技术说5 小时前
在 KES AI 数据库上搭一条 NL2SQL 链路:语义、召回、权限各放哪
人工智能·sql·代码规范
jay神5 小时前
基于 YOLOv8 的手势识别系统(Web 版)
前端·人工智能·python·深度学习·yolo·计算机视觉·计算机毕业设计