RAG向量检索增强生成

        GPT 3.5/4.0 数据集仅支持截止到 2021 年 9 月之前的数据。因此,该模型表示它不知道该日期之后的知识,因此它无法很好的应对需要用最新知识才能回答的问题。

        现有三种技术可定制 AI 模型以整合你自己的数据:

  • Fine Tuning 微调:这种传统的机器学习技术涉及定制模型并更改其内部权重。然而,即使对于机器学习专家来说,这是一个具有挑战性的过程,而且由于 GPT 等模型的大小,它极其耗费资源。此外,有些模型可能不提供此选项。
  • Prompt Stuffing 提示词填充:一种更实用的替代方案是将您的数据嵌入到提供给模型的提示中。考虑到模型的令牌限制,我们需要具备过滤相关数据的能力,并将过滤出的数据填充到在模型交互的上下文窗口中,这种方法俗称“提示词填充”。Spring AI 库可帮助您基于“提示词填充” 技术,也称为检索增强生成 (RAG)实现解决方案。
  • Function Calling:此技术允许注册自定义的用户函数,将大型语言模型连接到外部系统的 API。Spring AI 大大简化了支持函数调用所需编写的代码。

        下面我们先来了解一下检索增强生成 (RAG):

一、工作原理

        检索增强生成 (RAG)旨在解决为 AI 模型提供额外的知识输入,以辅助模型更好的回答问题。

        该方法涉及批处理式的编程模型,其中涉及到:从文档中读取非结构化数据、对其进行转换、然后将其写入矢量数据库

        RAG 的工作流程可拆解为离线准备在线交互两大阶段,整体逻辑是 “先检索、再增强、最后生成”:

1.离线准备阶段:构建可检索的知识库

        该阶段的目标是将原始数据(如文档、网页、PDF 等)处理成大模型能高效检索的格式,核心步骤包括:

  1. 数据加载与清洗
    收集相关领域的原始数据(如企业手册、学术论文、产品说明书等),并进行清洗(去除重复内容、修正格式错误、提取关键信息等)。

  2. 文本分割(Chunking)
    将长文本拆分为短片段(称为 “Chunk”,通常几百字),因为大模型的输入长度有限,且短片段更易匹配用户问题;

  3. 生成向量嵌入(Embedding)
    通过嵌入模型(Embedding Model),将每个 Chunk 转换为向量

    原理:语义相似的文本会被映射为距离接近的向量(如 “猫” 和 “犬” 的向量距离比 “猫” 和 “汽车” 更近)。
  4. 存储向量与元数据
    将向量、原始文本 Chunk、来源信息(如文档名称、页码)等存入向量数据库,供后续快速检索。

2.在线交互阶段:基于检索结果生成回答

        当用户提出问题时,RAG 系统实时完成以下步骤,生成最终回答:

  1. 用户问题处理
    接收用户输入的问题(如 “Spring AI 的 RAG 组件如何配置?”),并通过同一嵌入模型将问题转换为向量。

  2. 相似性检索
    向量数据库计算 “问题向量” 与 “知识库中所有 Chunk 向量” 的相似度(如余弦相似度),返回最相关的 Top N 个 Chunk(通常 3-5 个,称为 “上下文”)。

    例:若用户问 “Spring AI 的 RAG”,检索可能返回《Spring AI 官方文档》中 “RAG 模块配置”“向量存储集成” 等相关片段。
  3. 提示词构建
    将用户问题、检索到的上下文 Chunk 组合成一个提示词(Prompt),格式大致为:

    “基于以下信息回答问题:[检索到的 Chunk1]、[Chunk2]...
    问题:[用户的问题]
    要求:仅用提供的信息回答,不编造内容。”

  4. 生成回答
    将构建好的提示词输入大模型,模型结合上下文信息生成回答,确保内容基于检索到的知识库,而非空想。

二、向量生成

1.向量模型

        通过嵌入模型(Embedding Model),将每个 Chunk 转换为向量,检索时通过向量来比较文本的相似度。为什么通过向量就能找到内容相似的知识?这就不得不提到向量模型的知识了。

  • 向量是空间中有方向和长度的量,空间可以是二维,也可以是多维;

  • 向量既然是在空间中,那么两个向量之间就一定能计算距离

  • 二维向量为例,向量之间的距离有两种计算方法:

        通常,两个向量之间欧式距离越近,就认为两个向量的相似度越高(余弦距离相反,越大相似度越高);

        所以,如果能把文本转为向量,就可以通过向量距离来判断文本的相似度了;

        现在有不少的专门的向量模型,就可以实现将文本向量化。一个好的向量模型,就是要尽可能让文本含义相似的向量,在空间中距离更近:

2.嵌入模型 (Embedding Model)

        嵌入(Embedding)的工作原理是将文本、图像和视频转换为称为向量(Vectors)的浮点数数组。这些向量旨在捕捉文本、图像和视频的含义。嵌入数组的长度称为向量的维度(Dimensionality)。

        嵌入模型(EmbeddingModel)是嵌入过程中采用的模型。当前EmbeddingModel的接口主要用于将文本转换为数值向量。。

2.1 API介绍

        Spring AI 为 DashScope Embedding Model提供了 Spring Boot 的自动配置(虽然官图尚未更新)。

        嵌入模型 API :: Spring AI 中文文档

        要启用此功能,请将以下依赖项添加到您项目的 Maven pom.xml文件中:

<!-- spring-ai-alibaba -->
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
        </dependency>
