Spring AI ETL Pipeline Transformers 详细指南

一、API 概述

在RAG(Retrieval-Augmented Generation)系统中,ETL(Extract, Transform, Load)管道是将原始数据转换为AI模型可处理格式的核心组件。Transform阶段负责对文档进行转换,确保数据以最适合AI模型处理的格式存在。

Spring AI Alibaba 提供了强大的Transformers接口和实现,专注于文档内容的转换处理,包括文本分割和内容增强。这些转换器是构建高质量RAG系统的基石,它们将原始文档转换为结构化、可检索的格式,为后续的向量化和检索提供高质量输入。

二、ETL 接口

ETL Pipeline由以下三个核心接口组成,它们定义了数据处理的三个关键阶段:

DocumentReader

java 复制代码
public interface DocumentReader extends Supplier<List<Document>> {
    default List<Document> read() {
        return get();
    }
}

作用 :从各种数据源(文件、数据库、云存储等)读取文档内容,返回List<Document>

关键点

  • 实现Supplier<List<Document>>,提供数据源访问

  • read()方法是默认实现,调用get()方法

  • 用于数据提取阶段

DocumentTransformer

java 复制代码
public interface DocumentTransformer extends Function<List<Document>, List<Document>> {
    default List<Document> transform(List<Document> transform) {
        return apply(transform);
    }
}

作用:转换一批文档,作为处理工作流的一部分。

关键点

  • 实现Function<List<Document>, List<Document>>,输入和输出都是文档列表

  • transform()是默认实现,调用apply()方法

  • 用于数据转换阶段

DocumentWriter

java 复制代码
public interface DocumentWriter extends Consumer<List<Document>> {
    default void write(List<Document> documents) {
        accept(documents);
    }
}

作用:管理ETL过程的最后阶段,准备文档以供存储。

关键点

  • 实现Consumer<List<Document>>,仅接受文档列表

  • write()是默认实现,调用accept()方法

  • 用于数据加载阶段


三、DocumentReaders(Markdown为例子)

MarkdownDocumentReader

MarkdownDocumentReader用于处理Markdown文档,将它们转换为Document对象列表。

使用示例
java 复制代码
@Component
public class MarkdownDocumentReaderLoader {
​
    @Value("classpath:documents/psychology.md")
    private Resource resource;
​
    public List<Document> loadMarkdown() {
        // 创建配置
        MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder()
                .withHorizontalRuleCreateDocument(true)
                .withIncludeCodeBlock(false)
                .withIncludeBlockquote(false)
                .withAdditionalMetadata("filename", "psychology.md")
                .build();
​
        // 使用配置和资源创建 MarkdownDocumentReader
        MarkdownDocumentReader reader = new MarkdownDocumentReader(this.resource, config);
​
        // 读取文档并返回
        return reader.get();
    }
}
配置选项详解
配置项 说明 默认值
horizontalRuleCreateDocument 当设置为 true 时,Markdown 中的水平规则将创建新的 Document 对象 false
includeCodeBlock 当设置为 true 时,代码块将包含在与周围文本相同的 Document 中。当 false 时,代码块创建单独的 Document 对象 true
includeBlockquote 当设置为 true 时,引用块将包含在与周围文本相同的 Document 中。当 false 时,引用块创建单独的 Document 对象 true
additionalMetadata 允许向所有创建的 Document 对象添加自定义元数据
行为说明

MarkdownDocumentReader 处理 Markdown 内容并根据配置创建 Document 对象:

  1. 标题成为 Document 对象中的元数据

  2. 段落成为 Document 对象的内容

  3. 代码块可以分离到它们自己的 Document 对象中,或者与周围文本一起包含

  4. 引用块可以分离到它们自己的 Document 对象中,或者与周围文本一起包含

  5. 水平规则可用于将内容拆分为单独的 Document 对象 ​

四、Transformers(转换器)

1. TextSplitter(文本分割器)

概述

TextSplitter 是一个抽象基类,有助于将文档分割以适合 AI 模型的上下文窗口。

TokenTextSplitter(基于token的文本分割)

TokenTextSplitterTextSplitter 的实现,它使用 CL100K_BASE 编码基于 token 计数将文本拆分为块。

