深入RAG:从理论到实践的 ETL 核心流程

0 阅读8分钟

在上一节教程中,我们初步体验了如何基于 Spring AI 快速搭建一个 RAG(检索增强生成)应用。然而,一个生产级别的 RAG 系统远不止简单的“文档 -> 向量 -> 检索”流程。在本节中,我们将深入 Spring AI 的底层,学习 RAG 知识库应用开发的核心特性和高级知识点,带你掌握构建高效、精准 RAG 应用的最佳实践和调优技巧

我们将重点拆解 RAG 流程中的关键环节,并以 Spring AI 框架为例,详细讲解其核心组件。

一、重温 RAG 工作流程

首先,让我们回顾一下 RAG 应用的标准工作流程,它主要分为两个阶段:

  1. 建立索引:将原始知识库文档进行预处理、切割、向量化,并存入向量数据库。
  2. 检索与生成:根据用户问题,在向量数据库中进行相似性搜索,找到最相关的文档片段,最后将它们与问题一起提交给大模型(LLM)生成答案。

这个流程在 Spring AI 中得到了完整的支持和抽象。

二、RAG 核心流程的工程化实现

在 Spring AI 中,对文档的处理遵循经典的 ETL(抽取、转换、加载)  模式。这是整个RAG应用的基石。

1. 文档 (Document)

首先,我们需要理解 Spring AI 中的核心数据模型——Document。它不仅仅是一个文本块,还可以包含丰富的元数据(Metadata,如来源、标题、作者等)和多媒体附件。这使得文档在检索时可以携带更多上下文信息,从而提高检索的精准度。

2. 抽取 (Extract) - DocumentReader

这是 ETL 的第一步,目标是从各种数据源加载原始文档,并将其转换为 Spring AI 的 Document 对象。

Spring AI 提供了丰富的 DocumentReader 实现类,方便处理不同类型的数据源:

  • JsonReader:读取 JSON 文档。

  • TextReader:读取纯文本文件。

  • MarkdownReader:读取 Markdown 文件。

  • PDFReader:读取 PDF 文档。Spring AI 基于 Apache PdfBox 库提供了更细粒度的解析:

    • PagePdfDocumentReader:按页读取 PDF。
    • ParagraphPdfDocumentReader:按段落读取 PDF。
  • HtmlReader:读取 HTML 文档,基于 jsoup 库实现。

  • TikaDocumentReader:基于 Apache Tika 库,可以处理多种格式的文档,功能强大且灵活。

核心源码
DocumentReader 接口非常简单,它实现了 Supplier<List<Document>> 接口,其核心方法 read() 负责返回一个 Document 列表。

java

复制下载

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

此外,Spring AI Alibaba 社区还提供了更多实用的 DocumentReader,例如加载飞书文档、提取 B 站视频信息、加载邮件等,极大地扩展了数据源的多样性。

3. 转换 (Transform) - DocumentTransformer

抽取后的文档往往需要进一步处理,才能用于向量化。这个阶段是保证 RAG 效果的核心步骤,特别是如何将大文档合理拆分为便于检索的知识碎片(即文本切分)。

Spring AI 通过 DocumentTransformer 组件实现这一功能,它负责将一组 Document 转换为另一组 Document。常见的转换操作包括:

  • 文本分割器 (TextSplitter) :将长文档切分成更小的、语义完整的块。TokenTextSplitter 是其核心实现,它基于 Token 数量进行分割,并会尝试在句子边界(语义边界)处进行切分,以创建有意义的文本段落。这是一种成本较低且效果不错的文本切分方式。

  • 元数据增强器 (MetadataEnricher) :为文档补充更多的元信息,以便在后续检索中提供更丰富的过滤条件。例如:

    • KeywordMetadataEnricher:使用 AI 模型提取文档关键词并添加到元数据中。
    • SummaryMetadataEnricher:使用 AI 模型为文档生成摘要并添加到元数据。高级用法:它不仅可以为当前文档生成摘要,还能关联前一个和后一个相邻的文档,从而联系上下文,生成更完整的摘要。
  • 内容格式化工具 (ContentFormatter) :用于统一文档的格式,例如进行格式化、元数据过滤或使用自定义模板。

4. 加载 (Load) - DocumentWriter

ETL 的最后一步是将处理好的文档写入到最终的存储中,以便后续检索。DocumentWriter 组件负责这一过程。

Spring AI 提供了两种内置的 DocumentWriter 实现:

  • FileDocumentWriter:将文档写入文件系统。
  • VectorStoreWriter:将文档写入向量数据库(这也是 RAG 应用中最常用的方式)。

5. ETL 流程实例

通过组合上述三大组件,我们可以轻松实现一个完整的 ETL 流程:

// 抽取:从PDF文件中抽取文档
PDFReader pdfReader = new PagePdfDocumentReader("knowledge_base.pdf");
List<Document> documents = pdfReader.read();

// 转换:分割文本并添加摘要
TokenTextSplitter splitter = new TokenTextSplitter(500, 50);
List<Document> splitDocuments = splitter.apply(documents);