private EmbeddingModel embeddingModel;
    @Autowired
    public EmbeddingController(DashScopeEmbeddingModel dashScopeEmbeddingModel) {
        this.embeddingModel = dashScopeEmbeddingModel;
    }
1.EmbeddingModel

        Embedding Model API提供多种选项,将文本转换为Embeddings,支持单个字符串、结构化的Document对象或文本批处理。

        所有方法都围绕着call方法实现,这是调用 Embedding Model的主要方法。当然也有多种快捷方式可以获得文本Embeddings。例如embed(String text)方法,它接受单个字符串并返回相应的 Embedding 向量。

public interface EmbeddingModel extends Model<EmbeddingRequest, EmbeddingResponse> {

    @Override
    EmbeddingResponse call(EmbeddingRequest request);

    /**
     * Embeds the given document's content into a vector.
     * @param document the document to embed.
     * @return the embedded vector.
     */
    List<Double> embed(Document document);

    /**
     * Embeds the given text into a vector.
     * @param text the text to embed.
     * @return the embedded vector.
     */
    default List<Double> embed(String text) {
        Assert.notNull(text, "Text must not be null");
        return this.embed(List.of(text)).iterator().next();
    }

    /**
     * Embeds a batch of texts into vectors.
     * @param texts list of texts to embed.
     * @return list of list of embedded vectors.
     */
    default List<List<Double>> embed(List<String> texts) {
        Assert.notNull(texts, "Texts must not be null");
        return this.call(new EmbeddingRequest(texts, EmbeddingOptions.EMPTY))
                .getResults()
                .stream()
                .map(Embedding::getOutput)
                .toList();
    }

    /**
     * Embeds a batch of texts into vectors and returns the {@link EmbeddingResponse}.
     * @param texts list of texts to embed.
     * @return the embedding response.
     */
    default EmbeddingResponse embedForResponse(List<String> texts) {
        Assert.notNull(texts, "Texts must not be null");
        return this.call(new EmbeddingRequest(texts, EmbeddingOptions.EMPTY));
    }

    /**
     * @return the number of dimensions of the embedded vectors. It is generative
     * specific.
     */
    default int dimensions() {
        return embed("Test String").size();
    }

}
2.EmbeddingRequest

        EmbeddingRequest是一种ModelRequest,它接受文本对象列表和可选的Embedding请求选项。

public class EmbeddingRequest implements ModelRequest<List<String>> {
private final List<String> inputs;
private final EmbeddingOptions options;
// other methods omitted
}

        前缀spring.ai.dashscope是用于配置连接至 DashScope 的属性前缀。

PropertyActionDefault
spring.ai.dashscope.api-key来自百炼平台的API KEY-

        所有以 spring.ai.dashscope 开头的属性都可以在运行过程中通过在 EmbeddingRequest 调用中添加特定请求的Runtime Options来覆盖。 

3.Runtime Options

        DashScopeEmbeddingOptions提供了Embedding Request的配置信息,它提供了一个构建器,用于创建选项。

        在启动时,可以使用DashScopeEmbeddingModel构造函数设置所有嵌入请求使用的默认选项。在运行时,可以通过将一个DashScopeEmbeddingOptions实例传递给EmbeddingRequest请求来覆盖默认选项。

例如,使用指定的请求覆盖默认的模型名称:

// 调用嵌入模型并获取结果
        var embeddingResponse = embeddingModel.call(new EmbeddingRequest(
                texts,
                DashScopeEmbeddingOptions.builder()
                        .withModel("text-embedding-v4")
                        .build()
        ));
4.EmbeddingResponse

        EmbeddingResponse类保存了AI模型的输出,其中每个 Embedding 实例包含来自单个文本输入的结果向量数据。同时,它还携带了有关 AI 模型响应的EmbeddingResponseMetadata元数据。

public class EmbeddingResponse implements ModelResponse<Embedding> {
    private List<Embedding> embeddings;
    private EmbeddingResponseMetadata metadata = new EmbeddingResponseMetadata();
    // other methods omitted
}
5.Embedding

Embedding表示一个 Embedding 向量。

public class Embedding implements ModelResult<List<Double>> {
    private List<Double> embedding;
    private Integer index;
    private EmbeddingResultMetadata metadata;
// other methods omitted
}

2.2 使用Embedding Model向量化

说明:选择阿里百炼的向量模型——通用文本向量-v4(text-embedding-v4),实现文本的向量化。

controller:

@RestController
@RequestMapping("/embedding")
public class EmbeddingController {

    private EmbeddingModel embeddingModel;
    @Autowired
    public EmbeddingController(DashScopeEmbeddingModel dashScopeEmbeddingModel) {
        this.embeddingModel = dashScopeEmbeddingModel;
    }

    @GetMapping("/embed")
    public List<float[]> embed() {
        List<String> texts = List.of(
                "哈马斯称加沙下阶段停火谈判仍在进行 以方尚未做出承诺",
                "土耳其、芬兰、瑞典与北约代表将继续就瑞典\"入约\"问题进行谈判",
                "日本航空基地水井中检测出有机氟化物超标",
                "国家游泳中心(水立方):恢复游泳、嬉水乐园等水上项目运营",
                "我国首次在空间站开展舱外辐射生物学暴露实验"
        );

        // 调用嵌入模型并获取结果
        var embeddingResponse = embeddingModel.call(new EmbeddingRequest(
                texts,
                DashScopeEmbeddingOptions.builder()
                        .withModel("text-embedding-v4")
                        .build()
        ));

        // 使用集合接收嵌入结果
        List<float[]> embeddings = embeddingResponse.getResults().stream()
                .map(Embedding::getOutput)
                .collect(Collectors.toList());

        return embeddings;
    }

}

