ETL Pipeline

提取、转换和加载(ETL)框架构成了检索增强生成(RAG)应用场景中数据处理的核心支柱。

ETL 管道协调从原始数据源到结构化向量存储的流程,确保数据以最适合 AI 模型检索的格式呈现。

RAG(检索增强生成)应用场景通过从数据集中检索相关信息,增强生成模型的能力,从而提高生成内容的质量和相关性。

API 概览

TL 管道负责创建、转换并存储文档实例。

Document 类包含文本、元数据以及可选的额外媒体类型,如图像、音频和视频。

ETL 管道主要由三个核心组件构成,

  • 实现 Supplier<List<Document>>DocumentReader
  • 实现Function<List<Document>,List<Document>>DocumentTransformer
  • 实现 Consumer<List<Document>> 接口的 DocumentWriter

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 管道由以下接口和实现构成。

文档阅读器(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)
参数(Parameters )
  • resource:指向 JSON 文件的 Spring 资源对象。
  • jsonKeysToUse:一个包含 JSON 中键的数组,这些键将用作生成 Document 对象时的文本内容。
  • jsonMetadataGenerator:一个可选的 JsonMetadataGenerator,用于为每个 Document 创建元数据。
行为(Behavior)

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 对象:它返回一个包含单个文档的列表。
    • 对于 JSON 数组:它返回一个文档列表,数组中的每个元素对应一个文档。
  • 如果指针无效或指向不存在的元素,则会抛出 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 中指定了多个键,内容将是这些键对应值的串联。
  • 该读取器具有灵活性,可通过自定义 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 资源对象。
配置
  • setCharset(Charset charset):设置用于读取文本文件的字符集。默认值为 UTF-8。
  • getCustomMetadata():返回一个可变映射,您可以在其中为文档添加自定义元数据。
行为

TextReader 按以下方式处理文本内容:

  • 它将文本文件的全部内容读取到一个单一的 Document 对象中。
  • 文件的内容成为 Document 的内容。
  • 元数据会自动添加到文档中:
    • charset:用于读取文件的字符集(默认:”UTF-8”)。
    • source:源文本文件的文件名。
  • 通过 getCustomMetadata () 添加的任何自定义元数据都会包含在 Document 中。
笔记
  • TextReader 会将整个文件内容读入内存,因此可能不适合处理非常大的文件。
  • 若需将文本分割成较小片段,可在读取文档后使用诸如 TokenTextSplitter 之类的文本分割工具:
    List<Document> documents = textReader.get();
    List<Document> splitDocuments = new TokenTextSplitter().apply(this.documents);
  • 读者利用 Spring 的资源抽象机制,使其能够从多种来源(类路径、文件系统、URL 等)读取内容。
  • 通过 getCustomMetadata() 方法,可以为阅读器创建的所有文档添加自定义元数据。

HTML(JSoup)

JsoupDocumentReader 处理 HTML 文档,利用 JSoup 库将其转换为一组 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 选择器,用于指定提取文本的元素(默认为 “body”)。
  • separator:用于连接多个选定元素文本的字符串(默认为 “\n”)。
  • allElements:如果true,则从 <body> 元素中提取所有文本,忽略选择器(默认为 false)。
  • groupByElement:如果为 true,则为选择器匹配的每个元素创建一个单独的 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,则每个与选择器匹配的元素都将创建一个单独的 Document。
  • 如果 allElements 和 groupByElement 均未设置为 true,则使用分隔符连接所有匹配选择器的元素的文本。
  • 文档标题、指定 <meta> 标签中的内容以及(可选)链接 URL 会被添加到文档元数据中。
  • 用于解析相对链接的基础 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 时,代码块将与周围的文本包含在同一文档中。若为 false,代码块则会创建独立的 Document 对象。
  • includeBlockquote:当设置为 true 时,块引用将与周围的文本包含在同一文档中。当设置为 false 时,块引用将创建单独的 Document 对象。
  • additionalMetadata:允许您向所有创建的 Document 对象添加自定义元数据。

示例文档:code.md