使用示例
java 复制代码
import org.springframework.ai.document.Document;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.stereotype.Component;
​
import java.util.List;
/*
author: atg
*/
​
​
// 文本分割器
@Component
public class MarkdownTokenTextSplitter {
    public List<Document> splitDocuments(List<Document> documents) {
        TokenTextSplitter splitter = new TokenTextSplitter();
        return splitter.apply(documents);
    }
​
    public List<Document> splitCustomized(List<Document> documents) {
        TokenTextSplitter splitter = new TokenTextSplitter(1000, 400, 10, 5000, true);
        return splitter.apply(documents);
    }
}
​
构造函数选项

TokenTextSplitter 提供两个构造函数选项:

  1. TokenTextSplitter():使用默认设置创建拆分器。

  2. TokenTextSplitter(int defaultChunkSize, int minChunkSizeChars, int minChunkLengthToEmbed, int maxNumChunks, boolean keepSeparator)

参数详解
参数 说明 默认值
defaultChunkSize 每个文本块的目标大小(以 token 为单位) 800
minChunkSizeChars 每个文本块的最小大小(以字符为单位) 350
minChunkLengthToEmbed 要包含的块的最小长度 5
maxNumChunks 从文本生成的最大块数 10000
keepSeparator 是否在块中保留分隔符(如换行符) true
行为说明

TokenTextSplitter 按以下方式处理文本内容:

  1. 使用 CL100K_BASE 编码将输入文本编码为 token

  2. 根据 defaultChunkSize 将编码的文本拆分为块

  3. 对于每个块: a. 将块解码回文本 b. 尝试在 minChunkSizeChars 之后找到合适的断点(句号、问号、感叹号或换行符) c. 如果找到断点,它会在该点截断块 d. 修剪块,并根据 keepSeparator 设置可选地删除换行符 e. 如果结果块长于 minChunkLengthToEmbed,则将其添加到输出中

  4. 此过程继续进行,直到处理完所有 token 或达到 maxNumChunks

  5. 如果剩余文本长于 minChunkLengthToEmbed,则将其作为最终块添加

最佳实践
  1. 根据模型调整defaultChunkSize

    • 对于大多数模型,800 token是合理的默认值

    • 对于更长上下文的模型(如GPT-4),可以增加到1500-2000

    • 对于资源受限的环境,可以减少到500

  2. 平衡minChunkSizeCharsdefaultChunkSize

    • minChunkSizeChars应小于defaultChunkSize

    • 例如,如果defaultChunkSize是800,可以设置minChunkSizeChars为350

  3. 使用keepSeparator

    • 保留分隔符可以保持文本的可读性

    • 对于需要保持段落结构的文档,建议设置为true

  4. 处理超长文档

    • 设置maxNumChunks防止无限分割

    • 例如,对于超长文档,可以设置为5000

  5. 针对特定文档类型调整参数

    • 技术文档:defaultChunkSize=1000, minChunkSizeChars=500

    • 长篇小说:defaultChunkSize=800, minChunkSizeChars=300

    • 会议记录:defaultChunkSize=600, minChunkSizeChars=200

2. KeywordMetadataEnricher(关键词元数据增强器)

概述

KeywordMetadataEnricher 是一个 DocumentTransformer,它使用生成式 AI 模型从文档内容中提取关键字并将它们添加为元数据。

使用示例
java 复制代码
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.document.Document;
​
import org.springframework.ai.model.transformer.KeywordMetadataEnricher;
import org.springframework.stereotype.Component;
​
import java.util.List;
​
/*
author: atg
*/
​
​
// 关键词元数据增强器
​
@Component
public class MarkdownKeywordEnricher {
    private final ChatModel dashScopeChatModel;
​
    public MarkdownKeywordEnricher(ChatModel dashScopeChatModel) {
        this.dashScopeChatModel = dashScopeChatModel;
    }
​
​
    List<Document> enrichDocuments(List<Document> documents) {
        KeywordMetadataEnricher enricher = new KeywordMetadataEnricher(this.dashScopeChatModel, 5);
​
        // Or 使用自定义模板增强器
//        KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
//                .keywordsTemplate(YOUR_CUSTOM_TEMPLATE)
//                .build();
​
        return enricher.apply(documents);
    }
}
 }
}
构造函数选项

