From 14e39d40a3e15cae33938d6539a723a1ad64222e Mon Sep 17 00:00:00 2001 From: WMK666666 <3496319356@qq.com> Date: Thu, 12 Nov 2026 20:32:24 +0800 Subject: [PATCH] camel-docling: return DocTags content and reject html_split_page on docling-serve Rebased on main now that #29595 is in, as asked. The only conflict was the 4.24 upgrade guide, where #29595 appended its OCR-headers entry after #29591's apiKeyHeader one: the file now carries all three camel-docling sections, apiKeyHeader first, then the OCR headers, then html_split_page. DoclingProducer.java merged cleanly with the OcrSettings refactor from #29595: the html_split_page rejection and the package-private mapToOutputFormat sit below resolveOcrSettings and are untouched by it. DoclingContentExtractor.java is unchanged on main. Content is otherwise the version approved in 55f1def: DoclingContentExtractor reads doctags_content when DocTags is requested, and mapToOutputFormat rejects html_split_page with the reason and the way out, because DocumentResponse has no field for the split pages. --- .../docling/DoclingContentExtractor.java | 5 ++ .../component/docling/DoclingProducer.java | 17 +++- .../docling/DoclingContentExtractorTest.java | 81 +++++++++++++++++++ .../docling/DoclingServeOutputFormatTest.java | 80 ++++++++++++++++++ .../pages/camel-4x-upgrade-guide-4_24.adoc | 7 ++ 5 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 components/camel-ai/camel-docling/src/test/java/org/apache/camel/component/docling/DoclingContentExtractorTest.java create mode 100644 components/camel-ai/camel-docling/src/test/java/org/apache/camel/component/docling/DoclingServeOutputFormatTest.java diff --git a/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingContentExtractor.java b/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingContentExtractor.java index d2766a96216df..e1f56f290bce0 100644 --- a/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingContentExtractor.java +++ b/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingContentExtractor.java @@ -61,6 +61,9 @@ static String extract(ConvertDocumentResponse response, String outputFormat) thr case "text": String text = document.getTextContent(); return text != null ? text : ""; + case "doctags": + String doctags = document.getDoctagsContent(); + return doctags != null ? doctags : ""; case "json": // Return the document JSON content var jsonDoc = document.getJsonContent(); @@ -98,6 +101,8 @@ private static String mapOutputFormat(String outputFormat) { case "text": case "txt": return "text"; + case "doctags": + return "doctags"; default: return "md"; } diff --git a/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingProducer.java b/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingProducer.java index 430581c930304..206d5b3dfa614 100644 --- a/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingProducer.java +++ b/components/camel-ai/camel-docling/src/main/java/org/apache/camel/component/docling/DoclingProducer.java @@ -1726,7 +1726,16 @@ private String extractConvertedContent(ConvertDocumentResponse response, String return DoclingContentExtractor.extract(response, outputFormat); } - private OutputFormat mapToOutputFormat(String outputFormat) { + /** + * Maps the {@code outputFormat} of the exchange to the format asked of docling-serve. + *
+ * docling-serve fills only the content fields of the formats it was asked for, and it has no field for the split + * pages: {@code DocumentResponse} carries {@code doctagsContent}, {@code htmlContent}, {@code jsonContent}, + * {@code markdownContent} and {@code textContent}, and the html one is filled only when HTML is requested. Asking + * it for {@code html_split_page} therefore returns a response whose content fields are all null, and the exchange + * body comes out empty, so it fails with the reason instead. + */ + OutputFormat mapToOutputFormat(String outputFormat) { if (outputFormat == null) { return OutputFormat.MARKDOWN; } @@ -1745,7 +1754,11 @@ private OutputFormat mapToOutputFormat(String outputFormat) { case "doctags": return OutputFormat.DOCTAGS; case "html_split_page": - return OutputFormat.HTML_SPLIT_PAGE; + throw new IllegalArgumentException( + "outputFormat " + outputFormat + " is not supported: docling-serve does not return the split" + + " page HTML in the response body, so the exchange body would be" + + " empty. Use outputFormat=html to get the HTML of the whole" + + " document"); default: return OutputFormat.MARKDOWN; } diff --git a/components/camel-ai/camel-docling/src/test/java/org/apache/camel/component/docling/DoclingContentExtractorTest.java b/components/camel-ai/camel-docling/src/test/java/org/apache/camel/component/docling/DoclingContentExtractorTest.java new file mode 100644 index 0000000000000..c0a10b56d9a3e --- /dev/null +++ b/components/camel-ai/camel-docling/src/test/java/org/apache/camel/component/docling/DoclingContentExtractorTest.java @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.camel.component.docling; + +import ai.docling.serve.api.convert.response.ConvertDocumentResponse; +import ai.docling.serve.api.convert.response.DocumentResponse; +import ai.docling.serve.api.convert.response.InBodyConvertDocumentResponse; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests that {@link DoclingContentExtractor} returns the content matching the requested output format. + *
+ * {@code doctags} is one of the documented values of the {@code outputFormat} option, and docling-serve returns it in a
+ * field of its own, so asking for it must not fall through to the markdown branch: that branch finds no markdown in a
+ * response that only carries DocTags and returns an empty body.
+ */
+class DoclingContentExtractorTest {
+
+ @Test
+ void doctagsReturnsTheDoctagsContent() throws Exception {
+ // docling-serve only fills the fields of the formats that were requested, so a request for doctags returns a
+ // document with doctags content and no markdown at all
+ ConvertDocumentResponse response = responseWith(document().doctagsContent("
+ * docling-serve fills only the content fields of the formats it was asked for, and it has no field for the split pages: + * {@code html_split_page} was asked of the service, the response carried no content at all, and the body came out + * empty. It now fails with the reason instead, and the five values the service can return still map to the format that + * fills their field. + */ +class DoclingServeOutputFormatTest extends CamelTestSupport { + + @Test + void htmlSplitPageFailsWithTheReasonInsteadOfAnEmptyBody() { + assertThatThrownBy(() -> toFormat("html_split_page")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("html_split_page") + .hasMessageContaining("docling-serve") + .hasMessageContaining("outputFormat=html"); + } + + @Test + void htmlSplitPageIsRejectedInUpperCaseToo() { + assertThatThrownBy(() -> toFormat("HTML_SPLIT_PAGE")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void theFormatsDoclingServeCanReturnAreStillRequested() throws Exception { + assertThat(toFormat("markdown")).isEqualTo(OutputFormat.MARKDOWN); + assertThat(toFormat("md")).isEqualTo(OutputFormat.MARKDOWN); + assertThat(toFormat("html")).isEqualTo(OutputFormat.HTML); + assertThat(toFormat("json")).isEqualTo(OutputFormat.JSON); + assertThat(toFormat("text")).isEqualTo(OutputFormat.TEXT); + assertThat(toFormat("txt")).isEqualTo(OutputFormat.TEXT); + assertThat(toFormat("doctags")).isEqualTo(OutputFormat.DOCTAGS); + } + + private OutputFormat toFormat(String outputFormat) throws Exception { + DoclingEndpoint endpoint + = context.getEndpoint("docling:convert?operation=CONVERT_TO_MARKDOWN", DoclingEndpoint.class); + DoclingProducer producer = (DoclingProducer) endpoint.createProducer(); + + return producer.mapToOutputFormat(outputFormat); + } + + @Override + protected RouteBuilder createRouteBuilder() { + return new RouteBuilder() { + @Override + public void configure() { + from("direct:noop").to("mock:noop"); + } + }; + } +} diff --git a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc index e80edda3d3533..98b10b67c039a 100644 --- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc +++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_24.adoc @@ -8198,3 +8198,10 @@ not pass a language on; a language header alone turns OCR on, unless the endpoin The language itself is passed to docling untouched, in both forms: it is docling's own identifier (language codes such as `en,de`, BCP-47 tags such as `iso:zh-Hant`, or `script/Latin`), and docling validates it. + +=== camel-docling - html_split_page is rejected by docling-serve + +`outputFormat=html_split_page` produced an empty body on the docling-serve path: the request asked docling-serve for +the split pages, whose content it does not return in the response, so every content field of the reply was empty. It +now fails with an `IllegalArgumentException` that names the reason and the way out, `outputFormat=html` to get the +HTML of the whole document.