Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion docs/code_samples/default_v2.txt
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const inferenceParams = {
const inputSource = new mindee.PathInput({ inputPath: filePath });

// Send for processing
const response = mindeeClient.enqueueAndGetInference(
const response = mindeeClient.enqueueAndGetExtraction(
inputSource,
inferenceParams
);
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"clean": "rm -rf ./dist ./docs/_build",
"test": "mocha \"tests/**/*.spec.ts\"",
"test-integration": "mocha \"tests/**/*.integration.ts\"",
"test-v2": "mocha \"tests/v2/**/*.spec.ts\"",
"lint": "tsc --noEmit && eslint './src/**/*.ts' --report-unused-disable-directives && echo 'Your .ts files look good.'",
"lint-fix": "eslint './src/**/*.ts' --fix",
"docs": "typedoc --out docs/_build ./src/index.ts",
Expand Down
2 changes: 1 addition & 1 deletion src/http/apiCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export async function sendRequestAndReadResponse(
}
try {
const parsedResponse = JSON.parse(responseBody);
logger.debug("JSON parsed successfully, returning object.");
logger.debug("JSON parsed successfully, returning plain object.");
return {
messageObj: response,
data: parsedResponse,
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export * as v2 from "./v2/index.js";
export {
Client,
InferenceFile,
InferenceResponse,
ExtractionResponse,
JobResponse,
RawText,
RagMetadata,
Expand Down
2 changes: 1 addition & 1 deletion src/input/localInputSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ export abstract class LocalInputSource extends InputSource {
);
}
this.inputType = inputType;
logger.debug(`Loading file from: ${inputType}`);
logger.debug(`New local input source of type: ${inputType}`);
}

protected async checkMimetype(): Promise<string> {
Expand Down
69 changes: 57 additions & 12 deletions src/v2/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Command, OptionValues } from "commander";
import { Client } from "./client.js";
import { PathInput } from "../input/index.js";
import * as console from "console";
import { Inference } from "@/v2/parsing/index.js";
import { BaseInference } from "@/v2/parsing/inference/index.js";

const program = new Command();

Expand All @@ -18,13 +18,33 @@ function initClient(options: OptionValues): Client {
});
}

async function callEnqueueAndGetInference(
async function enqueueAndGetInference(
inputPath: string,
options: any
options: OptionValues
): Promise<void> {
const mindeeClient = initClient(options);
const inputSource = new PathInput({ inputPath: inputPath });
const response = await mindeeClient.enqueueAndGetInference(inputSource, {
const response = await mindeeClient.enqueueAndGetExtraction(inputSource, {
modelId: options.model,
pollingOptions: {
initialDelaySec: 2,
delaySec: 1.5,
maxRetries: 80,
}
});
if (!response.inference) {
throw Error("Inference could not be retrieved");
}
printResponse(response.inference);
}

async function enqueueAndGetUtility(
inputPath: string,
options: OptionValues
): Promise<void> {
const mindeeClient = initClient(options);
const inputSource = new PathInput({ inputPath: inputPath });
const response = await mindeeClient.enqueueAndGetUtility(inputSource, {
modelId: options.model,
pollingOptions: {
initialDelaySec: 2,
Expand All @@ -39,7 +59,7 @@ async function callEnqueueAndGetInference(
}

function printResponse(
document: Inference,
document: BaseInference,
): void {
if (document) {
console.log(`\n${document}`);
Expand All @@ -55,25 +75,50 @@ function addMainOptions(prog: Command) {
"-m, --model <model_id>",
"Model ID (required)"
);
prog.option("-k, --api-key <api_key>", "API key for document endpoint");
prog.argument("<input_path>", "full path to the file");
}

export function cli() {
program.name("mindee")
.description("Command line interface for Mindee products.")
.option("-d, --debug", "high verbosity mode");

const inferenceCmd: Command = program.command("parse")
.description("Send a file and retrieve the inference results.");
.description("Command line interface for Mindee V2 products.")
.option("-d, --debug", "high verbosity mode")
.option("-k, --api-key <api_key>", "your Mindee API key");

const inferenceCmd: Command = program.command("extract")
.description("Send a file and extract results.");
addMainOptions(inferenceCmd);

const cropCmd: Command = program.command("crop")
.description("Send a file and crop it.");
addMainOptions(cropCmd);

const splitCmd: Command = program.command("split")
.description("Send a file and split it.");
addMainOptions(splitCmd);

const ocrCmd: Command = program.command("ocr")
.description("Send a file and read its text content.");
addMainOptions(ocrCmd);

const classifyCmd: Command = program.command("classify")
.description("Send a file and classify its content.");
addMainOptions(classifyCmd);


inferenceCmd.action(function (
inputPath: string,
options: OptionValues,
) {
return callEnqueueAndGetInference(inputPath, options);
const allOptions = { ...program.opts(), ...options };
return enqueueAndGetInference(inputPath, allOptions);
});

cropCmd.action(function (
inputPath: string,
options: OptionValues,
) {
const allOptions = { ...program.opts(), ...options };
return enqueueAndGetUtility(inputPath, allOptions);
});

program.parse(process.argv);
Expand Down
160 changes: 125 additions & 35 deletions src/v2/client.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { setTimeout } from "node:timers/promises";
import { Dispatcher } from "undici";
import { InputSource } from "@/input/index.js";
import { MindeeError } from "@/errors/index.js";
import { errorHandler } from "@/errors/handler.js";
import { LOG_LEVELS, logger } from "@/logger.js";
import { ErrorResponse, InferenceResponse, JobResponse } from "./parsing/index.js";
import {
ErrorResponse,
ExtractionResponse,
JobResponse,
ResponseConstructor,
} from "./parsing/index.js";
import { MindeeApiV2 } from "./http/mindeeApiV2.js";
import { MindeeHttpErrorV2 } from "./http/errors.js";
import { InferenceParameters } from "./client/index.js";
import { InferenceParameters, UtilityParameters, ValidatedPollingOptions } from "./client/index.js";
import { CropResponse, BaseInferenceResponse } from "@/v2/parsing/inference/index.js";

/**
* Options for the V2 Mindee Client.
Expand Down Expand Up @@ -66,20 +73,75 @@ export class Client {
* @category Asynchronous
* @returns a `Promise` containing the job (queue) corresponding to a document.
*/
async enqueueInference(
async enqueueExtraction(
inputSource: InputSource,
params: InferenceParameters| ConstructorParameters<typeof InferenceParameters>[0]
): Promise<JobResponse> {
if (inputSource === undefined) {
throw new Error("The 'enqueue' function requires an input document.");
throw new MindeeError("An input document is required.");
}
const inferenceParams = params instanceof InferenceParameters
const paramsInstance = params instanceof InferenceParameters
? params
: new InferenceParameters(params);

await inputSource.init();
const jobResponse = await this.mindeeApi.reqPostInferenceEnqueue(
inputSource, paramsInstance
);
if (jobResponse.job.id === undefined || jobResponse.job.id.length === 0) {
logger.error(`Failed enqueueing:\n${jobResponse.getRawHttp()}`);
throw new MindeeError("Enqueueing of the document failed.");
}
logger.debug(
`Successfully enqueued document with job ID: ${jobResponse.job.id}.`
);
return jobResponse;
}

return await this.mindeeApi.reqPostInferenceEnqueue(inputSource, inferenceParams);
async enqueueUtility(
inputSource: InputSource,
params: UtilityParameters | ConstructorParameters<typeof UtilityParameters>[0]
): Promise<JobResponse> {
if (inputSource === undefined) {
throw new MindeeError("An input document is required.");
}
const paramsInstance = params instanceof UtilityParameters
? params
: new UtilityParameters(params);

await inputSource.init();
const jobResponse = await this.mindeeApi.reqPostUtilityEnqueue(inputSource, paramsInstance);
if (jobResponse.job.id === undefined || jobResponse.job.id.length === 0) {
logger.error(`Failed enqueueing:\n${jobResponse.getRawHttp()}`);
throw new MindeeError("Enqueueing of the document failed.");
}
logger.debug(
`Successfully enqueued document with job ID: ${jobResponse.job.id}.`
);
return jobResponse;
}

async getInference<T extends BaseInferenceResponse>(
responseType: ResponseConstructor<T>,
inferenceId: string
): Promise<T> {
logger.debug(
`Attempting to get inference with ID: ${inferenceId} using response type: ${responseType.name}`
);
return await this.mindeeApi.reqGetInference(responseType, inferenceId);
}

/**
* Retrieves an inference.
*
* @param inferenceId id of the queue to poll.
* @typeParam T an extension of an `Inference`. Can be omitted as it will be inferred from the `productClass`.
* @category Asynchronous
* @returns a `Promise` containing a `Job`, which also contains a `Document` if the
* parsing is complete.
*/
async getExtraction(inferenceId: string): Promise<ExtractionResponse> {
return await this.getInference(ExtractionResponse, inferenceId);
}

/**
Expand All @@ -91,8 +153,8 @@ export class Client {
* @returns a `Promise` containing a `Job`, which also contains a `Document` if the
* parsing is complete.
*/
async getInference(inferenceId: string): Promise<InferenceResponse> {
return await this.mindeeApi.reqGetInference(inferenceId);
async getUtility(inferenceId: string): Promise<CropResponse> {
return await this.getInference(CropResponse, inferenceId);
}

/**
Expand Down Expand Up @@ -120,58 +182,86 @@ export class Client {
* @category Synchronous
* @returns a `Promise` containing parsing results.
*/
async enqueueAndGetInference(
async enqueueAndGetExtraction(
inputSource: InputSource,
params: InferenceParameters| ConstructorParameters<typeof InferenceParameters>[0]
): Promise<InferenceResponse> {
const inferenceParams = params instanceof InferenceParameters
params: InferenceParameters | ConstructorParameters<typeof InferenceParameters>[0]
): Promise<ExtractionResponse> {
const paramsInstance = params instanceof InferenceParameters
? params
: new InferenceParameters(params);

const pollingOptions = inferenceParams.getValidatedPollingOptions();
const pollingOptions = paramsInstance.getValidatedPollingOptions();

const enqueueResponse: JobResponse = await this.enqueueInference(inputSource, params);
if (enqueueResponse.job.id === undefined || enqueueResponse.job.id.length === 0) {
logger.error(`Failed enqueueing:\n${enqueueResponse.getRawHttp()}`);
throw Error("Enqueueing of the document failed.");
}
const queueId: string = enqueueResponse.job.id;
logger.debug(
`Successfully enqueued document with job id: ${queueId}.`
const jobResponse: JobResponse = await this.enqueueExtraction(inputSource, params);
return await this.pollForInference(
ExtractionResponse, pollingOptions, jobResponse.job.id
);
}

async enqueueAndGetUtility(
inputSource: InputSource,
params: UtilityParameters | ConstructorParameters<typeof UtilityParameters>[0]
): Promise<CropResponse> {
const paramsInstance = params instanceof UtilityParameters
? params
: new UtilityParameters(params);

const pollingOptions = paramsInstance.getValidatedPollingOptions();

const jobResponse: JobResponse = await this.enqueueUtility(inputSource, params);
return await this.pollForInference(
CropResponse, pollingOptions, jobResponse.job.id
);
}

/**
* Send a document to an endpoint and poll the server until the result is sent or
* until the maximum number of tries is reached.
* @protected
*/
protected async pollForInference<T extends BaseInferenceResponse>(
responseType: ResponseConstructor<T>,
pollingOptions: ValidatedPollingOptions,
queueId: string,
): Promise<T> {
logger.debug(
`Waiting ${pollingOptions.initialDelaySec} seconds before polling.`
);
await setTimeout(
pollingOptions.initialDelaySec * 1000,
undefined,
pollingOptions.initialTimerOptions
);
logger.debug(
`Start polling for inference using job ID: ${queueId}.`
);
let retryCounter: number = 1;
let pollResults: JobResponse = await this.getJob(queueId);
while (retryCounter < pollingOptions.maxRetries) {
let pollResults: JobResponse;
while (retryCounter < pollingOptions.maxRetries + 1) {
logger.debug(
`Attempt ${retryCounter} of ${pollingOptions.maxRetries}`
);
pollResults = await this.getJob(queueId);
const error: ErrorResponse | undefined = pollResults.job.error;
if (error) {
throw new MindeeHttpErrorV2(error);
}
logger.debug(`Job status: ${pollResults.job.status}.`);
if (pollResults.job.status === "Failed") {
break;
}
if (pollResults.job.status === "Processed") {
return this.getInference(pollResults.job.id);
return this.getInference(responseType, pollResults.job.id);
}
logger.debug(
`Polling server for parsing result with queueId: ${queueId}.
Attempt no. ${retryCounter} of ${pollingOptions.maxRetries}.
Job status: ${pollResults.job.status}.`
);
await setTimeout(
pollingOptions.delaySec * 1000,
undefined,
pollingOptions.recurringTimerOptions
);
pollResults = await this.getJob(queueId);
retryCounter++;
}
const error: ErrorResponse | undefined = pollResults.job.error;
if (error) {
throw new MindeeHttpErrorV2(error);
}
throw Error(

throw new MindeeError(
"Asynchronous parsing request timed out after " +
pollingOptions.delaySec * retryCounter +
" seconds"
Expand Down
Loading
Loading