ETL 流水线
提取、转换和加载(ETL)框架是检索增强生成(RAG)应用场景中数据处理的骨干。
ETL流水线协调从原始数据源到结构化向量存储的流程,确保数据格式为AI模型检索的最佳格式。
RAG的用例是通过从数据体中提取相关信息,增强生成模型的能力,从而提升生成输出的质量和相关性。
API 概述
ETL 流水线负责创建、转换和存储公文实例。
这公文类包含文本、元数据以及可选的额外媒体类型,如图片、音频和视频。
ETL流水线主要包含三个组成部分,
-
文档阅读器实现提供商<列表<文档>> -
DocumentTransformer实现Function<List<Document>, List<Document>> -
文档作者实现消费者<列表<文档>>
这公文课程内容由 PDF、文本文件及其他文档类型创建,并借助文档阅读器.
要构建一个简单的ETL流水线,你可以将每种类型的实例串联起来。
假设我们有以下三种ETL类型的实例
-
PagePdf文档阅读器一个实现文档阅读器 -
TokenTextSplitter一个实现DocumentTransformer -
VectorStore一个实现文档作者
要将数据加载到向量数据库中以配合检索增强生成模式,请使用以下 Java 函数式语法代码。
vectorStore.accept(tokenTextSplitter.apply(pdfReader.get()));
或者,你也可以使用更自然地表达该领域的方法名称
vectorStore.write(tokenTextSplitter.split(pdfReader.read()));
ETL 接口
ETL流水线由以下接口和实现组成。详细的ETL类图见ETL类图部分。
文档阅读器
提供来自多元来源的文献资料来源。
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);
}
}
文档阅读者
JSON
这JsonReader处理 JSON 文档,并将其转换为公文对象。
示例
@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(资源资源) -
JsonReader(资源资源,字符串...jsonKeysToUse) -
JsonReader(资源资源,JsonMetadataGenerator jsonMetadataGenerator, String...jsonKeysToUse)
参数
-
资源:一个Spring资源指向 JSON 文件的对象。 -
jsonKeysToUse:来自JSON的数组键,应用作文本内容公文对象。 -
jsonMetadataGenerator:可选JsonMetadataGenerator为每个节点创建元数据公文.
行为
这JsonReader处理JSON内容的方式如下:
-
它可以同时处理 JSON 数组和单个 JSON 对象。
-
对于每个 JSON 对象(无论是数组还是单个对象):
-
它根据指定内容提取内容
jsonKeysToUse. -
如果没有指定键,它会使用整个 JSON 对象作为内容。
-
它生成元数据,使用提供的
JsonMetadataGenerator(如果没有提供,则是空的)。 -
它创造了
公文包含提取内容和元数据的对象。
-
使用 JSON 指针
这JsonReader现在支持使用 JSON 指针检索 JSON 文档的特定部分。此功能允许您轻松从复杂的 JSON 结构中提取嵌套数据。
文本
这文本阅读器处理纯文本文档,将其转换为公文对象。
示例
@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();
}
}
HTML(JSoup)
这Jsoup文档阅读器处理HTML文档,并将其转换为公文使用JSoup库的对象。
示例
@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允许你自定义Jsoup文档阅读器:
-
字符集: 指定HTML文档的字符编码(默认为“UTF-8”)。 -
选择器: 一个JSoup CSS选择器,用于指定从哪些元素中提取文本(默认为“正体”)。 -
分隔符:用于连接多个选定元素文本的字符串(默认为“\n”)。 -
allElements(所有元素):如果true,从 中提取所有文本<身体>元素,忽略选择器(默认为false). -
groupByElement:如果true,会创建独立的公文对于每个元素,均匹配选择器(默认为false). -
includeLinkUrls:如果true,提取绝对链接URL并将其添加到元数据中(默认为false). -
元数据标签:一份列表<元论文>标签名称用于提取内容(默认为[“描述”、“关键词”]). -
附加元数据: 允许你为所有创建的自定义元数据添加公文对象。
示例文档: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>
行为:
这Jsoup文档阅读器处理HTML内容并创建公文基于配置的对象:
-
这
选择器决定用于文本提取的元素。 -
如果
allElements(所有元素)是true,所有文本<身体>被提取为单一公文. -
如果
groupByElement是true,每个元素都与选择器产生了独立的公文. -
如果都不是
allElements(所有元素)也不groupByElement是true,所有与选择器通过分隔符. -
文档标题,内容指定
<元论文>标签,以及(可选的)链接URL会添加到公文元数据。 -
用于解析相对链接的基础URI将从URL资源中提取。
阅读器保留所选元素的文本内容,但去除其中的任何HTML标签。
折扣
这Markdown文档阅读器处理 Markdown 文档,将其转换为公文对象。
示例
@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 中的水平规则将创造新的公文对象。 -
includeCodeBlock: 当设置为true,代码块将被包含在同一中公文就像周围的文字一样。什么时候false,代码块分别创建公文对象。 -
includeBlockquote: 当设置为true,块状引用将包含在同一条文中公文就像周围的文字一样。什么时候false,块引号单独生成公文对象。 -
附加元数据: 允许你为所有创建的自定义元数据添加公文对象。
示例文档: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 内容,并根据配置创建文档对象:
-
头部会成为文档对象中的元数据。
-
段落成为文档对象的内容。
-
代码块可以被拆分成独立的文档对象,或与周围文本一起包含。
-
块引号可以拆分成单独的文档对象,也可以与周围的文本一起包含。
-
可以使用水平规则将内容拆分为独立的文档对象。
阅读器保留了文档对象内容中的格式化,如内联代码、列表和文本样式。
PDF页面
这PagePdf文档阅读器使用 Apache PdfBox 库来解析 PDF 文档
用 Maven 或 Gradle 把依赖添加到你的项目中。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>
或者去你的Gradlebuild.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段落
这段落PDF文档阅读器利用 PDF 目录(例如目录)信息将输入的 PDF 拆分成文本段落,输出为单一公文每段。
注意:并非所有PDF文档都包含PDF目录。
依赖
用 Maven 或 Gradle 把依赖添加到你的项目中。
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>
或者去你的Gradlebuild.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(DOCX、PPTX、HTML......)
这Tika文档阅读器使用 Apache Tika 从多种文档格式中提取文本,如 PDF、DOC/DOCX、PPT/PPTX 和 HTML。有关支持格式的全面列表,请参阅 Tika 文档。
依赖
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
</dependency>
或者去你的Gradlebuild.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();
}
}
变形金刚
TokenTextSplitter
这TokenTextSplitter是 的实现文本分配器它根据Tokens数量将文本分割成块,使用CL100K_BASE编码。
用法
@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提供两种构造器选项:
-
Tokens文本分流器(): 创建一个默认设置的分线器。 -
TokenTextSplitter(int defaultChunkSize, int minChunkSizeChars, int minChunkLengthToEmbed, int maxNumChunks, boolean keepSeparator)
参数
-
defaultChunkSize:每个文本块的目标大小(以Tokens为单位)(默认:800)。 -
minChunkSizeChars:每个文本块的最小字符大小(默认:350)。 -
minChunkLengthToEmbed:包含区块的最小长度(默认:5)。 -
maxNumChunks: 文本生成的最大区块数(默认:10000)。 -
keepSeparator:是否在区块中保留分隔符(如换行符)(默认:true)。
行为
这TokenTextSplitter文本内容处理方式如下:
-
它将输入文本编码为符号,使用CL100K_BASE编码。
-
它会根据
defaultChunkSize. -
对于每个区块:
-
它会将该段落解码回文本。
-
它试图在
minChunkSizeChars. -
如果找到断点,它会截断该点的块。
-
它会根据
keepSeparator设置。 -
如果得到的块长于
minChunkLengthToEmbed,它被添加到输出中。
-
-
该过程持续,直到所有Tokens被处理完毕或
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它利用生成式人工智能模型从文档内容中提取关键词并添加为元数据。
用法
@Component
class MyKeywordEnricher {
private final ChatModel chatModel;
MyKeywordEnricher(ChatModel chatModel) {
this.chatModel = chatModel;
}
List<Document> enrichDocuments(List<Document> documents) {
KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
.keywordCount(5)
.build();
// Or use custom templates
KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
.keywordsTemplate(YOUR_CUSTOM_TEMPLATE)
.build();
return enricher.apply(documents);
}
}
制造商选项
这KeywordMetadataEnricher提供两种构造器选项:
-
KeywordMetadataEnricher(ChatModel chatModel, int keywordCount): 使用默认模板并提取指定数量的关键词。 -
KeywordMetadataEnricher(ChatModel chatModel, PromptTemplate keywordsTemplate): 使用自定义模板进行关键词提取。
行为
这KeywordMetadataEnricher文件处理方式如下:
-
对于每个输入文档,它会根据文档内容生成一个提示。
-
它会将此提示发送到所提供的
聊天模型生成关键词。 -
生成的关键词会被添加到文档的元数据中,关键字为“excerpt_keywords”。
-
丰富的文件被归还。
定制
您可以使用默认模板,或通过 keywordsTemplate 参数自定义模板。 默认模板如下:
\{context_str}. Give %s unique keywords for this document. Format as comma separated. Keywords:
哪里{context_str}被替换为文档内容,且%s被指定的关键词数量取代。
示例
ChatModel chatModel = // initialize your chat model
KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
.keywordCount(5)
.build();
// Or use custom templates
KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
.keywordsTemplate(new PromptTemplate("Extract 5 important keywords from the following text and separate them with commas:\n{context_str}"))
.build();
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);
摘要MetadataEnricher
这摘要MetadataEnricher是DocumentTransformer它利用生成式人工智能模型为文档创建摘要并添加元数据。它可以生成当前文档以及相邻文档(上一页和下一页)的摘要。
用法
@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);
}
}
构造 函数
这摘要MetadataEnricher提供两个构造子:
-
SummaryMetadataEnricher(ChatModel chatModel, List<SummaryType> summaryTypes) -
SummaryMetadataEnricher(ChatModel chatModel, List<SummaryType> summaryTypes, String summaryTemplate, MetadataMode metadataMode)
参数
-
chatModel用于生成摘要的人工智能模型。 -
类型摘要:一份列表类型摘要ENUM 值表示要生成哪些摘要(PREIOUS、CURRENT、NEXT)。 -
摘要模板:用于摘要生成的自定义模板(可选)。 -
元数据模式:规定生成摘要时如何处理文档元数据(可选)。
行为
这摘要MetadataEnricher文件处理方式如下:
-
对于每个输入文档,它会根据文档内容和指定的摘要模板生成一个提示。
-
它会将此提示发送到所提供的
聊天模型生成摘要。 -
具体情况不同
类型摘要它会为每个文档添加以下元数据:-
section_summary:现行文件摘要。 -
prev_section_summary:前一文件的摘要(如有且需提供)。 -
next_section_summary:下一份文件的摘要(如有且有请求)。
-
-
丰富的文件被归还。
定制
摘要生成提示可以通过提供自定义提示来进行定制摘要模板.默认模板如下:
"""
Here is the content of the section:
{context_str}
Summarize the key topics and entities of the section.
Summary:
"""
示例
ChatModel chatModel = // initialize your 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));
// Check the metadata of the enriched documents
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第二份文件。
作家
文件
这文件文档编写器是文档作者实现了写入列表内容的公文对象变成一个文件。
用法
@Component
class MyDocumentWriter {
public void writeDocuments(List<Document> documents) {
FileDocumentWriter writer = new FileDocumentWriter("output.txt", true, MetadataMode.ALL, false);
writer.accept(documents);
}
}
构造 函数
这文件文档编写器提供三个构造子:
-
FileDocumentWriter(字符串文件名) -
FileDocumentWriter(String fileName, boolean withDocumentMarkers) -
FileDocumentWriter(String fileName, boolean withDocumentMarkers, MetadataMode metadataMode, boolean append)
参数
-
文件名: 是写入文件的文件名称。 -
withDocumentMarkers: 是否在输出中包含文档标记(默认:false)。 -
元数据模式: 指定要写入文件的文档内容(默认:MetadataMode.NONE)。 -
附加如果为真,数据将写入文件末尾而非开头(默认:false)。
行为
这文件文档编写器文件处理方式如下:
-
它会为指定文件名打开一个文件编写器。
-
对于输入列表中的每个文档:
-
如果
withDocumentMarkers是真的,它会写入包含文档索引和页码的文档标记。 -
它根据指定的格式化内容编写文档
元数据模式.
-
-
所有文件写完后,档案即关闭。
文档标记
什么时候withDocumentMarkers设置为 true 时,写者会为每个文档添加以下格式的标记:
### Doc: [index], pages:[start_page_number,end_page_number]
VectorStore
提供与多种向量存储的集成。 完整列表请参见Vector数据库文档。