106-Spring AI Alibaba RAG ETL Pipeline 完整案例

本案例将引导您一步步构建一个 Spring Boot 应用,演示如何利用 Spring AI Alibaba 的 RAG (Retrieval-Augmented Generation) ETL (Extract-Transform-Load) Pipeline 功能,实现文档的读取、转换、加载和向量检索。

1. 案例目标

我们将创建一个包含三个核心功能的 Web 应用,展示完整的 RAG ETL Pipeline 流程:

  1. 文档读取 (Reader):提供多种格式的文档读取功能,包括文本、JSON、PDF、Markdown、HTML等。
  2. 文档转换 (Transformer):对读取的文档进行分块、格式化、元数据增强等处理。
  3. 文档写入 (Writer):将处理后的文档写入文件或向量存储,并支持相似性搜索。

2. 技术栈与核心依赖

  • Spring Boot 3.x
  • Spring AI Alibaba (用于对接阿里云 DashScope 通义大模型)
  • Spring AI Commons (提供通用的AI功能)
  • Spring AI RAG (提供RAG相关功能)
  • Spring AI Document Readers (提供多种文档读取器)
  • Maven (项目构建工具)

pom.xml 中,你需要引入以下核心依赖:

复制代码
<dependencies>
    <!-- Spring Web 用于构建 RESTful API -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
    <!-- Spring AI Alibaba 核心启动器,集成 DashScope -->
    <dependency>
        <groupId>com.alibaba.cloud.ai</groupId>
        <artifactId>spring-ai-alibaba-starter-dashscope</artifactId>
    </dependency>
    
    <!-- Spring AI 通用功能 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-commons</artifactId>
    </dependency>
    
    <!-- Spring AI RAG 功能 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-rag</artifactId>
    </dependency>
    
    <!-- Spring AI 文档读取器 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-jsoup-document-reader</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-markdown-document-reader</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-pdf-document-reader</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-tika-document-reader</artifactId>
    </dependency>
</dependencies>

3. 项目配置

src/main/resources/application.yml 文件中,配置你的 DashScope API Key 和模型设置。

复制代码
server:
  port: 8080

spring:
  application:
    name: rag-etl-pipeline

  ai:
    dashscope:
      api-key: ${AI_DASHSCOPE_API_KEY}
      base-url: https://dashscope.aliyuncs.com/
      chat:
        options:
          model: qwen-max
      embedding:
        options:
          model: text-embedding-v1

重要提示 :请将 AI_DASHSCOPE_API_KEY 环境变量设置为你从阿里云获取的有效 API Key。你也可以直接将其写在配置文件中,但这不推荐用于生产环境。

4. 准备示例数据文件

src/main/resources 目录下创建以下文件结构:

复制代码
src/main/resources/
└── data/
    ├── text.txt
    ├── text.json
    ├── text.md
    ├── google-ai-agents-whitepaper.pdf
    ├── spring-ai.html
    └── 阿里云百炼.html

4.1 data/text.txt

这是一个简单的文本文件示例,用于演示文本读取功能。

4.2 data/text.json

这是一个JSON格式文件示例,用于演示JSON读取功能。

4.3 data/text.md

这是一个Markdown格式文件示例,用于演示Markdown读取功能。

4.4 data/google-ai-agents-whitepaper.pdf

这是一个PDF文件示例,用于演示PDF读取功能。本示例使用的是Google AI Agents白皮书。

4.5 data/spring-ai.html

这是一个HTML文件示例,用于演示HTML读取功能。

4.6 data/阿里云百炼.html

这是一个中文HTML文件示例,用于演示HTML读取功能。

5. 编写 Java 代码

5.1 Constant.java

定义项目中使用的常量,主要是各种数据文件的路径。

复制代码
package com.alibaba.cloud.ai.example.rag.model;


public class Constant {

    public static final String PREFIX = "classpath:data/";

    public static final String TEXT_FILE_PATH = PREFIX + "/text.txt";

    public static final String JSON_FILE_PATH = PREFIX + "/text.json";

    public static final String MARKDOWN_FILE_PATH = PREFIX + "/text.md";

    public static final String PDF_FILE_PATH = PREFIX + "/google-ai-agents-whitepaper.pdf";

