ETL 管道
提取、转换和加载 (ETL) 框架作为检索增强生成 (RAG) 用例中数据处理的支柱。
ETL 管道编排从原始数据源到结构化向量存储的流程,确保数据以 AI 模型检索的最佳格式存在。
RAG 用例通过从数据体中检索相关信息来增强生成模型的能力,从而提高生成输出的质量和相关性。
API 概述
ETL 管道创建、转换和存储Document实例。

Document类包含文本、元数据以及可选的额外媒体类型,如图像、音频和视频。
ETL 管道有三个主要组件:
-
DocumentReader- 实现Supplier<List<Document>> -
DocumentTransformer- 实现Function<List<Document>, List<Document>> -
DocumentWriter- 实现Consumer<List<Document>>
Document类的内容是通过DocumentReader的帮助从 PDF、文本文件和其他文档类型创建的。
要构建一个简单的 ETL 管道,您可以将这三种类型的实例链接在一起。

假设我们有以下 ETL 类型的实例:
-
PagePdfDocumentReader-DocumentReader的实现 -
TokenTextSplitter-DocumentTransformer的实现 -
VectorStore-DocumentWriter的实现
要执行将数据加载到向量数据库的基本操作以用于检索增强生成模式,请使用以下 Java 函数式语法代码:
vectorStore.accept(tokenTextSplitter.apply(pdfReader.get()));
或者,您可以使用更自然地表达领域的方法名:
vectorStore.write(tokenTextSplitter.split(pdfReader.read()));
ETL 接口
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);
}
}
DocumentWriter
管理 ETL 过程的最后阶段,准备文档进行存储。
public interface DocumentWriter extends Consumer<List<Document>> {
default void write(List<Document> documents) {
accept(documents);
}
}
ETL 类图
以下类图说明了 ETL 接口和实现。

DocumentReaders
JSON
JsonReader处理 JSON 文档,将它们转换为Document对象列表。
示例
@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 文件的 Spring Resource对象。
jsonKeysToUse: 从 JSON 中应该用作结果Document对象中文本内容的键数组。
jsonMetadataGenerator: 用于为每个Document创建元数据的可选JsonMetadataGenerator。
行为
JsonReader按以下方式处理 JSON 内容:
-
它可以处理 JSON 数组和单个 JSON 对象。
-
对于每个 JSON 对象(在数组中或单个对象中):
-
它根据指定的
jsonKeysToUse提取内容。 -
如果未指定键,则使用整个 JSON 对象作为内容。
-
它使用提供的
JsonMetadataGenerator(如果未提供则使用空生成器)生成元数据。 -
它创建一个包含提取内容和元数据的
Document对象。
-
使用 JSON 指针
JsonReader现在支持使用 JSON 指针检索 JSON 文档的特定部分。此功能允许您轻松地从复杂的 JSON 结构中提取嵌套数据。
get(String pointer)方法
public List<Document> get(String pointer)
此方法允许您使用 JSON 指针检索 JSON 文档的特定部分。
参数
pointer: 用于在 JSON 结构中定位所需元素的 JSON 指针字符串(如 RFC 6901 中定义)。
返回值
返回一个List<Document>,包含从指针定位的 JSON 元素解析出的文档。
行为
-
该方法使用提供的 JSON 指针导航到 JSON 结构中的特定位置。
-
如果指针有效并指向现有元素:
-
对于 JSON 对象:返回包含单个 Document 的列表。
-
对于 JSON 数组:返回 Document 列表,数组中的每个元素一个。
-
-
如果指针无效或指向不存在的元素,则抛出
IllegalArgumentException。
示例
JsonReader jsonReader = new JsonReader(resource, "description");
List<Document> documents = this.jsonReader.get("/store/books/0");
示例 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配置为使用"description"作为jsonKeysToUse,它将创建Document对象,其中内容是数组中每辆自行车的description字段的值。
注意事项
-
JsonReader使用 Jackson 进行 JSON 解析。 -
它可以通过使用流式处理来高效处理大型 JSON 文件。
-
如果在
jsonKeysToUse中指定了多个键,内容将是这些键值的连接。 -
通过自定义
jsonKeysToUse和JsonMetadataGenerator,读取器可以适应各种 JSON 结构。
文本
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();
}
}
构造函数选项
TextReader提供了两个构造函数选项:
-
TextReader(String resourceUrl) -
TextReader(Resource resource)
参数
-
resourceUrl: 表示要读取的资源 URL 的字符串。 -
resource: 指向文本文件的 SpringResource对象。
配置
-
setCharset(Charset charset): 设置用于读取文本文件的字符集。默认为 UTF-8。 -
getCustomMetadata(): 返回一个可变映射,您可以 在其中添加文档的自定义元数据。
行为
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);
-
读取器使用 Spring 的
Resource抽象,允许从各种源(类路径、文件系统、URL 等)读取。 -
可以使用
getCustomMetadata()方法向读取器创建的所有文档添加自定义元数据。
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") // 提取<article>标签内的段落
.charset("ISO-8859-1") // 使用 ISO-8859-1 编码
.includeLinkUrls(true) // 在元数据中包含链接 URL
.metadataTags(List.of("author", "date")) // 提取作者和日期元标签
.additionalMetadata("source", "my-page.html") // 添加自定义元数据
.build();
JsoupDocumentReader reader = new JsoupDocumentReader(this.resource, config);
return reader.get();
}
}
-
JsoupDocumentReaderConfig允许您自定义JsoupDocumentReader的行为: -
charset: 指定 HTML 文档的字符编码(默认为"UTF-8")。 -
selector: 一个 JSoup CSS 选择器,用于指定要提取文本的元素(默认为"body")。 -
separator: 用于连接多个选定元素的文本的字符串(默认为"\n")。 -
allElements: 如果为true,则提取<body>元素中的所有文本,忽略selector(默认为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确定用于文本提取的元素。 -
如果
allElements为true,则从<body>中提取所有文本到单个Document中。 -
如果
groupByElement为true,则为selector匹配的每个元素创建单独的Document。 -
如果
allElements和groupByElement都不为true,则使用separator连接selector匹配的所有元素的文本。 -
文档标题、指定
<meta>标签的内容以及(可选)链接 URL 被添加到Document元数据中。 -
基本 URI(用于解析相对链接)将从 URL 资源中提取。
读取器保留选定元素的文本内容,但删除其中的任何 HTML 标签。
Markdown
MarkdownDocumentReader处理 Markdown 文档,将它们转换为Document对象列表。