ETL パイプライン

抽出、変換、ロード (ETL) フレームワークは、検索拡張生成 (RAG) ユースケース内のデータ処理のバックボーンとして機能します。

ETL パイプラインは、生データソースから構造化ベクトルストアへのフローを調整し、データが AI モデルによる取得に最適な形式であることを保証します。

RAG ユースケースは、データ本体から関連情報を取得して、生成される出力の品質と関連性を向上させることで、生成モデルの機能を強化するためのテキストです。

API の概要

ETL パイプラインは、Document インスタンスを作成、変換、保存します。

Spring AI Message API

Document クラスには、テキスト、メタデータ、オプションでイメージ、オーディオ、ビデオなどの追加のメディア型が含まれます。

ETL パイプラインには 3 つの主要なコンポーネントがあります。

  •  Supplier<List<Document>> を実装した DocumentReader 

  •  Function<List<Document>, List<Document>> を実装した DocumentTransformer 

  •  Consumer<List<Document>> を実装した DocumentWriter 

Document クラスのコンテンツは、DocumentReader の助けを借りて、PDF、テキストファイル、その他のドキュメント型から作成されます。

単純な ETL パイプラインを構築するには、各型のインスタンスを チェーンで結合します。

etl pipeline

これら 3 つの 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 インターフェースと実装を示しています。

etl class diagram

DocumentReaders

JSON

JsonReader は JSON ドキュメントを処理し、それを Document オブジェクトのリストに変換します。

サンプル

@Component
class MyJsonReader {

	private final Resource resource;

    MyAiAppComponent(@Value("classpath:bikes.json") Resource resource) {
        this.resource = resource;
    }

	List<Document> loadJsonAsDocuments() {
        JsonReader jsonReader = new JsonReader(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: 結果の Document オブジェクトのテキストコンテンツとして使用される JSON からのキーの配列。

  • jsonMetadataGenerator: 各 Document のメタデータを作成するためのオプションの JsonMetadataGenerator

振る舞い

JsonReader は JSON コンテンツを次のように処理します。

  • JSON 配列と単一の JSON オブジェクトの両方を処理できます。

  • 各 JSON オブジェクト (配列または単一のオブジェクト) について:

    • 指定された jsonKeysToUse に基づいてコンテンツを抽出します。

    • キーが指定されていない場合は、JSON オブジェクト全体がコンテンツとして使用されます。

    • 提供された JsonMetadataGenerator (提供されていない場合は空の JsonMetadataGenerator ) を使用してメタデータを生成します。

    • 抽出されたコンテンツとメタデータを含む Document オブジェクトを作成します。

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 が jsonKeysToUse として "description" で構成されている場合、配列内の各バイクの「説明」フィールドの値がコンテンツとなる Document オブジェクトが作成されます。

ノート

  • JsonReader は JSON 解析に Jackson を使用します。

  • 配列のストリーミングを使用することで、大きな 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(resource);
		textReader.getCustomMetadata().put("filename", "text-source.txt");

		return textReader.read();
    }
}

コンストラクターオプション

TextReader には 2 つのコンストラクターオプションがあります。

  1. TextReader(String resourceUrl)

  2. TextReader(Resource resource)

パラメーター

  • resourceUrl: 読み取るリソースの URL を表す文字列。

  • resource: テキストファイルを指す Spring Resource オブジェクト。

構成

  • setCharset(Charset charset): テキストファイルの読み取りに使用する文字セットを設定します。デフォルトは UTF-8 です。

  • getCustomMetadata(): ドキュメントのカスタムメタデータを追加できる変更可能なマップを返します。

振る舞い

TextReader はテキストコンテンツを次のように処理します。

  • テキストファイルの内容全体を単一の Document オブジェクトに読み込みます。

  • ファイルの内容が Document の内容になります。

  • メタデータは Document に自動的に追加されます:

    • charset: The character set used to read the file (default: "UTF-8").

    • source: The filename of the source text file.

  • Any custom metadata added via getCustomMetadata() is included in the Document.

ノート

  • The TextReader reads the entire file content into memory, so it may not be suitable for very large files.

  • If you need to split the text into smaller chunks, you can use a text splitter like TokenTextSplitter after reading the document:

List<Document> documents = textReader.get();
List<Document> splitDocuments = new TokenTextSplitter().apply(documents);
  • The reader uses Spring’s Resource abstraction, allowing it to read from various sources (classpath, file system, URL, etc.).

  • Custom metadata can be added to all documents created by the reader using the getCustomMetadata() method.

マークダウン

The MarkdownDocumentReader processes Markdown documents, converting them into a list of Document objects.

サンプル

@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(resource, config);
        return reader.get();
    }
}

The MarkdownDocumentReaderConfig allows you to customize the behavior of the MarkdownDocumentReader:

  • horizontalRuleCreateDocument: When set to true, horizontal rules in the Markdown will create new Document objects.

  • includeCodeBlock: When set to true, code blocks will be included in the same Document as the surrounding text. When false, code blocks create separate Document objects.

  • includeBlockquote: When set to true, blockquotes will be included in the same Document as the surrounding text. When false, blockquotes create separate Document objects.

  • additionalMetadata: Allows you to add custom metadata to all created Document objects.