This is a Java sample application:


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
行为:Markdown 文档阅读器处理 Markdown 内容,并根据配置创建文档对象:
  • 标题成为 Document 对象中的元数据。
  • 段落成为 Document 对象的内容。
  • 代码块可以独立成为自己的 Document 对象,也可以与周围的文本一起包含在内。
  • 引用块可以独立成文为 Document 对象,亦可与周围文本合并处理。
  • 水平线可用于将内容分割成独立的 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 目录(例如,目录信息)将输入的 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(DOCX、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();
    }
}

变换器(Transformers)

文本分割器(TextSplitter)

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

TokenTextSplitter

TokenTextSplitter 是 TextSplitter 的一个实现,它根据令牌计数使用 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 提供了两种构造选项:

  • TokenTextSplitter():创建一个使用默认设置的分割器。
  • 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. 对于每个分块:
    • 它将分块解码回文本。
    • 它尝试在 minChunkSizeChars 后找到合适的断点(句号、问号、叹号或换行符)。
    • 如果找到一个断点,它会在该点截断块。
    • 它会根据 keepSeparator 设置对块进行修剪,并可选地移除换行符。
    • 如果生成的块长度超过 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();

        // 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 按以下方式处理文档:

  • 对于每份输入文档,它都会利用文档内容生成一个提示。
  • 它将此提示发送至所提供的 ChatModel 以生成关键词。
  • 生成的关键词被添加到文档元数据中,键名为 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);
笔记
  • KeywordMetadataEnricher 需要一个正常运行的 ChatModel 来生成关键词。
  • 关键字数量必须为 1 或更大。
  • 该增强器为每个处理过的文档添加了 “excerpt_keywords” 元数据字段。
  • 生成的关键词以逗号分隔的字符串形式返回。
  • 该增强器特别有助于提升文档的可搜索性,并生成文档的标签或分类。
  • 在构建者模式中,如果设置了 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 提供了两种构造函数:

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

SummaryMetadataEnricher 处理文档的过程如下:

  • 对于每个输入文档,它会利用文档内容和指定的摘要模板创建一个提示。
  • 它将此提示发送至提供的 ChatModel 以生成摘要。
  • 根据指定的 summaryTypes,它为每个文档添加以下元数据:
    • section_summary:当前文档的摘要。
    • prev_section_summary:上一文档的摘要(如有且请求提供)。
    • next_section_summary:下一文档的摘要(如有且需提供)。
  • 返回丰富的文档。
自定义

摘要生成提示可以通过提供自定义的 summaryTemplate 进行定制。默认模板为:

"""
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 相匹配。
  • 第一份文档的下一节摘要与第二份文档的节摘要相符。
笔记
  • 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 提供了三种构造函数:

  • 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 按以下方式处理文档:

  1. 它为指定的文件名打开一个 FileWriter。
  2. 对于输入列表中的每个文档:
    • 如果 withDocumentMarkers 为真,则写入包含文档索引和页码的文档标记。
    • 它根据指定的 metadataMode 写入文档的格式化内容。
  3. 文件在所有文档写入完毕后关闭。
文档标记(Document Markers)

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

### Doc: [index], pages:[start_page_number,end_page_number]
元数据处理(Metadata Handling)

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

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

这些用于写入文档标记时。

例子
List<Document> documents = // initialize your documents
FileDocumentWriter writer = new FileDocumentWriter("output.txt", true, MetadataMode.ALL, true);
writer.accept(documents);

这将把所有文档写入 “output.txt”,包括文档标记,利用所有可用的元数据,并在文件已存在的情况下追加内容。

笔记
  • 编写器使用了 FileWriter,因此它采用操作系统默认的字符编码来写入文本文件。
  • 如果在写入过程中发生错误,将会抛出一个 RuntimeException,并将原始异常作为其原因。
  • MetadataMode 参数允许控制现有元数据如何融入所撰写的内容中。
  • 此编写器在调试或生成文档集合的可读性输出方面尤为实用。
作者:Jeebiz  创建时间:2025-08-03 21:17
最后编辑:Jeebiz  更新时间:2025-09-28 09:15