调用接口,输出如下:

三、向量存储(Vector Store)

        向量存储(VectorStore)是一种用于存储和检索高维向量数据的数据库或存储解决方案,它特别适用于处理那些经过嵌入模型转化后的数据。在 VectorStore 中,查询与传统关系数据库不同。它们执行相似性搜索,而不是精确匹配。当给定一个向量作为查询时,VectorStore 返回与查询向量“相似”的向量。

        VectorStore 用于将您的数据与 AI 模型集成。在使用它们时的第一步是将您的数据加载到矢量数据库中。然后,当要将用户查询发送到 AI 模型时,首先检索一组相似文档。然后,这些文档作为用户问题的上下文,并与用户的查询一起发送到 AI 模型。这种技术被称为检索增强生成Retrieval Augmented Generation,RAG)。

1.API介绍

        VectorStore API提供了简单易用的接口供开发者对 VectorStore 进行操作。

1.1 Document(文档内容)

文档内容核心类,主要用于管理和存储文档的文本或媒体内容及其元数据。

作用:

  • 内容管理:存储文本内容(text)或媒体内容(media),但不能同时存储两者,提供了对内容的访问和格式化功能
  • 元数据管理:支持存储与文档相关的元数据,值限制为简单类型(如字符串、整数、浮点数、布尔值),以便与向量数据库兼容
  • 唯一标识:每个文档的唯一 Id,当未指定时会随机生成 UUID.randomUUID().toString()
  • 评分机制:为文档设置一个评分,用于表示文档的相似性
public class Document {

    private final String id;

    private final String text;
    private final Media media;
    private final Map<String, Object> metadata;
    @Nullable
    private final Double score;
    ......
}

示例:

1.2 VectorStore

        VectorStore 接口定义了用于管理和查询向量数据库中的文档的操作。向量数据库专为 AI 应用设计,支持基于数据的向量表示进行相似性搜索,而非精确匹配。

方法名称描述
getName返回当前向量存储实现的类名
getNativeClient返回向量存储实现的原生客户端(如果可用)
add添加一组文档到向量数据库
delete根据文档Id、过滤条件等删除文档
similaritySearch基于文本、查询嵌入、元数据过滤条件等进行相似性查询
public interface VectorStore extends DocumentWriter {
    default String getName() {
        return this.getClass().getSimpleName();
    }

    void add(List<Document> documents);

    default void accept(List<Document> documents) {
        this.add(documents);
    }

    Optional<Boolean> delete(List<String> idList);

    List<Document> similaritySearch(SearchRequest request);

    default List<Document> similaritySearch(String query) {
        return this.similaritySearch(SearchRequest.query(query));
    }
}

        要将数据插入 VectorStore,应先将其封装在Document对象中。Document类封装了来自数据源(如 PDF 或 Word 文档)的内容,并将文本表示为字符串。它还包含键值对形式的元数据,包括文件名等详细信息。

        在将文本内容插入 VectorStore 时,使用 EmbeddingModel 将文本内容转换为数值数组或float[],这一步称为向量 Embedding。EmbeddingModel 用于将单词、句子或段落转换为这些向量 Embeddings。

        VectorStore 的作用是存储并支持对这些 Embeddings 的相似性搜索,它不会生成 Embeddings 本身。因此,VectorStore 需要和 EmbeddingModel 一起使用

1.3 SearchRequest

        主要用于向量存储中的相似性搜索:

public class SearchRequest {

    public final String query;
    private int topK = 4;
    private double similarityThreshold = SIMILARITY_THRESHOLD_ALL;
    private Filter.Expression filterExpression;

    public static SearchRequest query(String query) { return new SearchRequest(query); }

    private SearchRequest(String query) { this.query = query; }

    public SearchRequest topK(int topK) {...}
    public SearchRequest similarityThreshold(double threshold) {...}
    public SearchRequest similarityThresholdAll() {...}
    public SearchRequest filterExpression(Filter.Expression expression) {...}
    public SearchRequest filterExpression(String textExpression) {...}

    public String getQuery() {...}
    public int getTopK() {...}
    public double getSimilarityThreshold() {...}
    public Filter.Expression getFilterExpression() {...}
}

        SearchRequest可以微调相似性搜索的这些参数:

  • k:一个整数,指定要返回的相似文档的最大数量。这通常被称为 “top K” 搜索或 “K最近邻”(KNN)。
  • threshold:一个值从 0 到 1 的双精度数,接近1的值表示更高的相似性。默认情况下,如果您设置了阈值为0.75,那么只有相似度超过此值的文档才会被返回。
  • Filter.Expression:一个用于传递流畅 DSL(特定域语言)表达式的类,其功能类似于 SQL 中的 “where”子句,但它仅适用于Document的元数据键值对。
  • filterExpression:基于 ANTLR4 的外部 DSL,接受字符串形式的过滤表达式。例如,对于元数据键如country、year 和isActive,您可以使用如下表达式:country == 'UK' && year >= 2020 && isActive == true

1.4 Runtime Options

前缀spring.ai.dashscope是用于配置连接至 DashScope 的属性前缀。

PropertyActionDefault
spring.ai.dashscope.api-key来自百炼平台的API KEY-
所有以 spring.ai.dashscope 开头的属性都可以在构造 DashScopeCloudStore 时传入Runtime Options来覆盖。

        DashScopeStoreOptions提供了 DashScopeCloudStore 的配置信息,它通过构造函数来创建选项。

        在构造DashScopeCloudStore时,通过将一个DashScopeStoreOptions实例传入,已完成配置。