Sample Document: 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
```

振る舞い: The MarkdownDocumentReader processes the Markdown content and creates Document objects based on the configuration:

  • Headers become metadata in the Document objects.

  • Paragraphs become the content of Document objects.

  • Code blocks can be separated into their own Document objects or included with surrounding text.

  • Blockquotes can be separated into their own Document objects or included with surrounding text.

  • Horizontal rules can be used to split the content into separate Document objects.

The reader preserves formatting like inline code, lists, and text styling within the content of the Document objects.

PDF Page

PagePdfDocumentReader は Apache PdfBox ライブラリを使用して PDF ドキュメントを解析します

Add the dependency to your project using Maven or 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 Paragraph

ParagraphPdfDocumentReader は、PDF カタログ (TOC など) 情報を使用して、入力 PDF をテキスト段落に分割し、段落ごとに 1 つの Document を出力します。注: すべての PDF ドキュメントに PDF カタログが含まれているわけではありません。

依存関係

Add the dependency to your project using Maven or 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();
    }
}

ティカ (DOCX, PPTX, HTML…​)

TikaDocumentReader は、Apache Tika を使用して、PDF、DOC/DOCX、PPT/PPTX、HTML などのさまざまなドキュメント形式からテキストを抽出します。サポートされている形式の包括的なリストについては、Tika ドキュメント [Apache] (英語) を参照してください。

依存関係

<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(resource);
        return tikaDocumentReader.read();
    }
}

Transformers

TextSplitter

TextSplitter は、AI モデルのコンテキストウィンドウに合わせてドキュメントを分割するのに役立つ抽象基本クラスです。

TokenTextSplitter

The TokenTextSplitter is an implementation of TextSplitter that splits text into chunks based on token count, using the `CL100K_BASE encoding.

使用方法

@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 には 2 つのコンストラクターオプションがあります。

  1. TokenTextSplitter(): Creates a splitter with default settings.

  2. TokenTextSplitter(int defaultChunkSize, int minChunkSizeChars, int minChunkLengthToEmbed, int maxNumChunks, boolean keepSeparator)

パラメーター

  • defaultChunkSize: The target size of each text chunk in tokens (default: 800).

  • minChunkSizeChars: The minimum size of each text chunk in characters (default: 350).

  • minChunkLengthToEmbed: The minimum length of a chunk to be included (default: 5).

  • maxNumChunks: The maximum number of chunks to generate from a text (default: 10000).

  • keepSeparator: Whether to keep separators (like newlines) in the chunks (default: true).

振る舞い

TokenTextSplitter はテキストコンテンツを次のように処理します。

  1. It encodes the input text into tokens using the CL100K_BASE encoding.

  2. It splits the encoded text into chunks based on the defaultChunkSize.

  3. For each chunk:

    1. It decodes the chunk back into text.

    2. It attempts to find a suitable break point (period, question mark, exclamation mark, or newline) after the minChunkSizeChars.

    3. If a break point is found, it truncates the chunk at that point.

    4. It trims the chunk and optionally removes newline characters based on the keepSeparator setting.

    5. If the resulting chunk is longer than minChunkLengthToEmbed, it’s added to the output.

  4. This process continues until all tokens are processed or maxNumChunks is reached.

  5. Any remaining text is added as a final chunk if it’s longer than 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());
}

ノート

  • The TokenTextSplitter uses the CL100K_BASE encoding from the jtokkit library, which is compatible with newer OpenAI models.

  • The splitter attempts to create semantically meaningful chunks by breaking at sentence boundaries where possible.

  • Metadata from the original documents is preserved and copied to all chunks derived from that document.

  • The content formatter (if set) from the original document is also copied to the derived chunks if copyContentFormatter is set to true (default behavior).

  • This splitter is particularly useful for preparing text for large language models that have token limits, ensuring that each chunk is within the model’s processing capacity. === ContentFormatTransformer Ensures uniform content formats across all documents.

KeywordMetadataEnricher

The KeywordMetadataEnricher is a DocumentTransformer that uses a generative AI model to extract keywords from document content and add them as metadata.

使用方法

@Component
class MyKeywordEnricher {

    private final ChatModel chatModel;

    MyKeywordEnricher(ChatModel chatModel) {
        this.chatModel = chatModel;
    }

    List<Document> enrichDocuments(List<Document> documents) {
        KeywordMetadataEnricher enricher = new KeywordMetadataEnricher(chatModel, 5);
        return enricher.apply(documents);
    }
}

コンストラクター

The KeywordMetadataEnricher constructor takes two parameters:

  1. ChatModel chatModel: The AI model used for generating keywords.

  2. int keywordCount: The number of keywords to extract for each document.

振る舞い

The KeywordMetadataEnricher processes documents as follows:

  1. For each input document, it creates a prompt using the document’s content.

  2. It sends this prompt to the provided ChatModel to generate keywords.

  3. The generated keywords are added to the document’s metadata under the key "excerpt_keywords".

  4. The enriched documents are returned.

