Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b39d3b7
[DRAFT] add support for Mindee Client V2
sebastianMindee Jul 4, 2025
c41ecb3
:sparkles: add support for V2 client
sebastianMindee Jul 8, 2025
b31cb2e
add sample code
sebastianMindee Jul 8, 2025
d34365e
fix lingering issues (NO TESTS)
sebastianMindee Jul 9, 2025
52742d5
fix PR trigger target
sebastianMindee Jul 9, 2025
e15675b
fix display + add basic unit test
sebastianMindee Jul 9, 2025
df7ca94
add tests
sebastianMindee Jul 10, 2025
4c00a35
fix permissions for workflows
sebastianMindee Jul 10, 2025
dc8e25e
fix perms again
sebastianMindee Jul 10, 2025
40e3e9c
bump test lib, fix tests, add missing object
sebastianMindee Jul 10, 2025
35f30cf
refactor v2 internals
sebastianMindee Jul 15, 2025
5945983
apply fixes to match latest test syntaxes
sebastianMindee Jul 15, 2025
f496e4d
fix incorrect args, fix code sample & remove ugly comments that I hate
sebastianMindee Jul 16, 2025
365f15f
remove invalid jsonproperty annotations
sebastianMindee Jul 16, 2025
76b6eb6
tweak internal JobResponse syntax
sebastianMindee Jul 16, 2025
f159a96
rename InferenceOptions -> InferenceParameters
sebastianMindee Jul 16, 2025
3f34ecf
fix valid response check
sebastianMindee Jul 17, 2025
f175489
apply naming fixes
sebastianMindee Jul 17, 2025
6518b7a
tweak client calls to split up polling & inference retrieval
sebastianMindee Jul 17, 2025
6a3ec0a
rework inference & page options
sebastianMindee Jul 17, 2025
c4dc4d4
fix test
sebastianMindee Jul 17, 2025
90d7752
rework function syntaxes
sebastianMindee Jul 17, 2025
5cd5da5
fix typos & rename enqueue() method on V2
sebastianMindee Jul 17, 2025
d34f587
add missing test
sebastianMindee Jul 17, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,29 @@ name: Pull Request
on:
pull_request:

permissions:
contents: read
pull-requests: read