例如,使用指定的知识库:

DashScopeCloudStore cloudStore = new DashScopeCloudStore(
        dashscopeApi, new DashScopeStoreOptions("spring-ai知识库"));

List<Document> documentList = cloudStore.similaritySearch("What's spring ai");

2.元数据过滤器

        在向量检索中,元数据过滤器(Metadata Filter) 是一种基于文档的 “非语义属性” 对检索结果进行筛选的机制。它通过预先定义的结构化元数据(如文档类型、日期、来源等)缩小检索范围,提升检索效率和相关性,与基于向量的 “语义相似性搜索” 形成互补。

2.1 Filter.Expression

        你可以使用 FilterExpressionBuilder 创建一个 Filter.Expression 实例,该 Builder 提供了 Fluent 式 API。简单示例如下:

FilterExpressionBuilder b = new FilterExpressionBuilder();
        Filter.Expression expression = b.and(
                b.in("year", 2025, 2024),
                b.eq("name", "HL")
        ).build();

2.2 示例

你可以使用以下运算符构建复杂的表达式:

EQUALS: '=='
MINUS : '-'
PLUS: '+'
GT: '>'
GE: '>='
LT: '<'
LE: '<='
NE: '!='

你可以使用以下运算符组合表达式:

AND: 'AND' | 'and' | '&&';
OR: 'OR' | 'or' | '||';

考虑如下示例:

Expression exp = b.and(b.eq("genre", "drama"), b.gte("year", 2020)).build();

你也可以使用如下操作符:

IN: 'IN' | 'in';
NIN: 'NIN' | 'nin';
NOT: 'NOT' | 'not';

考虑如下示例:

Expression exp = b.and(b.in("genre", "drama", "documentary"), b.not(b.lt("year", 2020))).build();

四、检索增强生成

        Spring AI 通过 Advisor API 为常见 RAG 流程提供开箱即用的支持。包含QuestionAnswerAdvisor 和 RetrievalAugmentationAdvisor。

        QuestionAnswerAdvisor 使用默认模板通过检索到的文档增强用户问题。若要自定义RAG 流程可使用RetrievalAugmentationAdvisor(与RAG模块化相关)。

        要使用 QuestionAnswerAdvisor 或 RetrievalAugmentationAdvisor,需添加 spring-ai-advisors-vector-store 依赖至项目:

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

1.QuestionAnswerAdvisor 

        Spring AI 通过 Advisor API 为常见 RAG 流程提供开箱即用的支持。

        向量数据库存储 AI 模型未知的数据。当用户问题发送至 AI 模型时, QuestionAnswerAdvisor 会查询向量数据库获取与用户问题相关的文档。向量数据库的响应会附加到用户文本后,为 AI 模型生成响应提供上下文。

        假设已向 VectorStore 加载数据,通过向 ChatClient 提供 QuestionAnswerAdvisor 实例即可执行检索增强生成(RAG)。

示例:

        QuestionAnswerAdvisor 将对向量数据库中的所有文档执行相似性搜索。

ChatResponse response = ChatClient.builder(chatModel)
        .build().prompt()
        .advisors(new QuestionAnswerAdvisor(vectorStore))
        .user(userText)
        .call()
        .chatResponse();

1.1 动态过滤器表达式

        为限制搜索文档类型,SearchRequest 采用类似 SQL 的过滤表达式,这种表达式可以在所有 VectorStores 中使用。

        该过滤表达式可在创建 QuestionAnswerAdvisor 时配置(此时将应用于所有 ChatClient 请求),也可在运行时按请求动态提供。

var qaAdvisor = QuestionAnswerAdvisor.builder(vectorStore)
        .searchRequest(SearchRequest.builder().similarityThreshold(0.8d).topK(6).build())
        .build();

        通过 FILTER_EXPRESSION Advisor 上下文参数在运行时更新 SearchRequest 过滤表达式:

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

2.RetrievalAugmentationAdvisor

        RAG 增强器,利用模块化 RAG 组件(Query、Pre-Retrieval、Retrieval、Post-Retrieval、Generation)为用户文本添加额外信息。

public final class RetrievalAugmentationAdvisor implements BaseAdvisor {
    public static final String DOCUMENTCONTEXT = "ragdocumentcontext";
    private final List<QueryTransformer> queryTransformers;
    @Nullable
    private final QueryExpander queryExpander;
    private final DocumentRetriever documentRetriever;
    private final DocumentJoiner documentJoiner;
    private final List<DocumentPostProcessor> documentPostProcessors;
    private final QueryAugmenter queryAugmenter;
    private final TaskExecutor taskExecutor;
    private final Scheduler scheduler;
    private final int order;

    ......

    public static Builder builder() {
        return new Builder();
    }
    ......
}

五、ETL Pipeline (管道)

        ETL(提取、转换、加载)框架构成检索增强生成(RAG)用例中数据处理的核心支柱。ETL 流水线协调从原始数据源到结构化向量存储的完整流程,确保数据最终转换为 AI 模型检索所需的最优格式。RAG(检索增强生成)用例通过从数据集中检索相关信息来增强生成模型的能力,从而提升输出内容的质量与相关性。

1.概述

ETL 流水线负责创建、转换并存储 Document 文档实例。

ETL 流水线包含三大核心组件:

  • 实现 Supplier<List<Document>> 接口的 DocumentReader 文档读取器(提供多源异构文档的标准化输入接口)。

  • 实现 Function<List<Document>, List<Document>> 接口的 DocumentTransformer 文档转换器(对批量文档进行流程化转换处理的组件)。

  • 实现 Consumer<List<Document>> 接口的 DocumentWriter 文档存储器(负责 ETL 最终阶段,将文档处理为可存储形态的输出管理器)。

