10.1ETL Pipeline

31 阅读16分钟

ETL Pipeline

前言

提取、转换和加载(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 类内容是通过 PDF、文本文件和其他文档类型的 DocumentReader 创建的。

要构建简单的 ETL 管道,您可以链接每种类型的一个实例。

假设我们有以下三种 ETL 类型的实例:

  • PagePdfDocumentReaderDocumentReader 的实现
  • TokenTextSplitterDocumentTransformer 的实现
  • VectorStoreDocumentWriter 的实现

要执行将数据加载到向量数据库中以用于检索增强生成模式的基本操作,请使用以下 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 提供了几个构造函数选项:

  1. JsonReader(Resource resource)
  2. JsonReader(Resource resource, String…​ jsonKeysToUse)
  3. JsonReader(Resource resource, JsonMetadataGenerator jsonMetadataGenerator, String…​ jsonKeysToUse)
参数
  • resource:指向 JSON 文件的 Spring Resource 对象
  • jsonKeysToUse:JSON 中应该用作结果 Document 对象文本内容的键数组
  • jsonMetadataGenerator:可选的 JsonMetadataGenerator,用于为每个 Document 创建元数据
行为

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 指针字符串(根据 RFC 6901 定义),用于定位 JSON 结构中所需的元素

返回值 返回包含从指针定位的 JSON 元素解析的文档的 List<Document>

行为

  • 该方法使用提供的 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": "一款用于越野骑行的高性能山地自行车。"
  },
  {
    "id": 2,
    "brand": "Cannondale",
    "description": "一款为赛车爱好者设计的气动公路自行车。"
  }
]

在此示例中,如果 JsonReader 配置了 "description" 作为 jsonKeysToUse,它将创建 Document 对象,其中内容是数组中每辆自行车的 "description" 字段的值。

注意事项
  • JsonReader 使用 Jackson 进行 JSON 解析
  • 它可以通过对数组使用流式处理来高效处理大型 JSON 文件
  • 如果在 jsonKeysToUse 中指定了多个键,内容将是这些键值的串联
  • 读取器是灵活的,可以通过自定义 jsonKeysToUseJsonMetadataGenerator 来适应各种 JSON 结构

Text

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 提供两个构造函数选项:

  1. TextReader(String resourceUrl)
  2. TextReader(Resource resource)
参数
  • resourceUrl:表示要读取的资源 URL 的字符串
  • resource:指向文本文件的 Spring Resource 对象
配置
  • 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>&copy; 2024 John Doe</p>
    </footer>
</body>
</html>
行为

JsoupDocumentReader 处理 HTML 内容并根据配置创建 Document 对象:

  • selector 确定用于文本提取的元素
  • 如果 allElements 为 true,则将 <body> 中的所有文本提取到单个 Document 中
  • 如果 groupByElement 为 true,则为 selector 匹配的每个元素创建单独的 Document
  • 如果 allElementsgroupByElement 都不为 true,则使用 separator 连接 selector 匹配的所有元素的文本
  • 文档标题、指定 <meta> 标签的内容和(可选的)链接 URL 被添加到 Document 元数据中
  • 用于解析相对链接的基本 URI 将从 URL 资源中提取
  • 读取器保留选中元素的文本内容,但删除其中的任何 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
这是一个 Java 示例应用程序:

​```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 还提供了在整个句子中 use inline code formatting throughout 的可能性。


另一种可能性是设置没有特定高亮的块代码:

./mvnw spring-javaformat:apply
行为

MarkdownDocumentReader 处理 Markdown 内容并根据配置创建 Document 对象:

  • 标题成为 Document 对象中的元数据
  • 段落成为 Document 对象的内容
  • 代码块可以分离到自己的 Document 对象中或包含在周围文本中
  • 块引用可以分离到自己的 Document 对象中或包含在周围文本中
  • 水平规则可用于将内容分割成单独的 Document 对象
  • 读取器保留格式,如内联代码、列表和 Document 对象内容中的文本样式

PDF Page

PagePdfDocumentReader 使用 Apache PdfBox 库解析 PDF 文档。

依赖项

使用 Maven 或 Gradle 将依赖项添加到您的项目中。

Maven:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>

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 Paragraph

ParagraphPdfDocumentReader 使用 PDF 目录(例如 TOC)信息将输入 PDF 分割为文本段落,并为每个段落输出单个 Document

注意: 并非所有 PDF 文档都包含 PDF 目录。

依赖项

使用 Maven 或 Gradle 将依赖项添加到您的项目中。

Maven:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>

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…)

TikaDocumentReader 使用 Apache Tika 从各种文档格式中提取文本,如 PDF、DOC/DOCX、PPT/PPTX 和 HTML。有关支持格式的完整列表,请参阅 Tika 文档

依赖项

Maven:

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-tika-document-reader</artifactId>
</dependency>

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();
    }
}

Transformers

TextSplitter

TextSplitter 是一个抽象基类,帮助分割文档以适应 AI 模型的上下文窗口。

TokenTextSplitter

TokenTextSplitterTextSplitter 的实现,它基于令牌计数将文本分割成块,使用 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 提供两个构造函数选项:

  1. TokenTextSplitter():创建具有默认设置的分割器
  2. TokenTextSplitter(int defaultChunkSize, int minChunkSizeChars, int minChunkLengthToEmbed, int maxNumChunks, boolean keepSeparator)
参数
  • defaultChunkSize:每个文本块的令牌目标大小(默认:800)
  • minChunkSizeChars:每个文本块的最小字符大小(默认:350)
  • minChunkLengthToEmbed:要包含的块的最小长度(默认:5)
  • maxNumChunks:从文本生成的最大块数(默认:10000)
  • keepSeparator:是否在块中保留分隔符(如换行符)(默认:true)
行为

TokenTextSplitter 处理文本内容的方式如下:

  1. 它使用 CL100K_BASE 编码将输入文本编码为令牌
  2. 它基于 defaultChunkSize 将编码文本分割成块
  3. 对于每个块: a. 它将块解码回文本 b. 它尝试在 minChunkSizeChars 之后找到合适的断点(句号、问号、感叹号或换行符) c. 如果找到断点,则在该点截断块 d. 它修剪块并根据 keepSeparator 设置可选择地删除换行符 e. 如果结果块长于 minChunkLengthToEmbed,则将其添加到输出中
  4. 此过程继续,直到处理完所有令牌或达到 maxNumChunks
  5. 如果剩余文本长于 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());
}
注意事项
  • TokenTextSplitter 使用来自 jtokkit 库的 CL100K_BASE 编码,这与较新的 OpenAI 模型兼容
  • 分割器尝试通过在句子边界断开来创建语义上有意义的块
  • 来自原始文档的元数据被保留并复制到从该文档派生的所有块中
  • 如果设置了内容格式化器(如果设置),当 copyContentFormatter 设置为 true(默认行为)时,也会将其复制到派生的块中
  • 此分割器对于准备具有令牌限制的大型语言模型的文本特别有用,确保每个块都在模型的处理能力范围内

ContentFormatTransformer

确保所有文档的内容格式统一。

KeywordMetadataEnricher

KeywordMetadataEnricher 是一个 DocumentTransformer,它使用生成式 AI 模型从文档内容中提取关键字并将其添加为元数据。

用法
@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();

        // 或使用自定义模板
        KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
               .keywordsTemplate(YOUR_CUSTOM_TEMPLATE)
               .build();

        return enricher.apply(documents);
    }
}
构造函数选项

KeywordMetadataEnricher 提供两个构造函数选项:

  1. KeywordMetadataEnricher(ChatModel chatModel, int keywordCount):使用默认模板并提取指定数量的关键字
  2. KeywordMetadataEnricher(ChatModel chatModel, PromptTemplate keywordsTemplate):使用自定义模板进行关键字提取
行为

KeywordMetadataEnricher 处理文档的方式如下:

  1. 对于每个输入文档,它使用文档的内容创建提示
  2. 它将此提示发送到提供的 ChatModel 以生成关键字
  3. 生成的关键字被添加到文档的元数据中的 "excerpt_keywords" 键下
  4. 返回富集的文档
自定义

您可以使用默认模板或通过 keywordsTemplate 参数自定义模板。默认模板是:

\{context_str}. Give %s unique keywords for this document. Format as comma separated. Keywords:

其中 {context_str} 被替换为文档内容,%s 被替换为指定的关键字计数。

示例
ChatModel chatModel = // 初始化您的聊天模型
KeywordMetadataEnricher enricher = KeywordMetadataEnricher.builder(chatModel)
                .keywordCount(5)
                .build();

// 或使用自定义模板
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);
注意事项
  • KeywordMetadataEnricher 需要正常的 ChatModel 来生成关键字
  • 关键字计数必须为 1 或更大
  • 富集器向每个处理的文档添加 "excerpt_keywords" 元数据字段
  • 生成的关键字作为逗号分隔的字符串返回
  • 此富集器对于提高文档可搜索性以及为文档生成标签或类别特别有用
  • 在 Builder 模式中,如果设置了 keywordsTemplate 参数,keywordCount 参数将被忽略

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 提供两个构造函数:

  1. SummaryMetadataEnricher(ChatModel chatModel, List<SummaryType> summaryTypes)
  2. SummaryMetadataEnricher(ChatModel chatModel, List<SummaryType> summaryTypes, String summaryTemplate, MetadataMode metadataMode)
参数
  • chatModel:用于生成摘要的 AI 模型
  • summaryTypesSummaryType 枚举值列表,指示要生成哪些摘要(PREVIOUS、CURRENT、NEXT)
  • summaryTemplate:用于摘要生成的自定义模板(可选)
  • metadataMode:指定生成摘要时如何处理文档元数据(可选)
行为

SummaryMetadataEnricher 处理文档的方式如下:

  1. 对于每个输入文档,它使用文档的内容和指定的摘要模板创建提示
  2. 它将此提示发送到提供的 ChatModel 以生成摘要
  3. 根据指定的 summaryTypes,它将以下元数据添加到每个文档:
    • section_summary:当前文档的摘要
    • prev_section_summary:上一个文档的摘要(如果可用且请求)
    • next_section_summary:下一个文档的摘要(如果可用且请求)
  4. 返回富集的文档
自定义

可以通过提供自定义 summaryTemplate 来自定义摘要生成提示。默认模板是:

""""
这里是部分的内容:
{context_str}

总结部分的关键主题和实体。

摘要:
"""
示例
ChatModel chatModel = // 初始化您的聊天模型
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 匹配
注意事项
  • SummaryMetadataEnricher 需要正常的 ChatModel 来生成摘要
  • 富集器可以处理任何大小的文档列表,正确处理第一个和最后一个文档的边缘情况
  • 此富集器对于创建上下文感知的摘要特别有用,允许更好地理解序列中文档的关系
  • MetadataMode 参数允许控制在摘要生成过程中如何合并现有元数据

Writers

File

FileDocumentWriter 是一个 DocumentWriter 实现,将 Document 对象列表的内容写入文件。

用法
@Component
class MyDocumentWriter {

    public void writeDocuments(List<Document> documents) {
        FileDocumentWriter writer = new FileDocumentWriter("output.txt", true, MetadataMode.ALL, false);
        writer.accept(documents);
    }
}
构造函数

FileDocumentWriter 提供三个构造函数:

  1. FileDocumentWriter(String fileName)
  2. FileDocumentWriter(String fileName, boolean withDocumentMarkers)
  3. FileDocumentWriter(String fileName, boolean withDocumentMarkers, MetadataMode metadataMode, boolean append)
参数
  • fileName:要将文档写入的文件的名称
  • withDocumentMarkers:是否在输出中包含文档标记(默认:false)
  • metadataMode:指定要写入文件的文档内容(默认:MetadataMode.NONE)
  • append:如果为 true,数据将写入文件的末尾而不是开头(默认:false)
行为

FileDocumentWriter 处理文档的方式如下:

  1. 它为指定的文件名打开 FileWriter
  2. 对于输入列表中的每个文档: a. 如果 withDocumentMarkers 为 true,它会写入包含文档索引和页码的文档标记 b. 它根据指定的 metadataMode 写入文档的格式化内容
  3. 写入所有文档后关闭文件
文档标记

withDocumentMarkers 设置为 true 时,写入器为每个文档包含以下格式的标记:

### Doc: [index], pages:[start_page_number,end_page_number]
元数据处理

写入器使用两个特定的元数据键:

  • page_number:表示文档的起始页码
  • end_page_number:表示文档的结束页码

这些在写入文档标记时使用。

示例
List<Document> documents = // 初始化您的文档
FileDocumentWriter writer = new FileDocumentWriter("output.txt", true, MetadataMode.ALL, true);
writer.accept(documents);

这会将所有文档写入 "output.txt",包括文档标记,使用所有可用的元数据,如果文件已存在则追加。

注意事项
  • 写入器使用 FileWriter,因此它使用操作系统的默认字符编码写入文本文件
  • 如果写入期间发生错误,将抛出 RuntimeException,原始异常作为其原因
  • metadataMode 参数允许控制在写入内容中如何合并现有元数据
  • 此写入器对于调试或创建文档集合的人类可读输出特别有用

VectorStore

提供与各种向量存储的集成。 有关完整列表,请参阅 Vector DB 文档