ETL 管道(Pipeline)
本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。 |
ETL(提取、转换、加载)框架构成检索增强生成(RAG)用例中数据处理的核心支柱。
ETL 流水线协调从原始数据源到结构化向量存储的完整流程,确保数据最终转换为 AI 模型检索所需的最优格式。
RAG(检索增强生成)用例通过从数据集中检索相关信息来增强生成模型的能力,从而提升输出内容的质量与相关性。
API 概览
ETL 流水线负责创建、转换并存储 Document
文档实例。

Document
类包含文本内容、元数据,以及可选的图像/音频/视频等多媒体附件。
ETL 流水线包含三大核心组件:
-
实现
Supplier<List<Document>>
接口的DocumentReader
文档读取器 -
实现
Function<List<Document>, List<Document>>
接口的DocumentTransformer
文档转换器 -
实现
Consumer<List<Document>>
接口的DocumentWriter
文档存储器
通过 DocumentReader
组件,可从 PDF、文本文件等各类文档中提取内容构建 Document
对象。
构建简易 ETL 流水线时,可将各类组件的实例按流程链式组合。

假设我们已拥有以下三类 ETL 组件的实例:
-
PDF 分页文档读取器
PagePdfDocumentReader
(`DocumentReader`的实现类) -
文本分块处理器
TokenTextSplitter
(DocumentTransformer
的实现类) -
向量存储器
VectorStore
(DocumentWriter
的实现类)
以下 Java 函数式语法代码演示了如何将数据加载至向量数据库,以支持检索增强生成模式的基础实现:
vectorStore.accept(tokenTextSplitter.apply(pdfReader.get()));
亦可采用领域驱动设计中更符合语义的命名方式实现相同功能:
vectorStore.write(tokenTextSplitter.split(pdfReader.read()));
ETL 接口
ETL 流水线由以下接口与实现类构成,详细类图参见 ETL 类关系图 章节。
DocumentReader
提供多源异构文档的标准化输入接口。
public interface DocumentReader extends Supplier<List<Document>> {
default List<Document> read() {
return get();
}
}
DocumentTransformer
对批量文档进行流程化转换处理的组件。
public interface DocumentTransformer extends Function<List<Document>, List<Document>> {
default List<Document> transform(List<Document> transform) {
return apply(transform);
}
}
DocumentReader
JSON
JsonReader
负责解析 JSON 文档,将其转换为 Document
对象列表。
Example
@Component
class MyJsonReader {
private final Resource resource;
MyJsonReader(@Value("classpath:bikes.json") Resource resource) {
this.resource = resource;
}
List<Document> loadJsonAsDocuments() {
JsonReader jsonReader = new JsonReader(this.resource, "description", "content");
return jsonReader.get();
}
}
构造函数选项
JsonReader
提供了多个构造函数选项:
-
JsonReader(Resource resource)
-
JsonReader(Resource resource, String… jsonKeysToUse)
-
JsonReader(Resource resource, JsonMetadataGenerator jsonMetadataGenerator, String… jsonKeysToUse)
参数
-
resource
:指向 JSON 文件的 SpringResource
对象。 -
jsonKeysToUse
:指定 JSON 文档中哪些 Key 对应的值应转换为Document
对象的文本内容。 -
jsonMetadataGenerator
:可选的JsonMetadataGenerator
接口实现,用于为每个Document
生成元数据。
行为
JsonReader
处理 JSON 内容的逻辑如下:
-
该组件既能处理 JSON 数组,也能解析单个 JSON 对象。
-
针对每个 JSON 对象(无论来自数组或独立对象):
-
根据
jsonKeysToUse
配置提取目标 Key 值作为内容。 -
若未指定键 Key,则将整个JSON对象作为内容处理。
-
通过配置的
JsonMetadataGenerator
生成元数据(未配置时使用空元数据)。 -
最终组合提取内容和元数据构建
Document
对象。
-
采用 JSON 指针规范
JsonReader
现已支持通过 JSON 指针提取文档特定部分,该特性可便捷地从复杂 JSON 结构中获取嵌套数据。
示例 JSON
[
{
"id": 1,
"brand": "Trek",
"description": "A high-performance mountain bike for trail riding."
},
{
"id": 2,
"brand": "Cannondale",
"description": "An aerodynamic road bike for racing enthusiasts."
}
]
本示例(一组自行车信息)中,若配置 JsonReader
的 jsonKeysToUse
为 "description",则会为数组中每辆自行车创建 Document
对象,其内容即为对应 "description" 字段的值。
文本
TextReader
负责解析纯文本文档,将其转换为 Document
对象列表。
示例
@Component
class MyTextReader {
private final Resource resource;
MyTextReader(@Value("classpath:text-source.txt") Resource resource) {
this.resource = resource;
}
List<Document> loadText() {
TextReader textReader = new TextReader(this.resource);
textReader.getCustomMetadata().put("filename", "text-source.txt");
return textReader.read();
}
}
配置
-
setCharset(Charset charset)
:设置文本文件的字符编码集,默认采用 UTF-8。 -
getCustomMetadata()
:返回可变的元数据 Map,用于为文档添加自定义元数据。
行为
TextReader
按以下流程处理文本内容:
-
将文本文件的全部内容读取到单个
Document
对象中。 -
文件内容直接作为
Document
对象的内容体。 -
自动为
Document
添加以下元数据:-
charset
:文件读取使用的字符集(默认:"UTF-8")。 -
source
:源文本文件的文件名。
-
-
通过
getCustomMetadata()
添加的自定义元数据将包含在Document
中。
注意
-
TextReader
会将整个文件内容读入内存,因此可能不适用于处理超大文件。 -
如需将文本分割为更小的片段,可在读取文档后使用
TokenTextSplitter
等文本分割器进行处理。
List<Document> documents = textReader.get();
List<Document> splitDocuments = new TokenTextSplitter().apply(this.documents);
-
该 Reader 采用 Spring 的
Resource
抽象机制,支持从多种来源(类路径、文件系统、URL 等)读取内容。 -
通过
getCustomMetadata()
方法可为该 Reader 生成的所有文档添加自定义元数据。
HTML(JSoup)
JsoupDocumentReader
利用 JSoup 库处理 HTML 文档,将其转换为 Document
对象列表。
示例
@Component
class MyHtmlReader {
private final Resource resource;
MyHtmlReader(@Value("classpath:/my-page.html") Resource resource) {
this.resource = resource;
}
List<Document> loadHtml() {
JsoupDocumentReaderConfig config = JsoupDocumentReaderConfig.builder()
.selector("article p") // Extract paragraphs within <article> tags
.charset("ISO-8859-1") // Use ISO-8859-1 encoding
.includeLinkUrls(true) // Include link URLs in metadata
.metadataTags(List.of("author", "date")) // Extract author and date meta tags
.additionalMetadata("source", "my-page.html") // Add custom metadata
.build();
JsoupDocumentReader reader = new JsoupDocumentReader(this.resource, config);
return reader.get();
}
}
JsoupDocumentReaderConfig
支持对 JsoupDocumentReader
的行为进行定制:
-
charset
:指定 HTML 文档的字符编码(默认为 "UTF-8")。 -
selector
:JSoup CSS 选择器,指定提取文本的 HTML 元素(默认为 "body")。 -
separator
:用于连接多个选中元素文本的分隔符(默认为 "\n")。 -
allElements
:设为true
时忽略selector
,提取<body>
元素内全部文本(默认为false
)。 -
groupByElement
:设为true
时为每个selector
匹配的元素创建独立Document
(默认为false
)。 -
includeLinkUrls
:设为true
时提取绝对链接 URL 并添加到元数据(默认为false
)。 -
metadataTags
:指定要提取内容的<meta>
标签名称列表(默认为["description", "keywords"]
)。 -
additionalMetadata
:允许为所有生成的Document
对象添加自定义元数据。
示例文档:my-page.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>My Web Page</title>
<meta name="description" content="A sample web page for Spring AI">
<meta name="keywords" content="spring, ai, html, example">
<meta name="author" content="John Doe">
<meta name="date" content="2024-01-15">
<link rel="stylesheet" href="style.css">
</head>
<body>
<header>
<h1>Welcome to My Page</h1>
</header>
<nav>
<ul>
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
</ul>
</nav>
<article>
<h2>Main Content</h2>
<p>This is the main content of my web page.</p>
<p>It contains multiple paragraphs.</p>
<a href="https://www.example.com">External Link</a>
</article>
<footer>
<p>© 2024 John Doe</p>
</footer>
</body>
</html>
行为:
JsoupDocumentReader
根据配置将 HTML 内容处理为 Document
对象:
-
selector
决定提取文本的目标 HTML 元素。 -
若
allElements
为true
,则提取<body>
内全部文本至单个Document
。 -
若
groupByElement
为true
,则每个匹配selector
的元素生成独立Document
。 -
若
allElements
和groupByElement
均为false
,则使用separator
拼接所有匹配selector
元素的文本。 -
文档标题、指定 · 标签内容及(可选)链接 URL 将添加到
Document
元数据中。 -
用于解析相对链接的基准 URI 将从 URL 资源中自动提取。
该 Reader 保留选中元素的文本内容,但会移除其中的所有 HTML 标签。
Markdown
MarkdownDocumentReader
负责解析 Markdown
文档,将其转换为 Document
对象列表。
示例
@Component
class MyMarkdownReader {
private final Resource resource;
MyMarkdownReader(@Value("classpath:code.md") Resource resource) {
this.resource = resource;
}
List<Document> loadMarkdown() {
MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder()
.withHorizontalRuleCreateDocument(true)
.withIncludeCodeBlock(false)
.withIncludeBlockquote(false)
.withAdditionalMetadata("filename", "code.md")
.build();
MarkdownDocumentReader reader = new MarkdownDocumentReader(this.resource, config);
return reader.get();
}
}
MarkdownDocumentReaderConfig
支持对 MarkdownDocumentReader
的行为进行定制:
-
horizontalRuleCreateDocument
:设为true
时,Markdown
中的水平分隔符将生成新Document
对象。 -
includeCodeBlock
:设为true
时,代码块将与周边文本合并到同一Document
;设为false
时,代码块生成独立Document
对象。 -
includeBlockquote
:设为true
时,引用块将与周边文本合并到同一Document
;设为false
时,引用块生成独立Document
对象。 -
additionalMetadata
:允许为所有生成的Document
对象添加自定义元数据。
示例文档:code.md
This is a Java sample application:
```java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
```
Markdown also provides the possibility to `use inline code formatting throughout` the entire sentence.
---
Another possibility is to set block code without specific highlighting:
```
./mvnw spring-javaformat:apply
```
MarkdownDocumentReader
根据配置将 Markdown 内容处理为 Document
对象:
-
标题内容将转换为
Document
对象的元数据。 -
段落内容构成
Document
对象的主体文本。 -
代码块既可分离为独立
Document
对象,也可与周边文本合并处理。 -
引用块既可分离为独立
Document
对象,也可与周边文本合并处理。 -
水平分隔符可用于将内容分割为多个独立
Document
对象。
该 Reader 会保留行内代码、列表和文本样式等格式到 Document
对象内容中。
PDF 页
PagePdfDocumentReader
采用 Apache PdfBox 库解析 PDF 文档。
通过 Maven 或 Gradle 添加项目依赖。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>
或添加到 Gradle 的 build.gradle
构建文件。
dependencies {
implementation 'org.springframework.ai:spring-ai-pdf-document-reader'
}
示例
@Component
public class MyPagePdfDocumentReader {
List<Document> getDocsFromPdf() {
PagePdfDocumentReader pdfReader = new PagePdfDocumentReader("classpath:/sample1.pdf",
PdfDocumentReaderConfig.builder()
.withPageTopMargin(0)
.withPageExtractedTextFormatter(ExtractedTextFormatter.builder()
.withNumberOfTopTextLinesToDelete(0)
.build())
.withPagesPerDocument(1)
.build());
return pdfReader.read();
}
}
PDF 段落
ParagraphPdfDocumentReader
利用 PDF 目录(如 TOC)信息将输入 PDF 分割为文本段落,每个段落输出为一个独立 Document
。
注意:并非所有 PDF 文档都包含 PDF 目录结构。 |
依赖
通过 Maven 或 Gradle 添加项目依赖。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>
或添加到 Gradle 的 build.gradle
构建文件。
dependencies {
implementation 'org.springframework.ai:spring-ai-pdf-document-reader'
}
示例
@Component
public class MyPagePdfDocumentReader {
List<Document> getDocsFromPdfWithCatalog() {
ParagraphPdfDocumentReader pdfReader = new ParagraphPdfDocumentReader("classpath:/sample1.pdf",
PdfDocumentReaderConfig.builder()
.withPageTopMargin(0)
.withPageExtractedTextFormatter(ExtractedTextFormatter.builder()
.withNumberOfTopTextLinesToDelete(0)
.build())
.withPagesPerDocument(1)
.build());
return pdfReader.read();
}
}
Tika(OCX, PPTX, HTML…)
TikaDocumentReader
利用 Apache Tika 从多种文档格式(如 PDF、DOC/DOCX、PPT/PPTX 和 HTML)中提取文本。完整支持的格式列表请参阅 Tika 官方文档。
依赖
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
</dependency>
或添加到 Gradle 的 build.gradle
构建文件。
dependencies {
implementation 'org.springframework.ai:spring-ai-tika-document-reader'
}
示例
@Component
class MyTikaDocumentReader {
private final Resource resource;
MyTikaDocumentReader(@Value("classpath:/word-sample.docx")
Resource resource) {
this.resource = resource;
}
List<Document> loadText() {
TikaDocumentReader tikaDocumentReader = new TikaDocumentReader(this.resource);
return tikaDocumentReader.read();
}
}
Transformer(转换器组件)
TokenTextSplitter
TokenTextSplitter
是 TextSplitter
的实现类,采用 CL100K_BASE 编码按 Token 计数分割文本。
使用方法
@Component
class MyTokenTextSplitter {
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
提供了两个构造函数选项:
-
TokenTextSplitter()
:创建采用默认配置的分割器(Splitter)实例。 -
TokenTextSplitter(int defaultChunkSize, int minChunkSizeChars, int minChunkLengthToEmbed, int maxNumChunks, boolean keepSeparator)
参数
-
defaultChunkSize
:每个文本块的目标 Token 数(默认值:800)。 -
minChunkSizeChars
:每个文本块的最小字符数(默认值:350)。 -
minChunkLengthToEmbed
:可嵌入分块的最小长度要求(默认值:5)。 -
maxNumChunks
:单个文本生成的最大分块数限制(默认值:10000)。 -
keepSeparator
: 是否在数据块中保留分隔符(如换行符)(默认值:true
)。
行为
TokenTextSplitter
按以下流程处理文本内容:
-
使用 CL100K_BASE 编码将输入文本转换为 Token 序列。
-
根据
defaultChunkSize
将编码后的文本分割成块。 -
对于每个分块:
-
将分块后的 Token 序列解码还原为文本。
-
在达到
minChunkSizeChars
后,尝试寻找合适的分割点(句号、问号、感叹号或换行符)。 -
若找到分割点,则在该位置截断分块。
-
根据
keepSeparator
设置,对分块进行修剪并可选地移除换行符。 -
若结果分块长度大于
minChunkLengthToEmbed
,则将其加入输出。
-
-
该过程持续运行,直至处理完所有 Token 或达到
maxNumChunks
限制。 -
剩余文本若长度超过
minChunkLengthToEmbed
,则作为最终分块加入输出。
示例
Document doc1 = new Document("This is a long piece of text that needs to be split into smaller chunks for processing.",
Map.of("source", "example.txt"));
Document doc2 = new Document("Another document with content that will be split based on token count.",
Map.of("source", "example2.txt"));
TokenTextSplitter splitter = new TokenTextSplitter();
List<Document> splitDocuments = this.splitter.apply(List.of(this.doc1, this.doc2));
for (Document doc : splitDocuments) {
System.out.println("Chunk: " + doc.getContent());
System.out.println("Metadata: " + doc.getMetadata());
}
KeywordMetadataEnricher
KeywordMetadataEnricher
作为 DocumentTransformer
的实现,利用生成式 AI 模型从文档内容提取关键词并添加为元数据。
Usage
@Component
class MyKeywordEnricher {
private final ChatModel chatModel;
MyKeywordEnricher(ChatModel chatModel) {
this.chatModel = chatModel;
}
List<Document> enrichDocuments(List<Document> documents) {
KeywordMetadataEnricher enricher = new KeywordMetadataEnricher(this.chatModel, 5);
return enricher.apply(documents);
}
}
构造函数
KeywordMetadataEnricher
构造函数接收两个参数:
-
ChatModel chatModel
:用于生成关键词的 AI 模型。 -
int keywordCount
:为每个文档提取的关键词数量。
行为
KeywordMetadataEnricher
按以下方式处理文档:
-
对于每个输入文档,它使用文档内容创建提示词。
-
将该提示词发送至指定的
ChatModel
以生成关键词。 -
生成的关键词以 "excerpt_keywords" 为 Key 添加到文档元数据中。
-
返回增强后的文档。
Customization
关键词提取提示词可通过修改类中的 KEYWORDS_TEMPLATE
常量自定义。默认模板如下:
\{context_str}. Give %s unique keywords for this document. Format as comma separated. Keywords:
其中 {context_str}
会被替换为文档内容,%s
会被替换为指定的关键词数量。
示例
ChatModel chatModel = // 初始化 chat model
KeywordMetadataEnricher enricher = new KeywordMetadataEnricher(chatModel, 5);
Document doc = new Document("This is a document about artificial intelligence and its applications in modern technology.");
List<Document> enrichedDocs = enricher.apply(List.of(this.doc));
Document enrichedDoc = this.enrichedDocs.get(0);
String keywords = (String) this.enrichedDoc.getMetadata().get("excerpt_keywords");
System.out.println("Extracted keywords: " + keywords);
SummaryMetadataEnricher
SummaryMetadataEnricher
是 DocumentTransformer
的实现,利用生成式 AI 模型创建文档摘要并添加为元数据,可为当前文档及相邻文档(前序与后续)生成摘要。
用法
@Configuration
class EnricherConfig {
@Bean
public SummaryMetadataEnricher summaryMetadata(OpenAiChatModel aiClient) {
return new SummaryMetadataEnricher(aiClient,
List.of(SummaryType.PREVIOUS, SummaryType.CURRENT, SummaryType.NEXT));
}
}
@Component
class MySummaryEnricher {
private final SummaryMetadataEnricher enricher;
MySummaryEnricher(SummaryMetadataEnricher enricher) {
this.enricher = enricher;
}
List<Document> enrichDocuments(List<Document> documents) {
return this.enricher.apply(documents);
}
}
构造函数
SummaryMetadataEnricher
提供两个构造函数:
-
SummaryMetadataEnricher(ChatModel chatModel, List<SummaryType> summaryTypes)
-
SummaryMetadataEnricher(ChatModel chatModel, List<SummaryType> summaryTypes, String summaryTemplate, MetadataMode metadataMode)
参数
-
chatModel
:用于生成摘要的 AI 模型。 -
summaryTypes
:SummaryType
枚举值列表,指定要生成的摘要类型(PREVIOUS、CURRENT、NEXT)。 -
summaryTemplate
: A custom template for summary generation (optional). -
metadataMode
: Specifies how to handle document metadata when generating summaries (optional).
Behavior
SummaryMetadataEnricher
按以下方式处理文档:
-
对于每个输入文档,它使用文档内容和指定的摘要模板创建提示词。
-
将该提示词发送至指定的
ChatModel
以生成摘要。 -
根据指定的
summaryTypes
,它为每个文档添加以下元数据:-
section_summary
:当前文档的摘要。 -
prev_section_summary
:前一篇文档的摘要(若存在且被请求)。 -
next_section_summary
:下一篇文档的摘要(若存在且被请求)。
-
-
返回增强后的文档。
Customization
摘要生成提示词可通过提供自定义的 summaryTemplate
进行修改。默认模板如下:
"""
Here is the content of the section:
{context_str}
Summarize the key topics and entities of the section.
Summary:
"""
示例
ChatModel chatModel = // 初始化 chat model
SummaryMetadataEnricher enricher = new SummaryMetadataEnricher(chatModel,
List.of(SummaryType.PREVIOUS, SummaryType.CURRENT, SummaryType.NEXT));
Document doc1 = new Document("Content of document 1");
Document doc2 = new Document("Content of document 2");
List<Document> enrichedDocs = enricher.apply(List.of(this.doc1, this.doc2));
// 检查增强后文档的元数据。
for (Document doc : enrichedDocs) {
System.out.println("Current summary: " + doc.getMetadata().get("section_summary"));
System.out.println("Previous summary: " + doc.getMetadata().get("prev_section_summary"));
System.out.println("Next summary: " + doc.getMetadata().get("next_section_summary"));
}
提供的示例展示了预期行为:
-
对于包含两个文档的列表,两个文档都会收到
section_summary
。 -
第一个文档会收到
next_section_summary
但没有prev_section_summary
。 -
第二个文档会收到
prev_section_summary
但没有next_section_summary
。 -
第一个文档的
section_summary
与第二个文档的prev_section_summary
内容一致。 -
第一个文档的
next_section_summary
与第二个文档的section_summary
内容一致。
Writer
File
The FileDocumentWriter
is a DocumentWriter
implementation that writes the content of a list of Document
objects into a file.
Usage
@Component
class MyDocumentWriter {
public void writeDocuments(List<Document> documents) {
FileDocumentWriter writer = new FileDocumentWriter("output.txt", true, MetadataMode.ALL, false);
writer.accept(documents);
}
}
Constructors
FileDocumentWriter
是 DocumentWriter
的实现类,用于将 Document
对象列表的内容写入文件。
-
FileDocumentWriter(String fileName)
-
FileDocumentWriter(String fileName, boolean withDocumentMarkers)
-
FileDocumentWriter(String fileName, boolean withDocumentMarkers, MetadataMode metadataMode, boolean append)
参数
-
fileName
:写入文档的目标文件名。 -
withDocumentMarkers
:是否在输出中包含文档标记(默认值:false
)。 -
metadataMode
:指定写入文件的文档内容类型(默认值:MetadataMode.NONE
)。 -
append
:若为true
,数据将追加至文件末尾而非开头(默认值:false
)。
行为
FileDocumentWriter
按以下方式处理文档:
-
为指定文件名创建
FileWriter
。 -
对于输入列表中的每个文档:
-
若
withDocumentMarkers
为true
,则写入包含文档索引和页码的文档标记。 -
根据指定的
metadataMode
写入文档的格式化内容。
-
-
所有文档写入完成后关闭文件。
文档标记
当 withDocumentMarkers
设为 true
时,Writer 会为每个文档添加如下格式的标记:
### Doc: [index], pages:[start_page_number,end_page_number]
VectorStore
提供与多种向量数据库的集成支持。完整列表请参阅 向量数据库文档。