主题
Docling
Docling 是 IBM Research 的文档处理引擎,能从 PDF、DOCX、PPTX 等多种文档格式中提取文本与结构。它提供 OCR、表格抽取与版面分析等高级能力。
本集成通过 REST API 与一个运行中的 docling-serve 实例通信,并使用 官方 Docling Java 库 构建。
Maven 依赖
xml
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-document-parser-docling</artifactId>
<version>1.22.0-beta32</version>
</dependency>本模块依赖 docling-serve-api(接口),并把 docling-serve-client(参考 HTTP 客户端)作为可选的运行时依赖包含进来。
如果你不使用 Spring Boot 或 Quarkus(它们可能自带 DoclingServeApi 实现),你还必须显式添加参考客户端:
xml
<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 客户端并传给解析器:
java
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 或 classpath 加载文档——并让 file_name、absolute_directory_path、url 等标准元数据被自动填充——请将 langchain4j 模块的加载器与本解析器配合使用:
java
Document fromFile = FileSystemDocumentLoader.loadDocument(Path.of("/tmp/report.pdf"), parser);
Document fromUrl = UrlDocumentLoader.load("https://example.com/report.pdf", parser);选择 Docling 操作
Docling 所执行的操作——转换、层级切块或混合切块——由传给 builder 的 DocumentRequest 模板控制。该模板承载除文档来源外的一切:解析器在每次调用时,会把待解析文档的来源注入模板的一个新副本,并把它路由到匹配的 Docling 端点。若未提供模板,则使用一个普通的 ConvertDocumentRequest。
要自定义转换,请提供一个携带你 ConvertDocumentOptions 的 ConvertDocumentRequest:
java
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(...),但更建议你传入一个ConvertDocumentRequest,这样也能选择 Docling 的操作。
要对文档切块而非转换,请提供一个 HierarchicalChunkDocumentRequest 或 HybridChunkDocumentRequest:
java
DoclingDocumentParser parser = DoclingDocumentParser.builder()
.doclingClient(api)
.documentRequest(HybridChunkDocumentRequest.builder().build())
.build();模板所携带的任何文档来源都会被忽略——解析器会注入待解析文档的来源。该模板必须描述一个 in-body 操作:一个 BatchConvertDocumentRequest,或一个携带非 in-body 目标(例如 ZipTarget 或 PresignedUrlTarget)的请求,都会导致 build() 失败。
自定义文本抽取
默认情况下,解析器从转换响应中提取 markdown 内容,并从切块响应中拼接(以换行分隔)切块文本。你可以用两个 builder 方法自定义文本的抽取方式,它们各自接收具体的响应类型——无需强制转换:
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 抽取器二者之一——一对中的两个变体互斥,同时配置二者(documentTextExtractor + documentExtractor,或 chunkTextExtractor + chunkExtractor)会让 build() 抛出 IllegalArgumentException。
java
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 元数据条目。
例如,要提取 HTML 内容而非 markdown:
java
DoclingDocumentParser parser = DoclingDocumentParser.builder()
.doclingClient(api)
.documentTextExtractor(response -> response.getDocument().getHtmlContent())
.build();或者提取纯文本:
java
DoclingDocumentParser parser = DoclingDocumentParser.builder()
.doclingClient(api)
.documentTextExtractor(response -> response.getDocument().getTextContent())
.build();以及自定义切块的拼接方式:
java
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 契约。对于响应式或非阻塞流水线,请使用 parseAsync(InputStream),它返回一个 CompletionStage<Document>:
java
CompletionStage<Document> stage = parser.parseAsync(inputStream);parseAsync 把读取输入流与准备请求卸载到一个后台线程(经由 CompletableFuture.supplyAsync,即公共 ForkJoinPool),因此它会不阻塞调用线程地返回。解析得到的 Document——或任何失败(null/空流、请求构建错误、或来自 Docling 调用的错误)——都通过返回的 stage 投递:它以异常完成而非抛出异常,因此响应式调用方无需外层的 try/catch。
它可以直接接入一个响应式类型,例如 Mutiny:
java
Uni.createFrom().completionStage(() -> parser.parseAsync(inputStream));线程说明。
parseAsync只控制流在哪里被读取;Docling 网络调用本身在 Docling 客户端所拥有的线程上运行。参考实现docling-serve-client在公共ForkJoinPool上执行其 HTTP 与任务轮询,且不暴露任何执行器来改变这一点。若你需要控制阻塞的网络工作在哪里运行(例如专用线程池或虚拟线程),请提供一个自定义的DoclingRequestExecutor,在你的执行器上调用客户端。
自定义 Docling 的调用方式
默认情况下,解析器调用与请求类型匹配的 Docling 异步转换/切块端点。这些端点已经会提交任务、轮询完成并获取结果——全在公共 ForkJoinPool 上,因此无需为了获得这种任务/队列行为而自定义执行器。当你想要以下能力时,请提供一个 DoclingRequestExecutor:
- 在 Docling 调用外围添加重试或退避;
- 在你自己的执行器(专用线程池或虚拟线程)上运行阻塞调用,而非公共
ForkJoinPool;或 - 执行完全自定义的编排(例如以自定义轮询节奏自行驱动任务/队列端点)。
要在失败时重试转换一次:
java
DoclingDocumentParser parser = DoclingDocumentParser.builder()
.doclingClient(api)
.requestExecutor((client, request) -> {
ConvertDocumentRequest convertRequest = (ConvertDocumentRequest) request;
return client.convertSourceAsync(convertRequest)
.exceptionallyCompose(error -> client.convertSourceAsync(convertRequest));
})
.build();要在你自己的执行器上运行阻塞工作(这里是虚拟线程)——这使用同步的 convertSource,从而网络调用在你提供的线程池上运行,而非公共 ForkJoinPool:
java
Executor executor = Executors.newVirtualThreadPerTaskExecutor();
DoclingDocumentParser parser = DoclingDocumentParser.builder()
.doclingClient(api)
.requestExecutor((client, request) -> CompletableFuture.supplyAsync(
() -> client.convertSource((ConvertDocumentRequest) request), executor))
.build();当提供了自定义执行器时,解析器不再限制请求类型或目标(这些语义由执行器负责),因此诸如 BatchConvertDocumentRequest 或非 in-body 目标等操作也变得可用。
API
DoclingDocumentParserDoclingRequestExecutor