KeywordMetadataEnricher 提供两个构造函数选项:

  1. KeywordMetadataEnricher(ChatModel chatModel, int keywordCount):使用默认模板并提取指定数量的关键字。

  2. KeywordMetadataEnricher(ChatModel chatModel, PromptTemplate keywordsTemplate):使用自定义模板进行关键字提取。

行为说明

KeywordMetadataEnricher 按以下方式处理文档:

  1. 对于每个输入文档,它使用文档的内容创建提示。

  2. 它将此提示发送到提供的 ChatModel 以生成关键字。

  3. 生成的关键字作为键 "excerpt_keywords" 添加到文档的元数据中。

  4. 返回丰富的文档。

默认模板

默认模板是:

java 复制代码
{context_str}. Give %s unique keywords for this document. Format as comma separated. Keywords:

其中 {context_str} 被文档内容替换,%s 被指定的关键字计数替换。

自定义模板
java 复制代码
PromptTemplate customTemplate = new PromptTemplate(
    "Extract 5 important keywords from the following text and separate them with commas:\n{context_str}"
);
​
KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
    .keywordsTemplate(customTemplate)
    .build();
最佳实践
  1. 关键词数量选择

    • 对于一般用途,3-5个关键词是合适的

    • 对于更详细的分类,可以使用5-10个关键词

    • 关键词数量必须为1或更大

  2. 使用自定义模板

    • 根据特定领域调整提示词

    • 例如,对于医学文档,可以自定义提示词:"Extract 5 important medical terms from the following text and separate them with commas:\n{context_str}"

  3. 性能考虑

    • 关键词提取需要调用外部AI模型,会增加处理时间

    • 对于大量文档,考虑批量处理或异步处理

  4. 错误处理

    • 确保dashScopeChatModel正常工作

    • 添加异常处理以应对AI模型调用失败

  5. 与RAG系统集成

    • 增强后的元数据可以用于更精确的检索

    • 例如,搜索"machine learning"时,包含"machine learning"关键词的文档将获得更高的相关性分数


五、结合使用示例

完整的ETL Pipeline示例

java 复制代码
import com.atg.airag.rag.loader.MarkdownDocumentReaderLoader;
import org.springframework.ai.document.Document;
import org.springframework.ai.model.transformer.KeywordMetadataEnricher;
import org.springframework.ai.reader.markdown.MarkdownDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.stereotype.Component;
​
import java.util.List;
​
/*
author: atg
time: 2026/1/3 20:21
*/
​
​
​
@Component
public class RAGPipeline {
    private final MarkdownDocumentReaderLoader markdownReader;
    private final TokenTextSplitter textSplitter;
    private final KeywordMetadataEnricher keywordEnricher;
    private final VectorStore vectorStore;
​
    public RAGPipeline(
            MarkdownDocumentReaderLoader markdownReader,
            TokenTextSplitter textSplitter,
            KeywordMetadataEnricher keywordEnricher,
            VectorStore vectorStore
    ) {
        this.markdownReader = markdownReader;
        this.textSplitter = textSplitter;
        this.keywordEnricher = keywordEnricher;
        this.vectorStore = vectorStore;
    }
​
    public void processDocument() {
        // 1. 读取Markdown文档
        List<Document> documents = markdownReader.loadMarkdown();
​
        // 2. 文本分割
        List<Document> splitDocuments = textSplitter.apply(documents);
​
        // 3. 关键词元数据增强
        List<Document> enrichedDocuments = keywordEnricher.apply(splitDocuments);
​
        // 4. 存储到向量数据库
        vectorStore.write(enrichedDocuments);
​
        System.out.println("成功处理文档: " + documents.size());
        System.out.println("分割文档数量: " + splitDocuments.size());
        System.out.println("增强文档数量: " + enrichedDocuments.size());
    }
}
​

详细工作流程

  1. 文档读取

    • 读取Markdown文件

    • 使用MarkdownDocumentReader解析为Document列表

  2. 文本分割

    • 使用TokenTextSplitter将文档分割为适合AI模型的块

    • 每个块的大小约为800 tokens

  3. 关键词增强

    • 使用KeywordMetadataEnricher为每个文档块提取关键词

    • 关键词作为"excerpt_keywords"元数据添加到文档中

  4. 向量存储

    • 将增强后的文档块存储到向量数据库

    • 为后续的RAG查询做好准备