    public static final String HTML_FILE_PATH = PREFIX + "/spring-ai.html";
}

5.2 ReaderController.java

实现文档读取功能,支持多种格式的文档读取。

复制代码
package com.alibaba.cloud.ai.example.rag.controller;

import com.alibaba.cloud.ai.example.rag.model.Constant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.JsonReader;
import org.springframework.ai.reader.TextReader;
import org.springframework.ai.reader.jsoup.JsoupDocumentReader;
import org.springframework.ai.reader.markdown.MarkdownDocumentReader;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.reader.pdf.ParagraphPdfDocumentReader;
import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/reader")
public class ReaderController {

    private static final Logger logger = LoggerFactory.getLogger(ReaderController.class);

    @GetMapping("/text")
    public List<Document> readText() {
        logger.info("start read text file");
        Resource resource = new DefaultResourceLoader().getResource(Constant.TEXT_FILE_PATH);
        TextReader textReader = new TextReader(resource); // 适用于文本数据
        return textReader.read();
    }

    @GetMapping("/json")
    public List<Document> readJson() {
        logger.info("start read json file");
        Resource resource = new DefaultResourceLoader().getResource(Constant.JSON_FILE_PATH);
        JsonReader jsonReader = new JsonReader(resource); // 只可以传json格式文件
        return jsonReader.read();
    }

    @GetMapping("/pdf-page")
    public List<Document> readPdfPage() {
        logger.info("start read pdf file by page");
        Resource resource = new DefaultResourceLoader().getResource(Constant.PDF_FILE_PATH);
        PagePdfDocumentReader pagePdfDocumentReader = new PagePdfDocumentReader(resource); // 只可以传pdf格式文件
        return pagePdfDocumentReader.read();
    }

    @GetMapping("/pdf-paragraph")
    public List<Document> readPdfParagraph() {
        logger.info("start read pdf file by paragraph");
        Resource resource = new DefaultResourceLoader().getResource(Constant.PDF_FILE_PATH);
        ParagraphPdfDocumentReader paragraphPdfDocumentReader = new ParagraphPdfDocumentReader(resource); // 有目录的pdf文件
        return paragraphPdfDocumentReader.read();
    }

    @GetMapping("/markdown")
    public List<Document> readMarkdown() {
        logger.info("start read markdown file");
        MarkdownDocumentReader markdownDocumentReader = new MarkdownDocumentReader(Constant.MARKDOWN_FILE_PATH); // 只可以传markdown格式文件
        return markdownDocumentReader.read();
    }

    @GetMapping("/html")
    public List<Document> readHtml() {
        logger.info("start read html file");
        Resource resource = new DefaultResourceLoader().getResource(Constant.HTML_FILE_PATH);
        JsoupDocumentReader jsoupDocumentReader = new JsoupDocumentReader(resource); // 只可以传html格式文件
        return jsoupDocumentReader.read();
    }

    @GetMapping("/tika")
    public List<Document> readTika() {
        logger.info("start read file with Tika");
        Resource resource = new DefaultResourceLoader().getResource(Constant.HTML_FILE_PATH);
        TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(resource); // 可以传多种文档格式
        return tikaDocumentReader.read();
    }
}

5.3 TransformerController.java

实现文档转换功能,包括文本分块、格式化、元数据增强等。

复制代码
package com.alibaba.cloud.ai.example.rag.controller;

import com.alibaba.cloud.ai.example.rag.model.Constant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.document.DefaultContentFormatter;
import org.springframework.ai.document.Document;
import org.springframework.ai.model.transformer.KeywordMetadataEnricher;
import org.springframework.ai.model.transformer.SummaryMetadataEnricher;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.transformer.ContentFormatTransformer;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@RequestMapping("/transformer")
public class TransformerController {

    private static final Logger logger = LoggerFactory.getLogger(TransformerController.class);

    private final List<Document> documents;
    private final ChatModel chatModel;

    public TransformerController(ChatModel chatModel) {
        logger.info("start read pdf file by page");
        Resource resource = new DefaultResourceLoader().getResource(Constant.PDF_FILE_PATH);
        PagePdfDocumentReader pagePdfDocumentReader = new PagePdfDocumentReader(resource); // 只可以传pdf格式文件
        this.documents = pagePdfDocumentReader.read();

        this.chatModel = chatModel;
    }

