Skip to content

Commit 83cd9ee

Browse files
authored
[Form recognizer] Add classify API on documentAnalysis client (Azure#34345)
1 parent 30a9de7 commit 83cd9ee

File tree

11 files changed

+974
-5
lines changed

11 files changed

+974
-5
lines changed

sdk/formrecognizer/azure-ai-formrecognizer/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
### Bugs Fixed
2626

2727
### Other Changes
28+
#### Known Bug
29+
- `com.azure.core.exception.HttpResponseException: Deserialization Failed` error when using the `beginClasifyDocument`
30+
method on DocumentAnalysisAsyncClient and DocumentAnalysisClient.
2831

2932
## 4.0.5 (2023-03-16)
3033

sdk/formrecognizer/azure-ai-formrecognizer/src/main/java/com/azure/ai/formrecognizer/documentanalysis/DocumentAnalysisAsyncClient.java

Lines changed: 183 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
package com.azure.ai.formrecognizer.documentanalysis;
55

66
import com.azure.ai.formrecognizer.documentanalysis.administration.models.OperationStatus;
7+
import com.azure.ai.formrecognizer.documentanalysis.implementation.DocumentClassifiersImpl;
78
import com.azure.ai.formrecognizer.documentanalysis.implementation.DocumentModelsImpl;
89
import com.azure.ai.formrecognizer.documentanalysis.implementation.FormRecognizerClientImpl;
910
import com.azure.ai.formrecognizer.documentanalysis.implementation.models.AnalyzeDocumentRequest;
1011
import com.azure.ai.formrecognizer.documentanalysis.implementation.models.AnalyzeResultOperation;
12+
import com.azure.ai.formrecognizer.documentanalysis.implementation.models.ClassifyDocumentRequest;
1113
import com.azure.ai.formrecognizer.documentanalysis.implementation.models.StringIndexType;
1214
import com.azure.ai.formrecognizer.documentanalysis.implementation.util.Transforms;
1315
import com.azure.ai.formrecognizer.documentanalysis.models.AnalyzeDocumentOptions;
@@ -61,6 +63,7 @@
6163
public final class DocumentAnalysisAsyncClient {
6264
private final ClientLogger logger = new ClientLogger(DocumentAnalysisAsyncClient.class);
6365
private final DocumentModelsImpl documentModelsImpl;
66+
private final DocumentClassifiersImpl documentClassifiersImpl;
6467
private final DocumentAnalysisServiceVersion serviceVersion;
6568

6669
/**
@@ -72,6 +75,7 @@ public final class DocumentAnalysisAsyncClient {
7275
*/
7376
DocumentAnalysisAsyncClient(FormRecognizerClientImpl formRecognizerClientImpl, DocumentAnalysisServiceVersion serviceVersion) {
7477
this.documentModelsImpl = formRecognizerClientImpl.getDocumentModels();
78+
this.documentClassifiersImpl = formRecognizerClientImpl.getDocumentClassifiers();
7579
this.serviceVersion = serviceVersion;
7680
}
7781

@@ -171,7 +175,7 @@ public final class DocumentAnalysisAsyncClient {
171175
return beginAnalyzeDocumentFromUrl(documentUrl, modelId, analyzeDocumentOptions, Context.NONE);
172176
}
173177

174-
PollerFlux<OperationResult, AnalyzeResult>
178+
private PollerFlux<OperationResult, AnalyzeResult>
175179
beginAnalyzeDocumentFromUrl(String documentUrl, String modelId,
176180
AnalyzeDocumentOptions analyzeDocumentOptions,
177181
Context context) {
@@ -329,7 +333,7 @@ public final class DocumentAnalysisAsyncClient {
329333
return beginAnalyzeDocument(modelId, document, analyzeDocumentOptions, Context.NONE);
330334
}
331335

332-
PollerFlux<OperationResult, AnalyzeResult>
336+
private PollerFlux<OperationResult, AnalyzeResult>
333337
beginAnalyzeDocument(String modelId, BinaryData document,
334338
AnalyzeDocumentOptions analyzeDocumentOptions, Context context) {
335339
try {
@@ -380,6 +384,183 @@ public final class DocumentAnalysisAsyncClient {
380384
}
381385
}
382386

387+
/**
388+
* Classify a given document using a document classifier.
389+
* For more information on how to build a custom classifier model,
390+
* see <a href="https://aka.ms/azsdk/formrecognizer/buildclassifiermodel"></a>
391+
* <p>The service does not support cancellation of the long running operation and returns with an
392+
* error message indicating absence of cancellation support.</p>
393+
*
394+
* <p><strong>Code sample</strong></p>
395+
* <p> Analyze a document using the URL of the document. </p>
396+
* <!-- src_embed com.azure.ai.formrecognizer.documentanalysis.DocumentAnalysisAsyncClient.beginClassifyDocumentFromUrl#string-string -->
397+
* <pre>
398+
* String documentUrl = &quot;&#123;document_url&#125;&quot;;
399+
* &#47;&#47; analyze a receipt using prebuilt model
400+
* String classifierId = &quot;custom-trained-classifier-id&quot;;
401+
*
402+
* documentAnalysisAsyncClient.beginClassifyDocumentFromUrl&#40;classifierId, documentUrl&#41;
403+
* &#47;&#47; if polling operation completed, retrieve the final result.
404+
* .flatMap&#40;AsyncPollResponse::getFinalResult&#41;
405+
* .subscribe&#40;analyzeResult -&gt; &#123;
406+
* System.out.println&#40;analyzeResult.getModelId&#40;&#41;&#41;;
407+
* analyzeResult.getDocuments&#40;&#41;
408+
* .forEach&#40;analyzedDocument -&gt; System.out.printf&#40;&quot;Doc Type: %s%n&quot;, analyzedDocument.getDocType&#40;&#41;&#41;&#41;;
409+
* &#125;&#41;;
410+
*
411+
* </pre>
412+
* <!-- end com.azure.ai.formrecognizer.documentanalysis.DocumentAnalysisAsyncClient.beginClassifyDocumentFromUrl#string-string -->
413+
*
414+
* @param classifierId The unique classifier ID to be used. Use this to specify the custom classifier ID.
415+
* @param documentUrl The URL of the document to analyze.
416+
*
417+
* @return A {@link PollerFlux} that polls the progress of the analyze document operation until it has completed, has failed,
418+
* or has been cancelled. The completed operation returns an {@link AnalyzeResult}.
419+
* @throws HttpResponseException If analyze operation fails and the {@link AnalyzeResultOperation} returns
420+
* with an {@link OperationStatus#FAILED}..
421+
* @throws IllegalArgumentException If {@code documentUrl} or {@code classifierId} is null.
422+
*/
423+
@ServiceMethod(returns = ReturnType.LONG_RUNNING_OPERATION)
424+
public PollerFlux<OperationResult, AnalyzeResult>
425+
beginClassifyDocumentFromUrl(String classifierId, String documentUrl) {
426+
return beginClassifyDocumentFromUrl(classifierId, documentUrl, Context.NONE);
427+
}
428+
429+
private PollerFlux<OperationResult, AnalyzeResult>
430+
beginClassifyDocumentFromUrl(String documentUrl, String classifierId,
431+
Context context) {
432+
try {
433+
if (CoreUtils.isNullOrEmpty(documentUrl)) {
434+
throw logger.logExceptionAsError(new IllegalArgumentException("'documentUrl' is required and cannot"
435+
+ " be null or empty"));
436+
}
437+
if (CoreUtils.isNullOrEmpty(classifierId)) {
438+
throw logger.logExceptionAsError(new IllegalArgumentException("'classifierId' is required and cannot"
439+
+ " be null or empty"));
440+
}
441+
442+
return new PollerFlux<>(
443+
DEFAULT_POLL_INTERVAL,
444+
activationOperation(() ->
445+
documentClassifiersImpl.classifyDocumentWithResponseAsync(classifierId,
446+
StringIndexType.UTF16CODE_UNIT,
447+
new ClassifyDocumentRequest().setUrlSource(documentUrl),
448+
context)
449+
.map(analyzeDocumentResponse ->
450+
Transforms.toDocumentOperationResult(
451+
analyzeDocumentResponse.getDeserializedHeaders().getOperationLocation())),
452+
logger),
453+
pollingOperation(resultId ->
454+
documentClassifiersImpl.getClassifyResultWithResponseAsync(classifierId, resultId, context)),
455+
(activationResponse, pollingContext) ->
456+
Mono.error(new RuntimeException("Cancellation is not supported")),
457+
fetchingOperation(resultId ->
458+
documentClassifiersImpl.getClassifyResultWithResponseAsync(
459+
classifierId,
460+
resultId,
461+
context))
462+
.andThen(after -> after
463+
.map(modelSimpleResponse ->
464+
Transforms.toAnalyzeResultOperation(modelSimpleResponse.getValue().getAnalyzeResult()))
465+
.onErrorMap(Transforms::mapToHttpResponseExceptionIfExists)));
466+
} catch (RuntimeException ex) {
467+
return PollerFlux.error(ex);
468+
}
469+
}
470+
471+
/**
472+
* Classify a given document using a document classifier.
473+
* For more information on how to build a custom classifier model,
474+
* see <a href="https://aka.ms/azsdk/formrecognizer/buildclassifiermodel"></a>
475+
* <p>The service does not support cancellation of the long running operation and returns with an
476+
* error message indicating absence of cancellation support.</p>
477+
* <p>
478+
* Note that the {@code data} passed must be replayable if retries are enabled (the default). In other words, the
479+
* {@code Flux} must produce the same data each time it is subscribed to.
480+
*
481+
* <p><strong>Code sample</strong></p>
482+
* <p> Analyze a document with configurable options.</p>
483+
* <!-- src_embed com.azure.ai.formrecognizer.documentanalysis.DocumentAnalysisAsyncClient.beginAnalyzeDocument#string-BinaryData -->
484+
* <pre>
485+
* File document = new File&#40;&quot;&#123;local&#47;file_path&#47;fileName.jpg&#125;&quot;&#41;;
486+
* String modelId = &quot;&#123;model_id&#125;&quot;;
487+
* &#47;&#47; Utility method to convert input stream to Binary Data
488+
* BinaryData buffer = BinaryData.fromStream&#40;new ByteArrayInputStream&#40;Files.readAllBytes&#40;document.toPath&#40;&#41;&#41;&#41;&#41;;
489+
*
490+
* documentAnalysisAsyncClient.beginAnalyzeDocument&#40;modelId, buffer&#41;
491+
* &#47;&#47; if polling operation completed, retrieve the final result.
492+
* .flatMap&#40;AsyncPollResponse::getFinalResult&#41;
493+
* .subscribe&#40;analyzeResult -&gt;
494+
* analyzeResult.getDocuments&#40;&#41;
495+
* .stream&#40;&#41;
496+
* .forEach&#40;analyzedDocument -&gt;
497+
* analyzedDocument.getFields&#40;&#41;
498+
* .forEach&#40;&#40;key, documentField&#41; -&gt; &#123;
499+
* System.out.printf&#40;&quot;Field text: %s%n&quot;, key&#41;;
500+
* System.out.printf&#40;&quot;Field value data content: %s%n&quot;, documentField.getContent&#40;&#41;&#41;;
501+
* System.out.printf&#40;&quot;Confidence score: %.2f%n&quot;, documentField.getConfidence&#40;&#41;&#41;;
502+
* &#125;&#41;&#41;&#41;;
503+
* </pre>
504+
* <!-- end com.azure.ai.formrecognizer.documentanalysis.DocumentAnalysisAsyncClient.beginClassifyDocument#string-BinaryData -->
505+
*
506+
* @param classifierId The unique classifier ID to be used. Use this to specify the custom classifier ID.
507+
* @param document The data of the document to analyze information from. For service supported file types, see:
508+
* <a href="https://aka.ms/azsdk/formrecognizer/supportedfiles"></a>
509+
* @return A {@link PollerFlux} that polls the progress of the analyze document operation until it has completed,
510+
* has failed, or has been cancelled. The completed operation returns an {@link AnalyzeResult}.
511+
* @throws HttpResponseException If analyze operation fails and returns with an {@link OperationStatus#FAILED}.
512+
* @throws IllegalArgumentException If {@code document} or {@code classifierId} is null.
513+
* @throws IllegalArgumentException If {@code document} length is null or unspecified.
514+
* Use {@link BinaryData#fromStream(InputStream, Long)} to create an instance of the {@code document}
515+
* from given {@link InputStream} with length.
516+
*/
517+
@ServiceMethod(returns = ReturnType.LONG_RUNNING_OPERATION)
518+
public PollerFlux<OperationResult, AnalyzeResult>
519+
beginClassifyDocument(String classifierId, BinaryData document) {
520+
return beginClassifyDocument(classifierId, document, Context.NONE);
521+
}
522+
523+
private PollerFlux<OperationResult, AnalyzeResult>
524+
beginClassifyDocument(String classifierId, BinaryData document, Context context) {
525+
try {
526+
Objects.requireNonNull(document, "'document' is required and cannot be null.");
527+
if (CoreUtils.isNullOrEmpty(classifierId)) {
528+
throw logger.logExceptionAsError(new IllegalArgumentException("'classifierId' is required and cannot"
529+
+ " be null or empty"));
530+
}
531+
532+
if (document.getLength() == null) {
533+
throw logger.logExceptionAsError(new IllegalArgumentException("'document length' is required and cannot"
534+
+ " be null"));
535+
}
536+
537+
return new PollerFlux<>(
538+
DEFAULT_POLL_INTERVAL,
539+
activationOperation(() ->
540+
documentClassifiersImpl.classifyDocumentWithResponseAsync(classifierId,
541+
null,
542+
StringIndexType.UTF16CODE_UNIT,
543+
document,
544+
document.getLength(),
545+
context)
546+
.map(analyzeDocumentResponse -> Transforms.toDocumentOperationResult(
547+
analyzeDocumentResponse.getDeserializedHeaders().getOperationLocation())),
548+
logger),
549+
pollingOperation(
550+
resultId -> documentClassifiersImpl.getClassifyResultWithResponseAsync(
551+
classifierId, resultId, context)),
552+
(activationResponse, pollingContext) ->
553+
Mono.error(new RuntimeException("Cancellation is not supported")),
554+
fetchingOperation(resultId -> documentClassifiersImpl.getClassifyResultWithResponseAsync(
555+
classifierId, resultId, context))
556+
.andThen(after -> after.map(modelSimpleResponse ->
557+
Transforms.toAnalyzeResultOperation(modelSimpleResponse.getValue().getAnalyzeResult()))
558+
.onErrorMap(Transforms::mapToHttpResponseExceptionIfExists)));
559+
} catch (RuntimeException ex) {
560+
return PollerFlux.error(ex);
561+
}
562+
}
563+
383564
/*
384565
* Poller's POLLING operation.
385566
*/

0 commit comments

Comments
 (0)