优势与价值

  1. 提高检索质量

    • 关键词元数据使检索系统能够更精确地匹配查询

    • 例如,查询"machine learning"将优先返回包含"machine learning"关键词的文档

  2. 保持文档结构

    • 文本分割器保持了文档的语义结构

    • 保留了段落和句子边界,提高可读性

  3. 灵活的配置

    • 可以根据不同的文档类型和用途调整分割和增强参数

    • 例如,对于技术文档,可以增加关键词数量

  4. 可扩展性

    • 可以轻松添加新的Transformer(如摘要生成、情感分析等)

    • 为未来的RAG系统扩展提供基础


六、高级配置与技巧

以下是一些例子

1. 为不同文档类型定制处理流程

java 复制代码
import com.atg.airag.rag.loader.MarkdownDocumentReaderLoader;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.document.Document;
import org.springframework.ai.model.transformer.KeywordMetadataEnricher;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.List;

/*
author: atg
*/

/**
 * 为不同文档类型定制处理流程
 */

@Component
public class DocumentProcessor {

    private final MarkdownDocumentReaderLoader markdownReader;
    private final TokenTextSplitter textSplitter;
    private final KeywordMetadataEnricher keywordEnricher;
    private final VectorStore vectorStore;
    private final ChatModel dashScopeChatModel;

    public DocumentProcessor(MarkdownDocumentReaderLoader markdownReader,
                             TokenTextSplitter textSplitter,
                             KeywordMetadataEnricher keywordEnricher,
                             VectorStore vectorStore, ChatModel dashScopeChatModel) {
        this.markdownReader = markdownReader;
        this.textSplitter = textSplitter;
        this.keywordEnricher = keywordEnricher;
        this.vectorStore = vectorStore;
        this.dashScopeChatModel = dashScopeChatModel;
    }

    public void processMarkdownDocument() {
        // 为Markdown文档使用定制参数
        TokenTextSplitter customSplitter = new TokenTextSplitter(1000, 500, 10, 5000, true);
        KeywordMetadataEnricher customEnricher = new KeywordMetadataEnricher(
                this.dashScopeChatModel, 5
        );
        // 加载文档
        List<Document> documents = markdownReader.loadMarkdown();
        // 分割
        List<Document> splitDocuments = customSplitter.apply(documents);
        // 增强
        List<Document> enrichedDocuments = customEnricher.apply(splitDocuments);
        // 写入向量存储
        vectorStore.write(enrichedDocuments);
    }

    // 为技术文档使用更严格的分割
    public void processTechnicalDocument() {
        // 为技术文档使用更严格的分割
        TokenTextSplitter techSplitter = new TokenTextSplitter(800, 400, 15, 3000, false);
        KeywordMetadataEnricher techEnricher = new KeywordMetadataEnricher(
                this.dashScopeChatModel, 10
        );

        List<Document> documents = markdownReader.loadMarkdown();
        List<Document> splitDocuments = techSplitter.apply(documents);
        List<Document> enrichedDocuments = techEnricher.apply(splitDocuments);
        vectorStore.write(enrichedDocuments);
    }
}

2. 为特定领域优化关键词提取

java 复制代码
/*
author: atg
*/
// 以下是一个例子、特地为医疗文档设计的增强器
public class MedicalKeywordEnricher {
    private final ChatModel dashScopeChatModel;

    public MedicalKeywordEnricher(ChatModel dashScopeChatModel) {
        this.dashScopeChatModel = dashScopeChatModel;
    }


    public List<Document> enrichMedicalDocuments(List<Document> documents) {
        // 为医学文档定制提示词
        PromptTemplate medicalTemplate = new PromptTemplate(
                "Extract 7 important medical terms from the following text related to cardiology and heart disease:\n{context_str}"
        );

        KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
                .keywordsTemplate(medicalTemplate)
                .build();

        return enricher.apply(documents);
    }
}

七、常见问题与解决方案

1. 问题:关键词提取结果不理想

原因

  • AI模型对特定领域的理解不足

  • 提示词不够明确

