) response, Optional.ofNullable(body.toString()));
}
var statusCode = response.statusCode();
if (statusCode == 422) {
+ if(StreamResponse.class.equals(expectedReturnType)) {
+ try {
+ body = new String(((InputStream)body).readAllBytes(), StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new DoclingServeClientException(e);
+ }
+ }
throw new ValidationException(
- readValue(body, ValidationError.class),
+ readValue(body.toString(), ValidationError.class),
"An error occurred while making %s request to %s".formatted(request.method(), request.uri())
);
}
else if (statusCode >= 400) {
// Handle errors
// The Java HTTPClient doesn't throw exceptions on error codes
- throw new DoclingServeClientException("An error occurred: %s".formatted(body), statusCode, body);
+ if(StreamResponse.class.equals(expectedReturnType)) {
+ try {
+ body = new String(((InputStream)body).readAllBytes(), StandardCharsets.UTF_8);
+ } catch (IOException e) {
+ throw new DoclingServeClientException(e);
+ }
+ }
+ throw new DoclingServeClientException("An error occurred: %s".formatted(body.toString()), statusCode, body.toString());
}
- return readValue(body, expectedReturnType);
+ if(StreamResponse.class.equals(expectedReturnType)) {
+ return (T) StreamResponse
+ .builder()
+ .headers(headerName -> response.headers().firstValue(headerName))
+ .body((InputStream)body)
+ .build();
+ } else {
+ return readValue(body.toString(), expectedReturnType);
+ }
}
@Override
@@ -500,7 +550,7 @@ public B readTimeout(Duration readTimeout) {
* Sets the polling interval for async operations.
*
* This configures how frequently the client will check the status of async
- * conversion tasks when using {@link DoclingServeApi#convertSourceAsync(ConvertDocumentRequest)} (ConvertDocumentRequest)}.
+ * conversion tasks when using {@link DoclingServeApi#convertSourceAsync(ConvertDocumentRequest)} (ConvertDocumentRequest).
*
* @param asyncPollInterval the polling interval (must not be null or negative)
* @return this builder instance for method chaining
@@ -515,7 +565,7 @@ public B asyncPollInterval(Duration asyncPollInterval) {
* Sets the timeout for async operations.
*
*
This configures the maximum time to wait for an async conversion task to complete
- * when using {@link DoclingServeApi#convertSourceAsync(ConvertDocumentRequest)} (ConvertDocumentRequest)}.
+ * when using {@link DoclingServeApi#convertSourceAsync(ConvertDocumentRequest)} (ConvertDocumentRequest).
*
* @param asyncTimeout the timeout duration (must not be null or negative)
* @return this builder instance for method chaining
diff --git a/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/DoclingServeJackson2Client.java b/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/DoclingServeJackson2Client.java
index f7942f14..db3bbd74 100644
--- a/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/DoclingServeJackson2Client.java
+++ b/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/DoclingServeJackson2Client.java
@@ -76,7 +76,6 @@ public static final class Builder extends DoclingServeClientBuilder 1;
+ boolean isRemoteTarget = request.getTarget() instanceof S3Target || request.getTarget() instanceof PutTarget;
+ boolean isZipTarget = request.getTarget() instanceof ZipTarget;
+
+ if((hasMultipleSources && !isRemoteTarget) || isZipTarget) {
+ StreamResponse response = this.httpOperations
+ .executePostWithStreamResponse(createRequestContext(uri, request,
+ StreamResponse.class));
+ String fileName = Utils.getFileName(response.getHeaders()).orElse("converted_docs.zip");
+ return ZipArchiveConvertDocumentResponse
+ .builder().fileName(fileName)
+ .inputStream(response.getBody())
+ .build();
+ } else {
+ return this.httpOperations.executePost(createRequestContext(uri, request,
+ ConvertDocumentResponse.class));
+ }
}
- private RequestContext createRequestContext(String uri, I request) {
- return RequestContext.builder()
+ private RequestContext createRequestContext(String uri, I request, Class responseType) {
+ return RequestContext.builder()
.request(request)
- .responseType(ConvertDocumentResponse.class)
+ .responseType(responseType)
.uri(uri)
.build();
}
diff --git a/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/operations/HttpOperations.java b/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/operations/HttpOperations.java
index 0c846acf..2250b641 100644
--- a/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/operations/HttpOperations.java
+++ b/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/operations/HttpOperations.java
@@ -6,6 +6,21 @@
* implement these operations for specific use cases.
*/
public abstract class HttpOperations {
+
+ /**
+ * Content-Type header key
+ */
+ public static final String CONTENT_TYPE_HEADER = "Content-Type";
+ /**
+ * Content-Type header value for JSON body
+ */
+ public static final String CONTENT_TYPE_JSON = "application/json";
+
+ /**
+ * Content-Type header value for ZIP binary body
+ */
+ public static final String CONTENT_TYPE_ZIP = "application/zip";
+
/**
* The header name used to specify the API key in HTTP requests.
* This constant is commonly utilized in authentication mechanisms
@@ -24,6 +39,16 @@ public abstract class HttpOperations {
*/
protected abstract O executeGet(RequestContext requestContext);
+ /**
+ * Executes an HTTP GET request using the details specified in the provided {@code RequestContext}.
+ *
+ * @param the type of the request payload
+ * @param requestContext the context containing details such as the URI, request payload,
+ * and expected response type of the GET operation
+ * @return an instance of the {@link StreamResponse}, which represents the response.
+ */
+ protected abstract StreamResponse executeGetWithStreamResponse(RequestContext requestContext);
+
/**
* Executes an HTTP POST request using the details provided in the {@code RequestContext}.
* This method is designed to be implemented by subclasses and facilitates sending POST requests
@@ -36,4 +61,27 @@ public abstract class HttpOperations {
* @return an instance of the response type {@code O}, which represents the deserialized response data
*/
protected abstract O executePost(RequestContext requestContext);
+
+ /**
+ * Executes an HTTP POST request using the details provided in the {@code RequestContext}.
+ * This method is designed to be implemented by subclasses and facilitates sending POST requests
+ * with a specified request payload and receiving a stream response.
+ *
+ * @param the type of the request payload
+ * @param requestContext the context containing details such as the URI, request payload, and
+ * expected response type of the POST operation
+ * @return an instance of the {@link StreamResponse}, which represents the response.
+ */
+ protected abstract StreamResponse executePostWithStreamResponse(RequestContext requestContext);
+
+ /**
+ * Reads and deserializes the given JSON string into an instance of the specified type.
+ *
+ * @param json the JSON string to deserialize; must not be {@code null}
+ * @param valueType the {@link Class} of the target type; must not be {@code null}
+ * @param the type of the object to be deserialized
+ * @return an instance of {@code T} deserialized from the provided JSON
+ * @throws RuntimeException if the JSON parsing fails
+ */
+ protected abstract T readValue(String json, Class valueType);
}
diff --git a/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/operations/StreamResponse.java b/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/operations/StreamResponse.java
new file mode 100644
index 00000000..6ee33d0d
--- /dev/null
+++ b/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/operations/StreamResponse.java
@@ -0,0 +1,67 @@
+package ai.docling.serve.client.operations;
+
+import java.io.InputStream;
+import java.util.Optional;
+
+/**
+ * Wrapper for HTTP responses containing binary stream data.
+ * Provides an abstraction layer to decouple from specific HTTP client implementations.
+ */
+public class StreamResponse {
+ private final InputStream body;
+ private final ResponseHeaders headers;
+
+ private StreamResponse(Builder builder) {
+ this.body = builder.body;
+ this.headers = builder.headers;
+ }
+
+ public InputStream getBody() { return body; }
+
+
+ public ResponseHeaders getHeaders() { return headers; }
+
+ public static Builder builder() { return new Builder(); }
+
+ public Builder toBuilder() { return new Builder(this); }
+
+ public static class Builder {
+ private InputStream body;
+ private ResponseHeaders headers;
+
+ public Builder() {}
+
+ public Builder(StreamResponse streamResponse) {
+ this.body = streamResponse.body;
+ this.headers = streamResponse.headers;
+ }
+
+ public Builder body(InputStream body) {
+ this.body = body;
+ return this;
+ }
+
+ public Builder headers(ResponseHeaders headers) {
+ this.headers = headers;
+ return this;
+ }
+
+ public StreamResponse build() {
+ return new StreamResponse(this);
+ }
+ }
+
+ /**
+ * Abstraction for HTTP response headers.
+ */
+ @FunctionalInterface
+ public interface ResponseHeaders {
+ /**
+ * Gets the first value of the specified header.
+ *
+ * @param headerName the name of the header
+ * @return an Optional containing the first header value, or empty if not found
+ */
+ Optional getFirstValue(String headerName);
+ }
+}
diff --git a/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/operations/TaskOperations.java b/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/operations/TaskOperations.java
index 898957ef..4bf0702a 100644
--- a/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/operations/TaskOperations.java
+++ b/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/operations/TaskOperations.java
@@ -1,12 +1,18 @@
package ai.docling.serve.client.operations;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
import ai.docling.serve.api.DoclingServeTaskApi;
import ai.docling.serve.api.chunk.response.ChunkDocumentResponse;
import ai.docling.serve.api.convert.response.ConvertDocumentResponse;
+import ai.docling.serve.api.convert.response.ZipArchiveConvertDocumentResponse;
import ai.docling.serve.api.task.request.TaskResultRequest;
import ai.docling.serve.api.task.request.TaskStatusPollRequest;
import ai.docling.serve.api.task.response.TaskStatusPollResponse;
import ai.docling.serve.api.util.ValidationUtils;
+import ai.docling.serve.client.DoclingServeClientException;
+import ai.docling.serve.client.util.Utils;
/**
* Base class for task API operations. Provides operations for managing and querying
@@ -31,7 +37,7 @@ public TaskOperations(HttpOperations httpOperations) {
* unique task identifier and optional wait time for polling.
* Must not be null.
* @return a {@link TaskStatusPollResponse} containing the current status of
- * the task, its position in the queue, and any associated metadata.
+ * the task, its position in the queue, and any associated metadata.
* @throws IllegalArgumentException if the {@code request} is null.
*/
public TaskStatusPollResponse pollTaskStatus(TaskStatusPollRequest request) {
@@ -46,21 +52,39 @@ public TaskStatusPollResponse pollTaskStatus(TaskStatusPollRequest request) {
}
/**
- * Retrieves the result of a completed task identified by the specified task ID.
+ * Retrieves the result of a completed convert task identified by the specified task ID.
*
* This method sends a GET request to fetch the result of a task that has been processed.
- * The response includes details about the converted document, processing time, status,
- * and any potential errors or additional metadata related to the task.
*
* @param request an instance of {@link TaskResultRequest} containing the unique task
* identifier. Must not be null.
* @return a {@link ConvertDocumentResponse} containing details about the converted
- * document, processing time, status, and any associated errors or metadata.
+ * document, processing time, status, and any associated errors or metadata.
* @throws IllegalArgumentException if {@code request} is null.
*/
public ConvertDocumentResponse convertTaskResult(TaskResultRequest request) {
ValidationUtils.ensureNotNull(request, "request");
- return this.httpOperations.executeGet(createRequestContext("/v1/result/%s".formatted(request.getTaskId()), ConvertDocumentResponse.class));
+ StreamResponse response = this.httpOperations
+ .executeGetWithStreamResponse(createRequestContext("/v1/result/%s".formatted(request.getTaskId()), StreamResponse.class));
+ switch (Utils.getContentType(response.getHeaders()).orElse("Unknown Content-Type")) {
+ case HttpOperations.CONTENT_TYPE_JSON -> {
+ try {
+ return httpOperations
+ .readValue(new String(response.getBody().readAllBytes(), StandardCharsets.UTF_8)
+ , ConvertDocumentResponse.class);
+ } catch (IOException e) {
+ throw new DoclingServeClientException(e);
+ }
+ }
+ case HttpOperations.CONTENT_TYPE_ZIP -> {
+ String fileName = Utils.getFileName(response.getHeaders()).orElse("converted_docs.zip");
+ return ZipArchiveConvertDocumentResponse
+ .builder().fileName(fileName)
+ .inputStream(response.getBody())
+ .build();
+ }
+ default -> throw new DoclingServeClientException(null, "Content-Type missing in task api response");
+ }
}
/**
@@ -73,7 +97,7 @@ public ConvertDocumentResponse convertTaskResult(TaskResultRequest request) {
* @param request an instance of {@link TaskResultRequest} containing the unique task
* identifier. Must not be null.
* @return a {@link ChunkDocumentResponse} containing details about the chunks,
- * documents, processing time, and any associated metadata.
+ * documents, processing time, and any associated metadata.
* @throws IllegalArgumentException if {@code request} is null.
*/
public ChunkDocumentResponse chunkTaskResult(TaskResultRequest request) {
diff --git a/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/util/Utils.java b/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/util/Utils.java
new file mode 100644
index 00000000..ea83ad8d
--- /dev/null
+++ b/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/util/Utils.java
@@ -0,0 +1,30 @@
+package ai.docling.serve.client.util;
+
+import java.util.Optional;
+
+import ai.docling.serve.client.operations.HttpOperations;
+import ai.docling.serve.client.operations.StreamResponse;
+
+public class Utils {
+
+ public static Optional getFileName(StreamResponse.ResponseHeaders headers) {
+ return headers
+ .getFirstValue("Content-Disposition")
+ .filter(ai.docling.serve.api.util.Utils::isNotNullOrBlank)
+ .map(contentDisposition -> {
+ int filenameIndex = contentDisposition.indexOf("filename=");
+ if (filenameIndex==-1) {
+ return null;
+ }
+
+ String fileName = contentDisposition.substring(filenameIndex + "filename=".length());
+ return fileName.replaceAll("^\"|\"$", "").trim();
+ })
+ .filter(ai.docling.serve.api.util.Utils::isNotNullOrBlank);
+ }
+
+ public static Optional getContentType(StreamResponse.ResponseHeaders headers) {
+ return headers.getFirstValue(HttpOperations.CONTENT_TYPE_HEADER)
+ .filter(ai.docling.serve.api.util.Utils::isNotNullOrBlank);
+ }
+}
diff --git a/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/util/package-info.java b/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/util/package-info.java
new file mode 100644
index 00000000..449971ae
--- /dev/null
+++ b/docling-serve/docling-serve-client/src/main/java/ai/docling/serve/client/util/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package ai.docling.serve.client.util;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/docling-serve/docling-serve-client/src/test/java/ai/docling/serve/client/AbstractDoclingServeClientTests.java b/docling-serve/docling-serve-client/src/test/java/ai/docling/serve/client/AbstractDoclingServeClientTests.java
index 3acfbbd2..85783935 100644
--- a/docling-serve/docling-serve-client/src/test/java/ai/docling/serve/client/AbstractDoclingServeClientTests.java
+++ b/docling-serve/docling-serve-client/src/test/java/ai/docling/serve/client/AbstractDoclingServeClientTests.java
@@ -17,6 +17,7 @@
import static org.awaitility.Awaitility.await;
import java.io.IOException;
+import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.http.HttpClient;
@@ -33,8 +34,12 @@
import java.util.List;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
import java.util.concurrent.Flow.Subscriber;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.jspecify.annotations.Nullable;
@@ -63,17 +68,25 @@
import ai.docling.serve.api.clear.response.ClearResponse;
import ai.docling.serve.api.convert.request.ConvertDocumentRequest;
import ai.docling.serve.api.convert.request.options.ConvertDocumentOptions;
+import ai.docling.serve.api.convert.request.options.ImageRefMode;
import ai.docling.serve.api.convert.request.options.OutputFormat;
import ai.docling.serve.api.convert.request.options.TableFormerMode;
import ai.docling.serve.api.convert.request.source.HttpSource;
import ai.docling.serve.api.convert.request.source.S3Source;
+import ai.docling.serve.api.convert.request.target.PutTarget;
import ai.docling.serve.api.convert.request.target.S3Target;
+import ai.docling.serve.api.convert.request.target.ZipTarget;
import ai.docling.serve.api.convert.response.ConvertDocumentResponse;
+import ai.docling.serve.api.convert.response.InBodyConvertDocumentResponse;
+import ai.docling.serve.api.convert.response.PreSignedUrlConvertDocumentResponse;
+import ai.docling.serve.api.convert.response.ResponseType;
+import ai.docling.serve.api.convert.response.ZipArchiveConvertDocumentResponse;
import ai.docling.serve.api.health.HealthCheckResponse;
import ai.docling.serve.api.task.request.TaskResultRequest;
import ai.docling.serve.api.task.request.TaskStatusPollRequest;
import ai.docling.serve.api.task.response.TaskStatus;
import ai.docling.serve.api.task.response.TaskStatusPollResponse;
+import ai.docling.serve.api.util.FileUtils;
import ai.docling.serve.api.validation.ValidationError;
import ai.docling.serve.api.validation.ValidationErrorContext;
import ai.docling.serve.api.validation.ValidationErrorDetail;
@@ -269,7 +282,7 @@ void convertUrlTaskResult() throws IOException, InterruptedException {
.build();
var result = getDoclingClient().convertTaskResult(request);
- ConvertTests.assertConvertHttpSource(result);
+ ConvertTests.assertConvertSingleHttpSourceWithDefaultTarget(result);
}
@Test
@@ -439,17 +452,34 @@ void shouldSuccessfullyCallHealthEndpoint() {
@Nested
class ConvertTests {
- static void assertConvertHttpSource(ConvertDocumentResponse response) {
+ static void assertConvertSingleHttpSourceWithDefaultTarget(ConvertDocumentResponse response) {
assertThat(response).isNotNull();
- assertThat(response.getStatus()).isNotEmpty();
- assertThat(response.getDocument()).isNotNull();
- assertThat(response.getDocument().getFilename()).isNotEmpty();
-
- if (response.getProcessingTime() != null) {
- assertThat(response.getProcessingTime()).isPositive();
+ assertThat(response.getResponseType().equals(ResponseType.InBodyConvertDocumentResponse)).isTrue();
+ var inBodyResponse = (InBodyConvertDocumentResponse)response;
+ assertThat(inBodyResponse.getStatus()).isNotEmpty();
+ assertThat(inBodyResponse.getDocument()).isNotNull();
+ assertThat(inBodyResponse.getDocument().getFilename()).isNotEmpty();
+
+ if (inBodyResponse.getProcessingTime() != null) {
+ assertThat(inBodyResponse.getProcessingTime()).isPositive();
}
- assertThat(response.getDocument().getMarkdownContent()).isNotEmpty();
+ assertThat(inBodyResponse.getDocument().getMarkdownContent()).isNotEmpty();
+ }
+
+ static void assertZipArchiveEntries(InputStream inputStream, Set expectedEntries) {
+ Set actualEntries = new TreeSet<>();
+ try (ZipInputStream zipInputStream = new ZipInputStream(inputStream)) {
+ ZipEntry entry;
+ while ((entry = zipInputStream.getNextEntry()) != null) {
+ actualEntries.add(entry.getName());
+ LOG.info("Found entry in ZIP: {} (size: {} bytes)", entry.getName(), entry.getSize());
+ zipInputStream.closeEntry();
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ assertThat(actualEntries).containsExactlyInAnyOrderElementsOf(expectedEntries);
}
@Test
@@ -497,6 +527,59 @@ void shouldThrowValidationError() {
);
}
+ @Test
+ void shouldConvertSourceWithPutTargetSuccessfully() {
+ var request = ConvertDocumentRequest.builder()
+ .source(
+ HttpSource
+ .builder()
+ .url(URI.create("https://docs.arconia.io/arconia-cli/latest/development/dev/"))
+ .build()
+ )
+ .target(
+ PutTarget.builder().url(URI.create("https://github.com/docling-project/docling-java/save")).build()
+ ).build();
+
+ var wireMockServer = getWiremockServer();
+
+ wireMockServer.stubFor(
+ post("/v1/convert/source")
+ .withRequestBody(equalToJson(writeValueAsString(request)))
+ .withHeader("Content-Type", equalTo("application/json"))
+ .withHeader("Accept", equalTo("application/json"))
+ .willReturn(okJson("""
+ {
+ "processing_time": 7,
+ "num_converted": 10,
+ "num_succeeded": 5,
+ "num_failed": 5
+ }
+ """))
+ );
+
+ var response = getDoclingClient(false, true).convertSource(request);
+ assertThat(response).isNotNull();
+ assertThat(response.getResponseType().equals(ResponseType.PreSignedUrlConvertDocumentResponse)).isTrue();
+ var preSignedUrlResponse = (PreSignedUrlConvertDocumentResponse)response;
+
+ assertThat(preSignedUrlResponse.getNumConverted()).isEqualTo(10);
+ assertThat(preSignedUrlResponse.getNumSucceeded()).isEqualTo(5);
+ assertThat(preSignedUrlResponse.getNumFailed()).isEqualTo(5);
+ assertThat(preSignedUrlResponse.getProcessingTime()).isPositive().isEqualTo(7);
+
+ wireMockServer.verify(
+ 1,
+ postRequestedFor(urlPathEqualTo("/v1/convert/source"))
+ .withHeader("Content-Type", equalTo("application/json"))
+ .withRequestBody(
+ matchingJsonPath("$.sources[0].kind", equalTo("http"))
+ .and(matchingJsonPath("$.sources[0].url", equalTo("https://docs.arconia.io/arconia-cli/latest/development/dev/")))
+ .and(matchingJsonPath("$.target.kind", equalTo("put")))
+ .and(matchingJsonPath("$.target.url", equalTo("https://github.com/docling-project/docling-java/save")))
+ )
+ );
+ }
+
@Test
void shouldConvertS3SourceSuccessfully() {
// Need to use Wiremock here rather than a "real" backend because Docling Serve requires kubeflow
@@ -528,7 +611,14 @@ void shouldConvertS3SourceSuccessfully() {
.withRequestBody(equalToJson(writeValueAsString(request)))
.withHeader("Content-Type", equalTo("application/json"))
.withHeader("Accept", equalTo("application/json"))
- .willReturn(okJson("{}"))
+ .willReturn(okJson("""
+ {
+ "processing_time": 7,
+ "num_converted": 10,
+ "num_succeeded": 5,
+ "num_failed": 5
+ }
+ """))
);
var response = getDoclingClient(false, true).convertSource(request);
@@ -556,29 +646,32 @@ void shouldConvertS3SourceSuccessfully() {
}
@Test
- void shouldConvertHttpSourceSuccessfully() {
+ void shouldConvertSingleHttpSourceWithDefaultTargetSuccessfully() {
var request = ConvertDocumentRequest.builder()
.source(HttpSource.builder().url(URI.create("https://docs.arconia.io/arconia-cli/latest/development/dev/")).build())
.build();
var response = getDoclingClient().convertSource(request);
- assertConvertHttpSource(response);
+ assertConvertSingleHttpSourceWithDefaultTarget(response);
}
@Test
void shouldConvertFileSuccessfully() {
var response = getDoclingClient().convertFiles(Path.of("src", "test", "resources", "story.pdf"));
-
+ assertThat(ResponseType.InBodyConvertDocumentResponse.equals(response.getResponseType())).isTrue();
assertThat(response).isNotNull();
- assertThat(response.getStatus()).isNotEmpty();
- assertThat(response.getDocument()).isNotNull();
- assertThat(response.getDocument().getFilename()).isEqualTo("story.pdf");
- if (response.getProcessingTime()!=null) {
- assertThat(response.getProcessingTime()).isPositive();
+ var inBodyResponse = (InBodyConvertDocumentResponse)response;
+
+ assertThat(inBodyResponse.getStatus()).isNotEmpty();
+ assertThat(inBodyResponse.getDocument()).isNotNull();
+ assertThat(inBodyResponse.getDocument().getFilename()).isEqualTo("story.pdf");
+
+ if (inBodyResponse.getProcessingTime()!=null) {
+ assertThat(inBodyResponse.getProcessingTime()).isPositive();
}
- assertThat(response.getDocument().getMarkdownContent()).isNotEmpty();
+ assertThat(inBodyResponse.getDocument().getMarkdownContent()).isNotEmpty();
}
@Test
@@ -597,9 +690,13 @@ void shouldHandleConversionWithDifferentDocumentOptions() {
var response = getDoclingClient().convertSource(request);
+ assertThat(ResponseType.InBodyConvertDocumentResponse.equals(response.getResponseType())).isTrue();
assertThat(response).isNotNull();
- assertThat(response.getStatus()).isNotEmpty();
- assertThat(response.getDocument()).isNotNull();
+
+ var inBodyResponse = (InBodyConvertDocumentResponse)response;
+
+ assertThat(inBodyResponse.getStatus()).isNotEmpty();
+ assertThat(inBodyResponse.getDocument()).isNotNull();
}
@Test
@@ -615,11 +712,15 @@ void shouldHandleResponseWithDoclingDocument() {
ConvertDocumentResponse response = getDoclingClient().convertSource(request);
+ assertThat(ResponseType.InBodyConvertDocumentResponse.equals(response.getResponseType())).isTrue();
assertThat(response).isNotNull();
- assertThat(response.getStatus()).isNotEmpty();
- assertThat(response.getDocument()).isNotNull();
- DoclingDocument doclingDocument = response.getDocument().getJsonContent();
+ var inBodyResponse = (InBodyConvertDocumentResponse)response;
+
+ assertThat(inBodyResponse.getStatus()).isNotEmpty();
+ assertThat(inBodyResponse.getDocument()).isNotNull();
+
+ DoclingDocument doclingDocument = inBodyResponse.getDocument().getJsonContent();
assertThat(doclingDocument).isNotNull();
assertThat(doclingDocument.getName()).isNotEmpty();
assertThat(doclingDocument.getTexts().get(0).getLabel()).isEqualTo(DocItemLabel.TITLE);
@@ -633,21 +734,30 @@ void shouldConvertSourceAsync() {
ConvertDocumentResponse response = getDoclingClient().convertSourceAsync(request).toCompletableFuture().join();
+ assertThat(ResponseType.InBodyConvertDocumentResponse.equals(response.getResponseType())).isTrue();
+ assertThat(response).isInstanceOf(InBodyConvertDocumentResponse.class);
assertThat(response).isNotNull();
- assertThat(response.getStatus()).isNotEmpty();
- assertThat(response.getDocument()).isNotNull();
- assertThat(response.getDocument().getMarkdownContent()).isNotEmpty();
+
+ var inBodyResponse = (InBodyConvertDocumentResponse)response;
+
+ assertThat(inBodyResponse.getStatus()).isNotEmpty();
+ assertThat(inBodyResponse.getDocument()).isNotNull();
+ assertThat(inBodyResponse.getDocument().getMarkdownContent()).isNotEmpty();
}
@Test
void shouldConvertFileAsync() {
ConvertDocumentResponse response = getDoclingClient().convertFilesAsync(Path.of("src", "test", "resources", "story.pdf")).toCompletableFuture().join();
+ assertThat(ResponseType.InBodyConvertDocumentResponse.equals(response.getResponseType())).isTrue();
assertThat(response).isNotNull();
- assertThat(response.getStatus()).isNotEmpty();
- assertThat(response.getDocument()).isNotNull();
- assertThat(response.getDocument().getFilename()).isEqualTo("story.pdf");
- assertThat(response.getDocument().getMarkdownContent()).isNotEmpty();
+
+ var inBodyResponse = (InBodyConvertDocumentResponse)response;
+
+ assertThat(inBodyResponse.getStatus()).isNotEmpty();
+ assertThat(inBodyResponse.getDocument()).isNotNull();
+ assertThat(inBodyResponse.getDocument().getFilename()).isEqualTo("story.pdf");
+ assertThat(inBodyResponse.getDocument().getMarkdownContent()).isNotEmpty();
}
@Test
@@ -666,9 +776,15 @@ void shouldHandleAsyncConversionWithDifferentDocumentOptions() {
ConvertDocumentResponse response = getDoclingClient().convertSourceAsync(request).toCompletableFuture().join();
+ assertThat(ResponseType.InBodyConvertDocumentResponse.equals(response.getResponseType())).isTrue();
assertThat(response).isNotNull();
- assertThat(response.getStatus()).isNotEmpty();
- assertThat(response.getDocument()).isNotNull();
+
+ var inBodyResponse = (InBodyConvertDocumentResponse)response;
+
+
+ assertThat(inBodyResponse).isNotNull();
+ assertThat(inBodyResponse.getStatus()).isNotEmpty();
+ assertThat(inBodyResponse.getDocument()).isNotNull();
}
@Test
@@ -679,12 +795,170 @@ void shouldChainAsyncOperations() {
// Test chaining with thenApply
String markdownContent = getDoclingClient().convertSourceAsync(request)
- .thenApply(response -> response.getDocument().getMarkdownContent())
+ .thenApply(response -> ((InBodyConvertDocumentResponse)response).getDocument().getMarkdownContent())
.toCompletableFuture().join();
assertThat(markdownContent).isNotEmpty();
}
+ @Test
+ void shouldConvertSingleFileSourceWithZipTargetAsync() {
+ Path[] files = new Path[]{Path.of("src", "test", "resources", "2408.09869.pdf")};
+
+ var requestBuilder = ConvertDocumentRequest
+ .builder()
+ .target(ZipTarget.builder().build());
+
+ FileUtils.createFileSources(files)
+ .forEach(requestBuilder::source);
+
+ var request = requestBuilder.build();
+
+ var response = getDoclingClient()
+ .convertSourceAsync(request).toCompletableFuture().join();
+
+ assertThat(response).isNotNull();
+ assertThat(response.getResponseType().equals(ResponseType.ZipArchiveConvertDocumentResponse)).isTrue();
+ assertThat(response).isInstanceOf(ZipArchiveConvertDocumentResponse.class);
+ assertThat(((ZipArchiveConvertDocumentResponse)response).getFileName()).isEqualTo("converted_docs.zip");
+ assertThat(((ZipArchiveConvertDocumentResponse)response).getInputStream()).isNotNull();
+ assertZipArchiveEntries(((ZipArchiveConvertDocumentResponse)response).getInputStream(), Set.of("2408.09869.md"));
+ }
+
+ @Test
+ void shouldConvertSingleFileSourceWithZipTargetAndReferencedImageExportModeAsync() {
+ Path[] files = new Path[]{Path.of("src", "test", "resources", "2408.09869.pdf")};
+
+ var requestBuilder = ConvertDocumentRequest
+ .builder()
+ .target(ZipTarget.builder().build())
+ .options(ConvertDocumentOptions.builder().imageExportMode(ImageRefMode.REFERENCED).build());
+
+ FileUtils.createFileSources(files)
+ .forEach(requestBuilder::source);
+
+ var request = requestBuilder.build();
+
+ var response = getDoclingClient()
+ .convertSourceAsync(request).toCompletableFuture().join();
+
+ assertThat(response).isNotNull();
+ assertThat(response.getResponseType().equals(ResponseType.ZipArchiveConvertDocumentResponse)).isTrue();
+ assertThat(response).isInstanceOf(ZipArchiveConvertDocumentResponse.class);
+ assertThat(((ZipArchiveConvertDocumentResponse)response).getFileName()).isEqualTo("converted_docs.zip");
+ assertThat(((ZipArchiveConvertDocumentResponse)response).getInputStream()).isNotNull();
+ assertZipArchiveEntries(((ZipArchiveConvertDocumentResponse)response).getInputStream(),
+ Set.of("2408.09869.md",
+ "artifacts/",
+ "artifacts/image_000000_4f05ea6de89ce20493a5d9cc2305a4feb948c7bb794d7b81ee29554ec56b8445.png"));
+ }
+
+ @Test
+ void shouldConvertMultipleFileSourcesAsync(){
+ Path[] files = new Path[]{Path.of("src", "test", "resources", "2408.09869.pdf"),
+ Path.of("src", "test", "resources", "story.pdf")};
+
+ var requestBuilder = ConvertDocumentRequest
+ .builder();
+
+ FileUtils.createFileSources(files)
+ .forEach(requestBuilder::source);
+
+ var request = requestBuilder.build();
+
+ var response = getDoclingClient()
+ .convertSourceAsync(request).toCompletableFuture().join();
+
+ assertThat(response).isNotNull();
+ assertThat(response.getResponseType().equals(ResponseType.ZipArchiveConvertDocumentResponse)).isTrue();
+ assertThat(response).isInstanceOf(ZipArchiveConvertDocumentResponse.class);
+ assertThat(((ZipArchiveConvertDocumentResponse)response).getFileName()).isEqualTo("converted_docs.zip");
+ assertThat(((ZipArchiveConvertDocumentResponse)response).getInputStream()).isNotNull();
+ assertZipArchiveEntries(((ZipArchiveConvertDocumentResponse)response).getInputStream(),
+ Set.of("2408.09869.md", "story.md"));
+ }
+
+ @Test
+ void shouldConvertMultipleFileSourcesWithReferencedImageExportModeAsync(){
+ Path[] files = new Path[]{Path.of("src", "test", "resources", "2408.09869.pdf"),
+ Path.of("src", "test", "resources", "story.pdf")};
+
+ var requestBuilder = ConvertDocumentRequest
+ .builder()
+ .options(ConvertDocumentOptions.builder().imageExportMode(ImageRefMode.REFERENCED).build());
+
+ FileUtils.createFileSources(files)
+ .forEach(requestBuilder::source);
+
+ var request = requestBuilder.build();
+
+ var response = getDoclingClient()
+ .convertSourceAsync(request).toCompletableFuture().join();
+
+ assertThat(response).isNotNull();
+ assertThat(response.getResponseType().equals(ResponseType.ZipArchiveConvertDocumentResponse)).isTrue();
+ assertThat(response).isInstanceOf(ZipArchiveConvertDocumentResponse.class);
+ assertThat(((ZipArchiveConvertDocumentResponse)response).getFileName()).isEqualTo("converted_docs.zip");
+ assertThat(((ZipArchiveConvertDocumentResponse)response).getInputStream()).isNotNull();
+ assertZipArchiveEntries(((ZipArchiveConvertDocumentResponse)response).getInputStream(),
+ Set.of("2408.09869.md", "story.md", "artifacts/",
+ "artifacts/image_000000_4f05ea6de89ce20493a5d9cc2305a4feb948c7bb794d7b81ee29554ec56b8445.png"));
+ }
+
+ @Test
+ void shouldConvertMultipleFileSourcesWithInBodyTargetAsync() {
+ Path[] files = new Path[]{Path.of("src", "test", "resources", "2408.09869.pdf"),
+ Path.of("src", "test", "resources", "story.pdf")};
+
+ var requestBuilder = ConvertDocumentRequest
+ .builder()
+ .target(ZipTarget.builder().build());
+
+ FileUtils.createFileSources(files)
+ .forEach(requestBuilder::source);
+
+ var request = requestBuilder.build();
+
+ var response = getDoclingClient()
+ .convertSourceAsync(request).toCompletableFuture().join();
+
+ assertThat(response).isNotNull();
+ assertThat(response.getResponseType().equals(ResponseType.ZipArchiveConvertDocumentResponse)).isTrue();
+ assertThat(response).isInstanceOf(ZipArchiveConvertDocumentResponse.class);
+ assertThat(((ZipArchiveConvertDocumentResponse)response).getFileName()).isEqualTo("converted_docs.zip");
+ assertThat(((ZipArchiveConvertDocumentResponse)response).getInputStream()).isNotNull();
+ assertZipArchiveEntries(((ZipArchiveConvertDocumentResponse)response).getInputStream(),
+ Set.of("2408.09869.md", "story.md"));
+ }
+
+ @Test
+ void shouldConvertMultipleFileSourcesWithInBodyTargetAndReferencedImageExportModeAsync() {
+ Path[] files = new Path[]{Path.of("src", "test", "resources", "2408.09869.pdf"),
+ Path.of("src", "test", "resources", "story.pdf")};
+
+ var requestBuilder = ConvertDocumentRequest
+ .builder()
+ .target(ZipTarget.builder().build())
+ .options(ConvertDocumentOptions.builder().imageExportMode(ImageRefMode.REFERENCED).build());
+
+ FileUtils.createFileSources(files)
+ .forEach(requestBuilder::source);
+
+ var request = requestBuilder.build();
+
+ var response = getDoclingClient()
+ .convertSourceAsync(request).toCompletableFuture().join();
+
+ assertThat(response).isNotNull();
+ assertThat(response.getResponseType().equals(ResponseType.ZipArchiveConvertDocumentResponse)).isTrue();
+ assertThat(response).isInstanceOf(ZipArchiveConvertDocumentResponse.class);
+ assertThat(((ZipArchiveConvertDocumentResponse)response).getFileName()).isEqualTo("converted_docs.zip");
+ assertThat(((ZipArchiveConvertDocumentResponse)response).getInputStream()).isNotNull();
+ assertZipArchiveEntries(((ZipArchiveConvertDocumentResponse)response).getInputStream(),
+ Set.of("2408.09869.md", "story.md", "artifacts/",
+ "artifacts/image_000000_4f05ea6de89ce20493a5d9cc2305a4feb948c7bb794d7b81ee29554ec56b8445.png"));
+ }
+
@Test
void convertFilesNullFiles() {
assertThatExceptionOfType(IllegalArgumentException.class)
diff --git a/docling-serve/docling-serve-client/src/test/resources/2408.09869.pdf b/docling-serve/docling-serve-client/src/test/resources/2408.09869.pdf
new file mode 100644
index 00000000..5c3267de
Binary files /dev/null and b/docling-serve/docling-serve-client/src/test/resources/2408.09869.pdf differ
diff --git a/docling-testing/docling-version-tests/src/main/java/ai/docling/client/tester/service/TagsTester.java b/docling-testing/docling-version-tests/src/main/java/ai/docling/client/tester/service/TagsTester.java
index 2720975c..a53ed477 100644
--- a/docling-testing/docling-version-tests/src/main/java/ai/docling/client/tester/service/TagsTester.java
+++ b/docling-testing/docling-version-tests/src/main/java/ai/docling/client/tester/service/TagsTester.java
@@ -24,6 +24,8 @@
import ai.docling.serve.api.convert.request.source.HttpSource;
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 ai.docling.serve.api.convert.response.PreSignedUrlConvertDocumentResponse;
import ai.docling.serve.api.health.HealthCheckResponse;
import ai.docling.testcontainers.serve.DoclingServeContainer;
import ai.docling.testcontainers.serve.config.DoclingServeContainerConfig;
@@ -109,57 +111,67 @@ private void doConversion(DoclingServeContainer doclingContainer) {
}
private void checkDoclingResponse(ConvertDocumentResponse response) {
- Log.debugf("Response: %s", response);
-
- assertThat(response)
- .as("Response should not be null")
- .isNotNull();
-
- assertThat(response.getStatus())
- .as("Response status should not be null or empty")
- .isNotEmpty();
-
- assertThat(response.getErrors())
- .as("Response should not have errors")
- .isNullOrEmpty();
-
- assertThat(response.getDocument())
- .as("Response should have a valid document")
- .isNotNull()
- .extracting(
- DocumentResponse::getFilename,
- DocumentResponse::getMarkdownContent,
- DocumentResponse::getTextContent,
- DocumentResponse::getJsonContent
- )
- .satisfies(o ->
- assertThat(o)
- .as("Document should have a filename")
- .asString()
- .isNotEmpty(),
- atIndex(0)
- )
- .satisfies(o ->
- assertThat(o)
- .as("Document should have markdown content")
- .asString()
- .isNotEmpty(),
- atIndex(1)
- )
- .satisfies(o ->
- assertThat(o)
- .as("Document should have text content")
- .asString()
- .isNotEmpty(),
- atIndex(2)
- )
- .satisfies(o ->
- assertThat(o)
- .as("Document should have JSON content")
- .asInstanceOf(InstanceOfAssertFactories.type(DoclingDocument.class))
- .isNotNull(),
- atIndex(3)
- );
+ switch(response.getResponseType()) {
+ case InBodyConvertDocumentResponse -> {
+ var inBodyResponse = (InBodyConvertDocumentResponse)response;
+ Log.debugf("Response: %s", inBodyResponse);
+
+ assertThat(inBodyResponse)
+ .as("Response should not be null")
+ .isNotNull();
+
+ assertThat(inBodyResponse.getStatus())
+ .as("Response status should not be null or empty")
+ .isNotEmpty();
+
+ assertThat(inBodyResponse.getErrors())
+ .as("Response should not have errors")
+ .isNullOrEmpty();
+
+ assertThat(inBodyResponse.getDocument())
+ .as("Response should have a valid document")
+ .isNotNull()
+ .extracting(
+ DocumentResponse::getFilename,
+ DocumentResponse::getMarkdownContent,
+ DocumentResponse::getTextContent,
+ DocumentResponse::getJsonContent
+ )
+ .satisfies(o ->
+ assertThat(o)
+ .as("Document should have a filename")
+ .asString()
+ .isNotEmpty(),
+ atIndex(0)
+ )
+ .satisfies(o ->
+ assertThat(o)
+ .as("Document should have markdown content")
+ .asString()
+ .isNotEmpty(),
+ atIndex(1)
+ )
+ .satisfies(o ->
+ assertThat(o)
+ .as("Document should have text content")
+ .asString()
+ .isNotEmpty(),
+ atIndex(2)
+ )
+ .satisfies(o ->
+ assertThat(o)
+ .as("Document should have JSON content")
+ .asInstanceOf(InstanceOfAssertFactories.type(DoclingDocument.class))
+ .isNotNull(),
+ atIndex(3)
+ );
+ }
+ case PreSignedUrlConvertDocumentResponse -> {
+ var preSignedUrlResponse = (PreSignedUrlConvertDocumentResponse)response;
+ Log.debugf("Response: %s", preSignedUrlResponse);
+ }
+ case ZipArchiveConvertDocumentResponse -> {}
+ }
}
private void checkDoclingHealthy(DoclingServeApi doclingClient) {
diff --git a/docs/src/doc/docs/docling-serve/serve-api.md b/docs/src/doc/docs/docling-serve/serve-api.md
index ff97e14f..a8b4fcb0 100644
--- a/docs/src/doc/docs/docling-serve/serve-api.md
+++ b/docs/src/doc/docs/docling-serve/serve-api.md
@@ -59,7 +59,7 @@ import ai.docling.serve.api.convert.request.options.ConvertDocumentOptions;
import ai.docling.serve.api.convert.request.options.OutputFormat;
import ai.docling.serve.api.convert.request.source.HttpSource;
import ai.docling.serve.api.convert.request.target.InBodyTarget;
-import ai.docling.serve.api.convert.response.ConvertDocumentResponse;
+import ai.docling.serve.api.convert.response.InBodyConvertDocumentResponse;
DoclingServeApi api = DoclingServeApi.builder()
.baseUrl("http://localhost:8000") // your Docling Serve URL
@@ -78,7 +78,7 @@ ConvertDocumentRequest request = ConvertDocumentRequest.builder()
.target(InBodyTarget.builder().build()) // get results in the HTTP response body
.build();
-ConvertDocumentResponse response = api.convertSource(request);
+InBodyConvertDocumentResponse response = (InBodyConvertDocumentResponse)api.convertSource(request);
System.out.println(response.getDocument().getMarkdownContent());
```
@@ -132,10 +132,12 @@ Options (`ai.docling.serve.api.convert.request.options.ConvertDocumentOptions`)
Explore the `options` package for the full list of knobs you can turn.
-### Responses: `ConvertDocumentResponse` and `DocumentResponse`
-
-- `ConvertDocumentResponse` contains the converted `document` (if any), `errors`, processing `status`,
- total `processing_time`, and detailed `timings` map.
+### Responses: `InBodyConvertDocumentResponse`, `PreSignedUrlConvertDocumentResponse`, `ZipArchiveConvertDocumentResponse` and `DocumentResponse`
+- `InBodyConvertDocumentResponse` contains the converted `document` (if any), `errors`, processing `status`,
+ total `processing_time`, and detailed `timings` map.
+- `PreSignedUrlConvertDocumentResponse` contains processing statistics - total `processing_time` and conversion metrics
+ `num_converted`, `num_succeeded`, `num_failed`.
+- `ZipArchiveConvertDocumentResponse` contains `file_name` and an input stream for the archive.
- `DocumentResponse` holds the actual content fields you requested, such as `md_content` (Markdown),
`html_content`, `text_content`, and a `json_content` map. It also includes the `filename` and
`doctags_content` when relevant.
@@ -154,10 +156,10 @@ System.out.println("Service status: " + health.getStatus());
## Error handling
Conversion may succeed partially (e.g., some pages) while returning warnings or errors. Always inspect
-`ConvertDocumentResponse#getErrors()` and consider `status`:
+`InBodyConvertDocumentResponse#getErrors()` and consider `status`:
```java
-ConvertDocumentResponse response = api.convertSource(request);
+InBodyConvertDocumentResponse response = (InBodyConvertDocumentResponse)api.convertSource(request);
if (response.getErrors() != null && !response.getErrors().isEmpty()) {
response.getErrors().forEach(err ->
diff --git a/docs/src/doc/docs/docling-serve/serve-client.md b/docs/src/doc/docs/docling-serve/serve-client.md
index 952fd70c..112c833c 100644
--- a/docs/src/doc/docs/docling-serve/serve-client.md
+++ b/docs/src/doc/docs/docling-serve/serve-client.md
@@ -59,7 +59,7 @@ import ai.docling.serve.api.convert.request.options.ConvertDocumentOptions;
import ai.docling.serve.api.convert.request.options.OutputFormat;
import ai.docling.serve.api.convert.request.source.HttpSource;
import ai.docling.serve.api.convert.request.target.InBodyTarget;
-import ai.docling.serve.api.convert.response.ConvertDocumentResponse;
+import ai.docling.serve.api.convert.response.InBodyConvertDocumentResponse;
DoclingServeApi api = DoclingServeApi.builder()
.baseUrl("http://localhost:8000") // your Docling Serve URL
@@ -77,7 +77,7 @@ ConvertDocumentRequest request = ConvertDocumentRequest.builder()
.target(InBodyTarget.builder().build())
.build();
-ConvertDocumentResponse response = api.convertSource(request);
+InBodyConvertDocumentResponse response = (InBodyConvertDocumentResponse)api.convertSource(request);
System.out.println(response.getDocument().getMarkdownContent());
```
@@ -198,6 +198,7 @@ from `HttpClient`. Conversion may also return structured errors in the response
`ConvertDocumentResponse#getErrors()` even when content is present:
```java
+// InBodyConvertDocumentResponse
var result = api.convertSource(request);
if (result.getErrors() != null && !result.getErrors().isEmpty()) {
result.getErrors().forEach(err ->
diff --git a/docs/src/doc/docs/getting-started.md b/docs/src/doc/docs/getting-started.md
index d895b206..65f046f6 100644
--- a/docs/src/doc/docs/getting-started.md
+++ b/docs/src/doc/docs/getting-started.md
@@ -6,7 +6,7 @@ Use the [`DoclingServeApi`](docling-serve/serve-api.md) to convert a document by
import ai.docling.serve.api.DoclingServeApi;
import ai.docling.serve.api.convert.request.ConvertDocumentRequest;
import ai.docling.serve.api.convert.request.source.HttpSource;
-import ai.docling.serve.api.convert.response.ConvertDocumentResponse;
+import ai.docling.serve.api.convert.response.InBodyConvertDocumentResponse;
DoclingServeApi doclingServeApi = DoclingServeApi.builder()
.baseUrl("")
@@ -20,7 +20,7 @@ ConvertDocumentRequest request = ConvertDocumentRequest.builder()
)
.build();
-ConvertDocumentResponse response = doclingServeApi.convertSource(request);
+InBodyConvertDocumentResponse response = (InBodyConvertDocumentResponse)doclingServeApi.convertSource(request);
System.out.println(response.getDocument().getMarkdownContent());
```
diff --git a/docs/src/doc/docs/testcontainers.md b/docs/src/doc/docs/testcontainers.md
index 7c3ef733..949553ac 100644
--- a/docs/src/doc/docs/testcontainers.md
+++ b/docs/src/doc/docs/testcontainers.md
@@ -95,7 +95,7 @@ import ai.docling.serve.api.convert.request.options.ConvertDocumentOptions;
import ai.docling.serve.api.convert.request.options.OutputFormat;
import ai.docling.serve.api.convert.request.source.HttpSource;
import ai.docling.serve.api.convert.request.target.InBodyTarget;
-import ai.docling.serve.api.convert.response.ConvertDocumentResponse;
+import ai.docling.serve.api.convert.response.InBodyConvertDocumentResponse;
String baseUrl = docling.getApiUrl();
DoclingServeApi api = DoclingServeApi.builder()
@@ -111,7 +111,7 @@ ConvertDocumentRequest request = ConvertDocumentRequest.builder()
.target(InBodyTarget.builder().build())
.build();
-ConvertDocumentResponse response = api.convertSource(request);
+InBodyConvertDocumentResponse response = (InBodyConvertDocumentResponse)api.convertSource(request);
// Assert on response.getDocument().getMarkdownContent(), errors, timings, etc.
```