Lzh on GitHub

ETL 管道

提取、转换与加载(ETL)框架是检索增强生成(RAG)场景中数据处理的核心支撑。

提取、转换与加载(ETL)框架是检索增强生成(RAG)场景中数据处理的核心支撑。

ETL 管道负责从原始数据源到结构化向量数据库的整个数据流编排,确保数据以最优格式供 AI 模型检索使用。

在 RAG 场景中,其主要作用是通过从数据集合中检索相关信息来增强生成模型的能力,从而提升生成内容的质量和相关性。

API 概览

ETL 管道用于创建、转换并存储 Document 实例。

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

ETL 管道的三个主要组件:

  1. DocumentReader:实现 Supplier<List<Document>>,负责读取文档
  2. DocumentTransformer:实现 Function<List<Document>, List<Document>>,负责转换文档
  3. DocumentWriter:实现 Consumer<List<Document>>,负责写入文档

Document 类的内容可通过 DocumentReader 从 PDF、文本文件或其他文档类型生成。

要构建简单 ETL 管道,可以将三种类型的实例串联起来,形成一个完整的 ETL 流程。

假设我们有以下实例:

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

将数据加载到向量数据库以用于检索增强生成(RAG)模式,可以使用如下 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();
    }
}

DocumentReader 接口用于读取文档,继承自 Supplier<List<Document>>,并提供默认的 read() 方法来获取文档列表。

文档转换器

在处理流程中,对一批文档进行转换。

public interface DocumentTransformer extends Function<List<Document>, List<Document>> {

    default List<Document> transform(List<Document> transform) {
        return apply(transform);
    }
}

DocumentTransformer 接口用于批量转换文档,继承自 Function<List<Document>, List<Document>>,并提供默认的 transform() 方法以便执行转换操作。

文档写入器

管理 ETL 流程的最终阶段,负责将文档准备好以便存储。

public interface DocumentWriter extends Consumer<List<Document>> {

    default void write(List<Document> documents) {
        accept(documents);
    }
}

DocumentWriter 接口用于写入文档,继承自 Consumer<List<Document>>,并提供默认的 write() 方法来执行文档写入操作。

ETL Class Diagram

下图展示了 ETL 接口及其实现类的类图。

文档读取器

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:可选,用于为每个 Document 生成元数据

行为说明

JsonReader 的处理流程如下:

  1. 支持 JSON 数组或单个 JSON 对象
  2. 对于每个 JSON 对象(数组元素或单对象):
  • 根据 jsonKeysToUse 提取内容
  • 如果未指定键,则使用整个 JSON 对象作为内容
  • 使用提供的 JsonMetadataGenerator 生成元数据(如果未提供,则为空)
  • 创建包含提取内容和元数据的 Document 对象

使用 JSON Pointer

JsonReader 支持使用 JSON Pointer 提取 JSON 文档的特定部分,便于从复杂嵌套结构中提取数据。

get(String pointer) 方法

public List<Document> get(String pointer)

此方法允许你使用 JSON Pointer 来检索 JSON 文档中的特定部分。

参数
  • pointer:JSON Pointer 字符串(RFC 6901 定义),用于定位 JSON 元素
返回值
  • 返回一个 List<Document>,包含通过 Pointer 定位的 JSON 元素解析出的文档
行为
  • 使用提供的 JSON Pointer 定位 JSON 中的特定位置
  • Pointer 有效且指向存在元素时:
    • JSON 对象:返回包含单个 Document 的列表
    • JSON 数组:返回每个元素对应的 Document 列表
  • Pointer 无效或指向不存在的元素时,抛出 IllegalArgumentException
示例
JsonReader jsonReader = new JsonReader(resource, "description");
List<Document> documents = 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 提供两个构造器选项:

  • TextReader(String resourceUrl)
  • TextReader(Resource resource)

参数说明

  • resourceUrl:要读取的资源 URL 字符串
  • resource:指向文本文件的 Spring Resource 对象

配置方法

  • setCharset(Charset charset):设置读取文本文件的字符集,默认 UTF-8
  • getCustomMetadata():返回一个可变 Map,可用于为生成的 Document 添加自定义元数据

行为说明