カスタム

The keyword extraction prompt can be customized by modifying the KEYWORDS_TEMPLATE constant in the class. The default template is:

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

Where {context_str} is replaced with the document content, and %s is replaced with the specified keyword count.

サンプル

ChatModel chatModel = // initialize your chat model
KeywordMetadataEnricher enricher = new KeywordMetadataEnricher(chatModel, 5);

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("Extracted keywords: " + keywords);

ノート

  • The KeywordMetadataEnricher requires a functioning ChatModel to generate keywords.

  • The keyword count must be 1 or greater.

  • The enricher adds the "excerpt_keywords" metadata field to each processed document.

  • The generated keywords are returned as a comma-separated string.

  • This enricher is particularly useful for improving document searchability and for generating tags or categories for documents.

SummaryMetadataEnricher

The SummaryMetadataEnricher is a DocumentTransformer that uses a generative AI model to create summaries for documents and add them as metadata. It can generate summaries for the current document, as well as adjacent documents (previous and next).

使用方法

@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 enricher.apply(documents);
    }
}

コンストラクター

The SummaryMetadataEnricher provides two constructors:

  1. SummaryMetadataEnricher(ChatModel chatModel, List<SummaryType> summaryTypes)

  2. SummaryMetadataEnricher(ChatModel chatModel, List<SummaryType> summaryTypes, String summaryTemplate, MetadataMode metadataMode)

パラメーター

  • chatModel: The AI model used for generating summaries.

  • summaryTypes: A list of SummaryType enum values indicating which summaries to generate (PREVIOUS, CURRENT, NEXT).

  • summaryTemplate: A custom template for summary generation (optional).

  • metadataMode: Specifies how to handle document metadata when generating summaries (optional).

振る舞い

The SummaryMetadataEnricher processes documents as follows:

  1. For each input document, it creates a prompt using the document’s content and the specified summary template.

  2. It sends this prompt to the provided ChatModel to generate a summary.

  3. Depending on the specified summaryTypes, it adds the following metadata to each document:

    • section_summary: Summary of the current document.

    • prev_section_summary: Summary of the previous document (if available and requested).

    • next_section_summary: Summary of the next document (if available and requested).

  4. The enriched documents are returned.

カスタム

The summary generation prompt can be customized by providing a custom summaryTemplate. The default template is:

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

The provided example demonstrates the expected behavior:

  • For a list of two documents, both documents receive a section_summary.

  • The first document receives a next_section_summary but no prev_section_summary.

  • The second document receives a prev_section_summary but no next_section_summary.

  • The section_summary of the first document matches the prev_section_summary of the second document.

  • The next_section_summary of the first document matches the section_summary of the second document.

ノート

  • The SummaryMetadataEnricher requires a functioning ChatModel to generate summaries.

  • The enricher can handle document lists of any size, properly handling edge cases for the first and last documents.

  • This enricher is particularly useful for creating context-aware summaries, allowing for better understanding of document relationships in a sequence.

  • The MetadataMode parameter allows control over how existing metadata is incorporated into the summary generation process.

Writers

ファイル

The FileDocumentWriter is a DocumentWriter implementation that writes the content of a list of Document objects into a file.

使用方法

@Component
class MyDocumentWriter {

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

コンストラクター

The FileDocumentWriter provides three constructors:

  1. FileDocumentWriter(String fileName)

  2. FileDocumentWriter(String fileName, boolean withDocumentMarkers)

  3. FileDocumentWriter(String fileName, boolean withDocumentMarkers, MetadataMode metadataMode, boolean append)

パラメーター

  • fileName: The name of the file to write the documents to.

  • withDocumentMarkers: Whether to include document markers in the output (default: false).

  • metadataMode: Specifies what document content to be written to the file (default: MetadataMode.NONE).

  • append: If true, data will be written to the end of the file rather than the beginning (default: false).

振る舞い

The FileDocumentWriter processes documents as follows:

  1. It opens a FileWriter for the specified file name.

  2. For each document in the input list:

    1. If withDocumentMarkers is true, it writes a document marker including the document index and page numbers.

    2. It writes the formatted content of the document based on the specified metadataMode.

  3. The file is closed after all documents have been written.

Document Markers

When withDocumentMarkers is set to true, the writer includes markers for each document in the following format:

### Doc: [index], pages:[start_page_number,end_page_number]

Metadata Handling

The writer uses two specific metadata keys:

  • page_number: Represents the starting page number of the document.

  • end_page_number: Represents the ending page number of the document.

These are used when writing document markers.

サンプル

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

This will write all documents to "output.txt", including document markers, using all available metadata, and appending to the file if it already exists.

ノート

  • The writer uses FileWriter, so it writes text files with the default character encoding of the operating system.

  • If an error occurs during writing, a RuntimeException is thrown with the original exception as its cause.

  • The metadataMode parameter allows control over how existing metadata is incorporated into the written content.

  • This writer is particularly useful for debugging or creating human-readable outputs of document collections.

VectorStore

さまざまなベクトルストアとの統合を提供します。完全なリストについては、ベクトル DB ドキュメントを参照してください。