构建简易 ETL 流水线时,可将各类组件的实例按流程链式组合。

2.ETL 类关系图

2.1 DocumentReader(读取文档数据接口类)

        提供多源异构文档的标准化输入接口。

package org.springframework.ai.document;

import java.util.List;
import java.util.function.Supplier;

public interface DocumentReader extends Supplier<List<Document>> {
    default List<Document> read() {
        return (List)this.get();
    }
}

        其中又包含有多种实现类,可参考官方文档具体使用:ETL 管道(Pipeline) :: Spring AI 中文文档

第六章:Rag 增强问答质量-阿里云Spring AI Alibaba官网官网

2.2 DocumentTransformer(转换文档数据接口类)

        对批量文档进行流程化转换处理的组件。

package org.springframework.ai.document;

import java.util.List;
import java.util.function.Function;

public interface DocumentTransformer extends Function<List<Document>, List<Document>> {
    default List<Document> transform(List<Document> transform) {
        return (List)this.apply(transform);
    }
}

2.3 DocumentWriter(文档写入接口类)

        负责 ETL 最终阶段,将文档处理为可存储形态的输出管理器。

package org.springframework.ai.document;

import java.util.List;
import java.util.function.Consumer;

public interface DocumentWriter extends Consumer<List<Document>> {
    default void write(List<Document> documents) {
        this.accept(documents);
    }
}

3.示例

说明:使用PagePdfDocumentReader 解析 PDF 文档(页)。

添加项目依赖:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>
private void writeToVectorStore(Resource resource) {
        // 1.创建PDF的读取器
        PagePdfDocumentReader reader = new PagePdfDocumentReader(
                resource, // 文件源
                PdfDocumentReaderConfig.builder()
                        .withPageExtractedTextFormatter(ExtractedTextFormatter.defaults())
                        .withPagesPerDocument(1) // 每1页PDF作为一个Document
                        .build()
        );
        // 2.读取PDF文档,拆分为Document
        List<Document> documents = reader.read();
        // 3.写入向量库
        weaviateVectorStore.add(documents);
    }

六、实现RAG示例

        市面上提供了许多向量数据库,这些库都实现了统一的接口:VectorStore

        SimpleVectorStore向量库是基于内存实现,操作起来方便快捷,下面我们使用SimpleVectorStore来完成一个简单的RAG示例。

        SimpleVectorStore(基于内存)类的作用:基于内存的向量存储实现类,提供了将向量数据存储到内存中,并支持将数据序列化到文件或从文件反序列化的功能。

引入依赖:

<!-- spring-ai-alibaba -->
        <dependency>
            <groupId>com.alibaba.cloud.ai</groupId>
            <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.ai</groupId>
            <artifactId>spring-ai-advisors-vector-store</artifactId>
        </dependency>

yml文件配置:

选择对应的嵌入模型:text-embedding-v4

server:
  port: 8080

spring:
  ai:
    chat:
      client:
        enabled: false

    dashscope:
      api-key: ${AI_DASHSCOPE_API_KEY}
      chat:
        options:
          model: qwen-max
      embedding:
        options:
          model: text-embedding-v4

VectorSimpleController:

package com.hl.springdemo.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.vectorstore.filter.Filter;
import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Slf4j
@Tag(name = "StoreController", description = "向量数据库")
@RestController
@RequestMapping("/store")
public class StoreController {

    private final String SAVEPATH = "src/main/resources/vectorstore.json";

    private final ChatClient chatClient;
    private final SimpleVectorStore simpleVectorStore;

    @Autowired
    public StoreController(EmbeddingModel embeddingModel, @Qualifier("dashscopeChatModel") ChatModel chatModel) {
        this.simpleVectorStore = SimpleVectorStore.builder(embeddingModel).build();
        this.chatClient = ChatClient.builder(chatModel).build();
    }

    @Operation(summary = "添加数据")
    @GetMapping("/add")
    public void add() {
        log.info("添加数据到向量数据库!");
        HashMap<String, Object> map = new HashMap<>();
        map.put("year", 2025);
        map.put("name", "HL");
        List<Document> documents = List.of(
                new Document("五年一班有只小猪叫GGBONG,它的英语六级过了!"),
                new Document("五年一班有只小猪叫FEIFEI,它是语文课代表!", Map.of("year", 2024)),
                new Document("陶渊明:“悟已往之不谏,知来者之可追。实迷途其未远,觉今是而昨非。”,此话深有感悟!", map),
                new Document("1", "test content", map));
        simpleVectorStore.add(documents);
    }

    @Operation(summary = "删除数据")
    @GetMapping("/delete")
    public void delete() {
        log.info("删除向量数据库的数据");
        simpleVectorStore.delete(List.of("1"));
    }

    @Operation(summary = "保存数据")
    @GetMapping("/save")
    public void save() throws IOException {
        log.info("将内存里数据保存到本地");
        File file = new File(SAVEPATH);
        if (file.exists()) {
            file.delete();
        }
        simpleVectorStore.save(file);
    }

    @Operation(summary = "加载数据")
    @GetMapping("/load")
    public void load() throws IOException {
        log.info("从本地加载数据到内存");
        File file = new File(SAVEPATH);
        simpleVectorStore.load(file);
    }