SummaryMetadataEnricher enricher = new SummaryMetadataEnricher(chatModel, 
    List.of(SummaryType.CURRENT));
List<Document> enrichedDocuments = enricher.apply(splitDocuments);

// 加载:将文档写入到向量数据库中
vectorStore.write(enrichedDocuments);

// 或者使用更优雅的链式调用
vectorStore.write(
    enricher.apply(
        splitter.apply(
            pdfReader.read()
        )
    )
);

三、向量转换与存储 - VectorStore

经过 ETL 处理的文档,最终需要被转换为向量并存储起来。VectorStore 是 Spring AI 中用于与向量数据库交互的核心接口,它继承自 DocumentWriter,定义了向量存储的基本 CRUD 操作,包括添加、删除和相似性搜索。

工作原理

  1. 嵌入转换:当通过 VectorStore.add() 添加文档时,Spring AI 会自动调用配置好的嵌入模型(如 OpenAI 的 text-embedding-ada-002)将文档内容转换为向量表示。
  2. 相似度计算:当执行搜索时,用户的查询文本同样会被转换为向量,然后通过 SearchRequest 对象构建搜索请求,并在向量数据库中执行相似性搜索(而非精确匹配)。
  3. 相似度度量:常用的相似度计算方法包括余弦相似度、欧氏距离和点积。
  4. 过滤与排序:系统根据相似度阈值过滤结果,并按相似度排序,返回最相关的文档。

Spring AI 支持多种向量数据库,如 PGVector、Chroma、Weaviate 等。以 PGVector 为例,整合步骤非常简单:

  1. 准备一个安装了 vector 插件的 PostgreSQL 数据库。
  2. 引入 spring-ai-starter-vector-store-pgvector 依赖。
  3. 在配置文件中设置数据库连接和向量存储参数(如索引类型、向量维度、距离类型等)。
  4. 在代码中自动注入 VectorStore 对象,即可开始使用。

四、文档过滤与检索 - SearchRequest

当向量数据库中存储了大量文档后,如何高效地检索到最相关的信息?这不仅仅是简单的相似度搜索,还涉及到过滤排序

Spring AI 通过 SearchRequest 类来构建复杂的相似度搜索请求:

List<Document> results = vectorStore.similaritySearch(
    SearchRequest.builder()
        .query("Spring AI 如何实现 RAG?")
        .topK(5)  // 返回前5个最相关的结果
        .similarityThreshold(0.7)  // 相似度阈值,低于0.7的结果将被过滤掉
        .filterExpression("category == 'tutorial'")  // 元数据过滤表达式
        .build()
);

核心特性

  • topK:控制返回结果的数量,K 值越大,召回的可能越多,但噪音也可能增加。
  • similarityThreshold:相似度阈值,只返回相似度高于此值的结果,有效过滤低质量匹配。
  • filterExpression:基于元数据的过滤表达式,例如只检索特定分类、时间范围或来源的文档。这是提高检索精准度的重要手段。

五、查询增强与关联 - 上下文查询增强器

这是 RAG 进阶的关键环节。用户的原始查询往往过于简单或缺乏上下文,直接用于检索可能效果不佳。查询增强旨在通过多种策略优化查询,提高检索质量。

Spring AI 提供了多种 QueryTransformer 实现来增强查询:

1. 查询扩展 (Query Expansion)

将用户的原始查询扩展为多个相关查询,提高召回率。

java

复制下载

// 使用 AI 模型将 "如何实现RAG?" 扩展为多个相关查询
QueryExpander expander = new QueryExpander(chatModel);
List<String> expandedQueries = expander.expand("如何实现RAG?");
// 可能得到: ["Spring AI RAG 实现步骤", "RAG 架构设计", "Spring AI 向量存储配置"]

2. 上下文关联 (Contextual Augmentation)

将对话历史或用户上下文信息融入当前查询,适用于多轮对话场景。

java

复制下载

// 将上一轮的对话内容作为上下文增强当前查询
ContextualQueryAugmenter augmenter = new ContextualQueryAugmenter(chatHistory);
String enhancedQuery = augmenter.augment("那具体怎么配置?");
// 增强后: "基于刚才提到的 Spring AI,如何具体配置 RAG?"

3. HyDE (Hypothetical Document Embeddings)

一种高级检索策略:先生成一个假设性的答案文档,再将该假设答案用于检索。这种方法能显著提升检索的语义匹配度。

// HyDE 策略:先用 LLM 生成一个假设答案,再用这个答案去检索
HydeQueryTransformer hyde = new HydeQueryTransformer(chatModel);
String hypotheticalAnswer = hyde.generateHypotheticalAnswer(query);
List<Document> results = vectorStore.similaritySearch(hypotheticalAnswer);

结语

通过深入理解 Spring AI 的 ETL 流程和 VectorStore 的机制,我们可以清晰地看到,一个健壮的 RAG 应用不仅仅是简单地调用几个 API。从文档的抽取、精细化的文本转换,到高效的向量存储和检索,每一个环节都至关重要。掌握这些核心组件,将为你后续优化 RAG 应用的性能、提升答案质量打下坚实的基础。

源代码

AI超级智能体