    @GetMapping("/token-text-splitter")
    public List<Document> tokenTextSplitter() {
        logger.info("start token text splitter");
        TokenTextSplitter tokenTextSplitter = TokenTextSplitter.builder()
                // 每个文本块的目标token数量
                .withChunkSize(800)
                // 每个文本块的最小字符数
                .withMinChunkSizeChars(350)
                // 丢弃小于此长度的文本块
                .withMinChunkLengthToEmbed(5)
                // 文本中生成的最大块数
                .withMaxNumChunks(10000)
                // 是否保留分隔符
                .withKeepSeparator(true)
                .build();
        return tokenTextSplitter.split(this.documents);
    }

    @GetMapping("/content-format-transformer")
    public List<Document> contentFormatTransformer() {
        logger.info("start content format transformer");
        DefaultContentFormatter defaultContentFormatter = DefaultContentFormatter.defaultConfig();

        ContentFormatTransformer contentFormatTransformer = new ContentFormatTransformer(defaultContentFormatter);

        return contentFormatTransformer.apply(this.documents);
    }

    @GetMapping("/keyword-metadata-enricher")
    public List<Document> keywordMetadataEnricher() {
        logger.info("start keyword metadata enricher");
        KeywordMetadataEnricher keywordMetadataEnricher = new KeywordMetadataEnricher(this.chatModel, 3);
        return keywordMetadataEnricher.apply(this.documents);
    }

    @GetMapping("/summary-metadata-enricher")
    public List<Document> summaryMetadataEnricher() {
        logger.info("start summary metadata enricher");
        List<SummaryMetadataEnricher.SummaryType> summaryTypes = List.of(
                SummaryMetadataEnricher.SummaryType.NEXT,
                SummaryMetadataEnricher.SummaryType.CURRENT,
                SummaryMetadataEnricher.SummaryType.PREVIOUS);
        SummaryMetadataEnricher summaryMetadataEnricher = new SummaryMetadataEnricher(this.chatModel, summaryTypes);

        return summaryMetadataEnricher.apply(this.documents);
    }
}

5.4 WriterController.java

实现文档写入功能,包括文件写入和向量存储写入,以及相似性搜索。

复制代码
package com.alibaba.cloud.ai.example.rag.controller;

import com.alibaba.cloud.ai.example.rag.model.Constant;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.SimpleVectorStore;
import org.springframework.ai.writer.FileDocumentWriter;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;


@RestController
@RequestMapping("/writer")
public class WriterController {

    private static final Logger logger = LoggerFactory.getLogger(WriterController.class);

    private final List<Document> documents;

    private final SimpleVectorStore simpleVectorStore;

    public WriterController(EmbeddingModel embeddingModel) {
        logger.info("start read pdf file by page");
        Resource resource = new DefaultResourceLoader().getResource(Constant.PDF_FILE_PATH);
        PagePdfDocumentReader pagePdfDocumentReader = new PagePdfDocumentReader(resource); // 只可以传pdf格式文件
        this.documents = pagePdfDocumentReader.read();

        this.simpleVectorStore = SimpleVectorStore
                .builder(embeddingModel).build();
    }

    @GetMapping("/file")
    public void writeFile() {
        logger.info("Writing file...");
        String fileName = "output.txt";
        FileDocumentWriter fileDocumentWriter = new FileDocumentWriter(fileName, true);
        fileDocumentWriter.accept(this.documents);
    }

    @GetMapping("/vector")
    public void writeVector() {
        logger.info("Writing vector...");
        simpleVectorStore.add(documents);
    }

    @GetMapping("/search")
    public List<Document> search() {
        logger.info("start search data");
        return simpleVectorStore.similaritySearch(SearchRequest
                .builder()
                .query("Spring")
                .topK(2)
                .build());
    }
}

5.5 RagEtlPipelineApplication.java

应用程序的主入口类。

复制代码
package com.alibaba.cloud.ai.example.rag;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;


@SpringBootApplication
public class RagEtlPipelineApplication {