    @Operation(summary = "查询数据")
    @GetMapping("/search")
    public String search() {
        log.info("查询数据");
        SearchRequest searchRequest = SearchRequest
                .builder()
                .topK(2)
                .build();
        QuestionAnswerAdvisor questionAnswerAdvisor = QuestionAnswerAdvisor.builder(simpleVectorStore).searchRequest(searchRequest).build();
        return chatClient.prompt().user("GGBONG是几班的?").advisors(questionAnswerAdvisor).call().content();

    }

    @Operation(summary = "元数据过滤")
    @GetMapping("/search-filter")
    public String searchFilter() {
        log.info("元数据过滤");
        FilterExpressionBuilder b = new FilterExpressionBuilder();
        Filter.Expression expression = b.and(
                b.in("year", 2025, 2024),
                b.eq("name", "HL")
        ).build();
        SearchRequest searchRequest = SearchRequest
                .builder()
                .query("Spring")
                .topK(2)
                .filterExpression(expression).build();
        QuestionAnswerAdvisor questionAnswerAdvisor = QuestionAnswerAdvisor.builder(simpleVectorStore).searchRequest(searchRequest).build();
        return chatClient.prompt().user("深有感悟的话!").advisors(questionAnswerAdvisor).call().content();
    }
}

运行测试:

添加数据到向量数据库!

将内存里数据保存到本地:

删除 id=1 的数据,再次保存本地:

查询数据:

元数据过滤查询:

重启项目,再次查询数据:

加载本地数据到内存,再次询问:

 七、Rag 模块化

        Spring AI 实现了模块化 RAG 架构,将检索增强生成(RAG)的完整流程拆分为独立、可替换、可扩展的功能模块,每个模块通过 Spring AI 提供的接口或组件实现特定职责,并通过标准化的交互方式协同工作。

        RAG模块主要包含四个核心组件:

  1. Pre-Retrieval(检索前处理):负责处理用户查询以获得最佳检索结果。
  2. Retrieval(检索):负责查询向量数据库等数据系统并获取最相关文档。
  3. Post-Retrieval(检索后处理):负责处理检索到的文档以获得最佳生成结果。
  4. Generation(生产阶段):负责基于用户查询和检索到的文档生成最终响应。

        本人能力不够,现在只看后面要用到的Retrieval(检索):

