
本案例将引导您一步步构建一个 Spring Boot 应用,演示如何利用 Spring AI Alibaba 的 RAG (Retrieval-Augmented Generation) ETL (Extract-Transform-Load) Pipeline 功能,实现文档的读取、转换、加载和向量检索。
1. 案例目标
我们将创建一个包含三个核心功能的 Web 应用,展示完整的 RAG ETL Pipeline 流程:
- 文档读取 (Reader):提供多种格式的文档读取功能,包括文本、JSON、PDF、Markdown、HTML等。
- 文档转换 (Transformer):对读取的文档进行分块、格式化、元数据增强等处理。
- 文档写入 (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. 运行与测试
- 启动应用:运行你的 Spring Boot 主程序。
- 使用浏览器或 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应用,如智能问答、知识库检索等。