    public static void main(String[] args) {
        SpringApplication.run(RagEtlPipelineApplication.class, args);
    }
}

6. 运行与测试

  1. 启动应用:运行你的 Spring Boot 主程序。
  2. 使用浏览器或 API 工具(如 Postman, curl)进行测试

测试 1:文档读取功能

访问以下 URL,测试不同格式的文档读取功能。

  • 文本文件:http://localhost:8080/reader/text
  • JSON文件:http://localhost:8080/reader/json
  • PDF文件(按页):http://localhost:8080/reader/pdf-page
  • PDF文件(按段落):http://localhost:8080/reader/pdf-paragraph
  • Markdown文件:http://localhost:8080/reader/markdown
  • HTML文件:http://localhost:8080/reader/html
  • Tika通用读取器:http://localhost:8080/reader/tika

测试 2:文档转换功能

访问以下 URL,测试文档转换功能。

  • 文本分块:http://localhost:8080/transformer/token-text-splitter
  • 内容格式化:http://localhost:8080/transformer/content-format-transformer
  • 关键词元数据增强:http://localhost:8080/transformer/keyword-metadata-enricher
  • 摘要元数据增强:http://localhost:8080/transformer/summary-metadata-enricher

测试 3:文档写入功能

访问以下 URL,测试文档写入功能。

  • 写入文件:http://localhost:8080/writer/file
  • 写入向量存储:http://localhost:8080/writer/vector
  • 相似性搜索:http://localhost:8080/writer/search

7. 实现思路与扩展建议

实现思路

本案例的核心思想是**"RAG ETL Pipeline"**,即通过提取(Extract)、转换(Transform)、加载(Load)三个步骤,构建一个完整的RAG流程。这使得:

  • 模块化设计:将文档处理流程分为读取、转换、写入三个独立模块,每个模块职责明确,便于维护和扩展。
  • 多格式支持:支持多种文档格式的读取和处理,满足不同场景的需求。
  • 灵活的转换策略:提供多种文档转换策略,如分块、格式化、元数据增强等,可根据具体需求选择。
  • 向量存储集成:将处理后的文档写入向量存储,为后续的相似性搜索和RAG应用提供支持。

扩展建议

  • 更多文档格式支持:可以添加更多文档格式的读取器,如Word、Excel、PPT等。
  • 自定义转换器:根据业务需求,实现自定义的文档转换器,如特定领域的文本处理、实体识别等。
  • 高级向量存储:将SimpleVectorStore替换为更专业的向量数据库,如Milvus、Chroma等,提供更好的性能和扩展性。
  • 流式处理:对于大型文档集合,可以实现流式处理,避免内存溢出问题。
  • 分布式处理:对于大规模文档处理,可以考虑使用分布式计算框架,如Spark、Flink等。
  • 完整的RAG应用:基于本案例构建的ETL Pipeline,可以进一步开发完整的RAG应用,如智能问答、知识库检索等。
相关推荐
一碗绿豆汤4 小时前
机器学习第一阶段
人工智能·笔记·机器学习
没有bug.的程序员4 小时前
Spring Boot 常见性能与配置优化
java·spring boot·后端·spring·动态代理
与开发同行4 小时前
大语言模型是如何听懂并会说人话的
人工智能
没有bug.的程序员4 小时前
Spring Boot Actuator 监控机制解析
java·前端·spring boot·spring·源码
倔强青铜三4 小时前
苦练Python第71天:一行代码就搭出服务器?别眨眼,http.server真有这么爽!
人工智能·python·面试
倔强青铜三4 小时前
苦练Python第70天:征服网络请求!揭开urllib.request的神秘面纱
人工智能·python·面试
倔强青铜三4 小时前
苦练Python第72天:colorsys 模块 10 分钟入门,让你的代码瞬间“好色”!
人工智能·python·面试
MicroTech20254 小时前
MLGO微算法科技发布多用户协同推理批处理优化系统,重构AI推理服务效率与能耗新标准
人工智能·科技·算法
说私域4 小时前
互联网企业外化能力与实体零售融合:基于定制开发开源AI智能名片S2B2C商城小程序的实践探索
人工智能·开源·零售