jobs:
static_analysis:
uses: mindee/mindee-api-java/.github/workflows/_static-analysis.yml@main
uses: ./.github/workflows/_static-analysis.yml
build:
uses: mindee/mindee-api-java/.github/workflows/_build.yml@main
uses: ./.github/workflows/_build.yml
needs: static_analysis
secrets: inherit
codeql:
uses: mindee/mindee-api-java/.github/workflows/_codeql.yml@main
uses: ./.github/workflows/_codeql.yml
needs: build
permissions:
contents: read
actions: read
security-events: write
test_integrations:
uses: mindee/mindee-api-java/.github/workflows/_test-integrations.yml@main
uses: ./.github/workflows/_test-integrations.yml
needs: build
secrets: inherit
test_code_samples:
uses: mindee/mindee-api-java/.github/workflows/_test-code-samples.yml@main
uses: ./.github/workflows/_test-code-samples.yml
needs: build
secrets: inherit
23 changes: 0 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,29 +207,6 @@ public class SimpleMindeeClient {
}
```


## Further Reading
Complete details on the working of the library are available in the following guides:

* [Getting started](https://developers.mindee.com/docs/java-ocr-getting-started)
* [Java Generated APIs](https://developers.mindee.com/docs/java-generated-ocr)
* [Java Custom APIs (API Builder - Deprecated)](https://developers.mindee.com/docs/java-api-builder)
* [Java Invoice OCR](https://developers.mindee.com/docs/java-invoice-ocr)
* [Java Receipt OCR](https://developers.mindee.com/docs/java-receipt-ocr)
* [Java Financial Document OCR](https://developers.mindee.com/docs/java-financial-document-ocr)
* [Java Passport OCR](https://developers.mindee.com/docs/java-passport-ocr)
* [Java Resume OCR](https://developers.mindee.com/docs/java-resume-ocr)
* [Java International Id OCR](https://developers.mindee.com/docs/java-international-id-ocr)
* [Java FR Bank Account Detail OCR](https://developers.mindee.com/docs/java-fr-bank-account-details-ocr)
* [Java FR Carte Grise OCR](https://developers.mindee.com/docs/java-fr-carte-grise-ocr)
* [Java FR Health Card OCR](https://developers.mindee.com/docs/java-fr-health-card-ocr)
* [Java FR ID Card OCR](https://developers.mindee.com/docs/java-fr-carte-nationale-didentite-ocr)
* [Java US Bank Check OCR](https://developers.mindee.com/docs/java-us-bank-check-ocr)
* [Java Barcode Reader API](https://developers.mindee.com/docs/java-barcode-reader-ocr)
* [Java Cropper API](https://developers.mindee.com/docs/java-cropper-ocr)
* [Java Invoice Splitter API](https://developers.mindee.com/docs/java-invoice-splitter-ocr)
* [Java Multi Receipts Detector API](https://developers.mindee.com/docs/java-multi-receipts-detector-ocr)

You can view the source code on [GitHub](https://github.com/mindee/mindee-api-java).

You can also take a look at the
Expand Down
34 changes: 34 additions & 0 deletions docs/code_samples/default_v2.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import com.mindee.MindeeClientV2;
import com.mindee.InferenceParameters;
import com.mindee.input.LocalInputSource;
import com.mindee.parsing.v2.InferenceResponse;
import java.io.File;
import java.io.IOException;

public class SimpleMindeeClient {

public static void main(String[] args) throws IOException, InterruptedException {
String apiKey = "MY_API_KEY";
String filePath = "/path/to/the/file.ext";
String modelId = "MY_MODEL_ID";

// Init a new client
MindeeClientV2 mindeeClient = new MindeeClientV2(apiKey);

// Load a file from disk
LocalInputSource inputSource = new LocalInputSource(new File(filePath));

// Prepare the enqueueing options
// Note: modelId is mandatory.
InferenceParameters options = InferenceParameters.builder(modelId).build();

// Parse the file
InferenceResponse response = mindeeClient.enqueueAndGetInference(
inputSource,
options
);

// Print a summary of the response
System.out.println(response.getInference().toString());
}
}
97 changes: 97 additions & 0 deletions src/main/java/com/mindee/InferenceParameters.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.mindee;

import com.mindee.input.PageOptions;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import lombok.Data;
import lombok.Getter;

/**
* Options to pass when calling methods using the API V2.
*/
@Getter
@Data
public final class InferenceParameters {
/**
* ID of the model (required).
*/
private final String modelId;
/**
* Enables Retrieval-Augmented Generation (optional, default: {@code false}).
*/
private final boolean rag;
/**
* Optional alias for the file.
*/
private final String alias;
/**
* IDs of webhooks to propagate the API response to (may be empty).
*/
private final List<String> webhookIds;
/*
* Asynchronous polling options.
*/
private final AsyncPollingOptions pollingOptions;

/**
* Create a new builder.
*
* @param modelId the mandatory model identifier
* @return a fresh {@link Builder}
*/
public static Builder builder(String modelId) {
return new Builder(modelId);
}

/**
* Fluent builder for {@link InferenceParameters}.
*/
public static final class Builder {

private final String modelId;
private boolean rag = false;
private String alias;
private List<String> webhookIds = Collections.emptyList();
private AsyncPollingOptions pollingOptions = AsyncPollingOptions.builder().build();

private Builder(String modelId) {
this.modelId = Objects.requireNonNull(modelId, "modelId must not be null");
}

/** Enable / disable Retrieval-Augmented Generation. */
public Builder rag(boolean rag) {
this.rag = rag;
return this;
}

/** Set an alias for the uploaded document. */
public Builder alias(String alias) {
this.alias = alias;
return this;
}

/** Provide IDs of webhooks to forward the API response to. */
public Builder webhookIds(List<String> webhookIds) {
this.webhookIds = webhookIds;
return this;
}


public Builder pollingOptions(AsyncPollingOptions pollingOptions) {
this.pollingOptions = pollingOptions;
return this;
}

/** Build an immutable {@link InferenceParameters} instance. */
public InferenceParameters build() {
return new InferenceParameters(
modelId,
rag,
alias,
webhookIds,
pollingOptions
);
}
}
}
39 changes: 23 additions & 16 deletions src/main/java/com/mindee/MindeeClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
*/
public class MindeeClient {

protected PdfOperation pdfOperation;
private final MindeeApi mindeeApi;
private final PdfOperation pdfOperation;

/**
* Create a default MindeeClient.
Expand Down Expand Up @@ -120,6 +120,28 @@ public <T extends Inference> AsyncPredictResponse<T> enqueue(
null);
}

/**
* Retrieves the file after applying page operations to it.
* @param localInputSource Local input source to apply operations to.
* @param pageOptions Options to apply.
* @return A byte array of the file after applying page operations.
* @throws IOException Throws if the file can't be accessed.
*/
protected byte[] getSplitFile(
LocalInputSource localInputSource,
PageOptions pageOptions
) throws IOException {
byte[] splitFile;
if (pageOptions == null || !localInputSource.isPdf()) {
splitFile = localInputSource.getFile();
} else {
splitFile = pdfOperation.split(
new SplitQuery(localInputSource.getFile(), pageOptions)
).getFile();
}
return splitFile;
}

/**
* Send a local file to an async queue.
* @param <T> Type of inference.
Expand Down Expand Up @@ -1124,19 +1146,4 @@ public <T extends Inference> AsyncPredictResponse<T> loadPrediction(
return objectMapper.readValue(localResponse.getFile(), parametricType);
}

private byte[] getSplitFile(
LocalInputSource localInputSource,
PageOptions pageOptions
) throws IOException {
byte[] splitFile;
if (pageOptions == null || !localInputSource.isPdf()) {
splitFile = localInputSource.getFile();
} else {
splitFile = pdfOperation.split(
new SplitQuery(localInputSource.getFile(), pageOptions)
).getFile();
}
return splitFile;
}

}
138 changes: 138 additions & 0 deletions src/main/java/com/mindee/MindeeClientV2.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package com.mindee;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mindee.http.MindeeApiV2;
import com.mindee.http.MindeeHttpApiV2;
import com.mindee.http.MindeeHttpExceptionV2;
import com.mindee.input.LocalInputSource;
import com.mindee.input.LocalResponse;
import com.mindee.parsing.v2.ErrorResponse;
import com.mindee.parsing.v2.InferenceResponse;
import com.mindee.parsing.v2.JobResponse;
import java.io.IOException;

/**
* Entry point for the Mindee **V2** API features.
*/
public class MindeeClientV2 {
private final MindeeApiV2 mindeeApi;

/** Uses an API-key read from the environment variables. */
public MindeeClientV2() {
this(createDefaultApiV2(""));
}

/** Uses the supplied API-key. */
public MindeeClientV2(String apiKey) {
this(createDefaultApiV2(apiKey));
}


/** Inject both a PDF implementation and a HTTP implementation. */
public MindeeClientV2(MindeeApiV2 mindeeApi) {
this.mindeeApi = mindeeApi;
}

/**
* Enqueue a document in the asynchronous queue.
*/
public JobResponse enqueueInference(
LocalInputSource inputSource,
InferenceParameters params) throws IOException {
return mindeeApi.reqPostInferenceEnqueue(inputSource, params);
}

/**
* Get the status of an inference that was previously enqueued.
* Can be used for polling.
*/
public JobResponse getJob(String jobId) {
if (jobId == null || jobId.trim().isEmpty()) {
throw new IllegalArgumentException("jobId must not be null or blank.");
}
return mindeeApi.reqGetJob(jobId);
}

/**
* Get the result of an inference that was previously enqueued.
* The inference will only be available after it has finished processing.
*/
public InferenceResponse getInference(String inferenceId) {
if (inferenceId == null || inferenceId.trim().isEmpty()) {
throw new IllegalArgumentException("inferenceId must not be null or blank.");
}

return mindeeApi.reqGetInference(inferenceId);
}

/**
* Send a local file to an async queue, poll, and parse when complete.
* @param inputSource The input source to send.
* @param options The options to send along with the file.
* @return an instance of {@link InferenceResponse}.
* @throws IOException Throws if the file can't be accessed.
* @throws InterruptedException Throws if the thread is interrupted.
*/
public InferenceResponse enqueueAndGetInference(
LocalInputSource inputSource,
InferenceParameters options) throws IOException, InterruptedException {

validatePollingOptions(options.getPollingOptions());

JobResponse job = enqueueInference(inputSource, options);

Thread.sleep((long) (options.getPollingOptions().getInitialDelaySec() * 1000));
JobResponse resp = job;
int attempts = 0;
int max = options.getPollingOptions().getMaxRetries();
while (attempts < max) {
Thread.sleep((long) (options.getPollingOptions().getIntervalSec() * 1000));
resp = getJob(job.getJob().getId());
if (resp.getJob().getStatus().equals("Failed")) {
break;
}
else if (resp.getJob().getStatus().equals("Processed")) {
return getInference(resp.getJob().getId());
}
attempts++;
}
ErrorResponse error = resp.getJob().getError();
if (error != null) {
throw new MindeeHttpExceptionV2(error.getStatus(), error.getDetail());
}
throw new RuntimeException("Max retries exceeded (" + max + ").");
}

/**
* Deserialize a webhook payload (or any saved response) into an
* {@link InferenceResponse}.
*/
public InferenceResponse loadInference(LocalResponse localResponse) throws IOException {
ObjectMapper mapper = new ObjectMapper().findAndRegisterModules();
InferenceResponse model =
mapper.readValue(localResponse.getFile(), InferenceResponse.class);
model.setRawResponse(localResponse.toString());
return model;
}

private static MindeeApiV2 createDefaultApiV2(String apiKey) {
MindeeSettingsV2 settings = apiKey == null || apiKey.trim().isEmpty()
? new MindeeSettingsV2()
: new MindeeSettingsV2(apiKey);
return MindeeHttpApiV2.builder()
.mindeeSettings(settings)
.build();
}

private static void validatePollingOptions(AsyncPollingOptions p) {
if (p.getInitialDelaySec() < 1) {
throw new IllegalArgumentException("Initial delay must be ≥ 1 s");
}
if (p.getIntervalSec() < 1) {
throw new IllegalArgumentException("Interval must be ≥ 1 s");
}
if (p.getMaxRetries() < 2) {
throw new IllegalArgumentException("Max retries must be ≥ 2");
}
}
}
Loading
Loading