TextReader 处理文本内容的流程如下:

  1. 将整个文本文件的内容读取为单个 Document 对象
  2. 文档内容即为文件内容
  3. 自动添加元数据到 Document
  • charset:读取文件所用字符集(默认 "UTF-8"
  • source:源文本文件名
  1. 通过 getCustomMetadata() 添加的自定义元数据也会包含在 Document

注意事项

  • TextReader 会将整个文件内容加载到内存中,因此不适合非常大的文件
  • 如果需要将文本拆分为更小的块,可以在读取后使用文本拆分器,例如 TokenTextSplitter
    List<Document> documents = textReader.get();
    List<Document> splitDocuments = new TokenTextSplitter().apply(documents);
    
  • TextReader 使用 Spring 的 Resource 抽象,可以从多种来源读取文本(classpath、文件系统、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")           // 提取 <article> 标签中的段落
            .charset("ISO-8859-1")           // 使用 ISO-8859-1 编码
            .includeLinkUrls(true)           // 在元数据中包含链接 URL
            .metadataTags(List.of("author", "date")) // 提取 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> 内的所有文本(默认 false
  • groupByElement:如果为 true,每个匹配选择器的元素创建一个单独的 Document(默认 false
  • includeLinkUrls:如果为 true,提取绝对链接 URL 并添加到元数据(默认 false
  • metadataTags:要提取内容的 <meta> 标签列表(默认 ["description", "keywords"]
  • additionalMetadata:为所有生成的 Document 对象添加自定义元数据

示例 HTML 文档: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 决定用于提取文本的 HTML 元素。
  • 如果 allElementstrue,则 <body> 内的所有文本会被提取到单个 Document 中。
  • 如果 groupByElementtrue,则每个匹配选择器的元素都会生成一个单独的 Document
  • 如果 allElementsgroupByElement 都为 false,则所有匹配选择器的元素文本会使用 separator 拼接到一个 Document 中。
  • 文档标题、指定 <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)   // 水平分隔线创建新 Document
            .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 示例应用:

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

读取器会保留文档内容中的格式,例如行内代码、列表和文本样式。

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 文档都包含目录信息。

依赖项

可以通过 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 文档。

依赖项

可以通过 Maven 或 Gradle 将依赖添加到项目中:

Maven:

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

转换器

文本分割器

TextSplitter 是一个抽象基类,用于将文档拆分成适合 AI 模型上下文窗口的片段。

Token 文本分割器

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

  • TokenTextSplitter():使用默认设置创建分割器。
  • TokenTextSplitter(int defaultChunkSize, int minChunkSizeChars, int minChunkLengthToEmbed, int maxNumChunks, boolean keepSeparator):自定义参数创建分割器。

参数说明:

  • defaultChunkSize:每个文本块的目标大小(以令牌计,默认 800)。
  • minChunkSizeChars:每个文本块的最小字符数(默认 350)。
  • minChunkLengthToEmbed:包含的最小文本块长度(默认 5)。
  • maxNumChunks:从文本生成的最大块数(默认 10000)。
  • keepSeparator:是否保留分隔符(如换行符,默认 true)。

行为说明:

  1. 使用 CL100K_BASE 编码将输入文本编码为令牌。
  2. 根据 defaultChunkSize 将编码后的文本分割为若干块。
  3. 对每个块:
  • 将块解码回文本。
  • 尝试在 minChunkSizeChars 之后寻找合适的断点(句号、问号、感叹号或换行)。
  • 找到断点则在该处截断。
  • 修剪文本,并根据 keepSeparator 决定是否移除换行符。
  • 如果块长度大于 minChunkLengthToEmbed,则加入输出列表。
  1. 重复以上步骤直至所有令牌处理完毕或达到 maxNumChunks
  2. 剩余文本若大于 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 = splitter.apply(List.of(doc1, doc2));

for (Document doc : splitDocuments) {
    System.out.println("Chunk: " + doc.getContent());
    System.out.println("Metadata: " + doc.getMetadata());
}

注意事项:

  • TokenTextSplitter 使用来自 jtokkit 库的 CL100K_BASE 编码,与最新的 OpenAI 模型兼容。
  • 分割器尽量在句子边界处分割文本,以生成语义完整的块。
  • 原始文档的元数据会被保留并复制到所有派生文本块。
  • 若原文档设置了内容格式化器,且 copyContentFormatter 为 true(默认),则会复制到派生块。
  • 该分割器特别适合为具有令牌限制的大型语言模型准备文本,确保每个块都在模型处理能力范围内。

内容格式转换器

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

关键词元数据富集器

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. 对每个输入文档,根据文档内容创建提示(prompt)。
  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 = // 初始化你的 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(doc));

Document enrichedDoc = enrichedDocs.get(0);
String keywords = (String) enrichedDoc.getMetadata().get("excerpt_keywords");
System.out.println("提取的关键词: " + keywords);

注意事项

  • KeywordMetadataEnricher 需要一个可用的 ChatModel 来生成关键词。
  • 关键词数量必须大于等于 1。
  • 每个处理过的文档都会增加 "excerpt_keywords" 元数据字段。
  • 生成的关键词以逗号分隔的字符串形式返回。
  • 这个组件特别适用于提升文档可检索性,以及为文档生成标签或分类。
  • 在 Builder 模式下,如果设置了 keywordsTemplate 参数,则会忽略 keywordCount 参数。

摘要元数据富集器

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 模型。
  • summaryTypes:指定要生成的摘要类型列表(PREVIOUSCURRENTNEXT)。
  • summaryTemplate:自定义摘要生成模板(可选)。
  • metadataMode:指定生成摘要时如何处理文档元数据(可选)。

行为

SummaryMetadataEnricher 的处理流程如下:

  1. 对每个输入文档,根据文档内容和指定的摘要模板创建提示(prompt)。
  2. 将提示发送给提供的 ChatModel,生成摘要。
  3. 根据 summaryTypes 参数,将以下元数据添加到每个文档:
  • section_summary:当前文档的摘要
  • prev_section_summary:前一篇文档的摘要(如果存在且请求)
  • next_section_summary:下一篇文档的摘要(如果存在且请求)
  1. 返回添加了摘要元数据的文档列表。

自定义

  • 可以通过提供自定义的 summaryTemplate 来定制摘要生成提示。
  • 默认模板如下:
Here is the content of the section:
{context_str}

Summarize the key topics and entities of the section.

Summary:

其中 {context_str} 会被文档内容替换。

示例

ChatModel 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(doc1, doc2));

// 查看摘要元数据
for (Document doc : enrichedDocs) {
    System.out.println("当前摘要: " + doc.getMetadata().get("section_summary"));
    System.out.println("前一节摘要: " + doc.getMetadata().get("prev_section_summary"));
    System.out.println("下一节摘要: " + 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 提供三个构造函数:

  • 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 时,标记格式如下:

### 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 (向量存储)

提供与各种向量数据库的集成。完整列表请参见 向量数据库文档