解决方案

java 复制代码
// 使用更具体的自定义提示词
PromptTemplate customTemplate = new PromptTemplate(
    "Extract 5 important technical keywords from the following text related to artificial intelligence and machine learning:\n{context_str}"
);
​
KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
    .keywordsTemplate(customTemplate)
    .build();

2. 问题:文本分割导致语义断裂

原因

  • minChunkSizeChars设置过小

  • 没有在合适的语义边界处断开

解决方案

java 复制代码
// 使用更大的minChunkSizeChars
TokenTextSplitter splitter = new TokenTextSplitter(
    1000,  // defaultChunkSize
    500,   // minChunkSizeChars (增加)
    10,    // minChunkLengthToEmbed
    5000,  // maxNumChunks
    true   // keepSeparator
);

3. 问题:处理超长文档时性能低下

原因

  • 文档过长导致分割和关键词提取时间过长

解决方案

java 复制代码
// 使用更小的默认块大小
TokenTextSplitter splitter = new TokenTextSplitter(
    500,  // 降低默认块大小
    200,  // 降低最小块大小
    5,    // 保持最小长度
    1000, // 限制最大块数
    true
);

4. 问题:关键词提取重复

原因

  • AI模型生成了重复的关键词

解决方案

java 复制代码
// 使用自定义提示词,要求唯一关键词
PromptTemplate customTemplate = new PromptTemplate(
    "Extract 5 unique important keywords from the following text, separated by commas:\n{context_str}"
);

八、总结

在RAG系统中,ETL Pipeline的Transform阶段是实现高质量文档处理的关键。Spring AI Alibaba提供了强大的工具,特别是:

  1. TokenTextSplitter:基于token的文本分割,确保文档块适合AI模型处理

  2. KeywordMetadataEnricher:通过AI生成关键词,增强文档的元数据,提高检索质量

通过合理配置和使用这些工具,您可以构建出高效的RAG系统,为用户提供更准确、更相关的回答。记住,高质量的文档处理是RAG系统成功的基石

关键配置建议

  • 对于大多数场景,使用默认的TokenTextSplitter配置

  • 关键词数量设置为5个左右

  • 对于特定领域,使用自定义的KeywordMetadataEnricher模板

  • 在处理大型文档时,适当调整分割参数

最佳实践总结

  1. 从简单开始:使用默认配置开始,然后根据实际效果调整

  2. 监控处理结果:定期检查分割和增强后的文档质量

  3. 针对不同文档类型定制:不要使用单一配置处理所有文档

  4. 优化性能:考虑文档大小和系统资源,合理设置分割参数

  5. 测试与迭代:通过实际查询测试RAG系统的检索效果,持续优化配置

通过实践和调整,您可以根据自己的具体需求优化这些配置,构建出最适合您应用场景的RAG系统。这些Transformers组件是Spring AI 中非常强大的工具,它们将帮助您在RAG系统中实现高质量的文档处理。

相关推荐
Jerryhut1 小时前
Opencv总结8——停车场项目实战
人工智能·opencv·计算机视觉
WWZZ20251 小时前
SLAM进阶——数据集
人工智能·计算机视觉·机器人·大模型·slam·具身智能
、BeYourself1 小时前
PGvector :在 Spring AI 中实现向量数据库存储与相似性搜索
数据库·人工智能·spring·springai
墨_浅-1 小时前
分阶段训练金融大模型02-百度千帆实际步骤
人工智能·金融·百度云
明天好,会的1 小时前
分形生成实验(三):Rust强类型驱动的后端分步实现与编译时契约
开发语言·人工智能·后端·rust
甄心爱学习1 小时前
计算机视觉-特征提取,特征点提取与描述,图像分割
人工智能·计算机视觉
雷焰财经1 小时前
科技普惠,织就乡村智慧网:中和农信赋能农业现代化新实践
人工智能·科技
草莓熊Lotso1 小时前
Python 库使用全攻略:从标准库到第三方库(附实战案例)
运维·服务器·汇编·人工智能·经验分享·git·python
冷雨夜中漫步1 小时前
Spring Cloud入门—— (1)Spring Cloud Alibaba生态组件Nacos3.0本地部署
后端·spring·spring cloud