Docling
Docling は IBM Research のドキュメント処理エンジンで、PDF、DOCX、PPTX などのさまざまなドキュメント形式からテキストと構造を抽出します。OCR、表の抽出、レイアウト解析などの高度な機能を提供します。
この統合は、REST API を介して実行中の docling-serve インスタンスと通信し、公式 Docling Java ライブラリを使用して構築されています。
Maven 依存関係
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-document-parser-docling</artifactId>
<version>1.23.0-beta33</version>
</dependency>
このモジュールは docling-serve-api(インターフェース)に依存しており、docling-serve-client(リファレンス HTTP クライアント)をオプションのランタイム依存関係として含んでいます。
Spring Boot や Quarkus を使用していない場合(これらは独自の DoclingServeApi 実装を提供している場合があります)、リファレンスクライアントも明示的に追加する必要があります:
<dependency>
<groupId>ai.docling</groupId>
<artifactId>docling-serve-client</artifactId>
<version>0.6.4</version>
</dependency>
Quarkus や Spring Boot などのフレームワークは、独自の Docling 統合を提供しています。これらの特定の実装を組み込む方法については、Docling Java ドキュメントを参照してください。
使い方
docling-serve インスタンスを起動し(docling-serve ドキュメントを参照)、DoclingServeApi クライアントを構築してパーサーに渡します:
DoclingServeApi api = DoclingServeApi.builder()
.baseUrl("http://localhost:5001")
.build();
DoclingDocumentParser parser = DoclingDocumentParser.builder()
.doclingClient(api)
.build();
Document document = parser.parse(inputStream);
String text = document.text();
ファイル、ディレクトリ、URL、またはクラスパスからドキュメントを読み込み、
file_name、absolute_directory_path、url などの標準的なメタデータを
自動的に設定するには、langchain4j モジュールのローダーを
パーサーと併せて使用します:
Document fromFile = FileSystemDocumentLoader.loadDocument(Path.of("/tmp/report.pdf"), parser);
Document fromUrl = UrlDocumentLoader.load("https://example.com/report.pdf", parser);
Docling の操作の選択
Docling が実行する操作(変換、階層的チャンク分割、ハイブリッドチャンク分割)は、
ビルダーに渡される DocumentRequest
テンプレートによって制御されます。このテンプレートには、ドキュメントのソース以外のすべてが含まれます。
パーサーは、呼び出しのたびに、解析対象のドキュメントのソースをテンプレートの
新しいコピーに注入し、対応する Docling
エンドポイントにルーティングします。テンプレートが指定されていない場合は、プレーンな ConvertDocumentRequest が使用されます。
変換をカスタマイズするには、ConvertDocumentOptions を保持する
ConvertDocumentRequest を渡します:
ConvertDocumentOptions options = ConvertDocumentOptions.builder()
// configure options here
.build();
DoclingDocumentParser parser = DoclingDocumentParser.builder()
.doclingClient(api)
.documentRequest(ConvertDocumentRequest.builder()
.options(options)
.build())
.build();
非推奨の
Builder.options(ConvertDocumentOptions)ショートカットは引き続き利用可能で、 単にdocumentRequest(...)に委譲しますが、Docling の操作も選択できるよう、ConvertDocumentRequestを渡すことを推奨します。
ドキュメントを変換する代わりにチャンク分割するには、
HierarchicalChunkDocumentRequest または HybridChunkDocumentRequest を渡します:
DoclingDocumentParser parser = DoclingDocumentParser.builder()
.doclingClient(api)
.documentRequest(HybridChunkDocumentRequest.builder().build())
.build();
テンプレートに含まれるドキュメントソースは無視されます。パーサーが
解析対象のドキュメントのソースを注入します。テンプレートは in-body 操作を記述している必要があります。
BatchConvertDocumentRequest、または in-body ではない
ターゲット(例:ZipTarget や PresignedUrlTarget)を持つリクエストの場合、build() は
失敗します。
カスタムテキスト抽出
デフォルトでは、パーサーは変換レスポンスから markdown コンテンツを抽出し、チャンクレスポンスからは チャンクのテキスト(改行区切り)を連結します。2 つのビルダーメソッドを使って、 テキストの抽出方法をカスタマ イズできます。各メソッドは具体的なレスポンス型を受け取るため、キャストは不要です:
documentTextExtractor(Function<InBodyConvertDocumentResponse, String>)— 変換 リクエストに使用されます。さまざまな形式(markdown、HTML、text、doctags、JSON)の変換済みドキュメント、変換エラー、 処理時間、ステータス情報にアクセスできます。chunkTextExtractor(Function<ChunkDocumentResponse, String>)— チャンク リクエストに使用されます。チャンクとそのメタデータにアクセスできます。
結果の Document 全体を制御したい場合(たとえば、構造化レスポンスから得られる出典情報などの
追加の Metadata を付与したい場合)は、代わりに
Document を返すバリアントを使用します:
documentExtractor(Function<InBodyConvertDocumentResponse, Document>)chunkExtractor(Function<ChunkDocumentResponse, Document>)
各レスポンスの種類ごとに、テキスト抽出器または Document 抽出器の
どちらか一方を設定します。ペアの 2 つのバリアントは相互に排他的であり、両方を設定すると
(documentTextExtractor + documentExtractor、または
chunkTextExtractor + chunkExtractor)、build() が
IllegalArgumentException をスローします。
DoclingDocumentParser parser = DoclingDocumentParser.builder()
.doclingClient(api)
.documentRequest(ConvertDocumentRequest.builder()
.options(ConvertDocumentOptions.builder().toFormat(OutputFormat.JSON).build())
.build())
.documentExtractor(response -> {
DoclingDocument doc = response.getDocument().getJsonContent();
String fullText = buildFullText(doc);
return Document.from(fullText, buildProvenanceMetadata(doc, fullText));
})
.build();
パーサーは、抽出器が返す内容に加えて、常に document_size_bytes メタデータエントリを追加します。
たとえば、markdown の代わりに HTML コンテンツを抽出するには:
DoclingDocumentParser parser = DoclingDocumentParser.builder()
.doclingClient(api)
.documentTextExtractor(response -> response.getDocument().getHtmlContent())
.build();
またはプレーンテキストを抽出するには:
DoclingDocumentParser parser = DoclingDocumentParser.builder()
.doclingClient(api)
.documentTextExtractor(response -> response.getDocument().getTextContent())
.build();
チャンクの連結方法をカスタマイズするには:
DoclingDocumentParser parser = DoclingDocumentParser.builder()
.doclingClient(api)
.documentRequest(HybridChunkDocumentRequest.builder().build())
.chunkTextExtractor(response -> response.getChunks().stream()
.map(Chunk::getText)
.collect(Collectors.joining("\n\n")))
.build();
非同期 / リアクティブな解析
DoclingDocumentParser.parse(InputStream) は、基盤となる非同期呼び出しをブロックすることで、同期的な
DocumentParser の契約を満たします。リアクティブまたはノンブロッキングなパイプラインでは、
CompletionStage<Document> を返す parseAsync(InputStream) を使用します:
CompletionStage<Document> stage = parser.parseAsync(inputStream);
parseAsync は、入力ストリームの読み取りとリクエストの準備をバックグラウンドスレッド
(CompletableFuture.supplyAsync 経由、つまり共通の
ForkJoinPool)にオフロードするため、呼び出しスレッドをブロックせずに戻ります。解析された
Document、または任意の失敗(null/空のストリーム、リクエスト構築エラー、
Docling 呼び出しからのエラー)は、返される stage を通じて配信されます。例外をスローするのではなく
例外的に完了するため、リアクティブな呼び出し側は
外側の try/catch を必要としません。
これは、たとえば Mutiny のようなリアクティブ型にそのまま組み込めます:
Uni.createFrom().completionStage(() -> parser.parseAsync(inputStream));
スレッドに関する注意。
parseAsyncが制御するのは、ストリームが読み取られる場所だけです。 Docling のネットワーク呼び出し自体は、Docling クライアントが所有するスレッド上で実行されます。リファレンスのdocling-serve-clientは、HTTP とタスクのポーリングを共通のForkJoinPool上で実行し、 これを変更するための executor は公開していません。ブロッキングなネットワーク処理が実行される場所 (たとえば専用のプールや仮想スレッド)を制御する必要がある場合は、 独自の executor 上でクライアントを呼び出すカスタムのDoclingRequestExecutorを指定してください。
Docling の呼び出し方法のカスタマイズ
デフォルトでは、パーサーはリクエストの型に対応する Docling の非同期 convert/chunk エンドポイントを呼び出します。
これらのエンドポイントは、すでにタスクの送信、完了のポーリング、結果の取得をすべて共通の ForkJoinPool 上で行うため、
そのタスク/キューの動作を得るためだけにカスタム executor は必要ありません。次のような場合に
DoclingRequestExecutor を指定します:
- Docling 呼び出しの前後にリトライやバックオフを追加する。
- ブロッキング呼び出しを、共通の
ForkJoinPoolではなく独自の executor(専用のプールや 仮想スレッド)上で実行する。 - 完全にカスタムなオーケストレーションを行う(たとえば、カスタムのポーリング間隔で タスク/キューのエンドポイントを自分で駆動する)。
失敗時に変換を 1 回リトライするには:
DoclingDocumentParser parser = DoclingDocumentParser.builder()
.doclingClient(api)
.requestExecutor((client, request) -> {
ConvertDocumentRequest convertRequest = (ConvertDocumentRequest) request;
return client.convertSourceAsync(convertRequest)
.exceptionallyCompose(error -> client.convertSourceAsync(convertRequest));
})
.build();
ブロッキング処理を独自の executor(ここでは仮想スレッド)上で実行するには、
同期の convertSource を使用します。これにより、ネットワーク呼び出しは共通の ForkJoinPool ではなく、
指定されたプール上で実行されます:
Executor executor = Executors.newVirtualThreadPerTaskExecutor();
DoclingDocumentParser parser = DoclingDocumentParser.builder()
.doclingClient(api)
.requestExecutor((client, request) -> CompletableFuture.supplyAsync(
() -> client.convertSource((ConvertDocumentRequest) request), executor))
.build();
カスタム executor が指定されると、パーサーはリクエストの型やターゲットを制限しなくなる
(そのセマンティクスは executor が担う)ため、
BatchConvertDocumentRequest や in-body ではないターゲットなどの操作が利用可能になります。
APIs
DoclingDocumentParserDoclingRequestExecutor