Skip to content

Commit b39d3b7

Browse files
[DRAFT] add support for Mindee Client V2
1 parent f9fa1c6 commit b39d3b7

27 files changed

Lines changed: 1111 additions & 38 deletions
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package com.mindee;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import com.mindee.http.MindeeApiV2;
5+
import com.mindee.http.MindeeHttpApiV2;
6+
import com.mindee.input.LocalInputSource;
7+
import com.mindee.input.LocalResponse;
8+
import com.mindee.input.PageOptions;
9+
import com.mindee.pdf.PdfBoxApi;
10+
import com.mindee.pdf.PdfOperation;
11+
import com.mindee.pdf.SplitQuery;
12+
import com.mindee.parsing.v2.AsyncInferenceResponse;
13+
import com.mindee.parsing.v2.AsyncJobResponse;
14+
import com.mindee.parsing.v2.PredictParameterV2;
15+
import com.mindee.parsing.v2.InferenceOptionsV2;
16+
import com.mindee.parsing.v2.AsyncPollingOptions;
17+
import java.io.IOException;
18+
19+
/**
20+
* Entry point for the Mindee **V2** API features.
21+
*/
22+
public class MindeeClientV2 {
23+
24+
private final PdfOperation pdfOperation;
25+
private final MindeeApiV2 mindeeApi;
26+
27+
/** Uses an API-key read from the environment variables. */
28+
public MindeeClientV2() {
29+
this(new PdfBoxApi(), createDefaultApiV2(""));
30+
}
31+
32+
/** Uses the supplied API-key. */
33+
public MindeeClientV2(String apiKey) {
34+
this(new PdfBoxApi(), createDefaultApiV2(apiKey));
35+
}
36+
37+
/** Directly inject an already configured {@link MindeeApiV2}. */
38+
public MindeeClientV2(MindeeApiV2 mindeeApi) {
39+
this(new PdfBoxApi(), mindeeApi);
40+
}
41+
42+
/** Inject both a PDF implementation and a HTTP implementation. */
43+
public MindeeClientV2(PdfOperation pdfOperation, MindeeApiV2 mindeeApi) {
44+
this.pdfOperation = pdfOperation;
45+
this.mindeeApi = mindeeApi;
46+
}
47+
48+
/* ------------------------------------------------------------------ */
49+
/* Queue helpers */
50+
/* ------------------------------------------------------------------ */
51+
52+
/**
53+
* Enqueue a document in the asynchronous “Generated” queue.
54+
*/
55+
public AsyncJobResponse enqueue(
56+
LocalInputSource inputSource,
57+
InferenceOptionsV2 options,
58+
PageOptions pageOptions) throws IOException {
59+
60+
if (pageOptions != null && inputSource.isPdf()) {
61+
inputSource.setFileBytes(
62+
pdfOperation.split(new SplitQuery(inputSource.getFileBytes(), pageOptions)).getFile());
63+
}
64+
65+
PredictParameterV2 payload = new PredictParameterV2(
66+
inputSource,
67+
options.getModelId(),
68+
options.getAlias(),
69+
options.getWebhookIds(),
70+
options.getRag());
71+
72+
return mindeeApi.enqueuePost(payload);
73+
}
74+
75+
/** Overload without page options. */
76+
public AsyncJobResponse enqueue(
77+
LocalInputSource inputSource,
78+
InferenceOptionsV2 options) throws IOException {
79+
return enqueue(inputSource, options, null);
80+
}
81+
82+
/**
83+
* Retrieve results for a previously enqueued document.
84+
*/
85+
public AsyncInferenceResponse parseQueued(String jobId) {
86+
if (jobId == null || jobId.isBlank()) {
87+
throw new IllegalArgumentException("jobId must not be null or blank.");
88+
}
89+
return mindeeApi.getInferenceFromQueue(jobId);
90+
}
91+
92+
/**
93+
* Convenience helper: enqueue, poll, and return the final inference.
94+
*/
95+
public AsyncInferenceResponse enqueueAndParse(
96+
LocalInputSource inputSource,
97+
InferenceOptionsV2 options,
98+
PageOptions pageOptions,
99+
AsyncPollingOptions polling) throws IOException, InterruptedException {
100+
101+
if (polling == null) {
102+
polling = new AsyncPollingOptions(); // default values
103+
}
104+
validatePollingOptions(polling);
105+
106+
AsyncJobResponse job = enqueue(inputSource, options, pageOptions);
107+
108+
Thread.sleep((long) (polling.getInitialDelaySec() * 1000));
109+
110+
int attempts = 0;
111+
int max = polling.getMaxRetries();
112+
while (attempts < max) {
113+
Thread.sleep((long) (polling.getIntervalSec() * 1000));
114+
AsyncInferenceResponse resp = parseQueued(job.getJob().getId());
115+
if (resp.getInference() != null) {
116+
return resp;
117+
}
118+
attempts++;
119+
}
120+
throw new RuntimeException("Max retries exceeded (" + max + ").");
121+
}
122+
123+
/** Overload with defaults (no page splitting, default polling). */
124+
public AsyncInferenceResponse enqueueAndParse(
125+
LocalInputSource inputSource,
126+
InferenceOptionsV2 options) throws IOException, InterruptedException {
127+
return enqueueAndParse(inputSource, options, null, null);
128+
}
129+
130+
/* ------------------------------------------------------------------ */
131+
/* Utility / helpers */
132+
/* ------------------------------------------------------------------ */
133+
134+
/**
135+
* Deserialize a webhook payload (or any saved response) into
136+
* {@link AsyncInferenceResponse}.
137+
*/
138+
public AsyncInferenceResponse loadInference(LocalResponse localResponse) throws IOException {
139+
ObjectMapper mapper = new ObjectMapper().findAndConfigureModules();
140+
AsyncInferenceResponse model =
141+
mapper.readValue(localResponse.getFile(), AsyncInferenceResponse.class);
142+
model.setRawResponse(localResponse.toString());
143+
return model;
144+
}
145+
146+
private static MindeeApiV2 createDefaultApiV2(String apiKey) {
147+
MindeeSettings settings = apiKey == null || apiKey.isBlank()
148+
? new MindeeSettings()
149+
: new MindeeSettings(apiKey);
150+
return MindeeHttpApiV2.builder()
151+
.mindeeSettings(settings)
152+
.build();
153+
}
154+
155+
private static void validatePollingOptions(AsyncPollingOptions p) {
156+
if (p.getInitialDelaySec() < 1) {
157+
throw new IllegalArgumentException("Initial delay must be ≥ 1 s");
158+
}
159+
if (p.getIntervalSec() < 1) {
160+
throw new IllegalArgumentException("Interval must be ≥ 1 s");
161+
}
162+
if (p.getMaxRetries() < 2) {
163+
throw new IllegalArgumentException("Max retries must be ≥ 2");
164+
}
165+
}
166+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.mindee;
2+
3+
import lombok.Getter;
4+
5+
import java.util.Optional;
6+
7+
/**
8+
* Mindee API V2 configuration.
9+
*/
10+
@Getter
11+
public class MindeeSettingsV2 {
12+
13+
private static final String DEFAULT_MINDEE_V2_API_URL = "https://api-v2.mindee.net/v1";
14+
private final String apiKey;
15+
private final String baseUrl;
16+
17+
public MindeeSettingsV2() {
18+
this("", "");
19+
}
20+
21+
public Optional<String> getApiKey() {
22+
return Optional.ofNullable(apiKey);
23+
}
24+
25+
public MindeeSettingsV2(String apiKey) {
26+
this(apiKey, "");
27+
}
28+
29+
public MindeeSettingsV2(String apiKey, String baseUrl) {
30+
31+
if (apiKey == null || apiKey.trim().isEmpty()) {
32+
String apiKeyFromEnv = System.getenv("MINDEE_V2_API_KEY");
33+
if (apiKeyFromEnv == null || apiKeyFromEnv.trim().isEmpty()) {
34+
this.apiKey = null;
35+
} else {
36+
this.apiKey = apiKeyFromEnv;
37+
}
38+
} else {
39+
this.apiKey = apiKey;
40+
}
41+
42+
if (baseUrl == null || baseUrl.trim().isEmpty()) {
43+
String baseUrlFromEnv = System.getenv("MINDEE_V2_API_URL");
44+
if (baseUrlFromEnv != null && !baseUrlFromEnv.trim().isEmpty()) {
45+
this.baseUrl = baseUrlFromEnv;
46+
} else {
47+
this.baseUrl = DEFAULT_MINDEE_V2_API_URL;
48+
}
49+
} else {
50+
this.baseUrl = baseUrl;
51+
}
52+
}
53+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.mindee;
2+
3+
import com.mindee.input.LocalInputSource;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
import lombok.Singular;
7+
8+
import java.util.Collections;
9+
import java.util.List;
10+
import java.util.Objects;
11+
12+
/**
13+
* Parameters for the V2 “predict” endpoint.
14+
*
15+
* <p>This is a pure config object – no Jackson annotations are used.</p>
16+
*/
17+
@Getter
18+
public final class PredictOptionsV2 {
19+
20+
/**
21+
* Optional alias for the file.
22+
*/
23+
private final String alias;
24+
25+
/**
26+
* ID of the model.
27+
*/
28+
private final String modelId;
29+
30+
/**
31+
* If {@code true}, enable Retrieval-Augmented Generation.
32+
*/
33+
private final boolean rag;
34+
35+
/**
36+
* IDs of webhooks to propagate the API response to (may be empty).
37+
*/
38+
private final List<String> webhookIds;
39+
40+
/**
41+
* Local input source.
42+
*/
43+
private final LocalInputSource localSource;
44+
45+
public PredictOptionsV2(
46+
LocalInputSource localSource,
47+
String modelId,
48+
boolean rag,
49+
String alias,
50+
List<String> webhookIds
51+
) {
52+
53+
this.localSource = Objects.requireNonNull(localSource, "localSource must not be null");
54+
this.modelId = Objects.requireNonNull(modelId, "modelId must not be null");
55+
this.rag = rag;
56+
this.alias = alias;
57+
this.webhookIds = webhookIds == null ? Collections.emptyList()
58+
: webhookIds;
59+
}
60+
61+
@Builder(builderMethodName = "builder")
62+
private static PredictOptionsV2 build(
63+
LocalInputSource localSource,
64+
String modelId,
65+
boolean rag,
66+
String alias,
67+
@Singular List<String> webhookIds
68+
) {
69+
70+
return new PredictOptionsV2(localSource, modelId, rag, alias, webhookIds);
71+
}
72+
}

src/main/java/com/mindee/http/MindeeApi.java

Lines changed: 2 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44
import com.mindee.parsing.common.Inference;
55
import com.mindee.parsing.common.PredictResponse;
66
import com.mindee.parsing.common.WorkflowResponse;
7-
import java.io.ByteArrayOutputStream;
7+
88
import java.io.IOException;
9-
import org.apache.hc.core5.http.HttpEntity;
109

1110
/**
1211
* Defines required methods for an API.
1312
*/
14-
abstract public class MindeeApi {
13+
abstract public class MindeeApi extends MindeeApiCommon {
1514

1615
/**
1716
* Get a document from the predict queue.
@@ -45,38 +44,4 @@ abstract public <DocT extends Inference> WorkflowResponse<DocT> executeWorkflowP
4544
String workflowId,
4645
RequestParameters requestParameters
4746
) throws IOException;
48-
49-
protected String getUserAgent() {
50-
String javaVersion = System.getProperty("java.version");
51-
String sdkVersion = getClass().getPackage().getImplementationVersion();
52-
String osName = System.getProperty("os.name").toLowerCase();
53-
54-
if (osName.contains("windows")) {
55-
osName = "windows";
56-
} else if (osName.contains("darwin")) {
57-
osName = "macos";
58-
} else if (osName.contains("mac")) {
59-
osName = "macos";
60-
} else if (osName.contains("linux")) {
61-
osName = "linux";
62-
} else if (osName.contains("bsd")) {
63-
osName = "bsd";
64-
} else if (osName.contains("aix")) {
65-
osName = "aix";
66-
}
67-
return String.format("mindee-api-java@v%s java-v%s %s", sdkVersion, javaVersion, osName);
68-
}
69-
70-
protected boolean is2xxStatusCode(int statusCode) {
71-
return statusCode >= 200 && statusCode <= 299;
72-
}
73-
74-
protected String readRawResponse(HttpEntity responseEntity) throws IOException {
75-
ByteArrayOutputStream contentRead = new ByteArrayOutputStream();
76-
byte[] buffer = new byte[1024];
77-
for (int length; (length = responseEntity.getContent().read(buffer)) != -1; ) {
78-
contentRead.write(buffer, 0, length);
79-
}
80-
return contentRead.toString("UTF-8");
81-
}
8247
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.mindee.http;
2+
3+
import org.apache.hc.core5.http.HttpEntity;
4+
5+
import java.io.ByteArrayOutputStream;
6+
import java.io.IOException;
7+
8+
public abstract class MindeeApiCommon {
9+
10+
protected String getUserAgent() {
11+
String javaVersion = System.getProperty("java.version");
12+
String sdkVersion = getClass().getPackage().getImplementationVersion();
13+
String osName = System.getProperty("os.name").toLowerCase();
14+
15+
if (osName.contains("windows")) {
16+
osName = "windows";
17+
} else if (osName.contains("darwin")) {
18+
osName = "macos";
19+
} else if (osName.contains("mac")) {
20+
osName = "macos";
21+
} else if (osName.contains("linux")) {
22+
osName = "linux";
23+
} else if (osName.contains("bsd")) {
24+
osName = "bsd";
25+
} else if (osName.contains("aix")) {
26+
osName = "aix";
27+
}
28+
return String.format("mindee-api-java@v%s java-v%s %s", sdkVersion, javaVersion, osName);
29+
}
30+
31+
protected boolean is2xxStatusCode(int statusCode) {
32+
return statusCode >= 200 && statusCode <= 299;
33+
}
34+
35+
protected String readRawResponse(HttpEntity responseEntity) throws IOException {
36+
ByteArrayOutputStream contentRead = new ByteArrayOutputStream();
37+
byte[] buffer = new byte[1024];
38+
for (int length; (length = responseEntity.getContent().read(buffer)) != -1; ) {
39+
contentRead.write(buffer, 0, length);
40+
}
41+
return contentRead.toString("UTF-8");
42+
}
43+
}

0 commit comments

Comments
 (0)