From dea4f8445ae48e130b2ebd917463fe60747e0de4 Mon Sep 17 00:00:00 2001 From: chienyuanchang Date: Thu, 16 Apr 2026 15:18:19 -0700 Subject: [PATCH 1/2] add usage into invoice sample --- .../samples/Sample03_AnalyzeInvoice.java | 39 ++++++++++++++++ .../Sample03_AnalyzeInvoiceAsyncTest.java | 46 ++++++++++++++++--- .../samples/Sample03_AnalyzeInvoiceTest.java | 32 +++++++++++++ 3 files changed, 110 insertions(+), 7 deletions(-) diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample03_AnalyzeInvoice.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample03_AnalyzeInvoice.java index 660df8d8d39f..8d9239fc20b9 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample03_AnalyzeInvoice.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample03_AnalyzeInvoice.java @@ -17,12 +17,14 @@ import com.azure.ai.contentunderstanding.models.DocumentSource; import com.azure.ai.contentunderstanding.models.AnalysisContent; import com.azure.ai.contentunderstanding.models.ContentObjectField; +import com.azure.ai.contentunderstanding.models.UsageDetails; import com.azure.core.credential.AzureKeyCredential; import com.azure.core.util.polling.SyncPoller; import com.azure.identity.DefaultAzureCredentialBuilder; import java.util.Arrays; import java.util.List; +import java.util.Map; /** * Sample demonstrating how to analyze invoices using Content Understanding service. @@ -67,6 +69,43 @@ public static void main(String[] args) { AnalysisResult result = operation.getFinalResult(); // END:ContentUnderstandingAnalyzeInvoice + // BEGIN:ContentUnderstandingAnalyzeInvoiceUsage + // Access usage details from the operation status (available after polling completes). + // Usage reports resource consumption for billing estimation: + // + // - documentPagesStandard/Basic/Minimal: Pages processed at each extraction tier. + // Standard = layout + OCR (scanned docs), Basic = OCR only, Minimal = digital formats + // (DOCX, XLSX, HTML, TXT) that need no OCR. Charged per 1,000 pages. + // + // - contextualizationTokens: Fixed-rate tokens charged by Content Understanding for + // preparing context, generating confidence scores, source grounding, and formatting + // output. Typically 1,000 tokens per page. Charged separately from LLM tokens. + // + // - tokens: Map of "{model}-input" / "{model}-output" token counts consumed by your + // Foundry model deployment (e.g. "gpt-4.1-input", "gpt-4.1-output"). These are + // billed on your Foundry deployment, not on Content Understanding. + // + // For full pricing details, see: + // https://learn.microsoft.com/azure/ai-services/content-understanding/pricing-explainer + UsageDetails usage = operation.waitForCompletion().getValue().getUsage(); + if (usage != null) { + System.out.println("\nUsage Details:"); + if (usage.getDocumentPagesStandard() != null) { + System.out.println(" Document pages (standard): " + usage.getDocumentPagesStandard()); + } + if (usage.getContextualizationTokens() != null) { + System.out.println(" Contextualization tokens: " + usage.getContextualizationTokens()); + } + Map tokens = usage.getTokens(); + if (tokens != null && !tokens.isEmpty()) { + System.out.println(" Model tokens:"); + for (Map.Entry entry : tokens.entrySet()) { + System.out.println(" " + entry.getKey() + ": " + entry.getValue()); + } + } + } + // END:ContentUnderstandingAnalyzeInvoiceUsage + System.out.println("Analysis operation completed"); System.out.println("Analysis result contains " + result.getContents().size() + " content(s)"); diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample03_AnalyzeInvoiceAsyncTest.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample03_AnalyzeInvoiceAsyncTest.java index b2496226300e..75f31b8de23e 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample03_AnalyzeInvoiceAsyncTest.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample03_AnalyzeInvoiceAsyncTest.java @@ -14,6 +14,7 @@ import com.azure.ai.contentunderstanding.models.AnalysisContent; import com.azure.ai.contentunderstanding.models.ContentObjectField; import com.azure.ai.contentunderstanding.models.DocumentSource; +import com.azure.ai.contentunderstanding.models.UsageDetails; import com.azure.core.util.polling.PollerFlux; import org.junit.jupiter.api.Test; import reactor.core.publisher.Mono; @@ -25,6 +26,7 @@ import java.util.Arrays; import java.util.List; +import java.util.Map; /** * Async sample demonstrating how to analyze invoices using Content Understanding service. @@ -51,16 +53,16 @@ public void testAnalyzeInvoiceAsync() { PollerFlux operation = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-invoice", Arrays.asList(input)); - // Use reactive pattern: chain operations using flatMap + // Wait for the operation to complete and get the operation status // In a real application, you would use subscribe() instead of block() - AnalysisResult result = operation.last().flatMap(pollResponse -> { - if (pollResponse.getStatus().isComplete()) { - return pollResponse.getFinalResult(); - } else { - return Mono.error( - new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + ContentAnalyzerAnalyzeOperationStatus operationStatus = operation.last().map(pollResponse -> { + if (!pollResponse.getStatus().isComplete()) { + throw new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus()); } + return pollResponse.getValue(); }).block(); // block() is used here for testing; in production, use subscribe() + + AnalysisResult result = operationStatus.getResult(); // END:ContentUnderstandingAnalyzeInvoiceAsync // BEGIN:Assertion_ContentUnderstandingAnalyzeInvoiceAsync @@ -74,6 +76,36 @@ public void testAnalyzeInvoiceAsync() { System.out.println("Analysis result contains " + result.getContents().size() + " content(s)"); // END:Assertion_ContentUnderstandingAnalyzeInvoiceAsync + // BEGIN:ContentUnderstandingAnalyzeInvoiceUsageAsync + // Get usage details from the operation status + UsageDetails usage = operationStatus.getUsage(); + if (usage != null) { + System.out.println("\nUsage Details:"); + if (usage.getDocumentPagesStandard() != null) { + System.out.println(" Document pages (standard): " + usage.getDocumentPagesStandard()); + } + if (usage.getContextualizationTokens() != null) { + System.out.println(" Contextualization tokens: " + usage.getContextualizationTokens()); + } + Map tokens = usage.getTokens(); + if (tokens != null && !tokens.isEmpty()) { + System.out.println(" Model tokens:"); + for (Map.Entry entry : tokens.entrySet()) { + System.out.println(" " + entry.getKey() + ": " + entry.getValue()); + } + } + } + // END:ContentUnderstandingAnalyzeInvoiceUsageAsync + + // BEGIN:Assertion_ContentUnderstandingAnalyzeInvoiceUsageAsync + assertNotNull(usage, "Usage details should not be null"); + assertNotNull(usage.getDocumentPagesStandard(), "Document pages (standard) should not be null"); + assertTrue(usage.getDocumentPagesStandard() > 0, "Document pages (standard) should be positive"); + assertNotNull(usage.getTokens(), "Tokens should not be null"); + assertTrue(!usage.getTokens().isEmpty(), "Tokens should not be empty"); + System.out.println("Usage details validated successfully"); + // END:Assertion_ContentUnderstandingAnalyzeInvoiceUsageAsync + // BEGIN:ContentUnderstandingExtractInvoiceFieldsAsync // Get the document content (invoices are documents) AnalysisContent firstContent = result.getContents().get(0); diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample03_AnalyzeInvoiceTest.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample03_AnalyzeInvoiceTest.java index e6a5a6b99fb8..8f204a163292 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample03_AnalyzeInvoiceTest.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample03_AnalyzeInvoiceTest.java @@ -15,6 +15,7 @@ import com.azure.ai.contentunderstanding.models.AnalysisContent; import com.azure.ai.contentunderstanding.models.ContentObjectField; import com.azure.ai.contentunderstanding.models.DocumentSource; +import com.azure.ai.contentunderstanding.models.UsageDetails; import com.azure.core.util.polling.SyncPoller; import org.junit.jupiter.api.Test; @@ -25,6 +26,7 @@ import java.util.Arrays; import java.util.List; +import java.util.Map; /** * Sample demonstrating how to analyze invoices using Content Understanding service. @@ -67,6 +69,36 @@ public void testAnalyzeInvoice() { System.out.println("Analysis result contains " + result.getContents().size() + " content(s)"); // END:Assertion_ContentUnderstandingAnalyzeInvoice + // BEGIN:ContentUnderstandingAnalyzeInvoiceUsage + // Get usage details from the operation status + UsageDetails usage = operation.waitForCompletion().getValue().getUsage(); + if (usage != null) { + System.out.println("\nUsage Details:"); + if (usage.getDocumentPagesStandard() != null) { + System.out.println(" Document pages (standard): " + usage.getDocumentPagesStandard()); + } + if (usage.getContextualizationTokens() != null) { + System.out.println(" Contextualization tokens: " + usage.getContextualizationTokens()); + } + Map tokens = usage.getTokens(); + if (tokens != null && !tokens.isEmpty()) { + System.out.println(" Model tokens:"); + for (Map.Entry entry : tokens.entrySet()) { + System.out.println(" " + entry.getKey() + ": " + entry.getValue()); + } + } + } + // END:ContentUnderstandingAnalyzeInvoiceUsage + + // BEGIN:Assertion_ContentUnderstandingAnalyzeInvoiceUsage + assertNotNull(usage, "Usage details should not be null"); + assertNotNull(usage.getDocumentPagesStandard(), "Document pages (standard) should not be null"); + assertTrue(usage.getDocumentPagesStandard() > 0, "Document pages (standard) should be positive"); + assertNotNull(usage.getTokens(), "Tokens should not be null"); + assertTrue(!usage.getTokens().isEmpty(), "Tokens should not be empty"); + System.out.println("Usage details validated successfully"); + // END:Assertion_ContentUnderstandingAnalyzeInvoiceUsage + // BEGIN:ContentUnderstandingExtractInvoiceFields // Get the document content (invoices are documents) AnalysisContent firstContent = result.getContents().get(0); From cdf6661921008ac99e22b608a97a0573b7e8153e Mon Sep 17 00:00:00 2001 From: chienyuanchang Date: Thu, 16 Apr 2026 16:26:23 -0700 Subject: [PATCH 2/2] revise per copilot comments --- .../tests/samples/Sample03_AnalyzeInvoiceAsyncTest.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample03_AnalyzeInvoiceAsyncTest.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample03_AnalyzeInvoiceAsyncTest.java index 75f31b8de23e..4f2716771a7b 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample03_AnalyzeInvoiceAsyncTest.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample03_AnalyzeInvoiceAsyncTest.java @@ -15,9 +15,9 @@ import com.azure.ai.contentunderstanding.models.ContentObjectField; import com.azure.ai.contentunderstanding.models.DocumentSource; import com.azure.ai.contentunderstanding.models.UsageDetails; +import com.azure.core.util.polling.LongRunningOperationStatus; import com.azure.core.util.polling.PollerFlux; import org.junit.jupiter.api.Test; -import reactor.core.publisher.Mono; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -57,7 +57,10 @@ public void testAnalyzeInvoiceAsync() { // In a real application, you would use subscribe() instead of block() ContentAnalyzerAnalyzeOperationStatus operationStatus = operation.last().map(pollResponse -> { if (!pollResponse.getStatus().isComplete()) { - throw new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus()); + throw new RuntimeException("Polling did not complete: " + pollResponse.getStatus()); + } + if (pollResponse.getStatus() != LongRunningOperationStatus.SUCCESSFULLY_COMPLETED) { + throw new RuntimeException("Operation failed with status: " + pollResponse.getStatus()); } return pollResponse.getValue(); }).block(); // block() is used here for testing; in production, use subscribe()