1.DocumentRetriever(文档检索

        文档检索(DocumentRetriever)是一种信息检索技术,旨在从大量未结构化或半结构化文档中快速找到与特定查询相关的文档或信息。文档检索通常以在线(online)方式运行。

        DocumentRetriever通常基于向量搜索。它将用户的查询问题(query)转化为Embeddings后,在存储文档中进行相似性搜索,返回相关的片段。片段的用途之一是作为提示词(prompt)的一部分,发送给大模型(LLM)汇总处理后,作为答案呈现给用户。

        DocumentRetriever API提供了简单、灵活的方式,供开发者使用自定义的检索系统。

1.1 Query

用于在 RAG 流程中表示查询的类

  • String text:查询的文本内容,用户输入的核心查询语句
  • List<Message> history:当前查询相关的对话历史记录
  • Map<String, Object> context:查询的上下文信息,键值对集合,用于存储与查询相关的额外数据
public record Query(String text, List<Message> history, Map<String, Object> context) {
        ...
      public static Builder builder() {
        return new Builder();
    }

    public static final class Builder {
        private String text;
        private List<Message> history = List.of();
        private Map<String, Object> context = Map.of();

        private Builder() {
        }
    }
    ...
}

1.2 DocumentRetriever

        DocumentRetriever API简单地将用户的查询作为输入,返回文档片段(Document)的列表。

        通过retrieve方法,用户可以执行自定义的检索步骤。

public interface DocumentRetriever extends Function<String, List<Document>> {
    List<Document> retrieve(Query query);

    default List<Document> apply(Query query) {
        return this.retrieve(query);
    }
}

        Spring AI 为 DashScopeDocumentRetriever 提供了 Spring Boot 的自动配置。

public class DashScopeDocumentRetriever implements DocumentRetriever {
    private final DashScopeDocumentRetrieverOptions options;
    private final DashScopeApi dashScopeApi;

    public DashScopeDocumentRetriever(DashScopeApi dashScopeApi, DashScopeDocumentRetrieverOptions options) {
        Assert.notNull(options, "RetrieverOptions must not be null");
        Assert.notNull(options.getIndexName(), "IndexName must not be null");
        this.options = options;
        this.dashScopeApi = dashScopeApi;
    }
}

手动配置: 

    private final DocumentRetriever documentRetriever;
    @Autowired
    public RetrieverController() {
        DashScopeApi dashScopeApi = DashScopeApi.builder()
                .apiKey(System.getenv("AI_DASHSCOPE_API_KEY"))
                .build();
        DocumentRetriever retriever = new DashScopeDocumentRetriever(dashScopeApi,
                DashScopeDocumentRetrieverOptions.builder()
                        .withIndexName("关于AI的相关知识和技术")
                        .build());
        this.documentRetriever = retriever;
    }

1.2 Runtime Options

        所有以 spring.ai.dashscope 开头的属性都可以在构造DashScopeDocumentRetriever时传入Runtime Options来覆盖。

        前缀spring.ai.dashscope是用于配置连接至 DashScope 的属性前缀。

PropertyActionDefault
spring.ai.dashscope.api-key来自百炼平台的API KEY-

        DashScopeDocumentRetrieverOptions提供了DashScopeDocumentRetriever的配置信息,它通过构建器创建选项。

        在构造DashScopeDocumentRetriever时,通过将一个DashScopeDocumentRetrieverOptions实例传入,已完成配置。

例如,使用指定的知识库:

DocumentRetriever retriever = new DashScopeDocumentRetriever(dashscopeApi,
        DashScopeDocumentRetrieverOptions.builder()
                .withIndexName("spring-ai知识库")
                .build());

1.3 Document

        Document表示一个文档片段,它包含一个文本内容,以及一个或多个元数据。

@JsonIgnoreProperties({"contentFormatter"})
public class Document implements MediaContent {
    public static final ContentFormatter DEFAULT_CONTENT_FORMATTER = DefaultContentFormatter.defaultConfig();
    public static final String EMPTY_TEXT = "";
    private final String id;
    private Map<String, Object> metadata;
    private final String content;
    private final Collection<Media> media;
    @JsonProperty(
            index = 100
    )
    private float[] embedding;
    @JsonIgnore
    private ContentFormatter contentFormatter;

    @JsonCreator(
            mode = Mode.PROPERTIES
    )
    // other methods omitted
}

 2.使用DocumentRetriever检索

说明:在阿里百炼平台创建知识库,使用DocumentRetriever在知识库中检索相似内容。

controller:

@RestController
@RequestMapping("/retriever")
public class RetrieverController {
    private final DocumentRetriever documentRetriever;
    @Autowired
    public RetrieverController() {
        DashScopeApi dashScopeApi = DashScopeApi.builder()
                .apiKey(System.getenv("AI_DASHSCOPE_API_KEY"))
                .build();
        DocumentRetriever retriever = new DashScopeDocumentRetriever(dashScopeApi,
                DashScopeDocumentRetrieverOptions.builder()
                        .withIndexName("关于AI的相关知识和技术")
                        .build());
        this.documentRetriever = retriever;
    }

    @GetMapping("/retrieve")
    public String retrieve() {
        List<Document> documents = documentRetriever.retrieve(new Query("什么是MCP?"));
        return documents.toString();
    }
}

 调用接口,输出如下:

八、RAG+阿里云百炼

        阿里云百炼是一款可视化 AI 智能体应用开发平台,它提供了三种大模型应用开发模式:智能体、工作流与智能体编排,支持知识库检索、互联网搜索、工作流设计及智能体协作等功能。百炼平台上提供了 0 代码基础就能创建 RAG 应用的方案,你只需要关注私有领域知识库的维护即可使用。

1.本地集成百炼智能体应用

说明:如何使用百炼开发并发布一款简单的智能体应用,随后演示如何将一个普通的 Spring Boot 微服务应用接入智能体,让普通应用具备智能化能力。

本地集成百炼智能体应用-阿里云Spring AI Alibaba官网官网

        在百炼平台创建自己的智能体应用,我的知识库如下:

# GGBONG日记

日期:2025年6月30日 天气:晴

​	今天一顿吃了三顿饭,分别是棒棒糖、棒棒糖和棒棒糖!

日期:2025年7月1日 天气:晴

​	今天被老师批评了,超级难过!

日期:2025年7月2日 天气:多云

​	嘿嘿,今天被老师表扬了,超级开心!

日期:2025年7月3日 天气:雨

​	我想找呆呆唱歌。

        创建智能体后测试:

        我们的重点是如何使用SpringBoot+Spring AI Alibaba接入RAG应用:

        在百炼平台获取应用标识、模型apikey等信息:

spring:
  ai:
    dashscope:
      agent:
        app-id: put-your-app-id-here
      api-key: ${AI_DASHSCOPE_API_KEY}

        controller:

@RestController
@RequestMapping("/bailian")
@Tag(name = "BailianAgentRagController", description = "百炼rag")
public class BailianAgentRagController {
    @Value("${spring.ai.dashscope.agent.options.app-id}")
    private String appID;

    private DashScopeAgent agent;
    @Autowired
    public BailianAgentRagController(DashScopeAgentApi  agentApi) {
        this.agent = new DashScopeAgent(agentApi);
    }

    @Operation(summary = "百炼rag")
    @GetMapping("/agent/call")
    public String call(@RequestParam(value = "message") String message) {
        DashScopeAgentOptions options = DashScopeAgentOptions.builder()
                .withAppId(appID)
                .build();
        Prompt prompt = new Prompt(message,options);
        return agent.call(prompt).getResult().getOutput().getText();
    }
}

        接口测试:

        这样一个非常非常非常非常简单的智能体就完成了,使用Spring AI Alibaba非常简单对吧!

2.本地RAG应用集成百炼知识库

说明:首先在百炼平台平台上新建一个知识库,将自己的文档上传到知识库并完成切片、向量化存储等。随后,我们使用 Spring AI Alibaba 开发一个智能体应用,使用 RAG 模式检索百炼中的知识库。

本地RAG应用集成百炼知识库-阿里云Spring AI Alibaba官网官网

        下面的创建知识库和智能体应用都将使用Spring AI Alibaba 框架开发,不使用百炼控制台:

2.1 通过 API 创建知识库

        如果您不想手动操作百炼控制台,则可以使用 Spring AI Alibaba 中的 DocumentReader 接口实现 DashScopeDocumentCloudReaderVectorStore 实现 DashScopeCloudStore 将本地数据上传到百炼,完成数据向量化。

还是上面的数据文件:


    @GetMapping("/agent/create-knowledge")
    public String createKnowledge() {
        String path = "src/main/resources/GGBONG日记.md";
        try {
            // 1. 导入和拆分文档
            DocumentReader reader = new DashScopeDocumentCloudReader(path, dashscopeApi, null);
            List<Document> documentList = reader.get();

            // 1. 将文档添加到 DashScope 云存储
            vectorStore.add(documentList);
            return "创建成功";
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

运行接口,查看百炼平台:

2.2 创建 RAG 智能体应用

        完成知识库之后,我们就可以开发自己的 RAG 应用了。

        接下来,就是使用 Spring AI Alibaba 框架开发 RAG 应用的标准流程了。其中,为了在检索环节和百炼建立连接,我们需要指定百炼知识库检索组件 DashScopeDocumentRetriever 。

全部代码如下:


@RestController
@RequestMapping("/bailian")
public class BailianAgentRagController {
    @Value("${spring.ai.dashscope.agent.options.app-id}")
    private String appID;
    private final String indexName = "GGBONG日记";

    private final DashScopeApi dashscopeApi;
    private final DashScopeCloudStore vectorStore;
    private final DashScopeDocumentRetriever retriever;
    private final ChatClient chatClient;
    @Autowired
    public BailianAgentRagController(@Qualifier("dashscopeChatModel") ChatModel chatModel) {

        this.dashscopeApi = DashScopeApi.builder().apiKey(System.getenv("AI_DASHSCOPE_API_KEY")).build();
        this.vectorStore = new DashScopeCloudStore(dashscopeApi, new DashScopeStoreOptions(indexName));
        this.retriever = new DashScopeDocumentRetriever(dashscopeApi, DashScopeDocumentRetrieverOptions.builder().withIndexName(indexName).build());
        this.chatClient = ChatClient.builder(chatModel)
                .defaultAdvisors(new DocumentRetrievalAdvisor(retriever))
                .build();
    }

    @GetMapping("/agent/create-knowledge")
    public String createKnowledge() {
        String path = "src/main/resources/GGBONG日记.md";
        try {
            // 1. 导入和拆分文档
            DocumentReader reader = new DashScopeDocumentCloudReader(path, dashscopeApi, null);
            List<Document> documentList = reader.get();

            // 1. 将文档添加到 DashScope 云存储
            vectorStore.add(documentList);
            return "创建成功";
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @GetMapping("/agent/retrieve")
    public String retrieve(@RequestParam(value = "message") String message) {
        String content = chatClient.prompt().user(message).call().content();
        return content;
    }
}

运行接口:

### 检索增强生成模型(RAG)的工作原理 检索增强生成模型(RAG)结合了检索模型和生成模型,通过动态检索外部知识库中的信息来补充文本生成能力。其核心思想是利用检索模型从大规模数据集中获取相关上下文信息,并将这些信息传递给生成模型以生成高质量的响应[^1]。具体而言,RAG技术通过融合大语言模型(LLMs)的生成能力和外部知识库的检索功能,实现了准确、丰富且时效性强的文本输出[^2]。 在工作流程中,RAG模型首先使用检索组件对输入问题进行处理,从外部知识库中提取与问题相关的文档片段或段落。然后,生成组件基于检索到的信息以及原始输入生成最终的响应。这种设计使得RAG能够突破传统预训练语言模型的静态知识局限性,支持动态引入最新的外部信息[^2]。 --- ### RAG 的实现方法 RAG的实现通常依赖于两种主要组件:**检索模型**和**生成模型**。以下为其实现的关键步骤: #### 1. 数据准备 构建一个包含丰富知识的私有或专有数据源作为检索模型的基础。这些数据可以包括结构化数据库、非结构化文档集合或其他形式的知识库。为了提高检索效率,需要对数据进行预处理,例如分词、向量化等操作[^3]。 #### 2. 检索模型 选择或开发适合的检索模型,如基于密集表示的学习到检索(DPR, Dense Passage Retrieval)模型。该模型通过编码器将查询和文档分别映射到同一语义空间中的向量表示,从而计算相似度并选出最相关的文档片段[^3]。 #### 3. 生成模型 将检索到的相关文档片段与原始输入一起传递给生成模型(如T5、BART等),以生成最终的响应。生成模型可以根据检索到的信息调整其输出内容,确保生成结果既符合用户需求又具备高准确性[^1]。 --- ### RAG 的代码示例 以下是一个基于 LangChain 实现 RAG 的简单代码示例,展示了如何结合检索生成模型完成任务[^3]。 ```python from langchain import OpenAI, VectorDBQA from langchain.prompts import PromptTemplate from langchain.chains import RetrievalQA from langchain.vectorstores import FAISS from langchain.embeddings import OpenAIEmbeddings # 初始化嵌入模型和向量数据库 embeddings = OpenAIEmbeddings() vectorstore = FAISS.load_local("path/to/faiss_index", embeddings) # 定义提示模板 prompt_template = """Use the following pieces of context to answer the question. {context} Question: {question} Answer:""" PROMPT = PromptTemplate(template=prompt_template, input_variables=["context", "question"]) # 初始化生成模型 llm = OpenAI(temperature=0) # 构建 RAG 链 qa_chain = RetrievalQA.from_chain_type( llm=llm, chain_type="stuff", retriever=vectorstore.as_retriever(), return_source_documents=True, chain_type_kwargs={"prompt": PROMPT} ) # 示例查询 query = "什么是检索增强生成模型?" result = qa_chain({"query": query}) print(result["result"]) ``` 上述代码中,`FAISS` 是一种高效的向量数据库工具,用于存储和检索文档的嵌入表示。`OpenAIEmbeddings` 负责生成文档和查询的嵌入向量,而 `RetrievalQA` 则整合了检索生成过程[^3]。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

汤姆大聪明

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值