diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/pom.xml b/sdk/contentunderstanding/azure-ai-contentunderstanding/pom.xml index 554a82ad5330..9f20c5b15742 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/pom.xml +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/pom.xml @@ -68,6 +68,12 @@ azure-identity 1.18.2 + + com.azure + azure-storage-blob + 12.33.2 + test + diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/implementation/ContentAnalyzerAnalyzeOperationStatusHelper.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/implementation/ContentAnalyzerAnalyzeOperationStatusHelper.java new file mode 100644 index 000000000000..3ab5f629a975 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/implementation/ContentAnalyzerAnalyzeOperationStatusHelper.java @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.azure.ai.contentunderstanding.implementation; + +import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; + +/** + * Helper class to access private members of ContentAnalyzerAnalyzeOperationStatus. + */ +public final class ContentAnalyzerAnalyzeOperationStatusHelper { + private static ContentAnalyzerAnalyzeOperationStatusAccessor accessor; + + /** + * Interface for accessing private members. + */ + public interface ContentAnalyzerAnalyzeOperationStatusAccessor { + void setOperationId(ContentAnalyzerAnalyzeOperationStatus status, String operationId); + } + + /** + * Sets the accessor. + * + * @param accessorInstance the accessor instance. + */ + public static void setAccessor(ContentAnalyzerAnalyzeOperationStatusAccessor accessorInstance) { + accessor = accessorInstance; + } + + /** + * Sets the operationId on a ContentAnalyzerAnalyzeOperationStatus instance. + * + * @param status the status instance. + * @param operationId the operationId to set. + */ + public static void setOperationId(ContentAnalyzerAnalyzeOperationStatus status, String operationId) { + accessor.setOperationId(status, operationId); + } + + private ContentAnalyzerAnalyzeOperationStatusHelper() { + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/ArrayField.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/ArrayField.java new file mode 100644 index 000000000000..8ffa5e49f3e7 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/ArrayField.java @@ -0,0 +1,136 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. +package com.azure.ai.contentunderstanding.models; + +import com.azure.core.annotation.Generated; +import com.azure.core.annotation.Immutable; +import com.azure.core.util.logging.ClientLogger; +import com.azure.json.JsonReader; +import com.azure.json.JsonToken; +import com.azure.json.JsonWriter; +import java.io.IOException; +import java.util.List; + +/** + * Array field extracted from the content. + */ +@Immutable +public final class ArrayField extends ContentField { + + /* + * Semantic data type of the field value. + */ + @Generated + private ContentFieldType type = ContentFieldType.ARRAY; + + /* + * Array field value. + */ + @Generated + private List valueArray; + + /** + * Creates an instance of ArrayField class. + */ + @Generated + private ArrayField() { + } + + /** + * Get the type property: Semantic data type of the field value. + * + * @return the type value. + */ + @Generated + @Override + public ContentFieldType getType() { + return this.type; + } + + /** + * Get the valueArray property: Array field value. + * + * @return the valueArray value. + */ + @Generated + public List getValueArray() { + return this.valueArray; + } + + /** + * {@inheritDoc} + */ + @Generated + @Override + public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { + jsonWriter.writeStartObject(); + jsonWriter.writeArrayField("spans", getSpans(), (writer, element) -> writer.writeJson(element)); + jsonWriter.writeNumberField("confidence", getConfidence()); + jsonWriter.writeStringField("source", getSource()); + jsonWriter.writeStringField("type", this.type == null ? null : this.type.toString()); + jsonWriter.writeArrayField("valueArray", this.valueArray, (writer, element) -> writer.writeJson(element)); + return jsonWriter.writeEndObject(); + } + + /** + * Reads an instance of ArrayField from the JsonReader. + * + * @param jsonReader The JsonReader being read. + * @return An instance of ArrayField if the JsonReader was pointing to an instance of it, or null if it was pointing + * to JSON null. + * @throws IOException If an error occurs while reading the ArrayField. + */ + @Generated + public static ArrayField fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject(reader -> { + ArrayField deserializedArrayField = new ArrayField(); + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + if ("spans".equals(fieldName)) { + List spans = reader.readArray(reader1 -> ContentSpan.fromJson(reader1)); + deserializedArrayField.setSpans(spans); + } else if ("confidence".equals(fieldName)) { + deserializedArrayField.setConfidence(reader.getNullable(JsonReader::getDouble)); + } else if ("source".equals(fieldName)) { + deserializedArrayField.setSource(reader.getString()); + } else if ("type".equals(fieldName)) { + deserializedArrayField.type = ContentFieldType.fromString(reader.getString()); + } else if ("valueArray".equals(fieldName)) { + List valueArray = reader.readArray(reader1 -> ContentField.fromJson(reader1)); + deserializedArrayField.valueArray = valueArray; + } else { + reader.skipChildren(); + } + } + return deserializedArrayField; + }); + } + + private static final ClientLogger LOGGER = new ClientLogger(ArrayField.class); + + /** + * Gets the number of items in the array. + * + * @return the number of items in the array, or 0 if the array is null. + */ + public int size() { + return getValueArray() != null ? getValueArray().size() : 0; + } + + /** + * Gets a field from the array by index. + * + * @param index The zero-based index of the field to retrieve. + * @return The field at the specified index. + * @throws IndexOutOfBoundsException if the index is out of range. + */ + public ContentField get(int index) { + if (getValueArray() == null || index < 0 || index >= getValueArray().size()) { + throw LOGGER.logThrowableAsError(new IndexOutOfBoundsException( + "Index " + index + " is out of range. Array has " + size() + " elements.")); + } + return getValueArray().get(index); + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/BooleanField.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/BooleanField.java new file mode 100644 index 000000000000..dbb5382aaad6 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/BooleanField.java @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.models; + +import com.azure.core.annotation.Generated; +import com.azure.core.annotation.Immutable; +import com.azure.json.JsonReader; +import com.azure.json.JsonToken; +import com.azure.json.JsonWriter; +import java.io.IOException; +import java.util.List; + +/** + * Boolean field extracted from the content. + */ +@Immutable +public final class BooleanField extends ContentField { + /* + * Semantic data type of the field value. + */ + @Generated + private ContentFieldType type = ContentFieldType.BOOLEAN; + + /* + * Boolean field value. + */ + @Generated + private Boolean valueBoolean; + + /** + * Creates an instance of BooleanField class. + */ + @Generated + private BooleanField() { + } + + /** + * Get the type property: Semantic data type of the field value. + * + * @return the type value. + */ + @Generated + @Override + public ContentFieldType getType() { + return this.type; + } + + /** + * Get the valueBoolean property: Boolean field value. + * + * @return the valueBoolean value. + */ + @Generated + public Boolean isValueBoolean() { + return this.valueBoolean; + } + + /** + * {@inheritDoc} + */ + @Generated + @Override + public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { + jsonWriter.writeStartObject(); + jsonWriter.writeArrayField("spans", getSpans(), (writer, element) -> writer.writeJson(element)); + jsonWriter.writeNumberField("confidence", getConfidence()); + jsonWriter.writeStringField("source", getSource()); + jsonWriter.writeStringField("type", this.type == null ? null : this.type.toString()); + jsonWriter.writeBooleanField("valueBoolean", this.valueBoolean); + return jsonWriter.writeEndObject(); + } + + /** + * Reads an instance of BooleanField from the JsonReader. + * + * @param jsonReader The JsonReader being read. + * @return An instance of BooleanField if the JsonReader was pointing to an instance of it, or null if it was + * pointing to JSON null. + * @throws IOException If an error occurs while reading the BooleanField. + */ + @Generated + public static BooleanField fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject(reader -> { + BooleanField deserializedBooleanField = new BooleanField(); + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + + if ("spans".equals(fieldName)) { + List spans = reader.readArray(reader1 -> ContentSpan.fromJson(reader1)); + deserializedBooleanField.setSpans(spans); + } else if ("confidence".equals(fieldName)) { + deserializedBooleanField.setConfidence(reader.getNullable(JsonReader::getDouble)); + } else if ("source".equals(fieldName)) { + deserializedBooleanField.setSource(reader.getString()); + } else if ("type".equals(fieldName)) { + deserializedBooleanField.type = ContentFieldType.fromString(reader.getString()); + } else if ("valueBoolean".equals(fieldName)) { + deserializedBooleanField.valueBoolean = reader.getNullable(JsonReader::getBoolean); + } else { + reader.skipChildren(); + } + } + + return deserializedBooleanField; + }); + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/DateField.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/DateField.java new file mode 100644 index 000000000000..1bd11e682574 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/DateField.java @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.models; + +import com.azure.core.annotation.Generated; +import com.azure.core.annotation.Immutable; +import com.azure.json.JsonReader; +import com.azure.json.JsonToken; +import com.azure.json.JsonWriter; +import java.io.IOException; +import java.time.LocalDate; +import java.util.List; +import java.util.Objects; + +/** + * Date field extracted from the content. + */ +@Immutable +public final class DateField extends ContentField { + /* + * Semantic data type of the field value. + */ + @Generated + private ContentFieldType type = ContentFieldType.DATE; + + /* + * Date field value, in ISO 8601 (YYYY-MM-DD) format. + */ + @Generated + private LocalDate valueDate; + + /** + * Creates an instance of DateField class. + */ + @Generated + private DateField() { + } + + /** + * Get the type property: Semantic data type of the field value. + * + * @return the type value. + */ + @Generated + @Override + public ContentFieldType getType() { + return this.type; + } + + /** + * Get the valueDate property: Date field value, in ISO 8601 (YYYY-MM-DD) format. + * + * @return the valueDate value. + */ + @Generated + public LocalDate getValueDate() { + return this.valueDate; + } + + /** + * {@inheritDoc} + */ + @Generated + @Override + public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { + jsonWriter.writeStartObject(); + jsonWriter.writeArrayField("spans", getSpans(), (writer, element) -> writer.writeJson(element)); + jsonWriter.writeNumberField("confidence", getConfidence()); + jsonWriter.writeStringField("source", getSource()); + jsonWriter.writeStringField("type", this.type == null ? null : this.type.toString()); + jsonWriter.writeStringField("valueDate", Objects.toString(this.valueDate, null)); + return jsonWriter.writeEndObject(); + } + + /** + * Reads an instance of DateField from the JsonReader. + * + * @param jsonReader The JsonReader being read. + * @return An instance of DateField if the JsonReader was pointing to an instance of it, or null if it was pointing + * to JSON null. + * @throws IOException If an error occurs while reading the DateField. + */ + @Generated + public static DateField fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject(reader -> { + DateField deserializedDateField = new DateField(); + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + + if ("spans".equals(fieldName)) { + List spans = reader.readArray(reader1 -> ContentSpan.fromJson(reader1)); + deserializedDateField.setSpans(spans); + } else if ("confidence".equals(fieldName)) { + deserializedDateField.setConfidence(reader.getNullable(JsonReader::getDouble)); + } else if ("source".equals(fieldName)) { + deserializedDateField.setSource(reader.getString()); + } else if ("type".equals(fieldName)) { + deserializedDateField.type = ContentFieldType.fromString(reader.getString()); + } else if ("valueDate".equals(fieldName)) { + deserializedDateField.valueDate + = reader.getNullable(nonNullReader -> LocalDate.parse(nonNullReader.getString())); + } else { + reader.skipChildren(); + } + } + + return deserializedDateField; + }); + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/IntegerField.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/IntegerField.java new file mode 100644 index 000000000000..2625923c42a5 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/IntegerField.java @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.models; + +import com.azure.core.annotation.Generated; +import com.azure.core.annotation.Immutable; +import com.azure.json.JsonReader; +import com.azure.json.JsonToken; +import com.azure.json.JsonWriter; +import java.io.IOException; +import java.util.List; + +/** + * Integer field extracted from the content. + */ +@Immutable +public final class IntegerField extends ContentField { + /* + * Semantic data type of the field value. + */ + @Generated + private ContentFieldType type = ContentFieldType.INTEGER; + + /* + * Integer field value. + */ + @Generated + private Long valueInteger; + + /** + * Creates an instance of IntegerField class. + */ + @Generated + private IntegerField() { + } + + /** + * Get the type property: Semantic data type of the field value. + * + * @return the type value. + */ + @Generated + @Override + public ContentFieldType getType() { + return this.type; + } + + /** + * Get the valueInteger property: Integer field value. + * + * @return the valueInteger value. + */ + @Generated + public Long getValueInteger() { + return this.valueInteger; + } + + /** + * {@inheritDoc} + */ + @Generated + @Override + public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { + jsonWriter.writeStartObject(); + jsonWriter.writeArrayField("spans", getSpans(), (writer, element) -> writer.writeJson(element)); + jsonWriter.writeNumberField("confidence", getConfidence()); + jsonWriter.writeStringField("source", getSource()); + jsonWriter.writeStringField("type", this.type == null ? null : this.type.toString()); + jsonWriter.writeNumberField("valueInteger", this.valueInteger); + return jsonWriter.writeEndObject(); + } + + /** + * Reads an instance of IntegerField from the JsonReader. + * + * @param jsonReader The JsonReader being read. + * @return An instance of IntegerField if the JsonReader was pointing to an instance of it, or null if it was + * pointing to JSON null. + * @throws IOException If an error occurs while reading the IntegerField. + */ + @Generated + public static IntegerField fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject(reader -> { + IntegerField deserializedIntegerField = new IntegerField(); + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + + if ("spans".equals(fieldName)) { + List spans = reader.readArray(reader1 -> ContentSpan.fromJson(reader1)); + deserializedIntegerField.setSpans(spans); + } else if ("confidence".equals(fieldName)) { + deserializedIntegerField.setConfidence(reader.getNullable(JsonReader::getDouble)); + } else if ("source".equals(fieldName)) { + deserializedIntegerField.setSource(reader.getString()); + } else if ("type".equals(fieldName)) { + deserializedIntegerField.type = ContentFieldType.fromString(reader.getString()); + } else if ("valueInteger".equals(fieldName)) { + deserializedIntegerField.valueInteger = reader.getNullable(JsonReader::getLong); + } else { + reader.skipChildren(); + } + } + + return deserializedIntegerField; + }); + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/JsonField.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/JsonField.java new file mode 100644 index 000000000000..df5617a1d2c9 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/JsonField.java @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.models; + +import com.azure.core.annotation.Generated; +import com.azure.core.annotation.Immutable; +import com.azure.core.util.BinaryData; +import com.azure.json.JsonReader; +import com.azure.json.JsonToken; +import com.azure.json.JsonWriter; +import java.io.IOException; +import java.util.List; + +/** + * JSON field extracted from the content. + */ +@Immutable +public final class JsonField extends ContentField { + /* + * Semantic data type of the field value. + */ + @Generated + private ContentFieldType type = ContentFieldType.JSON; + + /* + * JSON field value. + */ + @Generated + private BinaryData valueJson; + + /** + * Creates an instance of JsonField class. + */ + @Generated + private JsonField() { + } + + /** + * Get the type property: Semantic data type of the field value. + * + * @return the type value. + */ + @Generated + @Override + public ContentFieldType getType() { + return this.type; + } + + /** + * Get the valueJson property: JSON field value. + * + * @return the valueJson value. + */ + @Generated + public BinaryData getValueJson() { + return this.valueJson; + } + + /** + * {@inheritDoc} + */ + @Generated + @Override + public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { + jsonWriter.writeStartObject(); + jsonWriter.writeArrayField("spans", getSpans(), (writer, element) -> writer.writeJson(element)); + jsonWriter.writeNumberField("confidence", getConfidence()); + jsonWriter.writeStringField("source", getSource()); + jsonWriter.writeStringField("type", this.type == null ? null : this.type.toString()); + if (this.valueJson != null) { + jsonWriter.writeFieldName("valueJson"); + this.valueJson.writeTo(jsonWriter); + } + return jsonWriter.writeEndObject(); + } + + /** + * Reads an instance of JsonField from the JsonReader. + * + * @param jsonReader The JsonReader being read. + * @return An instance of JsonField if the JsonReader was pointing to an instance of it, or null if it was pointing + * to JSON null. + * @throws IOException If an error occurs while reading the JsonField. + */ + @Generated + public static JsonField fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject(reader -> { + JsonField deserializedJsonField = new JsonField(); + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + + if ("spans".equals(fieldName)) { + List spans = reader.readArray(reader1 -> ContentSpan.fromJson(reader1)); + deserializedJsonField.setSpans(spans); + } else if ("confidence".equals(fieldName)) { + deserializedJsonField.setConfidence(reader.getNullable(JsonReader::getDouble)); + } else if ("source".equals(fieldName)) { + deserializedJsonField.setSource(reader.getString()); + } else if ("type".equals(fieldName)) { + deserializedJsonField.type = ContentFieldType.fromString(reader.getString()); + } else if ("valueJson".equals(fieldName)) { + deserializedJsonField.valueJson + = reader.getNullable(nonNullReader -> BinaryData.fromObject(nonNullReader.readUntyped())); + } else { + reader.skipChildren(); + } + } + + return deserializedJsonField; + }); + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/NumberField.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/NumberField.java new file mode 100644 index 000000000000..58fe74c5dc15 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/NumberField.java @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.models; + +import com.azure.core.annotation.Generated; +import com.azure.core.annotation.Immutable; +import com.azure.json.JsonReader; +import com.azure.json.JsonToken; +import com.azure.json.JsonWriter; +import java.io.IOException; +import java.util.List; + +/** + * Number field extracted from the content. + */ +@Immutable +public final class NumberField extends ContentField { + /* + * Semantic data type of the field value. + */ + @Generated + private ContentFieldType type = ContentFieldType.NUMBER; + + /* + * Number field value. + */ + @Generated + private Double valueNumber; + + /** + * Creates an instance of NumberField class. + */ + @Generated + private NumberField() { + } + + /** + * Get the type property: Semantic data type of the field value. + * + * @return the type value. + */ + @Generated + @Override + public ContentFieldType getType() { + return this.type; + } + + /** + * Get the valueNumber property: Number field value. + * + * @return the valueNumber value. + */ + @Generated + public Double getValueNumber() { + return this.valueNumber; + } + + /** + * {@inheritDoc} + */ + @Generated + @Override + public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { + jsonWriter.writeStartObject(); + jsonWriter.writeArrayField("spans", getSpans(), (writer, element) -> writer.writeJson(element)); + jsonWriter.writeNumberField("confidence", getConfidence()); + jsonWriter.writeStringField("source", getSource()); + jsonWriter.writeStringField("type", this.type == null ? null : this.type.toString()); + jsonWriter.writeNumberField("valueNumber", this.valueNumber); + return jsonWriter.writeEndObject(); + } + + /** + * Reads an instance of NumberField from the JsonReader. + * + * @param jsonReader The JsonReader being read. + * @return An instance of NumberField if the JsonReader was pointing to an instance of it, or null if it was + * pointing to JSON null. + * @throws IOException If an error occurs while reading the NumberField. + */ + @Generated + public static NumberField fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject(reader -> { + NumberField deserializedNumberField = new NumberField(); + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + + if ("spans".equals(fieldName)) { + List spans = reader.readArray(reader1 -> ContentSpan.fromJson(reader1)); + deserializedNumberField.setSpans(spans); + } else if ("confidence".equals(fieldName)) { + deserializedNumberField.setConfidence(reader.getNullable(JsonReader::getDouble)); + } else if ("source".equals(fieldName)) { + deserializedNumberField.setSource(reader.getString()); + } else if ("type".equals(fieldName)) { + deserializedNumberField.type = ContentFieldType.fromString(reader.getString()); + } else if ("valueNumber".equals(fieldName)) { + deserializedNumberField.valueNumber = reader.getNullable(JsonReader::getDouble); + } else { + reader.skipChildren(); + } + } + + return deserializedNumberField; + }); + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/ObjectField.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/ObjectField.java new file mode 100644 index 000000000000..bc8d45246a6c --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/ObjectField.java @@ -0,0 +1,146 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. +package com.azure.ai.contentunderstanding.models; + +import com.azure.core.annotation.Generated; +import com.azure.core.annotation.Immutable; +import com.azure.core.util.logging.ClientLogger; +import com.azure.json.JsonReader; +import com.azure.json.JsonToken; +import com.azure.json.JsonWriter; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +/** + * Object field extracted from the content. + */ +@Immutable +public final class ObjectField extends ContentField { + + /* + * Semantic data type of the field value. + */ + @Generated + private ContentFieldType type = ContentFieldType.OBJECT; + + /* + * Object field value. + */ + @Generated + private Map valueObject; + + /** + * Creates an instance of ObjectField class. + */ + @Generated + private ObjectField() { + } + + /** + * Get the type property: Semantic data type of the field value. + * + * @return the type value. + */ + @Generated + @Override + public ContentFieldType getType() { + return this.type; + } + + /** + * Get the valueObject property: Object field value. + * + * @return the valueObject value. + */ + @Generated + public Map getValueObject() { + return this.valueObject; + } + + /** + * {@inheritDoc} + */ + @Generated + @Override + public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { + jsonWriter.writeStartObject(); + jsonWriter.writeArrayField("spans", getSpans(), (writer, element) -> writer.writeJson(element)); + jsonWriter.writeNumberField("confidence", getConfidence()); + jsonWriter.writeStringField("source", getSource()); + jsonWriter.writeStringField("type", this.type == null ? null : this.type.toString()); + jsonWriter.writeMapField("valueObject", this.valueObject, (writer, element) -> writer.writeJson(element)); + return jsonWriter.writeEndObject(); + } + + /** + * Reads an instance of ObjectField from the JsonReader. + * + * @param jsonReader The JsonReader being read. + * @return An instance of ObjectField if the JsonReader was pointing to an instance of it, or null if it was + * pointing to JSON null. + * @throws IOException If an error occurs while reading the ObjectField. + */ + @Generated + public static ObjectField fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject(reader -> { + ObjectField deserializedObjectField = new ObjectField(); + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + if ("spans".equals(fieldName)) { + List spans = reader.readArray(reader1 -> ContentSpan.fromJson(reader1)); + deserializedObjectField.setSpans(spans); + } else if ("confidence".equals(fieldName)) { + deserializedObjectField.setConfidence(reader.getNullable(JsonReader::getDouble)); + } else if ("source".equals(fieldName)) { + deserializedObjectField.setSource(reader.getString()); + } else if ("type".equals(fieldName)) { + deserializedObjectField.type = ContentFieldType.fromString(reader.getString()); + } else if ("valueObject".equals(fieldName)) { + Map valueObject = reader.readMap(reader1 -> ContentField.fromJson(reader1)); + deserializedObjectField.valueObject = valueObject; + } else { + reader.skipChildren(); + } + } + return deserializedObjectField; + }); + } + + private static final ClientLogger LOGGER = new ClientLogger(ObjectField.class); + + /** + * Gets a field from the object by name. + * + * @param fieldName The name of the field to retrieve. + * @return The field if found. + * @throws IllegalArgumentException if fieldName is null or empty. + * @throws NoSuchElementException if the field is not found. + */ + public ContentField getField(String fieldName) { + if (fieldName == null || fieldName.isEmpty()) { + throw LOGGER.logThrowableAsError(new IllegalArgumentException("fieldName cannot be null or empty.")); + } + if (getValueObject() != null && getValueObject().containsKey(fieldName)) { + return getValueObject().get(fieldName); + } + throw LOGGER.logThrowableAsError( + new java.util.NoSuchElementException("Field '" + fieldName + "' was not found in the object.")); + } + + /** + * Gets a field from the object by name, or null if the field does not exist. + * + * @param fieldName The name of the field to retrieve. + * @return The field if found, or null if not found. + */ + public ContentField getFieldOrDefault(String fieldName) { + if (fieldName == null || fieldName.isEmpty() || getValueObject() == null) { + return null; + } + return getValueObject().get(fieldName); + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/StringField.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/StringField.java new file mode 100644 index 000000000000..e7c149a7cbe5 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/StringField.java @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.models; + +import com.azure.core.annotation.Generated; +import com.azure.core.annotation.Immutable; +import com.azure.json.JsonReader; +import com.azure.json.JsonToken; +import com.azure.json.JsonWriter; +import java.io.IOException; +import java.util.List; + +/** + * String field extracted from the content. + */ +@Immutable +public final class StringField extends ContentField { + /* + * Semantic data type of the field value. + */ + @Generated + private ContentFieldType type = ContentFieldType.STRING; + + /* + * String field value. + */ + @Generated + private String valueString; + + /** + * Creates an instance of StringField class. + */ + @Generated + private StringField() { + } + + /** + * Get the type property: Semantic data type of the field value. + * + * @return the type value. + */ + @Generated + @Override + public ContentFieldType getType() { + return this.type; + } + + /** + * Get the valueString property: String field value. + * + * @return the valueString value. + */ + @Generated + public String getValueString() { + return this.valueString; + } + + /** + * {@inheritDoc} + */ + @Generated + @Override + public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { + jsonWriter.writeStartObject(); + jsonWriter.writeArrayField("spans", getSpans(), (writer, element) -> writer.writeJson(element)); + jsonWriter.writeNumberField("confidence", getConfidence()); + jsonWriter.writeStringField("source", getSource()); + jsonWriter.writeStringField("type", this.type == null ? null : this.type.toString()); + jsonWriter.writeStringField("valueString", this.valueString); + return jsonWriter.writeEndObject(); + } + + /** + * Reads an instance of StringField from the JsonReader. + * + * @param jsonReader The JsonReader being read. + * @return An instance of StringField if the JsonReader was pointing to an instance of it, or null if it was + * pointing to JSON null. + * @throws IOException If an error occurs while reading the StringField. + */ + @Generated + public static StringField fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject(reader -> { + StringField deserializedStringField = new StringField(); + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + + if ("spans".equals(fieldName)) { + List spans = reader.readArray(reader1 -> ContentSpan.fromJson(reader1)); + deserializedStringField.setSpans(spans); + } else if ("confidence".equals(fieldName)) { + deserializedStringField.setConfidence(reader.getNullable(JsonReader::getDouble)); + } else if ("source".equals(fieldName)) { + deserializedStringField.setSource(reader.getString()); + } else if ("type".equals(fieldName)) { + deserializedStringField.type = ContentFieldType.fromString(reader.getString()); + } else if ("valueString".equals(fieldName)) { + deserializedStringField.valueString = reader.getString(); + } else { + reader.skipChildren(); + } + } + + return deserializedStringField; + }); + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/TimeField.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/TimeField.java new file mode 100644 index 000000000000..0308c5c64844 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/main/java/com/azure/ai/contentunderstanding/models/TimeField.java @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.models; + +import com.azure.core.annotation.Generated; +import com.azure.core.annotation.Immutable; +import com.azure.json.JsonReader; +import com.azure.json.JsonToken; +import com.azure.json.JsonWriter; +import java.io.IOException; +import java.util.List; + +/** + * Time field extracted from the content. + */ +@Immutable +public final class TimeField extends ContentField { + /* + * Semantic data type of the field value. + */ + @Generated + private ContentFieldType type = ContentFieldType.TIME; + + /* + * Time field value, in ISO 8601 (hh:mm:ss) format. + */ + @Generated + private String valueTime; + + /** + * Creates an instance of TimeField class. + */ + @Generated + private TimeField() { + } + + /** + * Get the type property: Semantic data type of the field value. + * + * @return the type value. + */ + @Generated + @Override + public ContentFieldType getType() { + return this.type; + } + + /** + * Get the valueTime property: Time field value, in ISO 8601 (hh:mm:ss) format. + * + * @return the valueTime value. + */ + @Generated + public String getValueTime() { + return this.valueTime; + } + + /** + * {@inheritDoc} + */ + @Generated + @Override + public JsonWriter toJson(JsonWriter jsonWriter) throws IOException { + jsonWriter.writeStartObject(); + jsonWriter.writeArrayField("spans", getSpans(), (writer, element) -> writer.writeJson(element)); + jsonWriter.writeNumberField("confidence", getConfidence()); + jsonWriter.writeStringField("source", getSource()); + jsonWriter.writeStringField("type", this.type == null ? null : this.type.toString()); + jsonWriter.writeStringField("valueTime", this.valueTime); + return jsonWriter.writeEndObject(); + } + + /** + * Reads an instance of TimeField from the JsonReader. + * + * @param jsonReader The JsonReader being read. + * @return An instance of TimeField if the JsonReader was pointing to an instance of it, or null if it was pointing + * to JSON null. + * @throws IOException If an error occurs while reading the TimeField. + */ + @Generated + public static TimeField fromJson(JsonReader jsonReader) throws IOException { + return jsonReader.readObject(reader -> { + TimeField deserializedTimeField = new TimeField(); + while (reader.nextToken() != JsonToken.END_OBJECT) { + String fieldName = reader.getFieldName(); + reader.nextToken(); + + if ("spans".equals(fieldName)) { + List spans = reader.readArray(reader1 -> ContentSpan.fromJson(reader1)); + deserializedTimeField.setSpans(spans); + } else if ("confidence".equals(fieldName)) { + deserializedTimeField.setConfidence(reader.getNullable(JsonReader::getDouble)); + } else if ("source".equals(fieldName)) { + deserializedTimeField.setSource(reader.getString()); + } else if ("type".equals(fieldName)) { + deserializedTimeField.type = ContentFieldType.fromString(reader.getString()); + } else if ("valueTime".equals(fieldName)) { + deserializedTimeField.valueTime = reader.getString(); + } else { + reader.skipChildren(); + } + } + + return deserializedTimeField; + }); + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample04_CreateAnalyzer.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample04_CreateAnalyzer.java index 4b0f327bda7b..2935404f3829 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample04_CreateAnalyzer.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample04_CreateAnalyzer.java @@ -157,14 +157,14 @@ public static void main(String[] args) { SyncPoller analyzeOperation = client.beginAnalyze(analyzerId, Arrays.asList(input)); - AnalysisResult analyzeResult = analyzeOperation.getFinalResult(); + AnalysisResult AnalysisResult = analyzeOperation.getFinalResult(); // Extract custom fields from the result // Since EstimateFieldSourceAndConfidence is enabled, we can access confidence scores and source information - if (analyzeResult.getContents() != null - && !analyzeResult.getContents().isEmpty() - && analyzeResult.getContents().get(0) instanceof DocumentContent) { - DocumentContent content = (DocumentContent) analyzeResult.getContents().get(0); + if (AnalysisResult.getContents() != null + && !AnalysisResult.getContents().isEmpty() + && AnalysisResult.getContents().get(0) instanceof DocumentContent) { + DocumentContent content = (DocumentContent) AnalysisResult.getContents().get(0); // Extract field (literal text extraction) ContentField companyNameField diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample04_CreateAnalyzerAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample04_CreateAnalyzerAsync.java index febe782472da..b44297f9692e 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample04_CreateAnalyzerAsync.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample04_CreateAnalyzerAsync.java @@ -186,13 +186,13 @@ public static void main(String[] args) throws InterruptedException { } }); }) - .doOnNext(analyzeResult -> { + .doOnNext(AnalysisResult -> { // Extract custom fields from the result // Since EstimateFieldSourceAndConfidence is enabled, we can access confidence scores and source information - if (analyzeResult.getContents() != null - && !analyzeResult.getContents().isEmpty() - && analyzeResult.getContents().get(0) instanceof DocumentContent) { - DocumentContent content = (DocumentContent) analyzeResult.getContents().get(0); + if (AnalysisResult.getContents() != null + && !AnalysisResult.getContents().isEmpty() + && AnalysisResult.getContents().get(0) instanceof DocumentContent) { + DocumentContent content = (DocumentContent) AnalysisResult.getContents().get(0); // Extract field (literal text extraction) ContentField companyNameField diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample16_CreateAnalyzerWithLabels.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample16_CreateAnalyzerWithLabels.java index 09e9a58dff21..3a52695ca0f8 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample16_CreateAnalyzerWithLabels.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample16_CreateAnalyzerWithLabels.java @@ -15,9 +15,19 @@ import com.azure.ai.contentunderstanding.models.KnowledgeSource; import com.azure.ai.contentunderstanding.models.LabeledDataKnowledgeSource; import com.azure.core.credential.AzureKeyCredential; +import com.azure.core.credential.TokenCredential; import com.azure.core.util.polling.SyncPoller; import com.azure.identity.DefaultAzureCredentialBuilder; - +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.BlobServiceClientBuilder; +import com.azure.storage.blob.models.UserDelegationKey; +import com.azure.storage.blob.sas.BlobContainerSasPermission; +import com.azure.storage.blob.sas.BlobServiceSasSignatureValues; + +import java.io.File; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -43,7 +53,7 @@ * permissions. In Azure Portal: Storage account → Containers → your container → Shared access * token; set expiry and permissions, then generate the SAS URL. *
  • Set {@code CONTENTUNDERSTANDING_TRAINING_DATA_SAS_URL} to the full SAS URL - * (e.g., https://<account>.blob.core.windows.net/<container>?sv=...&se=...).
  • + * (e.g., {@code https://.blob.core.windows.net/?sv=...&se=...}). *
  • If you uploaded into a subfolder, set {@code CONTENTUNDERSTANDING_TRAINING_DATA_PREFIX} to * that path (e.g., "receipt_labels/"). If files are at the container root, omit the prefix * or leave it unset.
  • @@ -71,6 +81,10 @@ *
  • {@code CONTENTUNDERSTANDING_TRAINING_DATA_PREFIX} – Path prefix within the container * (e.g., "receipt_labels/" or "CreateAnalyzerWithLabels/"). Omit or leave unset if files * are at the container root.
  • + *
  • {@code CONTENTUNDERSTANDING_TRAINING_DATA_STORAGE_ACCOUNT} – Storage account name for + * auto-upload (Option B). Used when SAS URL is not set.
  • + *
  • {@code CONTENTUNDERSTANDING_TRAINING_DATA_CONTAINER} – Container name for auto-upload + * (Option B). Used when SAS URL is not set.
  • * */ public class Sample16_CreateAnalyzerWithLabels { @@ -81,6 +95,8 @@ public static void main(String[] args) { String key = System.getenv("CONTENTUNDERSTANDING_KEY"); String sasUrl = System.getenv("CONTENTUNDERSTANDING_TRAINING_DATA_SAS_URL"); String sasUrlPrefix = System.getenv("CONTENTUNDERSTANDING_TRAINING_DATA_PREFIX"); + String storageAccount = System.getenv("CONTENTUNDERSTANDING_TRAINING_DATA_STORAGE_ACCOUNT"); + String containerName = System.getenv("CONTENTUNDERSTANDING_TRAINING_DATA_CONTAINER"); // Build the client with appropriate authentication ContentUnderstandingClientBuilder builder = new ContentUnderstandingClientBuilder().endpoint(endpoint); @@ -97,6 +113,17 @@ public static void main(String[] args) { System.out.println("Client initialized successfully"); + // Option B fallback: upload local label files and generate SAS URL + if (sasUrl == null || sasUrl.trim().isEmpty()) { + if (storageAccount != null && !storageAccount.trim().isEmpty() + && containerName != null && !containerName.trim().isEmpty()) { + TokenCredential credential = new DefaultAzureCredentialBuilder().build(); + String localDir = new File("src/samples/resources/receipt_labels").getAbsolutePath(); + uploadTrainingData(storageAccount, containerName, credential, localDir, sasUrlPrefix); + sasUrl = generateUserDelegationSasUrl(storageAccount, containerName, credential); + } + } + String analyzerId = "test_receipt_analyzer_" + UUID.randomUUID().toString().replace("-", ""); try { @@ -148,11 +175,11 @@ public static void main(String[] args) { fields.put("Items", itemsField); // Total field - ContentFieldDefinition totalField = new ContentFieldDefinition(); - totalField.setType(ContentFieldType.STRING); - totalField.setMethod(GenerationMethod.EXTRACT); - totalField.setDescription("Total amount"); - fields.put("TotalPrice", totalField); + ContentFieldDefinition totalPriceField = new ContentFieldDefinition(); + totalPriceField.setType(ContentFieldType.STRING); + totalPriceField.setMethod(GenerationMethod.EXTRACT); + totalPriceField.setDescription("Total amount"); + fields.put("TotalPrice", totalPriceField); ContentFieldSchema fieldSchema = new ContentFieldSchema(); fieldSchema.setName("receipt_schema"); @@ -198,6 +225,7 @@ public static void main(String[] args) { System.out.println(" Description: " + result.getDescription()); System.out.println(" Base analyzer: " + result.getBaseAnalyzerId()); System.out.println(" Fields: " + result.getFieldSchema().getFields().size()); + System.out.println(" Knowledge sources: " + (result.getKnowledgeSources() != null ? result.getKnowledgeSources().size() : 0)); // END: com.azure.ai.contentunderstanding.createAnalyzerWithLabels // Verify analyzer creation @@ -210,7 +238,7 @@ public static void main(String[] args) { System.out.println(" MerchantName: String (Extract)"); System.out.println(" Items: Array of Objects (Generate)"); System.out.println(" - Quantity, Name, Price"); - System.out.println(" Total: String (Extract)"); + System.out.println(" TotalPrice: String (Extract)"); ContentFieldDefinition itemsFieldResult = resultFields.get("Items"); System.out.println("Items field verified:"); @@ -221,9 +249,9 @@ public static void main(String[] args) { System.out.println("\nCreateAnalyzerWithLabels API Pattern:"); System.out.println(" 1. Define field schema with nested structures (arrays, objects)"); System.out.println(" 2. Upload training data to Azure Blob Storage:"); - System.out.println(" - Documents: receipt1.pdf, receipt2.pdf, ..."); - System.out.println(" - Labels: receipt1.pdf.labels.json, receipt2.pdf.labels.json, ..."); - System.out.println(" - OCR: receipt1.pdf.result.json, receipt2.pdf.result.json, ..."); + System.out.println(" - Documents: receipt1.jpg, receipt2.jpg, ..."); + System.out.println(" - Labels: receipt1.jpg.labels.json, receipt2.jpg.labels.json, ..."); + System.out.println(" - OCR: receipt1.jpg.result.json, receipt2.jpg.result.json, ..."); System.out.println(" 3. Create LabeledDataKnowledgeSource with storage SAS URL"); System.out.println(" 4. Create analyzer with field schema and knowledge sources"); System.out.println(" 5. Use analyzer for document analysis"); @@ -245,4 +273,63 @@ public static void main(String[] args) { } } } + + /** + * Uploads local training data files (images, .labels.json, .result.json) to an + * Azure Blob container. Existing blobs with the same name are overwritten. + */ + private static void uploadTrainingData(String storageAccountName, String containerName, + TokenCredential credential, String localDirectory, String prefix) { + BlobContainerClient containerClient = new BlobServiceClientBuilder() + .endpoint("https://" + storageAccountName + ".blob.core.windows.net") + .credential(credential) + .buildClient() + .getBlobContainerClient(containerName); + + containerClient.createIfNotExists(); + + File dir = new File(localDirectory); + File[] files = dir.listFiles(); + if (files == null) { + return; + } + for (File file : files) { + if (!file.isFile()) { + continue; + } + String blobName = (prefix == null || prefix.trim().isEmpty()) + ? file.getName() + : prefix.replaceAll("/+$", "") + "/" + file.getName(); + + System.out.println("Uploading " + file.getName() + " -> " + blobName); + BlobClient blobClient = containerClient.getBlobClient(blobName); + blobClient.uploadFromFile(file.getAbsolutePath(), true); + } + } + + /** + * Generates a User Delegation SAS URL (Read + List) for an Azure Blob container. + */ + private static String generateUserDelegationSasUrl(String storageAccountName, String containerName, + TokenCredential credential) { + BlobServiceClient blobServiceClient = new BlobServiceClientBuilder() + .endpoint("https://" + storageAccountName + ".blob.core.windows.net") + .credential(credential) + .buildClient(); + + UserDelegationKey userDelegationKey = blobServiceClient.getUserDelegationKey( + OffsetDateTime.now(), OffsetDateTime.now().plusHours(1)); + + BlobContainerSasPermission permissions = new BlobContainerSasPermission() + .setReadPermission(true) + .setListPermission(true); + + BlobServiceSasSignatureValues sasValues = new BlobServiceSasSignatureValues( + OffsetDateTime.now().plusHours(1), permissions); + + String sasToken = blobServiceClient.getBlobContainerClient(containerName) + .generateUserDelegationSas(sasValues, userDelegationKey); + + return "https://" + storageAccountName + ".blob.core.windows.net/" + containerName + "?" + sasToken; + } } diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample16_CreateAnalyzerWithLabelsAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample16_CreateAnalyzerWithLabelsAsync.java index a16699fd9bf4..f7574665a436 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample16_CreateAnalyzerWithLabelsAsync.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/java/com/azure/ai/contentunderstanding/samples/Sample16_CreateAnalyzerWithLabelsAsync.java @@ -15,10 +15,20 @@ import com.azure.ai.contentunderstanding.models.KnowledgeSource; import com.azure.ai.contentunderstanding.models.LabeledDataKnowledgeSource; import com.azure.core.credential.AzureKeyCredential; +import com.azure.core.credential.TokenCredential; import com.azure.core.util.polling.PollerFlux; import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.BlobServiceClientBuilder; +import com.azure.storage.blob.models.UserDelegationKey; +import com.azure.storage.blob.sas.BlobContainerSasPermission; +import com.azure.storage.blob.sas.BlobServiceSasSignatureValues; import reactor.core.publisher.Mono; +import java.io.File; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -46,7 +56,7 @@ * permissions. In Azure Portal: Storage account → Containers → your container → Shared access * token; set expiry and permissions, then generate the SAS URL. *
  • Set {@code CONTENTUNDERSTANDING_TRAINING_DATA_SAS_URL} to the full SAS URL - * (e.g., https://<account>.blob.core.windows.net/<container>?sv=...&se=...).
  • + * (e.g., {@code https://.blob.core.windows.net/?sv=...&se=...}). *
  • If you uploaded into a subfolder, set {@code CONTENTUNDERSTANDING_TRAINING_DATA_PREFIX} to * that path (e.g., "receipt_labels/"). If files are at the container root, omit the prefix * or leave it unset.
  • @@ -74,6 +84,10 @@ *
  • {@code CONTENTUNDERSTANDING_TRAINING_DATA_PREFIX} – Path prefix within the container * (e.g., "receipt_labels/" or "CreateAnalyzerWithLabels/"). Omit or leave unset if files * are at the container root.
  • + *
  • {@code CONTENTUNDERSTANDING_TRAINING_DATA_STORAGE_ACCOUNT} – Storage account name for + * auto-upload (Option B). Used when SAS URL is not set.
  • + *
  • {@code CONTENTUNDERSTANDING_TRAINING_DATA_CONTAINER} – Container name for auto-upload + * (Option B). Used when SAS URL is not set.
  • * */ public class Sample16_CreateAnalyzerWithLabelsAsync { @@ -84,6 +98,8 @@ public static void main(String[] args) throws InterruptedException { String key = System.getenv("CONTENTUNDERSTANDING_KEY"); String sasUrl = System.getenv("CONTENTUNDERSTANDING_TRAINING_DATA_SAS_URL"); String sasUrlPrefix = System.getenv("CONTENTUNDERSTANDING_TRAINING_DATA_PREFIX"); + String storageAccount = System.getenv("CONTENTUNDERSTANDING_TRAINING_DATA_STORAGE_ACCOUNT"); + String containerName = System.getenv("CONTENTUNDERSTANDING_TRAINING_DATA_CONTAINER"); // Build the async client with appropriate authentication ContentUnderstandingClientBuilder builder = new ContentUnderstandingClientBuilder().endpoint(endpoint); @@ -100,6 +116,17 @@ public static void main(String[] args) throws InterruptedException { System.out.println("Client initialized successfully"); + // Option B fallback: upload local label files and generate SAS URL + if (sasUrl == null || sasUrl.trim().isEmpty()) { + if (storageAccount != null && !storageAccount.trim().isEmpty() + && containerName != null && !containerName.trim().isEmpty()) { + TokenCredential credential = new DefaultAzureCredentialBuilder().build(); + String localDir = new File("src/samples/resources/receipt_labels").getAbsolutePath(); + uploadTrainingData(storageAccount, containerName, credential, localDir, sasUrlPrefix); + sasUrl = generateUserDelegationSasUrl(storageAccount, containerName, credential); + } + } + String analyzerId = "test_receipt_analyzer_" + UUID.randomUUID().toString().replace("-", ""); String finalAnalyzerId = analyzerId; // For use in lambda @@ -151,11 +178,11 @@ public static void main(String[] args) throws InterruptedException { fields.put("Items", itemsField); // Total field - ContentFieldDefinition totalField = new ContentFieldDefinition(); - totalField.setType(ContentFieldType.STRING); - totalField.setMethod(GenerationMethod.EXTRACT); - totalField.setDescription("Total amount"); - fields.put("Total", totalField); + ContentFieldDefinition totalPriceField = new ContentFieldDefinition(); + totalPriceField.setType(ContentFieldType.STRING); + totalPriceField.setMethod(GenerationMethod.EXTRACT); + totalPriceField.setDescription("Total amount"); + fields.put("TotalPrice", totalPriceField); ContentFieldSchema fieldSchema = new ContentFieldSchema(); fieldSchema.setName("receipt_schema"); @@ -213,6 +240,7 @@ public static void main(String[] args) throws InterruptedException { System.out.println(" Description: " + result.getDescription()); System.out.println(" Base analyzer: " + result.getBaseAnalyzerId()); System.out.println(" Fields: " + result.getFieldSchema().getFields().size()); + System.out.println(" Knowledge sources: " + (result.getKnowledgeSources() != null ? result.getKnowledgeSources().size() : 0)); // END: com.azure.ai.contentunderstanding.createAnalyzerWithLabelsAsync // Verify analyzer creation @@ -225,7 +253,7 @@ public static void main(String[] args) throws InterruptedException { System.out.println(" MerchantName: String (Extract)"); System.out.println(" Items: Array of Objects (Generate)"); System.out.println(" - Quantity, Name, Price"); - System.out.println(" Total: String (Extract)"); + System.out.println(" TotalPrice: String (Extract)"); ContentFieldDefinition itemsFieldResult = resultFields.get("Items"); System.out.println("Items field verified:"); @@ -236,9 +264,9 @@ public static void main(String[] args) throws InterruptedException { System.out.println("\nCreateAnalyzerWithLabels API Pattern:"); System.out.println(" 1. Define field schema with nested structures (arrays, objects)"); System.out.println(" 2. Upload training data to Azure Blob Storage:"); - System.out.println(" - Documents: receipt1.pdf, receipt2.pdf, ..."); - System.out.println(" - Labels: receipt1.pdf.labels.json, receipt2.pdf.labels.json, ..."); - System.out.println(" - OCR: receipt1.pdf.result.json, receipt2.pdf.result.json, ..."); + System.out.println(" - Documents: receipt1.jpg, receipt2.jpg, ..."); + System.out.println(" - Labels: receipt1.jpg.labels.json, receipt2.jpg.labels.json, ..."); + System.out.println(" - OCR: receipt1.jpg.result.json, receipt2.jpg.result.json, ..."); System.out.println(" 3. Create LabeledDataKnowledgeSource with storage SAS URL"); System.out.println(" 4. Create analyzer with field schema and knowledge sources"); System.out.println(" 5. Use analyzer for document analysis"); @@ -275,4 +303,63 @@ public static void main(String[] args) throws InterruptedException { // Wait for async operations to complete latch.await(3, TimeUnit.MINUTES); } + + /** + * Uploads local training data files (images, .labels.json, .result.json) to an + * Azure Blob container. Existing blobs with the same name are overwritten. + */ + private static void uploadTrainingData(String storageAccountName, String containerName, + TokenCredential credential, String localDirectory, String prefix) { + BlobContainerClient containerClient = new BlobServiceClientBuilder() + .endpoint("https://" + storageAccountName + ".blob.core.windows.net") + .credential(credential) + .buildClient() + .getBlobContainerClient(containerName); + + containerClient.createIfNotExists(); + + File dir = new File(localDirectory); + File[] files = dir.listFiles(); + if (files == null) { + return; + } + for (File file : files) { + if (!file.isFile()) { + continue; + } + String blobName = (prefix == null || prefix.trim().isEmpty()) + ? file.getName() + : prefix.replaceAll("/+$", "") + "/" + file.getName(); + + System.out.println("Uploading " + file.getName() + " -> " + blobName); + BlobClient blobClient = containerClient.getBlobClient(blobName); + blobClient.uploadFromFile(file.getAbsolutePath(), true); + } + } + + /** + * Generates a User Delegation SAS URL (Read + List) for an Azure Blob container. + */ + private static String generateUserDelegationSasUrl(String storageAccountName, String containerName, + TokenCredential credential) { + BlobServiceClient blobServiceClient = new BlobServiceClientBuilder() + .endpoint("https://" + storageAccountName + ".blob.core.windows.net") + .credential(credential) + .buildClient(); + + UserDelegationKey userDelegationKey = blobServiceClient.getUserDelegationKey( + OffsetDateTime.now(), OffsetDateTime.now().plusHours(1)); + + BlobContainerSasPermission permissions = new BlobContainerSasPermission() + .setReadPermission(true) + .setListPermission(true); + + BlobServiceSasSignatureValues sasValues = new BlobServiceSasSignatureValues( + OffsetDateTime.now().plusHours(1), permissions); + + String sasToken = blobServiceClient.getBlobContainerClient(containerName) + .generateUserDelegationSas(sasValues, userDelegationKey); + + return "https://" + storageAccountName + ".blob.core.windows.net/" + containerName + "?" + sasToken; + } } diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/receipt_labels/receipt1.jpg b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/receipt_labels/receipt1.jpg new file mode 100644 index 000000000000..77f6b4cf9d26 Binary files /dev/null and b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/receipt_labels/receipt1.jpg differ diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/receipt_labels/receipt1.jpg.labels.json b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/receipt_labels/receipt1.jpg.labels.json new file mode 100644 index 000000000000..72fbf6c10de4 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/receipt_labels/receipt1.jpg.labels.json @@ -0,0 +1,134 @@ +{ + "$schema": "https://schema.ai.azure.com/mmi/2025-11-01/labels.json", + "fileId": "", + "fieldLabels": { + "MerchantName": { + "type": "string", + "valueString": "Contoso", + "source": "D(1,811,150,945,136,946,163,812,178)", + "kind": "corrected" + }, + "TotalPrice": { + "type": "string", + "valueString": "$14,50", + "spans": [ + { + "offset": 342, + "length": 6 + } + ], + "confidence": 0.581, + "source": "D(1,928,767,1027,771,1027,805,927,801)", + "kind": "confirmed" + }, + "Items": { + "type": "array", + "kind": "confirmed", + "valueArray": [ + { + "type": "object", + "kind": "confirmed", + "valueObject": { + "Quantity": { + "type": "string", + "valueString": "1", + "spans": [ + { + "offset": 127, + "length": 1 + } + ], + "confidence": 0.966, + "source": "D(1,697,462,703,462,702,489,696,489)", + "kind": "confirmed" + }, + "Name": { + "type": "string", + "valueString": "Cappuccino", + "spans": [ + { + "offset": 129, + "length": 10 + } + ], + "confidence": 0.956, + "source": "D(1,720,463,819,467,819,494,719,490)", + "kind": "confirmed" + }, + "Price": { + "type": "string", + "valueString": "$2.20", + "spans": [ + { + "offset": 140, + "length": 5 + } + ], + "confidence": 0.289, + "source": "D(1,949,460,997,459,997,485,949,487)", + "kind": "confirmed" + } + } + }, + { + "type": "object", + "kind": "confirmed", + "valueObject": { + "Quantity": { + "type": "string", + "valueString": "1", + "spans": [ + { + "offset": 147, + "length": 1 + } + ], + "confidence": 0.984, + "source": "D(1,695,536,700,536,700,561,695,561)", + "kind": "confirmed" + }, + "Name": { + "type": "string", + "valueString": "BACON & EGGS", + "spans": [ + { + "offset": 149, + "length": 5 + }, + { + "offset": 155, + "length": 1 + }, + { + "offset": 157, + "length": 4 + } + ], + "confidence": 0.961, + "source": "D(1,717,536,773,537,772,562,717,561);D(1,779,537,791,537,791,562,779,562);D(1,798,537,842,537,842,562,798,562)", + "kind": "confirmed" + }, + "Price": { + "type": "string", + "valueString": "$9.5", + "spans": [ + { + "offset": 176, + "length": 4 + } + ], + "confidence": 0.287, + "source": "D(1,957,569,996,569,996,597,957,598)", + "kind": "confirmed" + } + } + } + ] + } + }, + "metadata": { + "displayName": "receipt2.png", + "createdDateTime": "2024-12-13T02:00:04.775Z", + "mimeType": "image/png" + } +} \ No newline at end of file diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/receipt_labels/receipt1.jpg.result.json b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/receipt_labels/receipt1.jpg.result.json new file mode 100644 index 000000000000..f670d420eb8d --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/receipt_labels/receipt1.jpg.result.json @@ -0,0 +1 @@ +{"id":"3055de98-3f81-491d-b9fa-2c76285b3c1c","status":"Succeeded","result":{"analyzerId":"auto-labeling-model-1734055129725-388","apiVersion":"2025-11-01","createdAt":"2024-12-13T02:00:12Z","warnings":[],"contents":[{"markdown":"
    \n\nContoso\n\n
    \n\n\nContoso\n123 Main Street\nRedmond, WA 98052\n\n987-654-3210\n\n6/10/2019 13:59\nSales Associate: Paul\n\n1 Cappuccino\n$2.20\n\n1 BACON & EGGS\nSunny-side-up\n$9.5\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    Sub-Total$ 11.70
    Tax$ 1.17
    Tip$ 1.63
    Total$14,50
    \n","kind":"document","startPageNumber":1,"endPageNumber":1,"unit":"pixel","pages":[{"pageNumber":1,"angle":0.4344055,"width":1743,"height":878,"spans":[{"offset":0,"length":369}],"words":[{"content":"Contoso","span":{"offset":10,"length":7},"confidence":0.997,"source":"D(1,811,150,945,136,946,163,812,178)"},{"content":"Contoso","span":{"offset":31,"length":7},"confidence":0.989,"source":"D(1,714,165,776,183,775,202,712,186)"},{"content":"123","span":{"offset":39,"length":3},"confidence":0.994,"source":"D(1,712,197,737,203,735,224,710,218)"},{"content":"Main","span":{"offset":43,"length":4},"confidence":0.992,"source":"D(1,744,204,777,212,776,233,742,226)"},{"content":"Street","span":{"offset":48,"length":6},"confidence":0.994,"source":"D(1,783,213,829,223,828,244,781,234)"},{"content":"Redmond,","span":{"offset":55,"length":8},"confidence":0.992,"source":"D(1,709,229,786,242,785,265,708,251)"},{"content":"WA","span":{"offset":64,"length":2},"confidence":0.993,"source":"D(1,790,243,813,246,813,268,789,266)"},{"content":"98052","span":{"offset":67,"length":5},"confidence":0.992,"source":"D(1,818,246,866,251,866,273,818,269)"},{"content":"987-654-3210","span":{"offset":74,"length":12},"confidence":0.992,"source":"D(1,705,292,816,307,816,329,704,314)"},{"content":"6/10/2019","span":{"offset":88,"length":9},"confidence":0.992,"source":"D(1,702,357,778,364,777,390,700,383)"},{"content":"13:59","span":{"offset":98,"length":5},"confidence":0.997,"source":"D(1,784,364,824,368,824,393,783,390)"},{"content":"Sales","span":{"offset":104,"length":5},"confidence":0.993,"source":"D(1,699,393,746,396,745,419,699,415)"},{"content":"Associate:","span":{"offset":110,"length":10},"confidence":0.807,"source":"D(1,752,397,833,401,832,423,751,419)"},{"content":"Paul","span":{"offset":121,"length":4},"confidence":0.992,"source":"D(1,839,401,877,403,877,425,838,423)"},{"content":"1","span":{"offset":127,"length":1},"confidence":0.992,"source":"D(1,697,462,703,462,702,489,696,489)"},{"content":"Cappuccino","span":{"offset":129,"length":10},"confidence":0.962,"source":"D(1,720,463,819,467,819,494,719,490)"},{"content":"$2.20","span":{"offset":140,"length":5},"confidence":0.996,"source":"D(1,949,460,997,459,997,485,949,487)"},{"content":"1","span":{"offset":147,"length":1},"confidence":0.318,"source":"D(1,695,536,700,536,700,561,695,561)"},{"content":"BACON","span":{"offset":149,"length":5},"confidence":0.994,"source":"D(1,717,536,773,537,772,562,717,561)"},{"content":"&","span":{"offset":155,"length":1},"confidence":0.969,"source":"D(1,779,537,791,537,791,562,779,562)"},{"content":"EGGS","span":{"offset":157,"length":4},"confidence":0.992,"source":"D(1,798,537,842,537,842,562,798,562)"},{"content":"Sunny-side-up","span":{"offset":162,"length":13},"confidence":0.99,"source":"D(1,726,576,844,577,844,603,726,601)"},{"content":"$9.5","span":{"offset":176,"length":4},"confidence":0.977,"source":"D(1,957,569,996,569,996,597,957,598)"},{"content":"Sub-Total","span":{"offset":200,"length":9},"confidence":0.992,"source":"D(1,753,651,851,649,851,677,753,680)"},{"content":"$","span":{"offset":219,"length":1},"confidence":0.986,"source":"D(1,943,648,955,648,955,679,943,679)"},{"content":"11.70","span":{"offset":221,"length":5},"confidence":0.994,"source":"D(1,962,648,1008,648,1008,679,962,679)"},{"content":"Tax","span":{"offset":247,"length":3},"confidence":0.997,"source":"D(1,754,691,790,691,790,719,754,720)"},{"content":"$","span":{"offset":260,"length":1},"confidence":0.989,"source":"D(1,955,687,968,687,967,718,955,718)"},{"content":"1.17","span":{"offset":262,"length":4},"confidence":0.992,"source":"D(1,975,687,1008,688,1008,718,975,718)"},{"content":"Tip","span":{"offset":287,"length":3},"confidence":0.993,"source":"D(1,753,730,783,731,783,763,753,761)"},{"content":"$","span":{"offset":300,"length":1},"confidence":0.989,"source":"D(1,925,723,937,723,937,757,925,756)"},{"content":"1.63","span":{"offset":302,"length":4},"confidence":0.916,"source":"D(1,947,723,996,724,996,756,947,756)"},{"content":"Total","span":{"offset":327,"length":5},"confidence":0.896,"source":"D(1,751,774,804,772,804,802,751,804)"},{"content":"$14,50","span":{"offset":342,"length":6},"confidence":0.783,"source":"D(1,928,767,1027,771,1027,805,927,801)"}],"lines":[{"content":"Contoso","source":"D(1,811,150,944,137,946,166,812,178)","span":{"offset":10,"length":7}},{"content":"Contoso","source":"D(1,714,165,776,182,774,202,712,185)","span":{"offset":31,"length":7}},{"content":"123 Main Street","source":"D(1,712,197,829,224,827,244,710,217)","span":{"offset":39,"length":15}},{"content":"Redmond, WA 98052","source":"D(1,709,229,866,251,864,273,707,254)","span":{"offset":55,"length":17}},{"content":"987-654-3210","source":"D(1,705,292,816,307,815,329,703,314)","span":{"offset":74,"length":12}},{"content":"6/10/2019 13:59","source":"D(1,702,357,824,368,822,393,700,383)","span":{"offset":88,"length":15}},{"content":"Sales Associate: Paul","source":"D(1,699,393,877,403,876,425,698,416)","span":{"offset":104,"length":21}},{"content":"1 Cappuccino","source":"D(1,697,462,819,467,818,494,696,489)","span":{"offset":127,"length":12}},{"content":"$2.20","source":"D(1,949,460,996,459,997,485,949,487)","span":{"offset":140,"length":5}},{"content":"1 BACON & EGGS","source":"D(1,695,536,842,537,842,562,695,561)","span":{"offset":147,"length":14}},{"content":"Sunny-side-up","source":"D(1,726,576,844,577,844,602,726,601)","span":{"offset":162,"length":13}},{"content":"$9.5","source":"D(1,957,569,995,569,995,598,957,598)","span":{"offset":176,"length":4}},{"content":"Sub-Total","source":"D(1,753,651,850,649,851,677,753,680)","span":{"offset":200,"length":9}},{"content":"$ 11.70","source":"D(1,943,648,1007,648,1007,679,943,679)","span":{"offset":219,"length":7}},{"content":"Tax","source":"D(1,754,691,789,691,789,720,754,720)","span":{"offset":247,"length":3}},{"content":"$ 1.17","source":"D(1,955,687,1008,687,1008,718,955,718)","span":{"offset":260,"length":6}},{"content":"Tip","source":"D(1,753,730,782,730,782,763,753,762)","span":{"offset":287,"length":3}},{"content":"$ 1.63","source":"D(1,925,723,996,723,996,756,925,756)","span":{"offset":300,"length":6}},{"content":"Total","source":"D(1,751,774,803,772,804,802,751,804)","span":{"offset":327,"length":5}},{"content":"$14,50","source":"D(1,928,767,1027,771,1026,805,927,800)","span":{"offset":342,"length":6}}]}],"paragraphs":[{"content":"Contoso","source":"D(1,809,149,944,137,947,166,812,178)","span":{"offset":10,"length":7}},{"content":"Contoso 123 Main Street Redmond, WA 98052","source":"D(1,714,165,875,185,864,273,703,253)","span":{"offset":31,"length":41}},{"content":"987-654-3210","source":"D(1,705,292,818,307,815,329,702,314)","span":{"offset":74,"length":12}},{"content":"6/10/2019 13:59 Sales Associate: Paul","source":"D(1,701,357,879,366,876,425,698,416)","span":{"offset":88,"length":37}},{"content":"1 Cappuccino $2.20","source":"D(1,696,462,997,459,997,492,696,495)","span":{"offset":127,"length":18}},{"content":"1 BACON & EGGS Sunny-side-up $9.5","source":"D(1,695,536,996,538,995,604,694,601)","span":{"offset":147,"length":33}},{"content":"Sub-Total","source":"D(1,738,643,886,638,886,682,737,686)","span":{"offset":200,"length":9}},{"content":"$ 11.70","source":"D(1,886,638,1031,636,1031,680,886,682)","span":{"offset":219,"length":7}},{"content":"Tax","source":"D(1,737,686,886,682,886,721,737,725)","span":{"offset":247,"length":3}},{"content":"$ 1.17","source":"D(1,886,682,1031,680,1032,719,886,721)","span":{"offset":260,"length":6}},{"content":"Tip","source":"D(1,737,725,886,721,886,761,737,766)","span":{"offset":287,"length":3}},{"content":"$ 1.63","source":"D(1,886,721,1032,719,1032,761,886,761)","span":{"offset":300,"length":6}},{"content":"Total","source":"D(1,737,766,886,761,886,812,736,815)","span":{"offset":327,"length":5}},{"content":"$14,50","source":"D(1,886,761,1032,761,1032,812,886,812)","span":{"offset":342,"length":6}}],"sections":[{"span":{"offset":0,"length":368},"elements":["/figures/0","/paragraphs/1","/paragraphs/2","/paragraphs/3","/paragraphs/4","/paragraphs/5","/tables/0"]}],"tables":[{"rowCount":4,"columnCount":2,"cells":[{"kind":"content","rowIndex":0,"columnIndex":0,"rowSpan":1,"columnSpan":1,"content":"Sub-Total","source":"D(1,738,643,886,638,886,682,737,686)","span":{"offset":200,"length":9},"elements":["/paragraphs/6"]},{"kind":"content","rowIndex":0,"columnIndex":1,"rowSpan":1,"columnSpan":1,"content":"$ 11.70","source":"D(1,886,638,1031,636,1031,680,886,682)","span":{"offset":219,"length":7},"elements":["/paragraphs/7"]},{"kind":"content","rowIndex":1,"columnIndex":0,"rowSpan":1,"columnSpan":1,"content":"Tax","source":"D(1,737,686,886,682,886,721,737,725)","span":{"offset":247,"length":3},"elements":["/paragraphs/8"]},{"kind":"content","rowIndex":1,"columnIndex":1,"rowSpan":1,"columnSpan":1,"content":"$ 1.17","source":"D(1,886,682,1031,680,1032,719,886,721)","span":{"offset":260,"length":6},"elements":["/paragraphs/9"]},{"kind":"content","rowIndex":2,"columnIndex":0,"rowSpan":1,"columnSpan":1,"content":"Tip","source":"D(1,737,725,886,721,886,761,737,766)","span":{"offset":287,"length":3},"elements":["/paragraphs/10"]},{"kind":"content","rowIndex":2,"columnIndex":1,"rowSpan":1,"columnSpan":1,"content":"$ 1.63","source":"D(1,886,721,1032,719,1032,761,886,761)","span":{"offset":300,"length":6},"elements":["/paragraphs/11"]},{"kind":"content","rowIndex":3,"columnIndex":0,"rowSpan":1,"columnSpan":1,"content":"Total","source":"D(1,737,766,886,761,886,812,736,815)","span":{"offset":327,"length":5},"elements":["/paragraphs/12"]},{"kind":"content","rowIndex":3,"columnIndex":1,"rowSpan":1,"columnSpan":1,"content":"$14,50","source":"D(1,886,761,1032,761,1032,812,886,812)","span":{"offset":342,"length":6},"elements":["/paragraphs/13"]}],"source":"D(1,740,646,1030,646,1030,809,740,809)","span":{"offset":183,"length":185}}],"figures":[{"source":"D(1,748,120,946,120,946,176,748,176)","span":{"offset":0,"length":28},"elements":["/paragraphs/0"],"id":""}]}]}} \ No newline at end of file diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/receipt_labels/receipt2.jpg b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/receipt_labels/receipt2.jpg new file mode 100644 index 000000000000..ab0148de7e30 Binary files /dev/null and b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/receipt_labels/receipt2.jpg differ diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/receipt_labels/receipt2.jpg.labels.json b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/receipt_labels/receipt2.jpg.labels.json new file mode 100644 index 000000000000..2f5fdd82a9a7 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/receipt_labels/receipt2.jpg.labels.json @@ -0,0 +1,149 @@ +{ + "$schema": "https://schema.ai.azure.com/mmi/2025-11-01/labels.json", + "fileId": "", + "fieldLabels": { + "MerchantName": { + "type": "string", + "valueString": "Contoso", + "spans": [ + { + "offset": 0, + "length": 7 + } + ], + "confidence": 0.997, + "source": "D(1,807,157,909,150,912,173,809,181)", + "kind": "confirmed" + }, + "TotalPrice": { + "type": "string", + "valueString": "21.00", + "spans": [ + { + "offset": 366, + "length": 5 + } + ], + "confidence": 0.825, + "source": "D(1,943,528,998,528,998,547,944,548)", + "kind": "confirmed" + }, + "Items": { + "type": "array", + "kind": "confirmed", + "valueArray": [ + { + "type": "object", + "kind": "confirmed", + "valueObject": { + "Quantity": { + "type": "string", + "valueString": "1", + "spans": [ + { + "offset": 173, + "length": 1 + } + ], + "confidence": 0.984, + "source": "D(1,744,436,752,436,752,448,744,449)", + "kind": "confirmed" + }, + "Name": { + "type": "string", + "valueString": "GRAINE DE CHIA", + "spans": [ + { + "offset": 175, + "length": 6 + }, + { + "offset": 182, + "length": 2 + }, + { + "offset": 185, + "length": 4 + } + ], + "confidence": 0.974, + "source": "D(1,778,434,832,433,833,447,779,448);D(1,837,433,855,432,856,446,838,447);D(1,859,432,895,431,895,445,860,446)", + "kind": "confirmed" + }, + "Price": { + "type": "string", + "valueString": "3.00 B", + "spans": [ + { + "offset": 199, + "length": 4 + }, + { + "offset": 204, + "length": 1 + } + ], + "confidence": 0.507, + "source": "D(1,944,429,976,428,976,443,944,444);D(1,990,428,1000,428,1000,443,991,443)", + "kind": "confirmed" + } + } + }, + { + "type": "object", + "kind": "confirmed", + "valueObject": { + "Quantity": { + "type": "string", + "valueString": "1", + "spans": [ + { + "offset": 226, + "length": 1 + } + ], + "confidence": 0.981, + "source": "D(1,744,461,753,460,753,474,745,474)", + "kind": "confirmed" + }, + "Name": { + "type": "string", + "valueString": "POKE", + "spans": [ + { + "offset": 228, + "length": 4 + } + ], + "confidence": 0.986, + "source": "D(1,779,459,817,459,818,473,779,473)", + "kind": "confirmed" + }, + "Price": { + "type": "string", + "valueString": "20.00 C", + "spans": [ + { + "offset": 242, + "length": 5 + }, + { + "offset": 248, + "length": 1 + } + ], + "confidence": 0.892, + "source": "D(1,937,455,977,454,977,469,937,470);D(1,991,454,1002,454,1002,469,991,469)", + "kind": "confirmed" + } + } + } + ] + } + }, + "metadata": { + "displayName": "receipt3.png", + "createdDateTime": "2024-12-13T02:00:04.775Z", + "mimeType": "image/png" + } +} \ No newline at end of file diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/receipt_labels/receipt2.jpg.result.json b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/receipt_labels/receipt2.jpg.result.json new file mode 100644 index 000000000000..147578633a94 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/samples/resources/receipt_labels/receipt2.jpg.result.json @@ -0,0 +1 @@ +{"id":"4e1b0e8d-05ed-407b-8f2c-cb6f356aed6d","status":"Succeeded","result":{"analyzerId":"auto-labeling-model-1734055276709-309","apiVersion":"2025-11-01","createdAt":"2024-12-13T02:01:27Z","warnings":[],"contents":[{"markdown":"Contoso\n1 RUE DE LA DURANCE\n75012 PARIS FRANCE\n\nTEL : 09 88 77 66 55\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    21-10-2113:10:48
    TABLE 1
    1 GRAINE DE CHIA3.00 B
    1 POKE20.00 C
    \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
    TOTAL21.00
    \n\n\nVOUS REMERCIE DE VOTRE\nVISITE\n","kind":"document","startPageNumber":1,"endPageNumber":1,"unit":"pixel","pages":[{"pageNumber":1,"angle":-1.0826,"width":1743,"height":878,"spans":[{"offset":0,"length":424}],"words":[{"content":"Contoso","span":{"offset":0,"length":7},"confidence":0.995,"source":"D(1,807,157,909,150,912,173,809,181)"},{"content":"1","span":{"offset":8,"length":1},"confidence":0.995,"source":"D(1,782,199,789,199,791,215,783,215)"},{"content":"RUE","span":{"offset":10,"length":3},"confidence":0.993,"source":"D(1,795,198,823,196,825,212,797,214)"},{"content":"DE","span":{"offset":14,"length":2},"confidence":0.992,"source":"D(1,828,195,846,194,848,210,830,211)"},{"content":"LA","span":{"offset":17,"length":2},"confidence":0.988,"source":"D(1,851,193,867,192,869,208,853,209)"},{"content":"DURANCE","span":{"offset":20,"length":7},"confidence":0.992,"source":"D(1,873,191,944,185,945,201,874,207)"},{"content":"75012","span":{"offset":28,"length":5},"confidence":0.994,"source":"D(1,790,225,833,222,834,238,791,241)"},{"content":"PARIS","span":{"offset":34,"length":5},"confidence":0.992,"source":"D(1,839,221,877,218,878,234,841,237)"},{"content":"FRANCE","span":{"offset":40,"length":6},"confidence":0.995,"source":"D(1,881,218,939,213,940,229,883,234)"},{"content":"TEL","span":{"offset":48,"length":3},"confidence":0.994,"source":"D(1,783,279,808,277,809,292,785,294)"},{"content":":","span":{"offset":52,"length":1},"confidence":0.841,"source":"D(1,813,277,816,277,817,292,814,292)"},{"content":"09","span":{"offset":54,"length":2},"confidence":0.981,"source":"D(1,834,276,852,274,853,290,835,291)"},{"content":"88","span":{"offset":57,"length":2},"confidence":0.992,"source":"D(1,858,274,876,273,877,288,860,289)"},{"content":"77","span":{"offset":60,"length":2},"confidence":0.99,"source":"D(1,884,272,900,271,901,286,885,287)"},{"content":"66","span":{"offset":63,"length":2},"confidence":0.998,"source":"D(1,908,271,925,270,926,285,909,286)"},{"content":"55","span":{"offset":66,"length":2},"confidence":0.998,"source":"D(1,933,269,951,268,953,283,934,284)"},{"content":"21-10-21","span":{"offset":88,"length":8},"confidence":0.995,"source":"D(1,725,383,788,381,789,396,725,398)"},{"content":"13:10:48","span":{"offset":106,"length":8},"confidence":0.996,"source":"D(1,934,376,999,373,999,388,935,391)"},{"content":"TABLE","span":{"offset":135,"length":5},"confidence":0.996,"source":"D(1,724,410,769,408,769,423,725,424)"},{"content":"1","span":{"offset":141,"length":1},"confidence":0.985,"source":"D(1,774,408,783,408,783,422,775,423)"},{"content":"1","span":{"offset":173,"length":1},"confidence":0.997,"source":"D(1,744,436,752,436,752,448,744,449)"},{"content":"GRAINE","span":{"offset":175,"length":6},"confidence":0.994,"source":"D(1,778,434,832,433,833,447,779,448)"},{"content":"DE","span":{"offset":182,"length":2},"confidence":0.995,"source":"D(1,837,433,855,432,856,446,838,447)"},{"content":"CHIA","span":{"offset":185,"length":4},"confidence":0.989,"source":"D(1,859,432,895,431,895,445,860,446)"},{"content":"3.00","span":{"offset":199,"length":4},"confidence":0.992,"source":"D(1,944,429,976,428,976,443,944,444)"},{"content":"B","span":{"offset":204,"length":1},"confidence":0.993,"source":"D(1,990,428,1000,428,1000,443,991,443)"},{"content":"1","span":{"offset":226,"length":1},"confidence":0.997,"source":"D(1,744,461,753,460,753,474,745,474)"},{"content":"POKE","span":{"offset":228,"length":4},"confidence":0.992,"source":"D(1,779,459,817,459,818,473,779,473)"},{"content":"20.00","span":{"offset":242,"length":5},"confidence":0.995,"source":"D(1,937,455,977,454,977,469,937,470)"},{"content":"C","span":{"offset":248,"length":1},"confidence":0.926,"source":"D(1,991,454,1002,454,1002,469,991,469)"},{"content":"TOTAL","span":{"offset":351,"length":5},"confidence":0.995,"source":"D(1,752,532,827,531,827,550,753,550)"},{"content":"21.00","span":{"offset":366,"length":5},"confidence":0.998,"source":"D(1,943,528,998,528,998,547,944,548)"},{"content":"VOUS","span":{"offset":394,"length":4},"confidence":0.988,"source":"D(1,730,635,790,635,790,656,730,657)"},{"content":"REMERCIE","span":{"offset":399,"length":8},"confidence":0.993,"source":"D(1,799,635,906,634,907,656,799,656)"},{"content":"DE","span":{"offset":408,"length":2},"confidence":0.996,"source":"D(1,914,634,941,634,941,656,915,656)"},{"content":"VOTRE","span":{"offset":411,"length":5},"confidence":0.995,"source":"D(1,948,634,1018,634,1018,655,948,656)"},{"content":"VISITE","span":{"offset":417,"length":6},"confidence":0.992,"source":"D(1,841,662,909,662,909,682,842,682)"}],"lines":[{"content":"Contoso","source":"D(1,807,157,910,150,911,173,808,181)","span":{"offset":0,"length":7}},{"content":"1 RUE DE LA DURANCE","source":"D(1,782,199,943,185,944,201,783,215)","span":{"offset":8,"length":19}},{"content":"75012 PARIS FRANCE","source":"D(1,790,225,939,213,940,229,791,241)","span":{"offset":28,"length":18}},{"content":"TEL : 09 88 77 66 55","source":"D(1,783,278,951,268,952,283,784,294)","span":{"offset":48,"length":20}},{"content":"21-10-21","source":"D(1,725,383,788,381,788,395,725,398)","span":{"offset":88,"length":8}},{"content":"13:10:48","source":"D(1,934,376,998,373,999,388,935,390)","span":{"offset":106,"length":8}},{"content":"TABLE 1","source":"D(1,724,410,783,408,783,422,725,424)","span":{"offset":135,"length":7}},{"content":"1","source":"D(1,744,436,751,436,752,448,744,448)","span":{"offset":173,"length":1}},{"content":"GRAINE DE CHIA","source":"D(1,778,434,894,431,895,445,778,449)","span":{"offset":175,"length":14}},{"content":"3.00 B","source":"D(1,944,429,999,428,1000,443,944,444)","span":{"offset":199,"length":6}},{"content":"1","source":"D(1,744,461,753,460,753,474,745,474)","span":{"offset":226,"length":1}},{"content":"POKE","source":"D(1,779,459,817,458,817,472,779,473)","span":{"offset":228,"length":4}},{"content":"20.00 C","source":"D(1,937,455,1002,454,1002,469,937,470)","span":{"offset":242,"length":7}},{"content":"TOTAL","source":"D(1,752,532,826,531,826,550,753,550)","span":{"offset":351,"length":5}},{"content":"21.00","source":"D(1,943,528,997,528,997,547,944,547)","span":{"offset":366,"length":5}},{"content":"VOUS REMERCIE DE VOTRE","source":"D(1,730,635,1018,634,1018,655,730,656)","span":{"offset":394,"length":22}},{"content":"VISITE","source":"D(1,841,662,909,662,909,682,842,683)","span":{"offset":417,"length":6}}]}],"paragraphs":[{"content":"Contoso 1 RUE DE LA DURANCE 75012 PARIS FRANCE","source":"D(1,778,159,940,146,946,228,785,241)","span":{"offset":0,"length":46}},{"content":"TEL : 09 88 77 66 55","source":"D(1,783,278,951,268,952,283,784,294)","span":{"offset":48,"length":20}},{"content":"21-10-21","source":"D(1,714,366,913,361,913,396,714,402)","span":{"offset":88,"length":8}},{"content":"13:10:48","source":"D(1,913,361,1007,360,1007,394,913,396)","span":{"offset":106,"length":8}},{"content":"TABLE 1","source":"D(1,714,402,913,396,913,422,714,429)","span":{"offset":135,"length":7}},{"content":"1 GRAINE DE CHIA","source":"D(1,714,429,913,422,913,448,714,454)","span":{"offset":173,"length":16}},{"content":"3.00 B","source":"D(1,913,422,1008,419,1008,445,913,448)","span":{"offset":199,"length":6}},{"content":"1 POKE","source":"D(1,714,454,913,448,914,480,714,486)","span":{"offset":226,"length":6}},{"content":"20.00 C","source":"D(1,913,448,1008,445,1008,478,914,480)","span":{"offset":242,"length":7}},{"content":"TOTAL","source":"D(1,729,502,914,498,914,561,730,562)","span":{"offset":351,"length":5}},{"content":"21.00","source":"D(1,914,498,1009,496,1009,561,914,561)","span":{"offset":366,"length":5}},{"content":"VOUS REMERCIE DE VOTRE VISITE","source":"D(1,730,635,1018,634,1018,682,730,683)","span":{"offset":394,"length":29}}],"sections":[{"span":{"offset":0,"length":423},"elements":["/paragraphs/0","/paragraphs/1","/tables/0","/tables/1","/paragraphs/11"]}],"tables":[{"rowCount":4,"columnCount":2,"cells":[{"kind":"columnHeader","rowIndex":0,"columnIndex":0,"rowSpan":1,"columnSpan":1,"content":"21-10-21","source":"D(1,714,366,913,361,913,396,714,402)","span":{"offset":88,"length":8},"elements":["/paragraphs/2"]},{"kind":"columnHeader","rowIndex":0,"columnIndex":1,"rowSpan":1,"columnSpan":1,"content":"13:10:48","source":"D(1,913,361,1007,360,1007,394,913,396)","span":{"offset":106,"length":8},"elements":["/paragraphs/3"]},{"kind":"columnHeader","rowIndex":1,"columnIndex":0,"rowSpan":1,"columnSpan":1,"content":"TABLE 1","source":"D(1,714,402,913,396,913,422,714,429)","span":{"offset":135,"length":7},"elements":["/paragraphs/4"]},{"kind":"columnHeader","rowIndex":1,"columnIndex":1,"rowSpan":1,"columnSpan":1,"content":"","source":"D(1,913,396,1007,394,1008,419,913,422)","span":{"offset":152,"length":0}},{"kind":"content","rowIndex":2,"columnIndex":0,"rowSpan":1,"columnSpan":1,"content":"1 GRAINE DE CHIA","source":"D(1,714,429,913,422,913,448,714,454)","span":{"offset":173,"length":16},"elements":["/paragraphs/5"]},{"kind":"content","rowIndex":2,"columnIndex":1,"rowSpan":1,"columnSpan":1,"content":"3.00 B","source":"D(1,913,422,1008,419,1008,445,913,448)","span":{"offset":199,"length":6},"elements":["/paragraphs/6"]},{"kind":"content","rowIndex":3,"columnIndex":0,"rowSpan":1,"columnSpan":1,"content":"1 POKE","source":"D(1,714,454,913,448,914,480,714,486)","span":{"offset":226,"length":6},"elements":["/paragraphs/7"]},{"kind":"content","rowIndex":3,"columnIndex":1,"rowSpan":1,"columnSpan":1,"content":"20.00 C","source":"D(1,913,448,1008,445,1008,478,914,480)","span":{"offset":242,"length":7},"elements":["/paragraphs/8"]}],"source":"D(1,721,376,1001,371,1004,475,724,480)","span":{"offset":71,"length":198}},{"rowCount":3,"columnCount":2,"cells":[{"kind":"content","rowIndex":0,"columnIndex":0,"rowSpan":1,"columnSpan":1,"content":"","source":"D(1,727,422,914,416,914,447,728,453)","span":{"offset":289,"length":0}},{"kind":"content","rowIndex":0,"columnIndex":1,"rowSpan":1,"columnSpan":1,"content":"","source":"D(1,914,416,1009,415,1009,445,914,447)","span":{"offset":299,"length":0}},{"kind":"content","rowIndex":1,"columnIndex":0,"rowSpan":1,"columnSpan":1,"content":"","source":"D(1,728,453,914,447,914,498,729,502)","span":{"offset":320,"length":0}},{"kind":"content","rowIndex":1,"columnIndex":1,"rowSpan":1,"columnSpan":1,"content":"","source":"D(1,914,447,1009,445,1009,496,914,498)","span":{"offset":330,"length":0}},{"kind":"content","rowIndex":2,"columnIndex":0,"rowSpan":1,"columnSpan":1,"content":"TOTAL","source":"D(1,729,502,914,498,914,561,730,562)","span":{"offset":351,"length":5},"elements":["/paragraphs/9"]},{"kind":"content","rowIndex":2,"columnIndex":1,"rowSpan":1,"columnSpan":1,"content":"21.00","source":"D(1,914,498,1009,496,1009,561,914,561)","span":{"offset":366,"length":5},"elements":["/paragraphs/10"]}],"source":"D(1,735,428,1002,427,1004,553,736,554)","span":{"offset":272,"length":119}}]}]}} \ No newline at end of file diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/ContentUnderstandingClientTestBase.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/ContentUnderstandingClientTestBase.java index fa090cf441a3..c1992a44b2b3 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/ContentUnderstandingClientTestBase.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/ContentUnderstandingClientTestBase.java @@ -11,6 +11,7 @@ import com.azure.ai.contentunderstanding.ContentUnderstandingAsyncClient; import com.azure.ai.contentunderstanding.ContentUnderstandingClient; import com.azure.ai.contentunderstanding.ContentUnderstandingClientBuilder; +import com.azure.core.credential.TokenCredential; import com.azure.core.http.policy.HttpLogDetailLevel; import com.azure.core.http.policy.HttpLogOptions; import com.azure.core.test.TestMode; @@ -18,6 +19,16 @@ import com.azure.core.test.utils.MockTokenCredential; import com.azure.core.util.Configuration; import com.azure.identity.DefaultAzureCredentialBuilder; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobServiceClient; +import com.azure.storage.blob.BlobServiceClientBuilder; +import com.azure.storage.blob.models.UserDelegationKey; +import com.azure.storage.blob.sas.BlobContainerSasPermission; +import com.azure.storage.blob.sas.BlobServiceSasSignatureValues; + +import java.io.File; +import java.time.OffsetDateTime; class ContentUnderstandingClientTestBase extends TestProxyTestBase { protected ContentUnderstandingClient contentUnderstandingClient; @@ -70,4 +81,74 @@ protected void beforeTest() { interceptorManager.removeSanitizers(REMOVE_SANITIZER_ID); } } + + /** + * Uploads local training data files (images, .labels.json, .result.json) to an + * Azure Blob container. Existing blobs with the same name are overwritten. + * + * @param storageAccountName Storage account name. + * @param containerName Container name (created if it does not exist). + * @param credential Credential with write access to the container. + * @param localDirectory Local folder containing the label files. + * @param prefix Optional blob prefix (virtual folder) to prepend, e.g. "receipt_labels/". + */ + protected static void uploadTrainingData(String storageAccountName, String containerName, + TokenCredential credential, String localDirectory, String prefix) { + BlobContainerClient containerClient + = new BlobServiceClientBuilder().endpoint("https://" + storageAccountName + ".blob.core.windows.net") + .credential(credential) + .buildClient() + .getBlobContainerClient(containerName); + + containerClient.createIfNotExists(); + + File dir = new File(localDirectory); + File[] files = dir.listFiles(); + if (files == null) { + return; + } + for (File file : files) { + if (!file.isFile()) { + continue; + } + String blobName = (prefix == null || prefix.trim().isEmpty()) + ? file.getName() + : prefix.replaceAll("/+$", "") + "/" + file.getName(); + + System.out.println("Uploading " + file.getName() + " -> " + blobName); + BlobClient blobClient = containerClient.getBlobClient(blobName); + blobClient.uploadFromFile(file.getAbsolutePath(), true); + } + } + + /** + * Generates a User Delegation SAS URL (Read + List) for an Azure Blob container. + * Uses {@link TokenCredential} so no storage account key is needed. + * + * @param storageAccountName Storage account name. + * @param containerName Container name. + * @param credential Credential with permissions to generate user delegation key. + * @return SAS URL for the container. + */ + protected static String generateUserDelegationSasUrl(String storageAccountName, String containerName, + TokenCredential credential) { + BlobServiceClient blobServiceClient + = new BlobServiceClientBuilder().endpoint("https://" + storageAccountName + ".blob.core.windows.net") + .credential(credential) + .buildClient(); + + UserDelegationKey userDelegationKey + = blobServiceClient.getUserDelegationKey(OffsetDateTime.now(), OffsetDateTime.now().plusHours(1)); + + BlobContainerSasPermission permissions + = new BlobContainerSasPermission().setReadPermission(true).setListPermission(true); + + BlobServiceSasSignatureValues sasValues + = new BlobServiceSasSignatureValues(OffsetDateTime.now().plusHours(1), permissions); + + String sasToken = blobServiceClient.getBlobContainerClient(containerName) + .generateUserDelegationSas(sasValues, userDelegationKey); + + return "https://" + storageAccountName + ".blob.core.windows.net/" + containerName + "?" + sasToken; + } } diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample00_UpdateDefaults.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample00_UpdateDefaults.java new file mode 100644 index 000000000000..9f427dcd9c3f --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample00_UpdateDefaults.java @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.ContentUnderstandingDefaults; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Test class demonstrating how to configure and manage default settings for Content Understanding service. + * This test shows: + * 1. Getting current default configuration + * 2. Updating default configuration with model deployments + * 3. Verifying the updated configuration + */ +public class Sample00_UpdateDefaults extends ContentUnderstandingClientTestBase { + + @Test + public void testUpdateDefaults() { + // BEGIN:ContentUnderstandingGetDefaults + // Step 1: Get current defaults to see what's configured + System.out.println("Getting current default configuration..."); + ContentUnderstandingDefaults currentDefaults = contentUnderstandingClient.getDefaults(); + System.out.println("Current defaults retrieved successfully."); + System.out.println("Current model deployments: " + currentDefaults.getModelDeployments()); + // END:ContentUnderstandingGetDefaults + + // BEGIN:Assertion_ContentUnderstandingGetDefaults + assertNotNull(currentDefaults, "Current defaults should not be null"); + assertNotNull(currentDefaults.getModelDeployments(), "Model deployments should not be null"); + // END:Assertion_ContentUnderstandingGetDefaults + + // Step 2: Configure model deployments from environment variables + // These map model names to your deployed model names in Azure AI Foundry + System.out.println("\nConfiguring model deployments from environment variables..."); + + // Get deployment names from environment variables (with defaults) + String gpt41Deployment = getEnvOrDefault("GPT_4_1_DEPLOYMENT", "gpt-4.1"); + String gpt41MiniDeployment = getEnvOrDefault("GPT_4_1_MINI_DEPLOYMENT", "gpt-4.1-mini"); + String textEmbedding3LargeDeployment + = getEnvOrDefault("TEXT_EMBEDDING_3_LARGE_DEPLOYMENT", "text-embedding-3-large"); + + // Create model deployments map + Map modelDeployments = new HashMap<>(); + modelDeployments.put("gpt-4.1", gpt41Deployment); + modelDeployments.put("gpt-4.1-mini", gpt41MiniDeployment); + modelDeployments.put("text-embedding-3-large", textEmbedding3LargeDeployment); + + System.out.println("Model deployments to configure:"); + System.out.println(" gpt-4.1 -> " + gpt41Deployment); + System.out.println(" gpt-4.1-mini -> " + gpt41MiniDeployment); + System.out.println(" text-embedding-3-large -> " + textEmbedding3LargeDeployment); + + // BEGIN:ContentUnderstandingUpdateDefaults + // Step 3: Update defaults with the new configuration + System.out.println("\nUpdating default configuration..."); + + // Update defaults with the configuration using the typed convenience method + ContentUnderstandingDefaults updatedConfig = contentUnderstandingClient.updateDefaults(modelDeployments); + System.out.println("Defaults updated successfully."); + System.out.println("Updated model deployments: " + updatedConfig.getModelDeployments()); + // END:ContentUnderstandingUpdateDefaults + + // BEGIN:Assertion_ContentUnderstandingUpdateDefaults + assertNotNull(updatedConfig, "Updated config should not be null"); + assertNotNull(updatedConfig.getModelDeployments(), "Updated model deployments should not be null"); + assertFalse(updatedConfig.getModelDeployments().isEmpty(), "Updated model deployments should not be empty"); + // END:Assertion_ContentUnderstandingUpdateDefaults + + // BEGIN:ContentUnderstandingVerifyDefaults + // Step 4: Verify the updated configuration + System.out.println("\nVerifying updated configuration..."); + ContentUnderstandingDefaults updatedDefaults = contentUnderstandingClient.getDefaults(); + System.out.println("Updated defaults verified successfully."); + System.out.println("Updated model deployments: " + updatedDefaults.getModelDeployments()); + // END:ContentUnderstandingVerifyDefaults + + // BEGIN:Assertion_ContentUnderstandingVerifyDefaults + assertNotNull(updatedDefaults, "Verified defaults should not be null"); + assertNotNull(updatedDefaults.getModelDeployments(), "Verified model deployments should not be null"); + assertFalse(updatedDefaults.getModelDeployments().isEmpty(), "Verified model deployments should not be empty"); + + // Verify the model deployments contain the expected keys + assertTrue(updatedDefaults.getModelDeployments().containsKey("gpt-4.1"), + "Model deployments should contain gpt-4.1"); + assertTrue(updatedDefaults.getModelDeployments().containsKey("gpt-4.1-mini"), + "Model deployments should contain gpt-4.1-mini"); + assertTrue(updatedDefaults.getModelDeployments().containsKey("text-embedding-3-large"), + "Model deployments should contain text-embedding-3-large"); + + // Verify the values match what we set + assertEquals(gpt41Deployment, updatedDefaults.getModelDeployments().get("gpt-4.1"), + "gpt-4.1 deployment should match configured value"); + assertEquals(gpt41MiniDeployment, updatedDefaults.getModelDeployments().get("gpt-4.1-mini"), + "gpt-4.1-mini deployment should match configured value"); + assertEquals(textEmbedding3LargeDeployment, updatedDefaults.getModelDeployments().get("text-embedding-3-large"), + "text-embedding-3-large deployment should match configured value"); + // END:Assertion_ContentUnderstandingVerifyDefaults + + System.out.println("\nConfiguration management completed."); + } + + /** + * Gets an environment variable value or returns a default value if not set. + * + * @param envVar the environment variable name + * @param defaultValue the default value to return if the environment variable is not set + * @return the environment variable value or the default value + */ + private static String getEnvOrDefault(String envVar, String defaultValue) { + String value = System.getenv(envVar); + return (value != null && !value.trim().isEmpty()) ? value : defaultValue; + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample00_UpdateDefaultsAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample00_UpdateDefaultsAsync.java new file mode 100644 index 000000000000..9e2862d90fde --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample00_UpdateDefaultsAsync.java @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.ContentUnderstandingDefaults; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Async test class demonstrating how to configure and manage default settings for Content Understanding service. + * This test shows: + * 1. Getting current default configuration asynchronously + * 2. Updating default configuration with model deployments asynchronously + * 3. Verifying the updated configuration + */ +public class Sample00_UpdateDefaultsAsync extends ContentUnderstandingClientTestBase { + + @Test + public void testUpdateDefaultsAsync() { + // BEGIN:ContentUnderstandingGetDefaultsAsync + // Step 1: Get current defaults to see what's configured + System.out.println("Getting current default configuration..."); + ContentUnderstandingDefaults currentDefaults = contentUnderstandingAsyncClient.getDefaults().block(); + System.out.println("Current defaults retrieved successfully."); + System.out.println("Current model deployments: " + currentDefaults.getModelDeployments()); + // END:ContentUnderstandingGetDefaultsAsync + + // BEGIN:Assertion_ContentUnderstandingGetDefaultsAsync + assertNotNull(currentDefaults, "Current defaults should not be null"); + assertNotNull(currentDefaults.getModelDeployments(), "Model deployments should not be null"); + // END:Assertion_ContentUnderstandingGetDefaultsAsync + + // Step 2: Configure model deployments from environment variables + // These map model names to your deployed model names in Azure AI Foundry + System.out.println("\nConfiguring model deployments from environment variables..."); + + // Get deployment names from environment variables (with defaults) + String gpt41Deployment = getEnvOrDefault("GPT_4_1_DEPLOYMENT", "gpt-4.1"); + String gpt41MiniDeployment = getEnvOrDefault("GPT_4_1_MINI_DEPLOYMENT", "gpt-4.1-mini"); + String textEmbedding3LargeDeployment + = getEnvOrDefault("TEXT_EMBEDDING_3_LARGE_DEPLOYMENT", "text-embedding-3-large"); + + // Create model deployments map + Map modelDeployments = new HashMap<>(); + modelDeployments.put("gpt-4.1", gpt41Deployment); + modelDeployments.put("gpt-4.1-mini", gpt41MiniDeployment); + modelDeployments.put("text-embedding-3-large", textEmbedding3LargeDeployment); + + System.out.println("Model deployments to configure:"); + System.out.println(" gpt-4.1 -> " + gpt41Deployment); + System.out.println(" gpt-4.1-mini -> " + gpt41MiniDeployment); + System.out.println(" text-embedding-3-large -> " + textEmbedding3LargeDeployment); + + // BEGIN:ContentUnderstandingUpdateDefaultsAsync + // Step 3: Update defaults with the new configuration + System.out.println("\nUpdating default configuration..."); + + // Update defaults with the configuration using the typed convenience method + ContentUnderstandingDefaults updatedConfig + = contentUnderstandingAsyncClient.updateDefaults(modelDeployments).block(); + System.out.println("Defaults updated successfully."); + System.out.println("Updated model deployments: " + updatedConfig.getModelDeployments()); + // END:ContentUnderstandingUpdateDefaultsAsync + + // BEGIN:Assertion_ContentUnderstandingUpdateDefaultsAsync + assertNotNull(updatedConfig, "Updated config should not be null"); + assertNotNull(updatedConfig.getModelDeployments(), "Updated model deployments should not be null"); + assertFalse(updatedConfig.getModelDeployments().isEmpty(), "Updated model deployments should not be empty"); + // END:Assertion_ContentUnderstandingUpdateDefaultsAsync + + // BEGIN:ContentUnderstandingVerifyDefaultsAsync + // Step 4: Verify the updated configuration + System.out.println("\nVerifying updated configuration..."); + ContentUnderstandingDefaults updatedDefaults = contentUnderstandingAsyncClient.getDefaults().block(); + System.out.println("Updated defaults verified successfully."); + System.out.println("Updated model deployments: " + updatedDefaults.getModelDeployments()); + // END:ContentUnderstandingVerifyDefaultsAsync + + // BEGIN:Assertion_ContentUnderstandingVerifyDefaultsAsync + assertNotNull(updatedDefaults, "Verified defaults should not be null"); + assertNotNull(updatedDefaults.getModelDeployments(), "Verified model deployments should not be null"); + assertFalse(updatedDefaults.getModelDeployments().isEmpty(), "Verified model deployments should not be empty"); + + // Verify the model deployments contain the expected keys + assertTrue(updatedDefaults.getModelDeployments().containsKey("gpt-4.1"), + "Model deployments should contain gpt-4.1"); + assertTrue(updatedDefaults.getModelDeployments().containsKey("gpt-4.1-mini"), + "Model deployments should contain gpt-4.1-mini"); + assertTrue(updatedDefaults.getModelDeployments().containsKey("text-embedding-3-large"), + "Model deployments should contain text-embedding-3-large"); + + // Verify the values match what we set + assertEquals(gpt41Deployment, updatedDefaults.getModelDeployments().get("gpt-4.1"), + "gpt-4.1 deployment should match configured value"); + assertEquals(gpt41MiniDeployment, updatedDefaults.getModelDeployments().get("gpt-4.1-mini"), + "gpt-4.1-mini deployment should match configured value"); + assertEquals(textEmbedding3LargeDeployment, updatedDefaults.getModelDeployments().get("text-embedding-3-large"), + "text-embedding-3-large deployment should match configured value"); + // END:Assertion_ContentUnderstandingVerifyDefaultsAsync + + System.out.println("\nConfiguration management completed."); + } + + /** + * Gets an environment variable value or returns a default value if not set. + * + * @param envVar the environment variable name + * @param defaultValue the default value to return if the environment variable is not set + * @return the environment variable value or the default value + */ + private static String getEnvOrDefault(String envVar, String defaultValue) { + String value = System.getenv(envVar); + return (value != null && !value.trim().isEmpty()) ? value : defaultValue; + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample01_AnalyzeBinary.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample01_AnalyzeBinary.java new file mode 100644 index 000000000000..7ee5a2fd87ba --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample01_AnalyzeBinary.java @@ -0,0 +1,259 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.AnalysisResult; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.DocumentContent; +import com.azure.ai.contentunderstanding.models.DocumentPage; +import com.azure.ai.contentunderstanding.models.DocumentTable; +import com.azure.ai.contentunderstanding.models.DocumentTableCell; +import com.azure.ai.contentunderstanding.models.AnalysisContent; +import com.azure.core.util.BinaryData; +import com.azure.core.util.polling.SyncPoller; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashSet; +import java.util.Set; + +/** + * Sample demonstrating how to analyze binary documents using Content Understanding service. + * This sample shows: + * 1. Loading a binary file (PDF) + * 2. Analyzing the document + * 3. Extracting markdown content + * 4. Accessing document properties (pages, tables, etc.) + */ +public class Sample01_AnalyzeBinary extends ContentUnderstandingClientTestBase { + + @Test + public void testAnalyzeBinary() throws IOException { + + // Load the sample file + String filePath = "src/samples/resources/sample_invoice.pdf"; + Path path = Paths.get(filePath); + + byte[] fileBytes; + BinaryData binaryData; + boolean hasRealFile = Files.exists(path); + + // Check if sample file exists + fileBytes = Files.readAllBytes(path); + binaryData = BinaryData.fromBytes(fileBytes); + + // BEGIN:ContentUnderstandingAnalyzeBinary + // Use the simplified beginAnalyzeBinary overload - contentType defaults to "application/octet-stream" + // For PDFs, you can also explicitly specify "application/pdf" using the full method signature + SyncPoller operation + = contentUnderstandingClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData); + + AnalysisResult result = operation.getFinalResult(); + // END:ContentUnderstandingAnalyzeBinary + + // BEGIN:Assertion_ContentUnderstandingAnalyzeBinary + if (hasRealFile) { + assertTrue(Files.exists(path), "Sample file not found at " + filePath); + } + assertTrue(fileBytes.length > 0, "File should not be empty"); + assertNotNull(binaryData, "Binary data should not be null"); + assertNotNull(operation, "Analysis operation should not be null"); + assertTrue(operation.waitForCompletion().getStatus().isComplete(), "Operation should be completed"); + System.out.println("Analysis operation properties verified"); + + assertNotNull(result, "Analysis result should not be null"); + assertNotNull(result.getContents(), "Result contents should not be null"); + System.out.println("Analysis result contains " + + (result.getContents() != null ? result.getContents().size() : 0) + " content(s)"); + // END:Assertion_ContentUnderstandingAnalyzeBinary + + // BEGIN:ContentUnderstandingExtractMarkdown + // A PDF file has only one content element even if it contains multiple pages + AnalysisContent content = null; + if (result.getContents() == null || result.getContents().isEmpty()) { + System.out.println("(No content returned from analysis)"); + } else { + content = result.getContents().get(0); + if (content.getMarkdown() != null && !content.getMarkdown().isEmpty()) { + System.out.println(content.getMarkdown()); + } else { + System.out.println("(No markdown content available)"); + } + } + // END:ContentUnderstandingExtractMarkdown + + // BEGIN:Assertion_ContentUnderstandingExtractMarkdown + assertNotNull(result.getContents(), "Result should contain contents"); + assertTrue(result.getContents().size() > 0, "Result should have at least one content"); + assertEquals(1, result.getContents().size(), "PDF file should have exactly one content element"); + assertNotNull(content, "Content should not be null"); + assertTrue(content instanceof AnalysisContent, "Content should be of type AnalysisContent"); + + // Only validate markdown content if we have a real file + if (hasRealFile && content.getMarkdown() != null && !content.getMarkdown().isEmpty()) { + assertFalse(content.getMarkdown().trim().isEmpty(), "Markdown content should not be just whitespace"); + System.out + .println("Markdown content extracted successfully (" + content.getMarkdown().length() + " characters)"); + } else { + System.out + .println("⚠️ Skipping markdown content validation (using minimal test PDF or no markdown available)"); + } + // END:Assertion_ContentUnderstandingExtractMarkdown + + // BEGIN:ContentUnderstandingAccessDocumentProperties + // Check if this is document content to access document-specific properties + if (content instanceof DocumentContent) { + DocumentContent documentContent = (DocumentContent) content; + System.out.println("Document type: " + + (documentContent.getMimeType() != null ? documentContent.getMimeType() : "(unknown)")); + System.out.println("Start page: " + documentContent.getStartPageNumber()); + System.out.println("End page: " + documentContent.getEndPageNumber()); + System.out.println( + "Total pages: " + (documentContent.getEndPageNumber() - documentContent.getStartPageNumber() + 1)); + + // Check for pages + if (documentContent.getPages() != null && !documentContent.getPages().isEmpty()) { + System.out.println("Number of pages: " + documentContent.getPages().size()); + for (DocumentPage page : documentContent.getPages()) { + String unit = documentContent.getUnit() != null ? documentContent.getUnit().toString() : "units"; + System.out.println(" Page " + page.getPageNumber() + ": " + page.getWidth() + " x " + + page.getHeight() + " " + unit); + } + } + + // Check for tables + if (documentContent.getTables() != null && !documentContent.getTables().isEmpty()) { + System.out.println("Number of tables: " + documentContent.getTables().size()); + int tableCounter = 1; + for (DocumentTable table : documentContent.getTables()) { + System.out.println(" Table " + tableCounter + ": " + table.getRowCount() + " rows x " + + table.getColumnCount() + " columns"); + tableCounter++; + } + } + } else { + // Content is not DocumentContent - verify it's AnalysisContent + assertTrue(content instanceof AnalysisContent, + "Content should be AnalysisContent when not DocumentContent"); + System.out.println("Content is AnalysisContent (not document-specific), skipping document properties"); + } + // END:ContentUnderstandingAccessDocumentProperties + + // BEGIN:Assertion_ContentUnderstandingAccessDocumentProperties + assertNotNull(content, "Content should not be null for document properties validation"); + + if (content instanceof DocumentContent) { + DocumentContent docContent = (DocumentContent) content; + + // Validate MIME type + assertNotNull(docContent.getMimeType(), "MIME type should not be null"); + assertFalse(docContent.getMimeType().trim().isEmpty(), "MIME type should not be empty"); + assertEquals("application/pdf", docContent.getMimeType(), "MIME type should be application/pdf"); + System.out.println("MIME type verified: " + docContent.getMimeType()); + + // Validate page numbers + assertTrue(docContent.getStartPageNumber() >= 1, "Start page should be >= 1"); + assertTrue(docContent.getEndPageNumber() >= docContent.getStartPageNumber(), + "End page should be >= start page"); + int totalPages = docContent.getEndPageNumber() - docContent.getStartPageNumber() + 1; + assertTrue(totalPages > 0, "Total pages should be positive"); + System.out.println("Page range verified: " + docContent.getStartPageNumber() + " to " + + docContent.getEndPageNumber() + " (" + totalPages + " pages)"); + + // Validate pages collection + if (docContent.getPages() != null && !docContent.getPages().isEmpty()) { + assertTrue(docContent.getPages().size() > 0, "Pages collection should not be empty when not null"); + assertEquals(totalPages, docContent.getPages().size(), + "Pages collection count should match calculated total pages"); + System.out.println("Pages collection verified: " + docContent.getPages().size() + " pages"); + + // Track page numbers to ensure they're sequential and unique + Set pageNumbers = new HashSet<>(); + + for (DocumentPage page : docContent.getPages()) { + assertNotNull(page, "Page object should not be null"); + assertTrue(page.getPageNumber() >= 1, "Page number should be >= 1"); + assertTrue( + page.getPageNumber() >= docContent.getStartPageNumber() + && page.getPageNumber() <= docContent.getEndPageNumber(), + "Page number " + page.getPageNumber() + " should be within document range [" + + docContent.getStartPageNumber() + ", " + docContent.getEndPageNumber() + "]"); + assertTrue(page.getWidth() > 0, + "Page " + page.getPageNumber() + " width should be > 0, but was " + page.getWidth()); + assertTrue(page.getHeight() > 0, + "Page " + page.getPageNumber() + " height should be > 0, but was " + page.getHeight()); + + // Ensure page numbers are unique + assertTrue(pageNumbers.add(page.getPageNumber()), + "Page number " + page.getPageNumber() + " appears multiple times"); + + String unit = docContent.getUnit() != null ? docContent.getUnit().toString() : "units"; + System.out.println(" Page " + page.getPageNumber() + ": " + page.getWidth() + " x " + + page.getHeight() + " " + unit); + } + } else { + System.out.println("⚠️ No pages collection available in document content"); + } + + // Validate tables collection + if (docContent.getTables() != null && !docContent.getTables().isEmpty()) { + assertTrue(docContent.getTables().size() > 0, "Tables collection should not be empty when not null"); + System.out.println("Tables collection verified: " + docContent.getTables().size() + " tables"); + + int tableCounter = 1; + for (DocumentTable table : docContent.getTables()) { + assertNotNull(table, "Table " + tableCounter + " should not be null"); + assertTrue(table.getRowCount() > 0, + "Table " + tableCounter + " should have at least 1 row, but had " + table.getRowCount()); + assertTrue(table.getColumnCount() > 0, + "Table " + tableCounter + " should have at least 1 column, but had " + table.getColumnCount()); + + // Validate table cells if available + if (table.getCells() != null) { + assertTrue(table.getCells().size() > 0, + "Table " + tableCounter + " cells collection should not be empty when not null"); + + for (DocumentTableCell cell : table.getCells()) { + assertNotNull(cell, "Table cell should not be null"); + assertTrue(cell.getRowIndex() >= 0 && cell.getRowIndex() < table.getRowCount(), + "Cell row index " + cell.getRowIndex() + " should be within table row count " + + table.getRowCount()); + assertTrue(cell.getColumnIndex() >= 0 && cell.getColumnIndex() < table.getColumnCount(), + "Cell column index " + cell.getColumnIndex() + " should be within table column count " + + table.getColumnCount()); + assertTrue(cell.getRowSpan() >= 1, + "Cell row span should be >= 1, but was " + cell.getRowSpan()); + assertTrue(cell.getColumnSpan() >= 1, + "Cell column span should be >= 1, but was " + cell.getColumnSpan()); + } + } + + System.out.println(" Table " + tableCounter + ": " + table.getRowCount() + " rows x " + + table.getColumnCount() + " columns" + + (table.getCells() != null ? " (" + table.getCells().size() + " cells)" : "")); + tableCounter++; + } + } else { + System.out.println("No tables found in document content"); + } + + System.out.println("All document properties validated successfully"); + } else { + // Content is not DocumentContent - validate alternative types + assertTrue(content instanceof AnalysisContent, + "Content should be AnalysisContent when not DocumentContent, but got " + + (content != null ? content.getClass().getSimpleName() : "null")); + System.out.println("Content is not DocumentContent type, skipping document-specific validations"); + System.out + .println("⚠️ Content type: " + content.getClass().getSimpleName() + " (AnalysisContent validated)"); + } + // END:Assertion_ContentUnderstandingAccessDocumentProperties + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample01_AnalyzeBinaryAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample01_AnalyzeBinaryAsync.java new file mode 100644 index 000000000000..b699e8253b58 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample01_AnalyzeBinaryAsync.java @@ -0,0 +1,268 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.AnalysisResult; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.DocumentContent; +import com.azure.ai.contentunderstanding.models.DocumentPage; +import com.azure.ai.contentunderstanding.models.DocumentTable; +import com.azure.ai.contentunderstanding.models.DocumentTableCell; +import com.azure.ai.contentunderstanding.models.AnalysisContent; +import com.azure.core.util.BinaryData; +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.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashSet; +import java.util.Set; + +/** + * Async sample demonstrating how to analyze binary documents using Content Understanding service. + * This sample shows: + * 1. Loading a binary file (PDF) + * 2. Analyzing the document asynchronously + * 3. Extracting markdown content + * 4. Accessing document properties (pages, tables, etc.) + */ +public class Sample01_AnalyzeBinaryAsync extends ContentUnderstandingClientTestBase { + + @Test + public void testAnalyzeBinaryAsync() throws IOException { + + // Load the sample file + String filePath = "src/samples/resources/sample_invoice.pdf"; + Path path = Paths.get(filePath); + + byte[] fileBytes; + BinaryData binaryData; + boolean hasRealFile = Files.exists(path); + + // Check if sample file exists + fileBytes = Files.readAllBytes(path); + binaryData = BinaryData.fromBytes(fileBytes); + + // BEGIN:ContentUnderstandingAnalyzeBinaryAsync + // Use the simplified beginAnalyzeBinary overload - contentType defaults to "application/octet-stream" + // For PDFs, you can also explicitly specify "application/pdf" using the full method signature + PollerFlux operation + = contentUnderstandingAsyncClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData); + + // Use reactive pattern: chain operations using flatMap + // 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())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + // END:ContentUnderstandingAnalyzeBinaryAsync + + // BEGIN:Assertion_ContentUnderstandingAnalyzeBinaryAsync + if (hasRealFile) { + assertTrue(Files.exists(path), "Sample file not found at " + filePath); + } + assertTrue(fileBytes.length > 0, "File should not be empty"); + assertNotNull(binaryData, "Binary data should not be null"); + assertNotNull(operation, "Analysis operation should not be null"); + System.out.println("Analysis operation properties verified"); + + assertNotNull(result, "Analysis result should not be null"); + assertNotNull(result.getContents(), "Result contents should not be null"); + System.out.println("Analysis result contains " + + (result.getContents() != null ? result.getContents().size() : 0) + " content(s)"); + // END:Assertion_ContentUnderstandingAnalyzeBinaryAsync + + // BEGIN:ContentUnderstandingExtractMarkdown + // A PDF file has only one content element even if it contains multiple pages + AnalysisContent content = null; + if (result.getContents() == null || result.getContents().isEmpty()) { + System.out.println("(No content returned from analysis)"); + } else { + content = result.getContents().get(0); + if (content.getMarkdown() != null && !content.getMarkdown().isEmpty()) { + System.out.println(content.getMarkdown()); + } else { + System.out.println("(No markdown content available)"); + } + } + // END:ContentUnderstandingExtractMarkdown + + // BEGIN:Assertion_ContentUnderstandingExtractMarkdown + assertNotNull(result.getContents(), "Result should contain contents"); + assertTrue(result.getContents().size() > 0, "Result should have at least one content"); + assertEquals(1, result.getContents().size(), "PDF file should have exactly one content element"); + assertNotNull(content, "Content should not be null"); + assertTrue(content instanceof AnalysisContent, "Content should be of type AnalysisContent"); + + // Only validate markdown content if we have a real file + if (hasRealFile && content.getMarkdown() != null && !content.getMarkdown().isEmpty()) { + assertFalse(content.getMarkdown().trim().isEmpty(), "Markdown content should not be just whitespace"); + System.out + .println("Markdown content extracted successfully (" + content.getMarkdown().length() + " characters)"); + } else { + System.out + .println("⚠️ Skipping markdown content validation (using minimal test PDF or no markdown available)"); + } + // END:Assertion_ContentUnderstandingExtractMarkdown + + // BEGIN:ContentUnderstandingAccessDocumentProperties + // Check if this is document content to access document-specific properties + if (content instanceof DocumentContent) { + DocumentContent documentContent = (DocumentContent) content; + System.out.println("Document type: " + + (documentContent.getMimeType() != null ? documentContent.getMimeType() : "(unknown)")); + System.out.println("Start page: " + documentContent.getStartPageNumber()); + System.out.println("End page: " + documentContent.getEndPageNumber()); + System.out.println( + "Total pages: " + (documentContent.getEndPageNumber() - documentContent.getStartPageNumber() + 1)); + + // Check for pages + if (documentContent.getPages() != null && !documentContent.getPages().isEmpty()) { + System.out.println("Number of pages: " + documentContent.getPages().size()); + for (DocumentPage page : documentContent.getPages()) { + String unit = documentContent.getUnit() != null ? documentContent.getUnit().toString() : "units"; + System.out.println(" Page " + page.getPageNumber() + ": " + page.getWidth() + " x " + + page.getHeight() + " " + unit); + } + } + + // Check for tables + if (documentContent.getTables() != null && !documentContent.getTables().isEmpty()) { + System.out.println("Number of tables: " + documentContent.getTables().size()); + int tableCounter = 1; + for (DocumentTable table : documentContent.getTables()) { + System.out.println(" Table " + tableCounter + ": " + table.getRowCount() + " rows x " + + table.getColumnCount() + " columns"); + tableCounter++; + } + } + } else { + // Content is not DocumentContent - verify it's AnalysisContent + assertTrue(content instanceof AnalysisContent, + "Content should be AnalysisContent when not DocumentContent"); + System.out.println("Content is AnalysisContent (not document-specific), skipping document properties"); + } + // END:ContentUnderstandingAccessDocumentProperties + + // BEGIN:Assertion_ContentUnderstandingAccessDocumentProperties + assertNotNull(content, "Content should not be null for document properties validation"); + + if (content instanceof DocumentContent) { + DocumentContent docContent = (DocumentContent) content; + + // Validate MIME type + assertNotNull(docContent.getMimeType(), "MIME type should not be null"); + assertFalse(docContent.getMimeType().trim().isEmpty(), "MIME type should not be empty"); + assertEquals("application/pdf", docContent.getMimeType(), "MIME type should be application/pdf"); + System.out.println("MIME type verified: " + docContent.getMimeType()); + + // Validate page numbers + assertTrue(docContent.getStartPageNumber() >= 1, "Start page should be >= 1"); + assertTrue(docContent.getEndPageNumber() >= docContent.getStartPageNumber(), + "End page should be >= start page"); + int totalPages = docContent.getEndPageNumber() - docContent.getStartPageNumber() + 1; + assertTrue(totalPages > 0, "Total pages should be positive"); + System.out.println("Page range verified: " + docContent.getStartPageNumber() + " to " + + docContent.getEndPageNumber() + " (" + totalPages + " pages)"); + + // Validate pages collection + if (docContent.getPages() != null && !docContent.getPages().isEmpty()) { + assertTrue(docContent.getPages().size() > 0, "Pages collection should not be empty when not null"); + assertEquals(totalPages, docContent.getPages().size(), + "Pages collection count should match calculated total pages"); + System.out.println("Pages collection verified: " + docContent.getPages().size() + " pages"); + + // Track page numbers to ensure they're sequential and unique + Set pageNumbers = new HashSet<>(); + + for (DocumentPage page : docContent.getPages()) { + assertNotNull(page, "Page object should not be null"); + assertTrue(page.getPageNumber() >= 1, "Page number should be >= 1"); + assertTrue( + page.getPageNumber() >= docContent.getStartPageNumber() + && page.getPageNumber() <= docContent.getEndPageNumber(), + "Page number " + page.getPageNumber() + " should be within document range [" + + docContent.getStartPageNumber() + ", " + docContent.getEndPageNumber() + "]"); + assertTrue(page.getWidth() > 0, + "Page " + page.getPageNumber() + " width should be > 0, but was " + page.getWidth()); + assertTrue(page.getHeight() > 0, + "Page " + page.getPageNumber() + " height should be > 0, but was " + page.getHeight()); + + // Ensure page numbers are unique + assertTrue(pageNumbers.add(page.getPageNumber()), + "Page number " + page.getPageNumber() + " appears multiple times"); + + String unit = docContent.getUnit() != null ? docContent.getUnit().toString() : "units"; + System.out.println(" Page " + page.getPageNumber() + ": " + page.getWidth() + " x " + + page.getHeight() + " " + unit); + } + } else { + System.out.println("⚠️ No pages collection available in document content"); + } + + // Validate tables collection + if (docContent.getTables() != null && !docContent.getTables().isEmpty()) { + assertTrue(docContent.getTables().size() > 0, "Tables collection should not be empty when not null"); + System.out.println("Tables collection verified: " + docContent.getTables().size() + " tables"); + + int tableCounter = 1; + for (DocumentTable table : docContent.getTables()) { + assertNotNull(table, "Table " + tableCounter + " should not be null"); + assertTrue(table.getRowCount() > 0, + "Table " + tableCounter + " should have at least 1 row, but had " + table.getRowCount()); + assertTrue(table.getColumnCount() > 0, + "Table " + tableCounter + " should have at least 1 column, but had " + table.getColumnCount()); + + // Validate table cells if available + if (table.getCells() != null) { + assertTrue(table.getCells().size() > 0, + "Table " + tableCounter + " cells collection should not be empty when not null"); + + for (DocumentTableCell cell : table.getCells()) { + assertNotNull(cell, "Table cell should not be null"); + assertTrue(cell.getRowIndex() >= 0 && cell.getRowIndex() < table.getRowCount(), + "Cell row index " + cell.getRowIndex() + " should be within table row count " + + table.getRowCount()); + assertTrue(cell.getColumnIndex() >= 0 && cell.getColumnIndex() < table.getColumnCount(), + "Cell column index " + cell.getColumnIndex() + " should be within table column count " + + table.getColumnCount()); + assertTrue(cell.getRowSpan() >= 1, + "Cell row span should be >= 1, but was " + cell.getRowSpan()); + assertTrue(cell.getColumnSpan() >= 1, + "Cell column span should be >= 1, but was " + cell.getColumnSpan()); + } + } + + System.out.println(" Table " + tableCounter + ": " + table.getRowCount() + " rows x " + + table.getColumnCount() + " columns" + + (table.getCells() != null ? " (" + table.getCells().size() + " cells)" : "")); + tableCounter++; + } + } else { + System.out.println("No tables found in document content"); + } + + System.out.println("All document properties validated successfully"); + } else { + // Content is not DocumentContent - validate alternative types + assertTrue(content instanceof AnalysisContent, + "Content should be AnalysisContent when not DocumentContent, but got " + + (content != null ? content.getClass().getSimpleName() : "null")); + System.out.println("Content is not DocumentContent type, skipping document-specific validations"); + System.out + .println("⚠️ Content type: " + content.getClass().getSimpleName() + " (AnalysisContent validated)"); + } + // END:Assertion_ContentUnderstandingAccessDocumentProperties + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrl.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrl.java new file mode 100644 index 000000000000..d5d7eea5c710 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrl.java @@ -0,0 +1,407 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.AnalysisInput; +import com.azure.ai.contentunderstanding.models.AnalysisResult; +import com.azure.ai.contentunderstanding.models.AudioVisualContent; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.DocumentContent; +import com.azure.ai.contentunderstanding.models.DocumentPage; +import com.azure.ai.contentunderstanding.models.DocumentTable; +import com.azure.ai.contentunderstanding.models.DocumentTableCell; +import com.azure.ai.contentunderstanding.models.AnalysisContent; +import com.azure.ai.contentunderstanding.models.TranscriptPhrase; +import com.azure.core.util.polling.SyncPoller; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Sample demonstrating how to analyze documents from URL using Content Understanding service. + * This sample shows: + * 1. Providing a URL to a document + * 2. Analyzing the document + * 3. Extracting markdown content + * 4. Accessing document properties (pages, tables, etc.) + */ +public class Sample02_AnalyzeUrl extends ContentUnderstandingClientTestBase { + + @Test + public void testAnalyzeUrl() { + + // BEGIN:ContentUnderstandingAnalyzeUrl + // Using a publicly accessible sample file from Azure-Samples GitHub repository + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-dotnet/main/ContentUnderstanding.Common/data/invoice.pdf"; + + AnalysisInput input = new AnalysisInput(); + input.setUrl(uriSource); + + SyncPoller operation + = contentUnderstandingClient.beginAnalyze("prebuilt-documentSearch", Arrays.asList(input)); + + AnalysisResult result = operation.getFinalResult(); + // END:ContentUnderstandingAnalyzeUrl + + // BEGIN:Assertion_ContentUnderstandingAnalyzeUrl + assertNotNull(uriSource, "URI source should not be null"); + assertNotNull(operation, "Analysis operation should not be null"); + assertTrue(operation.waitForCompletion().getStatus().isComplete(), "Operation should be completed"); + System.out.println("Analysis operation properties verified"); + + assertNotNull(result, "Analysis result should not be null"); + assertNotNull(result.getContents(), "Result contents should not be null"); + System.out.println("Analysis result contains " + + (result.getContents() != null ? result.getContents().size() : 0) + " content(s)"); + // END:Assertion_ContentUnderstandingAnalyzeUrl + + // A PDF file has only one content element even if it contains multiple pages + AnalysisContent content = null; + if (result.getContents() == null || result.getContents().isEmpty()) { + System.out.println("(No content returned from analysis)"); + } else { + content = result.getContents().get(0); + if (content.getMarkdown() != null && !content.getMarkdown().isEmpty()) { + System.out.println(content.getMarkdown()); + } else { + System.out.println("(No markdown content available)"); + } + } + + assertNotNull(result.getContents(), "Result should contain contents"); + assertTrue(result.getContents().size() > 0, "Result should have at least one content"); + assertEquals(1, result.getContents().size(), "PDF file should have exactly one content element"); + assertNotNull(content, "Content should not be null"); + assertTrue(content instanceof AnalysisContent, "Content should be of type AnalysisContent"); + + if (content.getMarkdown() != null && !content.getMarkdown().isEmpty()) { + assertFalse(content.getMarkdown().trim().isEmpty(), "Markdown content should not be just whitespace"); + System.out + .println("Markdown content extracted successfully (" + content.getMarkdown().length() + " characters)"); + } + + // Check if this is document content to access document-specific properties + if (content instanceof DocumentContent) { + DocumentContent documentContent = (DocumentContent) content; + System.out.println("Document type: " + + (documentContent.getMimeType() != null ? documentContent.getMimeType() : "(unknown)")); + System.out.println("Start page: " + documentContent.getStartPageNumber()); + System.out.println("End page: " + documentContent.getEndPageNumber()); + System.out.println( + "Total pages: " + (documentContent.getEndPageNumber() - documentContent.getStartPageNumber() + 1)); + + // Check for pages + if (documentContent.getPages() != null && !documentContent.getPages().isEmpty()) { + System.out.println("Number of pages: " + documentContent.getPages().size()); + for (DocumentPage page : documentContent.getPages()) { + String unit = documentContent.getUnit() != null ? documentContent.getUnit().toString() : "units"; + System.out.println(" Page " + page.getPageNumber() + ": " + page.getWidth() + " x " + + page.getHeight() + " " + unit); + } + } + + // Check for tables + if (documentContent.getTables() != null && !documentContent.getTables().isEmpty()) { + System.out.println("Number of tables: " + documentContent.getTables().size()); + int tableCounter = 1; + for (DocumentTable table : documentContent.getTables()) { + System.out.println(" Table " + tableCounter + ": " + table.getRowCount() + " rows x " + + table.getColumnCount() + " columns"); + tableCounter++; + } + } + } else { + // Content is not DocumentContent - verify it's AnalysisContent + assertTrue(content instanceof AnalysisContent, + "Content should be AnalysisContent when not DocumentContent"); + System.out.println("Content is AnalysisContent (not document-specific), skipping document properties"); + } + + assertNotNull(content, "Content should not be null for document properties validation"); + + if (content instanceof DocumentContent) { + DocumentContent docContent = (DocumentContent) content; + + // Validate MIME type + assertNotNull(docContent.getMimeType(), "MIME type should not be null"); + assertFalse(docContent.getMimeType().trim().isEmpty(), "MIME type should not be empty"); + assertEquals("application/pdf", docContent.getMimeType(), "MIME type should be application/pdf"); + System.out.println("MIME type verified: " + docContent.getMimeType()); + + // Validate page numbers + assertTrue(docContent.getStartPageNumber() >= 1, "Start page should be >= 1"); + assertTrue(docContent.getEndPageNumber() >= docContent.getStartPageNumber(), + "End page should be >= start page"); + int totalPages = docContent.getEndPageNumber() - docContent.getStartPageNumber() + 1; + assertTrue(totalPages > 0, "Total pages should be positive"); + System.out.println("Page range verified: " + docContent.getStartPageNumber() + " to " + + docContent.getEndPageNumber() + " (" + totalPages + " pages)"); + + // Validate pages collection + if (docContent.getPages() != null && !docContent.getPages().isEmpty()) { + assertTrue(docContent.getPages().size() > 0, "Pages collection should not be empty when not null"); + assertEquals(totalPages, docContent.getPages().size(), + "Pages collection count should match calculated total pages"); + System.out.println("Pages collection verified: " + docContent.getPages().size() + " pages"); + + // Track page numbers to ensure they're sequential and unique + Set pageNumbers = new HashSet<>(); + + for (DocumentPage page : docContent.getPages()) { + assertNotNull(page, "Page object should not be null"); + assertTrue(page.getPageNumber() >= 1, "Page number should be >= 1"); + assertTrue( + page.getPageNumber() >= docContent.getStartPageNumber() + && page.getPageNumber() <= docContent.getEndPageNumber(), + "Page number " + page.getPageNumber() + " should be within document range [" + + docContent.getStartPageNumber() + ", " + docContent.getEndPageNumber() + "]"); + assertTrue(page.getWidth() > 0, + "Page " + page.getPageNumber() + " width should be > 0, but was " + page.getWidth()); + assertTrue(page.getHeight() > 0, + "Page " + page.getPageNumber() + " height should be > 0, but was " + page.getHeight()); + + // Ensure page numbers are unique + assertTrue(pageNumbers.add(page.getPageNumber()), + "Page number " + page.getPageNumber() + " appears multiple times"); + + String unit = docContent.getUnit() != null ? docContent.getUnit().toString() : "units"; + System.out.println(" Page " + page.getPageNumber() + ": " + page.getWidth() + " x " + + page.getHeight() + " " + unit); + } + } else { + System.out.println("⚠️ No pages collection available in document content"); + } + + // Validate tables collection + if (docContent.getTables() != null && !docContent.getTables().isEmpty()) { + assertTrue(docContent.getTables().size() > 0, "Tables collection should not be empty when not null"); + System.out.println("Tables collection verified: " + docContent.getTables().size() + " tables"); + + int tableCounter = 1; + for (DocumentTable table : docContent.getTables()) { + assertNotNull(table, "Table " + tableCounter + " should not be null"); + assertTrue(table.getRowCount() > 0, + "Table " + tableCounter + " should have at least 1 row, but had " + table.getRowCount()); + assertTrue(table.getColumnCount() > 0, + "Table " + tableCounter + " should have at least 1 column, but had " + table.getColumnCount()); + + // Validate table cells if available + if (table.getCells() != null) { + assertTrue(table.getCells().size() > 0, + "Table " + tableCounter + " cells collection should not be empty when not null"); + + for (DocumentTableCell cell : table.getCells()) { + assertNotNull(cell, "Table cell should not be null"); + assertTrue(cell.getRowIndex() >= 0 && cell.getRowIndex() < table.getRowCount(), + "Cell row index " + cell.getRowIndex() + " should be within table row count " + + table.getRowCount()); + assertTrue(cell.getColumnIndex() >= 0 && cell.getColumnIndex() < table.getColumnCount(), + "Cell column index " + cell.getColumnIndex() + " should be within table column count " + + table.getColumnCount()); + } + } + + System.out.println(" Table " + tableCounter + ": " + table.getRowCount() + " rows x " + + table.getColumnCount() + " columns" + + (table.getCells() != null ? " (" + table.getCells().size() + " cells)" : "")); + tableCounter++; + } + } else { + System.out.println("⚠️ No tables found in document content"); + } + + System.out.println("All document properties validated successfully"); + } else { + // Content is not DocumentContent - validate alternative types + assertTrue(content instanceof AnalysisContent, + "Content should be AnalysisContent when not DocumentContent, but got " + + (content != null ? content.getClass().getSimpleName() : "null")); + System.out.println("⚠️ Content is not DocumentContent type, skipping document-specific validations"); + System.out + .println("⚠️ Content type: " + content.getClass().getSimpleName() + " (AnalysisContent validated)"); + } + } + + @Test + public void testAnalyzeVideoUrl() { + // BEGIN:ContentUnderstandingAnalyzeVideoUrl + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/videos/sdk_samples/FlightSimulator.mp4"; + + AnalysisInput input = new AnalysisInput(); + input.setUrl(uriSource); + + SyncPoller operation + = contentUnderstandingClient.beginAnalyze("prebuilt-videoSearch", Arrays.asList(input)); + + AnalysisResult result = operation.getFinalResult(); + + // prebuilt-videoSearch can detect video segments, so we should iterate through all segments + int segmentIndex = 1; + for (AnalysisContent media : result.getContents()) { + // Cast AnalysisContent to AudioVisualContent to access audio/visual-specific properties + // AudioVisualContent derives from AnalysisContent and provides additional properties + // to access full information about audio/video, including timing, transcript phrases, and many others + AudioVisualContent videoContent = (AudioVisualContent) media; + System.out.println("--- Segment " + segmentIndex + " ---"); + System.out.println("Markdown:"); + System.out.println(videoContent.getMarkdown()); + + String summary = videoContent.getFields() != null && videoContent.getFields().containsKey("Summary") + ? (videoContent.getFields().get("Summary").getValue() != null + ? videoContent.getFields().get("Summary").getValue().toString() + : "") + : ""; + System.out.println("Summary: " + summary); + + System.out.println("Start: " + videoContent.getStartTime().toMillis() + " ms, End: " + + videoContent.getEndTime().toMillis() + " ms"); + System.out.println("Frame size: " + videoContent.getWidth() + " x " + videoContent.getHeight()); + + System.out.println("---------------------"); + segmentIndex++; + } + // END:ContentUnderstandingAnalyzeVideoUrl + + // BEGIN:Assertion_ContentUnderstandingAnalyzeVideoUrl + assertNotNull(operation, "Analysis operation should not be null"); + assertTrue(operation.waitForCompletion().getStatus().isComplete(), "Operation should be completed"); + assertNotNull(result, "Analysis result should not be null"); + assertNotNull(result.getContents(), "Result contents should not be null"); + assertTrue(result.getContents().size() > 0, "Result should have at least one content"); + + // Verify all contents are AudioVisualContent + for (AnalysisContent content : result.getContents()) { + assertTrue(content instanceof AudioVisualContent, "Video analysis should return audio/visual content."); + AudioVisualContent avContent = (AudioVisualContent) content; + assertNotNull(avContent.getFields(), "AudioVisualContent should have fields"); + assertTrue(avContent.getFields().containsKey("Summary"), "Video segment should have Summary field"); + assertNotNull(avContent.getFields().get("Summary").getValue(), "Summary value should not be null"); + String summaryStr = avContent.getFields().get("Summary").getValue().toString(); + assertFalse(summaryStr.trim().isEmpty(), "Summary should not be empty"); + } + System.out.println("Video analysis validation completed successfully"); + // END:Assertion_ContentUnderstandingAnalyzeVideoUrl + } + + @Test + public void testAnalyzeAudioUrl() { + // BEGIN:ContentUnderstandingAnalyzeAudioUrl + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/audio/callCenterRecording.mp3"; + + AnalysisInput input = new AnalysisInput(); + input.setUrl(uriSource); + + SyncPoller operation + = contentUnderstandingClient.beginAnalyze("prebuilt-audioSearch", Arrays.asList(input)); + + AnalysisResult result = operation.getFinalResult(); + + // Cast AnalysisContent to AudioVisualContent to access audio/visual-specific properties + // AudioVisualContent derives from AnalysisContent and provides additional properties + // to access full information about audio/video, including timing, transcript phrases, and many others + AudioVisualContent audioContent = (AudioVisualContent) result.getContents().get(0); + System.out.println("Markdown:"); + System.out.println(audioContent.getMarkdown()); + + String summary = audioContent.getFields() != null && audioContent.getFields().containsKey("Summary") + ? (audioContent.getFields().get("Summary").getValue() != null + ? audioContent.getFields().get("Summary").getValue().toString() + : "") + : ""; + System.out.println("Summary: " + summary); + + // Example: Access an additional field in AudioVisualContent (transcript phrases) + List transcriptPhrases = audioContent.getTranscriptPhrases(); + if (transcriptPhrases != null && !transcriptPhrases.isEmpty()) { + System.out.println("Transcript (first two phrases):"); + int count = 0; + for (TranscriptPhrase phrase : transcriptPhrases) { + if (count >= 2) { + break; + } + System.out.println( + " [" + phrase.getSpeaker() + "] " + phrase.getStartTime().toMillis() + " ms: " + phrase.getText()); + count++; + } + } + // END:ContentUnderstandingAnalyzeAudioUrl + + // BEGIN:Assertion_ContentUnderstandingAnalyzeAudioUrl + assertNotNull(operation, "Analysis operation should not be null"); + assertTrue(operation.waitForCompletion().getStatus().isComplete(), "Operation should be completed"); + assertNotNull(result, "Analysis result should not be null"); + assertNotNull(result.getContents(), "Result contents should not be null"); + assertTrue(result.getContents().size() > 0, "Result should have at least one content"); + + // Verify content is AudioVisualContent + assertTrue(audioContent instanceof AudioVisualContent, "Audio analysis should return audio/visual content."); + + // Verify all contents have Summary field + for (AnalysisContent content : result.getContents()) { + assertTrue(content instanceof AudioVisualContent, "Audio analysis should return audio/visual content."); + AudioVisualContent avContent = (AudioVisualContent) content; + assertNotNull(avContent.getFields(), "AudioVisualContent should have fields"); + assertTrue(avContent.getFields().containsKey("Summary"), "Audio content should have Summary field"); + assertNotNull(avContent.getFields().get("Summary").getValue(), "Summary value should not be null"); + String summaryStr = avContent.getFields().get("Summary").getValue().toString(); + assertFalse(summaryStr.trim().isEmpty(), "Summary should not be empty"); + } + System.out.println("Audio analysis validation completed successfully"); + // END:Assertion_ContentUnderstandingAnalyzeAudioUrl + } + + @Test + public void testAnalyzeImageUrl() { + // BEGIN:ContentUnderstandingAnalyzeImageUrl + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/image/pieChart.jpg"; + + AnalysisInput input = new AnalysisInput(); + input.setUrl(uriSource); + + SyncPoller operation + = contentUnderstandingClient.beginAnalyze("prebuilt-imageSearch", Arrays.asList(input)); + + AnalysisResult result = operation.getFinalResult(); + + AnalysisContent content = result.getContents().get(0); + System.out.println("Markdown:"); + System.out.println(content.getMarkdown()); + + String summary = content.getFields() != null && content.getFields().containsKey("Summary") + ? (content.getFields().get("Summary").getValue() != null + ? content.getFields().get("Summary").getValue().toString() + : "") + : ""; + System.out.println("Summary: " + summary); + // END:ContentUnderstandingAnalyzeImageUrl + + // BEGIN:Assertion_ContentUnderstandingAnalyzeImageUrl + assertNotNull(operation, "Analysis operation should not be null"); + assertTrue(operation.waitForCompletion().getStatus().isComplete(), "Operation should be completed"); + assertNotNull(result, "Analysis result should not be null"); + assertNotNull(result.getContents(), "Result contents should not be null"); + assertTrue(result.getContents().size() > 0, "Result should have at least one content"); + + // Verify content has Summary field + for (AnalysisContent AnalysisContent : result.getContents()) { + assertNotNull(AnalysisContent.getFields(), "Content should have fields"); + assertTrue(AnalysisContent.getFields().containsKey("Summary"), "Image content should have Summary field"); + assertNotNull(AnalysisContent.getFields().get("Summary").getValue(), "Summary value should not be null"); + String summaryStr = AnalysisContent.getFields().get("Summary").getValue().toString(); + assertFalse(summaryStr.trim().isEmpty(), "Summary should not be empty"); + } + System.out.println("Image analysis validation completed successfully"); + // END:Assertion_ContentUnderstandingAnalyzeImageUrl + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrlAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrlAsync.java new file mode 100644 index 000000000000..4e1691d013b6 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrlAsync.java @@ -0,0 +1,439 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.AnalysisInput; +import com.azure.ai.contentunderstanding.models.AnalysisResult; +import com.azure.ai.contentunderstanding.models.AudioVisualContent; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.DocumentContent; +import com.azure.ai.contentunderstanding.models.DocumentPage; +import com.azure.ai.contentunderstanding.models.DocumentTable; +import com.azure.ai.contentunderstanding.models.DocumentTableCell; +import com.azure.ai.contentunderstanding.models.AnalysisContent; +import com.azure.ai.contentunderstanding.models.TranscriptPhrase; +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.*; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Async sample demonstrating how to analyze documents from URL using Content Understanding service. + * This sample shows: + * 1. Providing a URL to a document + * 2. Analyzing the document asynchronously + * 3. Extracting markdown content + * 4. Accessing document properties (pages, tables, etc.) + */ +public class Sample02_AnalyzeUrlAsync extends ContentUnderstandingClientTestBase { + + @Test + public void testAnalyzeUrlAsync() { + + // BEGIN:ContentUnderstandingAnalyzeUrlAsyncAsync + // Using a publicly accessible sample file from Azure-Samples GitHub repository + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-dotnet/main/ContentUnderstanding.Common/data/invoice.pdf"; + + AnalysisInput input = new AnalysisInput(); + input.setUrl(uriSource); + + PollerFlux operation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-documentSearch", Arrays.asList(input)); + + // Use reactive pattern: chain operations using flatMap, doOnNext, doOnError + // 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())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + // END:ContentUnderstandingAnalyzeUrlAsyncAsync + + // BEGIN:Assertion_ContentUnderstandingAnalyzeUrlAsyncAsync + assertNotNull(uriSource, "URI source should not be null"); + assertNotNull(operation, "Analysis operation should not be null"); + assertNotNull(result, "Analysis result should not be null"); + assertNotNull(result.getContents(), "Result contents should not be null"); + System.out.println("Analysis operation properties verified"); + System.out.println("Analysis result contains " + + (result.getContents() != null ? result.getContents().size() : 0) + " content(s)"); + // END:Assertion_ContentUnderstandingAnalyzeUrlAsyncAsync + + // A PDF file has only one content element even if it contains multiple pages + AnalysisContent content = null; + if (result.getContents() == null || result.getContents().isEmpty()) { + System.out.println("(No content returned from analysis)"); + } else { + content = result.getContents().get(0); + if (content.getMarkdown() != null && !content.getMarkdown().isEmpty()) { + System.out.println(content.getMarkdown()); + } else { + System.out.println("(No markdown content available)"); + } + } + + assertNotNull(result.getContents(), "Result should contain contents"); + assertTrue(result.getContents().size() > 0, "Result should have at least one content"); + assertEquals(1, result.getContents().size(), "PDF file should have exactly one content element"); + assertNotNull(content, "Content should not be null"); + assertTrue(content instanceof AnalysisContent, "Content should be of type AnalysisContent"); + + if (content.getMarkdown() != null && !content.getMarkdown().isEmpty()) { + assertFalse(content.getMarkdown().trim().isEmpty(), "Markdown content should not be just whitespace"); + System.out + .println("Markdown content extracted successfully (" + content.getMarkdown().length() + " characters)"); + } + + // Check if this is document content to access document-specific properties + if (content instanceof DocumentContent) { + DocumentContent documentContent = (DocumentContent) content; + System.out.println("Document type: " + + (documentContent.getMimeType() != null ? documentContent.getMimeType() : "(unknown)")); + System.out.println("Start page: " + documentContent.getStartPageNumber()); + System.out.println("End page: " + documentContent.getEndPageNumber()); + System.out.println( + "Total pages: " + (documentContent.getEndPageNumber() - documentContent.getStartPageNumber() + 1)); + + // Check for pages + if (documentContent.getPages() != null && !documentContent.getPages().isEmpty()) { + System.out.println("Number of pages: " + documentContent.getPages().size()); + for (DocumentPage page : documentContent.getPages()) { + String unit = documentContent.getUnit() != null ? documentContent.getUnit().toString() : "units"; + System.out.println(" Page " + page.getPageNumber() + ": " + page.getWidth() + " x " + + page.getHeight() + " " + unit); + } + } + + // Check for tables + if (documentContent.getTables() != null && !documentContent.getTables().isEmpty()) { + System.out.println("Number of tables: " + documentContent.getTables().size()); + int tableCounter = 1; + for (DocumentTable table : documentContent.getTables()) { + System.out.println(" Table " + tableCounter + ": " + table.getRowCount() + " rows x " + + table.getColumnCount() + " columns"); + tableCounter++; + } + } + } else { + // Content is not DocumentContent - verify it's AnalysisContent + assertTrue(content instanceof AnalysisContent, + "Content should be AnalysisContent when not DocumentContent"); + System.out.println("Content is AnalysisContent (not document-specific), skipping document properties"); + } + + assertNotNull(content, "Content should not be null for document properties validation"); + + if (content instanceof DocumentContent) { + DocumentContent docContent = (DocumentContent) content; + + // Validate MIME type + assertNotNull(docContent.getMimeType(), "MIME type should not be null"); + assertFalse(docContent.getMimeType().trim().isEmpty(), "MIME type should not be empty"); + assertEquals("application/pdf", docContent.getMimeType(), "MIME type should be application/pdf"); + System.out.println("MIME type verified: " + docContent.getMimeType()); + + // Validate page numbers + assertTrue(docContent.getStartPageNumber() >= 1, "Start page should be >= 1"); + assertTrue(docContent.getEndPageNumber() >= docContent.getStartPageNumber(), + "End page should be >= start page"); + int totalPages = docContent.getEndPageNumber() - docContent.getStartPageNumber() + 1; + assertTrue(totalPages > 0, "Total pages should be positive"); + System.out.println("Page range verified: " + docContent.getStartPageNumber() + " to " + + docContent.getEndPageNumber() + " (" + totalPages + " pages)"); + + // Validate pages collection + if (docContent.getPages() != null && !docContent.getPages().isEmpty()) { + assertTrue(docContent.getPages().size() > 0, "Pages collection should not be empty when not null"); + assertEquals(totalPages, docContent.getPages().size(), + "Pages collection count should match calculated total pages"); + System.out.println("Pages collection verified: " + docContent.getPages().size() + " pages"); + + // Track page numbers to ensure they're sequential and unique + Set pageNumbers = new HashSet<>(); + + for (DocumentPage page : docContent.getPages()) { + assertNotNull(page, "Page object should not be null"); + assertTrue(page.getPageNumber() >= 1, "Page number should be >= 1"); + assertTrue( + page.getPageNumber() >= docContent.getStartPageNumber() + && page.getPageNumber() <= docContent.getEndPageNumber(), + "Page number " + page.getPageNumber() + " should be within document range [" + + docContent.getStartPageNumber() + ", " + docContent.getEndPageNumber() + "]"); + assertTrue(page.getWidth() > 0, + "Page " + page.getPageNumber() + " width should be > 0, but was " + page.getWidth()); + assertTrue(page.getHeight() > 0, + "Page " + page.getPageNumber() + " height should be > 0, but was " + page.getHeight()); + + // Ensure page numbers are unique + assertTrue(pageNumbers.add(page.getPageNumber()), + "Page number " + page.getPageNumber() + " appears multiple times"); + + String unit = docContent.getUnit() != null ? docContent.getUnit().toString() : "units"; + System.out.println(" Page " + page.getPageNumber() + ": " + page.getWidth() + " x " + + page.getHeight() + " " + unit); + } + } else { + System.out.println("⚠️ No pages collection available in document content"); + } + + // Validate tables collection + if (docContent.getTables() != null && !docContent.getTables().isEmpty()) { + assertTrue(docContent.getTables().size() > 0, "Tables collection should not be empty when not null"); + System.out.println("Tables collection verified: " + docContent.getTables().size() + " tables"); + + int tableCounter = 1; + for (DocumentTable table : docContent.getTables()) { + assertNotNull(table, "Table " + tableCounter + " should not be null"); + assertTrue(table.getRowCount() > 0, + "Table " + tableCounter + " should have at least 1 row, but had " + table.getRowCount()); + assertTrue(table.getColumnCount() > 0, + "Table " + tableCounter + " should have at least 1 column, but had " + table.getColumnCount()); + + // Validate table cells if available + if (table.getCells() != null) { + assertTrue(table.getCells().size() > 0, + "Table " + tableCounter + " cells collection should not be empty when not null"); + + for (DocumentTableCell cell : table.getCells()) { + assertNotNull(cell, "Table cell should not be null"); + assertTrue(cell.getRowIndex() >= 0 && cell.getRowIndex() < table.getRowCount(), + "Cell row index " + cell.getRowIndex() + " should be within table row count " + + table.getRowCount()); + assertTrue(cell.getColumnIndex() >= 0 && cell.getColumnIndex() < table.getColumnCount(), + "Cell column index " + cell.getColumnIndex() + " should be within table column count " + + table.getColumnCount()); + } + } + + System.out.println(" Table " + tableCounter + ": " + table.getRowCount() + " rows x " + + table.getColumnCount() + " columns" + + (table.getCells() != null ? " (" + table.getCells().size() + " cells)" : "")); + tableCounter++; + } + } else { + System.out.println("⚠️ No tables found in document content"); + } + + System.out.println("All document properties validated successfully"); + } else { + // Content is not DocumentContent - validate alternative types + assertTrue(content instanceof AnalysisContent, + "Content should be AnalysisContent when not DocumentContent, but got " + + (content != null ? content.getClass().getSimpleName() : "null")); + System.out.println("⚠️ Content is not DocumentContent type, skipping document-specific validations"); + System.out + .println("⚠️ Content type: " + content.getClass().getSimpleName() + " (AnalysisContent validated)"); + } + } + + @Test + public void testAnalyzeVideoUrlAsync() { + // BEGIN:ContentUnderstandingAnalyzeVideoUrlAsyncAsync + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/videos/sdk_samples/FlightSimulator.mp4"; + + AnalysisInput input = new AnalysisInput(); + input.setUrl(uriSource); + + PollerFlux operation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-videoSearch", Arrays.asList(input)); + + // Use reactive pattern: chain operations using flatMap + // 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())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + + // prebuilt-videoSearch can detect video segments, so we should iterate through all segments + int segmentIndex = 1; + for (AnalysisContent media : result.getContents()) { + // Cast AnalysisContent to AudioVisualContent to access audio/visual-specific properties + // AudioVisualContent derives from AnalysisContent and provides additional properties + // to access full information about audio/video, including timing, transcript phrases, and many others + AudioVisualContent videoContent = (AudioVisualContent) media; + System.out.println("--- Segment " + segmentIndex + " ---"); + System.out.println("Markdown:"); + System.out.println(videoContent.getMarkdown()); + + String summary = videoContent.getFields() != null && videoContent.getFields().containsKey("Summary") + ? (videoContent.getFields().get("Summary").getValue() != null + ? videoContent.getFields().get("Summary").getValue().toString() + : "") + : ""; + System.out.println("Summary: " + summary); + + System.out.println("Start: " + videoContent.getStartTime().toMillis() + " ms, End: " + + videoContent.getEndTime().toMillis() + " ms"); + System.out.println("Frame size: " + videoContent.getWidth() + " x " + videoContent.getHeight()); + + System.out.println("---------------------"); + segmentIndex++; + } + // END:ContentUnderstandingAnalyzeVideoUrlAsyncAsync + + // BEGIN:Assertion_ContentUnderstandingAnalyzeVideoUrlAsyncAsync + assertNotNull(operation, "Analysis operation should not be null"); + assertNotNull(result, "Analysis result should not be null"); + assertNotNull(result.getContents(), "Result contents should not be null"); + assertTrue(result.getContents().size() > 0, "Result should have at least one content"); + + // Verify all contents are AudioVisualContent + for (AnalysisContent content : result.getContents()) { + assertTrue(content instanceof AudioVisualContent, "Video analysis should return audio/visual content."); + AudioVisualContent avContent = (AudioVisualContent) content; + assertNotNull(avContent.getFields(), "AudioVisualContent should have fields"); + assertTrue(avContent.getFields().containsKey("Summary"), "Video segment should have Summary field"); + assertNotNull(avContent.getFields().get("Summary").getValue(), "Summary value should not be null"); + String summaryStr = avContent.getFields().get("Summary").getValue().toString(); + assertFalse(summaryStr.trim().isEmpty(), "Summary should not be empty"); + } + System.out.println("Video analysis validation completed successfully"); + // END:Assertion_ContentUnderstandingAnalyzeVideoUrlAsyncAsync + } + + @Test + public void testAnalyzeAudioUrlAsync() { + // BEGIN:ContentUnderstandingAnalyzeAudioUrlAsyncAsync + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/audio/callCenterRecording.mp3"; + + AnalysisInput input = new AnalysisInput(); + input.setUrl(uriSource); + + PollerFlux operation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-audioSearch", Arrays.asList(input)); + + // Use reactive pattern: chain operations using flatMap + // 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())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + + // Cast AnalysisContent to AudioVisualContent to access audio/visual-specific properties + // AudioVisualContent derives from AnalysisContent and provides additional properties + // to access full information about audio/video, including timing, transcript phrases, and many others + AudioVisualContent audioContent = (AudioVisualContent) result.getContents().get(0); + System.out.println("Markdown:"); + System.out.println(audioContent.getMarkdown()); + + String summary = audioContent.getFields() != null && audioContent.getFields().containsKey("Summary") + ? (audioContent.getFields().get("Summary").getValue() != null + ? audioContent.getFields().get("Summary").getValue().toString() + : "") + : ""; + System.out.println("Summary: " + summary); + + // Example: Access an additional field in AudioVisualContent (transcript phrases) + List transcriptPhrases = audioContent.getTranscriptPhrases(); + if (transcriptPhrases != null && !transcriptPhrases.isEmpty()) { + System.out.println("Transcript (first two phrases):"); + int count = 0; + for (TranscriptPhrase phrase : transcriptPhrases) { + if (count >= 2) { + break; + } + System.out.println( + " [" + phrase.getSpeaker() + "] " + phrase.getStartTime().toMillis() + " ms: " + phrase.getText()); + count++; + } + } + // END:ContentUnderstandingAnalyzeAudioUrlAsyncAsync + + // BEGIN:Assertion_ContentUnderstandingAnalyzeAudioUrlAsyncAsync + assertNotNull(operation, "Analysis operation should not be null"); + assertNotNull(result, "Analysis result should not be null"); + assertNotNull(result.getContents(), "Result contents should not be null"); + assertTrue(result.getContents().size() > 0, "Result should have at least one content"); + + // Verify content is AudioVisualContent + assertTrue(audioContent instanceof AudioVisualContent, "Audio analysis should return audio/visual content."); + + // Verify all contents have Summary field + for (AnalysisContent content : result.getContents()) { + assertTrue(content instanceof AudioVisualContent, "Audio analysis should return audio/visual content."); + AudioVisualContent avContent = (AudioVisualContent) content; + assertNotNull(avContent.getFields(), "AudioVisualContent should have fields"); + assertTrue(avContent.getFields().containsKey("Summary"), "Audio content should have Summary field"); + assertNotNull(avContent.getFields().get("Summary").getValue(), "Summary value should not be null"); + String summaryStr = avContent.getFields().get("Summary").getValue().toString(); + assertFalse(summaryStr.trim().isEmpty(), "Summary should not be empty"); + } + System.out.println("Audio analysis validation completed successfully"); + // END:Assertion_ContentUnderstandingAnalyzeAudioUrlAsyncAsync + } + + @Test + public void testAnalyzeImageUrlAsync() { + // BEGIN:ContentUnderstandingAnalyzeImageUrlAsyncAsync + String uriSource + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-assets/main/image/pieChart.jpg"; + + AnalysisInput input = new AnalysisInput(); + input.setUrl(uriSource); + + PollerFlux operation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-imageSearch", Arrays.asList(input)); + + // Use reactive pattern: chain operations using flatMap + // 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())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + + AnalysisContent content = result.getContents().get(0); + System.out.println("Markdown:"); + System.out.println(content.getMarkdown()); + + String summary = content.getFields() != null && content.getFields().containsKey("Summary") + ? (content.getFields().get("Summary").getValue() != null + ? content.getFields().get("Summary").getValue().toString() + : "") + : ""; + System.out.println("Summary: " + summary); + // END:ContentUnderstandingAnalyzeImageUrlAsyncAsync + + // BEGIN:Assertion_ContentUnderstandingAnalyzeImageUrlAsyncAsync + assertNotNull(operation, "Analysis operation should not be null"); + assertNotNull(result, "Analysis result should not be null"); + assertNotNull(result.getContents(), "Result contents should not be null"); + assertTrue(result.getContents().size() > 0, "Result should have at least one content"); + + // Verify content has Summary field + for (AnalysisContent AnalysisContent : result.getContents()) { + assertNotNull(AnalysisContent.getFields(), "Content should have fields"); + assertTrue(AnalysisContent.getFields().containsKey("Summary"), "Image content should have Summary field"); + assertNotNull(AnalysisContent.getFields().get("Summary").getValue(), "Summary value should not be null"); + String summaryStr = AnalysisContent.getFields().get("Summary").getValue().toString(); + assertFalse(summaryStr.trim().isEmpty(), "Summary should not be empty"); + } + System.out.println("Image analysis validation completed successfully"); + // END:Assertion_ContentUnderstandingAnalyzeImageUrlAsyncAsync + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrlAsyncTest.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrlAsyncTest.java index 9a632d4e1d5c..47e307fa8238 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrlAsyncTest.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrlAsyncTest.java @@ -432,11 +432,11 @@ public void testAnalyzeImageUrlAsync() { assertTrue(result.getContents().size() > 0, "Result should have at least one content"); // Verify content has Summary field - for (AnalysisContent mediaContent : result.getContents()) { - assertNotNull(mediaContent.getFields(), "Content should have fields"); - assertTrue(mediaContent.getFields().containsKey("Summary"), "Image content should have Summary field"); - assertNotNull(mediaContent.getFields().get("Summary").getValue(), "Summary value should not be null"); - String summaryStr = mediaContent.getFields().get("Summary").getValue().toString(); + for (AnalysisContent AnalysisContent : result.getContents()) { + assertNotNull(AnalysisContent.getFields(), "Content should have fields"); + assertTrue(AnalysisContent.getFields().containsKey("Summary"), "Image content should have Summary field"); + assertNotNull(AnalysisContent.getFields().get("Summary").getValue(), "Summary value should not be null"); + String summaryStr = AnalysisContent.getFields().get("Summary").getValue().toString(); assertFalse(summaryStr.trim().isEmpty(), "Summary should not be empty"); } System.out.println("Image analysis validation completed successfully"); diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrlTest.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrlTest.java index 7c538bb4a2b5..f6ecfca6e453 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrlTest.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample02_AnalyzeUrlTest.java @@ -400,11 +400,11 @@ public void testAnalyzeImageUrl() { assertTrue(result.getContents().size() > 0, "Result should have at least one content"); // Verify content has Summary field - for (AnalysisContent mediaContent : result.getContents()) { - assertNotNull(mediaContent.getFields(), "Content should have fields"); - assertTrue(mediaContent.getFields().containsKey("Summary"), "Image content should have Summary field"); - assertNotNull(mediaContent.getFields().get("Summary").getValue(), "Summary value should not be null"); - String summaryStr = mediaContent.getFields().get("Summary").getValue().toString(); + for (AnalysisContent AnalysisContent : result.getContents()) { + assertNotNull(AnalysisContent.getFields(), "Content should have fields"); + assertTrue(AnalysisContent.getFields().containsKey("Summary"), "Image content should have Summary field"); + assertNotNull(AnalysisContent.getFields().get("Summary").getValue(), "Summary value should not be null"); + String summaryStr = AnalysisContent.getFields().get("Summary").getValue().toString(); assertFalse(summaryStr.trim().isEmpty(), "Summary should not be empty"); } System.out.println("Image analysis validation completed successfully"); diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample03_AnalyzeInvoice.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample03_AnalyzeInvoice.java new file mode 100644 index 000000000000..c1300710a246 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample03_AnalyzeInvoice.java @@ -0,0 +1,207 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.AnalysisInput; +import com.azure.ai.contentunderstanding.models.AnalysisResult; +import com.azure.ai.contentunderstanding.models.ArrayField; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.DocumentContent; +import com.azure.ai.contentunderstanding.models.ContentField; +import com.azure.ai.contentunderstanding.models.ContentSource; +import com.azure.ai.contentunderstanding.models.ContentSpan; +import com.azure.ai.contentunderstanding.models.AnalysisContent; +import com.azure.ai.contentunderstanding.models.ObjectField; +import com.azure.core.util.polling.SyncPoller; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Arrays; +import java.util.List; + +/** + * Sample demonstrating how to analyze invoices using Content Understanding service. + * This sample shows: + * 1. Analyzing an invoice document + * 2. Extracting structured invoice fields + * 3. Accessing nested object fields (TotalAmount) + * 4. Accessing array fields (LineItems) + * 5. Working with field confidence and source information + */ +public class Sample03_AnalyzeInvoice extends ContentUnderstandingClientTestBase { + + @Test + public void testAnalyzeInvoice() { + + // BEGIN:ContentUnderstandingAnalyzeInvoice + // Using a publicly accessible sample file from Azure-Samples GitHub repository + String invoiceUrl + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-dotnet/main/ContentUnderstanding.Common/data/invoice.pdf"; + + AnalysisInput input = new AnalysisInput(); + input.setUrl(invoiceUrl); + + SyncPoller operation + = contentUnderstandingClient.beginAnalyze("prebuilt-invoice", Arrays.asList(input)); + + AnalysisResult result = operation.getFinalResult(); + // END:ContentUnderstandingAnalyzeInvoice + + // BEGIN:Assertion_ContentUnderstandingAnalyzeInvoice + assertNotNull(invoiceUrl, "Invoice URL should not be null"); + assertNotNull(operation, "Analysis operation should not be null"); + assertTrue(operation.waitForCompletion().getStatus().isComplete(), "Operation should be completed"); + System.out.println("Analysis operation properties verified"); + + assertNotNull(result, "Analysis result should not be null"); + assertNotNull(result.getContents(), "Result should contain contents"); + assertTrue(result.getContents().size() > 0, "Result should have at least one content"); + assertEquals(1, result.getContents().size(), "Invoice should have exactly one content element"); + System.out.println("Analysis result contains " + result.getContents().size() + " content(s)"); + // END:Assertion_ContentUnderstandingAnalyzeInvoice + + // BEGIN:ContentUnderstandingExtractInvoiceFields + // Get the document content (invoices are documents) + AnalysisContent firstContent = result.getContents().get(0); + if (firstContent instanceof DocumentContent) { + DocumentContent documentContent = (DocumentContent) firstContent; + + // Print document unit information + // The unit indicates the measurement system used for coordinates in the source field + System.out.println("Document unit: " + + (documentContent.getUnit() != null ? documentContent.getUnit().toString() : "unknown")); + System.out.println( + "Pages: " + documentContent.getStartPageNumber() + " to " + documentContent.getEndPageNumber()); + System.out.println(); + + // Extract simple string fields using getValue() convenience method + // getValue() returns the typed value regardless of field type (StringField, NumberField, DateField, etc.) + ContentField customerNameField + = documentContent.getFields() != null ? documentContent.getFields().get("CustomerName") : null; + ContentField invoiceDateField + = documentContent.getFields() != null ? documentContent.getFields().get("InvoiceDate") : null; + + // Use getValue() instead of casting to specific types + // Note: getValue() returns the actual typed value - String, Number, LocalDate, etc. + String customerName = customerNameField != null ? (String) customerNameField.getValue() : null; + // InvoiceDate is a DateField, so getValue() returns LocalDate - convert to String for display + Object invoiceDateValue = invoiceDateField != null ? invoiceDateField.getValue() : null; + String invoiceDate = invoiceDateValue != null ? invoiceDateValue.toString() : null; + + System.out.println("Customer Name: " + (customerName != null ? customerName : "(None)")); + if (customerNameField != null) { + System.out.println(" Confidence: " + (customerNameField.getConfidence() != null + ? String.format("%.2f", customerNameField.getConfidence()) + : "N/A")); + System.out.println(" Source: " + (customerNameField.getSources() != null + ? ContentSource.toRawString(customerNameField.getSources()) + : "N/A")); + List spans = customerNameField.getSpans(); + if (spans != null && !spans.isEmpty()) { + ContentSpan span = spans.get(0); + System.out + .println(" Position in markdown: offset=" + span.getOffset() + ", length=" + span.getLength()); + } + } + + System.out.println("Invoice Date: " + (invoiceDate != null ? invoiceDate : "(None)")); + if (invoiceDateField != null) { + System.out.println(" Confidence: " + (invoiceDateField.getConfidence() != null + ? String.format("%.2f", invoiceDateField.getConfidence()) + : "N/A")); + System.out.println(" Source: " + (invoiceDateField.getSources() != null + ? ContentSource.toRawString(invoiceDateField.getSources()) + : "N/A")); + List spans = invoiceDateField.getSpans(); + if (spans != null && !spans.isEmpty()) { + ContentSpan span = spans.get(0); + System.out + .println(" Position in markdown: offset=" + span.getOffset() + ", length=" + span.getLength()); + } + } + + // Extract object fields (nested structures) using getFieldOrDefault() convenience method + // getFieldOrDefault() returns null if the field doesn't exist (safe access pattern) + ContentField totalAmountField + = documentContent.getFields() != null ? documentContent.getFields().get("TotalAmount") : null; + if (totalAmountField instanceof ObjectField) { + ObjectField totalAmountObj = (ObjectField) totalAmountField; + + // Use getFieldOrDefault() for safe nested field access + ContentField amountField = totalAmountObj.getFieldOrDefault("Amount"); + ContentField currencyField = totalAmountObj.getFieldOrDefault("CurrencyCode"); + + // Use getValue() instead of type-specific getters + Double amount = amountField != null ? (Double) amountField.getValue() : null; + String currency = currencyField != null ? (String) currencyField.getValue() : null; + + System.out.println("Total: " + (currency != null ? currency : "$") + + (amount != null ? String.format("%.2f", amount) : "(None)")); + if (totalAmountObj.getConfidence() != null) { + System.out.println(" Confidence: " + String.format("%.2f", totalAmountObj.getConfidence())); + } + if (totalAmountObj.getSources() != null && !totalAmountObj.getSources().isEmpty()) { + System.out.println(" Source: " + ContentSource.toRawString(totalAmountObj.getSources())); + } + } + + // Extract array fields (collections like line items) using size() and get() convenience methods + ContentField lineItemsField + = documentContent.getFields() != null ? documentContent.getFields().get("LineItems") : null; + if (lineItemsField instanceof ArrayField) { + ArrayField lineItems = (ArrayField) lineItemsField; + // Use size() convenience method instead of getValueArray().size() + System.out.println("Line Items (" + lineItems.size() + "):"); + for (int i = 0; i < lineItems.size(); i++) { + // Use get(index) convenience method instead of getValueArray().get(i) + ContentField itemField = lineItems.get(i); + if (itemField instanceof ObjectField) { + ObjectField item = (ObjectField) itemField; + // Use getFieldOrDefault() for safe nested access + ContentField descField = item.getFieldOrDefault("Description"); + ContentField qtyField = item.getFieldOrDefault("Quantity"); + + // Use getValue() instead of type-specific getters + String description = descField != null ? (String) descField.getValue() : null; + Double quantity = qtyField != null ? (Double) qtyField.getValue() : null; + + System.out.println(" Item " + (i + 1) + ": " + (description != null ? description : "N/A") + + " (Qty: " + (quantity != null ? String.valueOf(quantity) : "N/A") + ")"); + if (item.getConfidence() != null) { + System.out.println(" Confidence: " + String.format("%.2f", item.getConfidence())); + } + } + } + } + } + // END:ContentUnderstandingExtractInvoiceFields + + // BEGIN:Assertion_ContentUnderstandingExtractInvoiceFields + AnalysisContent content = result.getContents().get(0); + assertNotNull(content, "Content should not be null"); + assertTrue(content instanceof DocumentContent, "Content should be of type DocumentContent"); + + if (content instanceof DocumentContent) { + DocumentContent docContent = (DocumentContent) content; + + // Verify basic document properties + assertTrue(docContent.getStartPageNumber() >= 1, "Start page should be >= 1"); + assertTrue(docContent.getEndPageNumber() >= docContent.getStartPageNumber(), + "End page should be >= start page"); + int totalPages = docContent.getEndPageNumber() - docContent.getStartPageNumber() + 1; + assertTrue(totalPages > 0, "Total pages should be positive"); + System.out.println("Document has " + totalPages + " page(s) from " + docContent.getStartPageNumber() + + " to " + docContent.getEndPageNumber()); + + System.out.println("All invoice fields validated successfully"); + } else { + // This should not happen given the assertTrue above, but handle it for completeness + fail("Content type validation failed: expected DocumentContent but got " + + (content != null ? content.getClass().getSimpleName() : "null")); + } + // END:Assertion_ContentUnderstandingExtractInvoiceFields + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample03_AnalyzeInvoiceAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample03_AnalyzeInvoiceAsync.java new file mode 100644 index 000000000000..9efa177068bc --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample03_AnalyzeInvoiceAsync.java @@ -0,0 +1,215 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.AnalysisInput; +import com.azure.ai.contentunderstanding.models.AnalysisResult; +import com.azure.ai.contentunderstanding.models.ArrayField; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.DocumentContent; +import com.azure.ai.contentunderstanding.models.ContentField; +import com.azure.ai.contentunderstanding.models.ContentSource; +import com.azure.ai.contentunderstanding.models.ContentSpan; +import com.azure.ai.contentunderstanding.models.AnalysisContent; +import com.azure.ai.contentunderstanding.models.ObjectField; +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.*; + +import java.util.Arrays; +import java.util.List; + +/** + * Async sample demonstrating how to analyze invoices using Content Understanding service. + * This sample shows: + * 1. Analyzing an invoice document asynchronously + * 2. Extracting structured invoice fields + * 3. Accessing nested object fields (TotalAmount) + * 4. Accessing array fields (LineItems) + * 5. Working with field confidence and source information + */ +public class Sample03_AnalyzeInvoiceAsync extends ContentUnderstandingClientTestBase { + + @Test + public void testAnalyzeInvoiceAsync() { + + // BEGIN:ContentUnderstandingAnalyzeInvoiceAsync + // Using a publicly accessible sample file from Azure-Samples GitHub repository + String invoiceUrl + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-dotnet/main/ContentUnderstanding.Common/data/invoice.pdf"; + + AnalysisInput input = new AnalysisInput(); + input.setUrl(invoiceUrl); + + PollerFlux operation + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-invoice", Arrays.asList(input)); + + // Use reactive pattern: chain operations using flatMap + // 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())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + // END:ContentUnderstandingAnalyzeInvoiceAsync + + // BEGIN:Assertion_ContentUnderstandingAnalyzeInvoiceAsync + assertNotNull(invoiceUrl, "Invoice URL should not be null"); + assertNotNull(operation, "Analysis operation should not be null"); + assertNotNull(result, "Analysis result should not be null"); + assertNotNull(result.getContents(), "Result should contain contents"); + assertTrue(result.getContents().size() > 0, "Result should have at least one content"); + assertEquals(1, result.getContents().size(), "Invoice should have exactly one content element"); + System.out.println("Analysis operation properties verified"); + System.out.println("Analysis result contains " + result.getContents().size() + " content(s)"); + // END:Assertion_ContentUnderstandingAnalyzeInvoiceAsync + + // BEGIN:ContentUnderstandingExtractInvoiceFieldsAsync + // Get the document content (invoices are documents) + AnalysisContent firstContent = result.getContents().get(0); + if (firstContent instanceof DocumentContent) { + DocumentContent documentContent = (DocumentContent) firstContent; + + // Print document unit information + // The unit indicates the measurement system used for coordinates in the source field + System.out.println("Document unit: " + + (documentContent.getUnit() != null ? documentContent.getUnit().toString() : "unknown")); + System.out.println( + "Pages: " + documentContent.getStartPageNumber() + " to " + documentContent.getEndPageNumber()); + System.out.println(); + + // Extract simple string fields using getValue() convenience method + // getValue() returns the typed value regardless of field type (StringField, NumberField, DateField, etc.) + ContentField customerNameField + = documentContent.getFields() != null ? documentContent.getFields().get("CustomerName") : null; + ContentField invoiceDateField + = documentContent.getFields() != null ? documentContent.getFields().get("InvoiceDate") : null; + + // Use getValue() instead of casting to specific types + // Note: getValue() returns the actual typed value - String, Number, LocalDate, etc. + String customerName = customerNameField != null ? (String) customerNameField.getValue() : null; + // InvoiceDate is a DateField, so getValue() returns LocalDate - convert to String for display + Object invoiceDateValue = invoiceDateField != null ? invoiceDateField.getValue() : null; + String invoiceDate = invoiceDateValue != null ? invoiceDateValue.toString() : null; + + System.out.println("Customer Name: " + (customerName != null ? customerName : "(None)")); + if (customerNameField != null) { + System.out.println(" Confidence: " + (customerNameField.getConfidence() != null + ? String.format("%.2f", customerNameField.getConfidence()) + : "N/A")); + System.out.println(" Source: " + (customerNameField.getSources() != null + ? ContentSource.toRawString(customerNameField.getSources()) + : "N/A")); + List spans = customerNameField.getSpans(); + if (spans != null && !spans.isEmpty()) { + ContentSpan span = spans.get(0); + System.out + .println(" Position in markdown: offset=" + span.getOffset() + ", length=" + span.getLength()); + } + } + + System.out.println("Invoice Date: " + (invoiceDate != null ? invoiceDate : "(None)")); + if (invoiceDateField != null) { + System.out.println(" Confidence: " + (invoiceDateField.getConfidence() != null + ? String.format("%.2f", invoiceDateField.getConfidence()) + : "N/A")); + System.out.println(" Source: " + (invoiceDateField.getSources() != null + ? ContentSource.toRawString(invoiceDateField.getSources()) + : "N/A")); + List spans = invoiceDateField.getSpans(); + if (spans != null && !spans.isEmpty()) { + ContentSpan span = spans.get(0); + System.out + .println(" Position in markdown: offset=" + span.getOffset() + ", length=" + span.getLength()); + } + } + + // Extract object fields (nested structures) using getFieldOrDefault() convenience method + // getFieldOrDefault() returns null if the field doesn't exist (safe access pattern) + ContentField totalAmountField + = documentContent.getFields() != null ? documentContent.getFields().get("TotalAmount") : null; + if (totalAmountField instanceof ObjectField) { + ObjectField totalAmountObj = (ObjectField) totalAmountField; + + // Use getFieldOrDefault() for safe nested field access + ContentField amountField = totalAmountObj.getFieldOrDefault("Amount"); + ContentField currencyField = totalAmountObj.getFieldOrDefault("CurrencyCode"); + + // Use getValue() instead of type-specific getters + Double amount = amountField != null ? (Double) amountField.getValue() : null; + String currency = currencyField != null ? (String) currencyField.getValue() : null; + + System.out.println("Total: " + (currency != null ? currency : "$") + + (amount != null ? String.format("%.2f", amount) : "(None)")); + if (totalAmountObj.getConfidence() != null) { + System.out.println(" Confidence: " + String.format("%.2f", totalAmountObj.getConfidence())); + } + if (totalAmountObj.getSources() != null && !totalAmountObj.getSources().isEmpty()) { + System.out.println(" Source: " + ContentSource.toRawString(totalAmountObj.getSources())); + } + } + + // Extract array fields (collections like line items) using size() and get() convenience methods + ContentField lineItemsField + = documentContent.getFields() != null ? documentContent.getFields().get("LineItems") : null; + if (lineItemsField instanceof ArrayField) { + ArrayField lineItems = (ArrayField) lineItemsField; + // Use size() convenience method instead of getValueArray().size() + System.out.println("Line Items (" + lineItems.size() + "):"); + for (int i = 0; i < lineItems.size(); i++) { + // Use get(index) convenience method instead of getValueArray().get(i) + ContentField itemField = lineItems.get(i); + if (itemField instanceof ObjectField) { + ObjectField item = (ObjectField) itemField; + // Use getFieldOrDefault() for safe nested access + ContentField descField = item.getFieldOrDefault("Description"); + ContentField qtyField = item.getFieldOrDefault("Quantity"); + + // Use getValue() instead of type-specific getters + String description = descField != null ? (String) descField.getValue() : null; + Double quantity = qtyField != null ? (Double) qtyField.getValue() : null; + + System.out.println(" Item " + (i + 1) + ": " + (description != null ? description : "N/A") + + " (Qty: " + (quantity != null ? String.valueOf(quantity) : "N/A") + ")"); + if (item.getConfidence() != null) { + System.out.println(" Confidence: " + String.format("%.2f", item.getConfidence())); + } + } + } + } + } + // END:ContentUnderstandingExtractInvoiceFieldsAsync + + // BEGIN:Assertion_ContentUnderstandingExtractInvoiceFieldsAsync + AnalysisContent content = result.getContents().get(0); + assertNotNull(content, "Content should not be null"); + assertTrue(content instanceof DocumentContent, "Content should be of type DocumentContent"); + + if (content instanceof DocumentContent) { + DocumentContent docContent = (DocumentContent) content; + + // Verify basic document properties + assertTrue(docContent.getStartPageNumber() >= 1, "Start page should be >= 1"); + assertTrue(docContent.getEndPageNumber() >= docContent.getStartPageNumber(), + "End page should be >= start page"); + int totalPages = docContent.getEndPageNumber() - docContent.getStartPageNumber() + 1; + assertTrue(totalPages > 0, "Total pages should be positive"); + System.out.println("Document has " + totalPages + " page(s) from " + docContent.getStartPageNumber() + + " to " + docContent.getEndPageNumber()); + + System.out.println("All invoice fields validated successfully"); + } else { + // This should not happen given the assertTrue above, but handle it for completeness + fail("Content type validation failed: expected DocumentContent but got " + + (content != null ? content.getClass().getSimpleName() : "null")); + } + // END:Assertion_ContentUnderstandingExtractInvoiceFieldsAsync + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample04_CreateAnalyzer.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample04_CreateAnalyzer.java new file mode 100644 index 000000000000..de6aef13917c --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample04_CreateAnalyzer.java @@ -0,0 +1,476 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.AnalysisInput; +import com.azure.ai.contentunderstanding.models.AnalysisResult; +import com.azure.ai.contentunderstanding.models.ContentAnalyzer; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerConfig; + +import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentFieldDefinition; +import com.azure.ai.contentunderstanding.models.ContentFieldSchema; +import com.azure.ai.contentunderstanding.models.ContentFieldType; +import com.azure.ai.contentunderstanding.models.DocumentContent; +import com.azure.ai.contentunderstanding.models.ContentField; +import com.azure.ai.contentunderstanding.models.ContentSource; +import com.azure.ai.contentunderstanding.models.ContentSpan; +import com.azure.ai.contentunderstanding.models.GenerationMethod; +import com.azure.ai.contentunderstanding.models.NumberField; +import com.azure.ai.contentunderstanding.models.StringField; +import com.azure.core.util.polling.SyncPoller; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * Sample demonstrating how to create a custom analyzer with field schema. + * This sample shows: + * 1. Defining a field schema with custom fields + * 2. Demonstrating three extraction methods: Extract, Generate, Classify + * 3. Creating a custom analyzer with configuration + * 4. Using the custom analyzer to analyze documents + */ +public class Sample04_CreateAnalyzer extends ContentUnderstandingClientTestBase { + + private String createdAnalyzerId; + + @AfterEach + public void cleanup() { + if (createdAnalyzerId != null) { + try { + contentUnderstandingClient.deleteAnalyzer(createdAnalyzerId); + System.out.println("Analyzer '" + createdAnalyzerId + "' deleted successfully."); + } catch (Exception e) { + // Ignore cleanup errors + } + } + } + + @Test + public void testCreateAnalyzer() { + + // BEGIN:ContentUnderstandingCreateAnalyzer + // Generate a unique analyzer ID + String analyzerId = testResourceNamer.randomName("my_custom_analyzer_", 50); + + // Define field schema with custom fields + // This example demonstrates three extraction methods: + // - extract: Literal text extraction (requires estimateSourceAndConfidence) + // - generate: AI-generated values based on content interpretation + // - classify: Classification against predefined categories + Map fields = new HashMap<>(); + + ContentFieldDefinition companyNameDef = new ContentFieldDefinition(); + companyNameDef.setType(ContentFieldType.STRING); + companyNameDef.setMethod(GenerationMethod.EXTRACT); + companyNameDef.setDescription("Name of the company"); + fields.put("company_name", companyNameDef); + + ContentFieldDefinition totalAmountDef = new ContentFieldDefinition(); + totalAmountDef.setType(ContentFieldType.NUMBER); + totalAmountDef.setMethod(GenerationMethod.EXTRACT); + totalAmountDef.setDescription("Total amount on the document"); + fields.put("total_amount", totalAmountDef); + + ContentFieldDefinition summaryDef = new ContentFieldDefinition(); + summaryDef.setType(ContentFieldType.STRING); + summaryDef.setMethod(GenerationMethod.GENERATE); + summaryDef.setDescription("A brief summary of the document content"); + fields.put("document_summary", summaryDef); + + ContentFieldDefinition documentTypeDef = new ContentFieldDefinition(); + documentTypeDef.setType(ContentFieldType.STRING); + documentTypeDef.setMethod(GenerationMethod.CLASSIFY); + documentTypeDef.setDescription("Type of document"); + documentTypeDef.setEnumProperty(Arrays.asList("invoice", "receipt", "contract", "report", "other")); + fields.put("document_type", documentTypeDef); + + ContentFieldSchema fieldSchema = new ContentFieldSchema(); + fieldSchema.setName("company_schema"); + fieldSchema.setDescription("Schema for extracting company information"); + fieldSchema.setFields(fields); + + // Create the custom analyzer with configuration + Map models = new HashMap<>(); + models.put("completion", "gpt-4.1"); + models.put("embedding", "text-embedding-3-large"); + + ContentAnalyzer customAnalyzer = new ContentAnalyzer().setBaseAnalyzerId("prebuilt-document") + .setDescription("Custom analyzer for extracting company information") + .setConfig(new ContentAnalyzerConfig().setOcrEnabled(true) + .setLayoutEnabled(true) + .setFormulaEnabled(true) + .setEstimateFieldSourceAndConfidence(true) + .setReturnDetails(true)) + .setFieldSchema(fieldSchema) + .setModels(models); + + // Create the analyzer + SyncPoller operation + = contentUnderstandingClient.beginCreateAnalyzer(analyzerId, customAnalyzer, true); + + ContentAnalyzer result = operation.getFinalResult(); + System.out.println("Analyzer '" + analyzerId + "' created successfully!"); + if (result.getDescription() != null && !result.getDescription().trim().isEmpty()) { + System.out.println(" Description: " + result.getDescription()); + } + + if (result.getFieldSchema() != null && result.getFieldSchema().getFields() != null) { + System.out.println(" Fields (" + result.getFieldSchema().getFields().size() + "):"); + result.getFieldSchema().getFields().forEach((fieldName, fieldDef) -> { + String method = fieldDef.getMethod() != null ? fieldDef.getMethod().toString() : "auto"; + String type = fieldDef.getType() != null ? fieldDef.getType().toString() : "unknown"; + System.out.println(" - " + fieldName + ": " + type + " (" + method + ")"); + }); + } + // END:ContentUnderstandingCreateAnalyzer + + createdAnalyzerId = analyzerId; // Track for cleanup + + // BEGIN:Assertion_ContentUnderstandingCreateAnalyzer + assertNotNull(analyzerId, "Analyzer ID should not be null"); + assertFalse(analyzerId.trim().isEmpty(), "Analyzer ID should not be empty"); + assertNotNull(fieldSchema, "Field schema should not be null"); + assertNotNull(customAnalyzer, "Custom analyzer should not be null"); + assertNotNull(operation, "Create analyzer operation should not be null"); + assertTrue(operation.waitForCompletion().getStatus().isComplete(), "Operation should be completed"); + System.out.println("Create analyzer operation properties verified"); + + assertNotNull(result, "Analyzer result should not be null"); + System.out.println("Analyzer '" + analyzerId + "' created successfully"); + + // Verify base analyzer + assertNotNull(result.getBaseAnalyzerId(), "Base analyzer ID should not be null"); + assertEquals("prebuilt-document", result.getBaseAnalyzerId(), "Base analyzer ID should match"); + System.out.println("Base analyzer ID verified: " + result.getBaseAnalyzerId()); + + // Verify analyzer config + assertNotNull(result.getConfig(), "Analyzer config should not be null"); + assertTrue(result.getConfig().isFormulaEnabled(), "EnableFormula should be true"); + assertTrue(result.getConfig().isLayoutEnabled(), "EnableLayout should be true"); + assertTrue(result.getConfig().isOcrEnabled(), "EnableOcr should be true"); + assertTrue(result.getConfig().isEstimateFieldSourceAndConfidence(), + "EstimateFieldSourceAndConfidence should be true"); + assertTrue(result.getConfig().isReturnDetails(), "ReturnDetails should be true"); + System.out.println("Analyzer config verified"); + + // Verify field schema + assertNotNull(result.getFieldSchema(), "Field schema should not be null"); + assertFalse(result.getFieldSchema().getName().trim().isEmpty(), "Field schema name should not be empty"); + assertEquals("company_schema", result.getFieldSchema().getName(), "Field schema name should match"); + assertFalse(result.getFieldSchema().getDescription().trim().isEmpty(), + "Field schema description should not be empty"); + System.out.println("Field schema verified: " + result.getFieldSchema().getName()); + + // Verify field schema fields + assertNotNull(result.getFieldSchema().getFields(), "Field schema fields should not be null"); + assertEquals(4, result.getFieldSchema().getFields().size(), "Should have 4 custom fields"); + System.out.println("Field schema contains " + result.getFieldSchema().getFields().size() + " fields"); + + // Verify company_name field + assertTrue(result.getFieldSchema().getFields().containsKey("company_name"), + "Should contain company_name field"); + ContentFieldDefinition companyNameDefResult = result.getFieldSchema().getFields().get("company_name"); + assertEquals(ContentFieldType.STRING, companyNameDefResult.getType(), "company_name should be String type"); + assertEquals(GenerationMethod.EXTRACT, companyNameDefResult.getMethod(), + "company_name should use Extract method"); + assertFalse(companyNameDefResult.getDescription().trim().isEmpty(), "company_name should have description"); + System.out.println(" company_name field verified (String, Extract)"); + + // Verify total_amount field + assertTrue(result.getFieldSchema().getFields().containsKey("total_amount"), + "Should contain total_amount field"); + ContentFieldDefinition totalAmountDefResult = result.getFieldSchema().getFields().get("total_amount"); + assertEquals(ContentFieldType.NUMBER, totalAmountDefResult.getType(), "total_amount should be Number type"); + assertEquals(GenerationMethod.EXTRACT, totalAmountDefResult.getMethod(), + "total_amount should use Extract method"); + assertFalse(totalAmountDefResult.getDescription().trim().isEmpty(), "total_amount should have description"); + System.out.println(" total_amount field verified (Number, Extract)"); + + // Verify document_summary field + assertTrue(result.getFieldSchema().getFields().containsKey("document_summary"), + "Should contain document_summary field"); + ContentFieldDefinition summaryDefResult = result.getFieldSchema().getFields().get("document_summary"); + assertEquals(ContentFieldType.STRING, summaryDefResult.getType(), "document_summary should be String type"); + assertEquals(GenerationMethod.GENERATE, summaryDefResult.getMethod(), + "document_summary should use Generate method"); + assertFalse(summaryDefResult.getDescription().trim().isEmpty(), "document_summary should have description"); + System.out.println(" document_summary field verified (String, Generate)"); + + // Verify document_type field + assertTrue(result.getFieldSchema().getFields().containsKey("document_type"), + "Should contain document_type field"); + ContentFieldDefinition documentTypeDefResult = result.getFieldSchema().getFields().get("document_type"); + assertEquals(ContentFieldType.STRING, documentTypeDefResult.getType(), "document_type should be String type"); + assertEquals(GenerationMethod.CLASSIFY, documentTypeDefResult.getMethod(), + "document_type should use Classify method"); + assertFalse(documentTypeDefResult.getDescription().trim().isEmpty(), "document_type should have description"); + assertNotNull(documentTypeDefResult.getEnumProperty(), "document_type should have enum values"); + assertEquals(5, documentTypeDefResult.getEnumProperty().size(), "document_type should have 5 enum values"); + assertTrue(documentTypeDefResult.getEnumProperty().contains("invoice"), + "document_type enum should contain 'invoice'"); + assertTrue(documentTypeDefResult.getEnumProperty().contains("receipt"), + "document_type enum should contain 'receipt'"); + assertTrue(documentTypeDefResult.getEnumProperty().contains("contract"), + "document_type enum should contain 'contract'"); + assertTrue(documentTypeDefResult.getEnumProperty().contains("report"), + "document_type enum should contain 'report'"); + assertTrue(documentTypeDefResult.getEnumProperty().contains("other"), + "document_type enum should contain 'other'"); + System.out.println(" document_type field verified (String, Classify, 5 enum values)"); + + // Verify models + assertNotNull(result.getModels(), "Models should not be null"); + assertTrue(result.getModels().size() >= 2, "Should have at least 2 model mappings"); + assertTrue(result.getModels().containsKey("completion"), "Should contain 'completion' model mapping"); + assertTrue(result.getModels().containsKey("embedding"), "Should contain 'embedding' model mapping"); + assertEquals("gpt-4.1", result.getModels().get("completion"), "Completion model should be 'gpt-4.1'"); + assertEquals("text-embedding-3-large", result.getModels().get("embedding"), + "Embedding model should be 'text-embedding-3-large'"); + System.out.println("Model mappings verified: " + result.getModels().size() + " model(s)"); + + // Verify description + if (result.getDescription() != null && !result.getDescription().trim().isEmpty()) { + System.out.println("Analyzer description: " + result.getDescription()); + } + + System.out.println("All analyzer creation properties validated successfully"); + // END:Assertion_ContentUnderstandingCreateAnalyzer + } + + @Test + public void testUseCustomAnalyzer() { + // First create an analyzer + String analyzerId = testResourceNamer.randomName("test_analyzer_", 50); + + Map fields = new HashMap<>(); + + ContentFieldDefinition companyNameDef = new ContentFieldDefinition(); + companyNameDef.setType(ContentFieldType.STRING); + companyNameDef.setMethod(GenerationMethod.EXTRACT); + companyNameDef.setDescription("Name of the company"); + fields.put("company_name", companyNameDef); + + ContentFieldDefinition totalAmountDef = new ContentFieldDefinition(); + totalAmountDef.setType(ContentFieldType.NUMBER); + totalAmountDef.setMethod(GenerationMethod.EXTRACT); + totalAmountDef.setDescription("Total amount on the document"); + fields.put("total_amount", totalAmountDef); + + ContentFieldDefinition summaryDef = new ContentFieldDefinition(); + summaryDef.setType(ContentFieldType.STRING); + summaryDef.setMethod(GenerationMethod.GENERATE); + summaryDef.setDescription("A brief summary of the document content"); + fields.put("document_summary", summaryDef); + + ContentFieldDefinition documentTypeDef = new ContentFieldDefinition(); + documentTypeDef.setType(ContentFieldType.STRING); + documentTypeDef.setMethod(GenerationMethod.CLASSIFY); + documentTypeDef.setDescription("Type of document"); + documentTypeDef.setEnumProperty(Arrays.asList("invoice", "receipt", "contract", "report", "other")); + fields.put("document_type", documentTypeDef); + + ContentFieldSchema fieldSchema = new ContentFieldSchema(); + fieldSchema.setName("company_schema"); + fieldSchema.setDescription("Schema for extracting company information"); + fieldSchema.setFields(fields); + + ContentAnalyzerConfig config = new ContentAnalyzerConfig(); + config.setFormulaEnabled(true); + config.setLayoutEnabled(true); + config.setOcrEnabled(true); + + ContentAnalyzer customAnalyzer = new ContentAnalyzer(); + customAnalyzer.setBaseAnalyzerId("prebuilt-document"); + customAnalyzer.setDescription("Custom analyzer for extracting company information"); + customAnalyzer.setConfig(config); + customAnalyzer.setFieldSchema(fieldSchema); + + Map models = new HashMap<>(); + models.put("completion", "gpt-4.1"); + models.put("embedding", "text-embedding-3-large"); + customAnalyzer.setModels(models); + + contentUnderstandingClient.beginCreateAnalyzer(analyzerId, customAnalyzer).getFinalResult(); + createdAnalyzerId = analyzerId; // Track for cleanup + + try { + // BEGIN:ContentUnderstandingUseCustomAnalyzer + // Using a publicly accessible sample file from Azure-Samples GitHub repository + String documentUrl + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-dotnet/main/ContentUnderstanding.Common/data/invoice.pdf"; + + AnalysisInput input = new AnalysisInput(); + input.setUrl(documentUrl); + + // Analyze a document using the custom analyzer + SyncPoller analyzeOperation + = contentUnderstandingClient.beginAnalyze(analyzerId, Arrays.asList(input)); + + AnalysisResult AnalysisResult = analyzeOperation.getFinalResult(); + + // Extract custom fields from the result + // Since EstimateFieldSourceAndConfidence is enabled, we can access confidence scores and source information + if (AnalysisResult.getContents() != null + && !AnalysisResult.getContents().isEmpty() + && AnalysisResult.getContents().get(0) instanceof DocumentContent) { + DocumentContent content = (DocumentContent) AnalysisResult.getContents().get(0); + + // Extract field (literal text extraction) + ContentField companyNameField + = content.getFields() != null ? content.getFields().get("company_name") : null; + if (companyNameField instanceof StringField) { + StringField sf = (StringField) companyNameField; + String companyName = sf.getValueString(); + System.out + .println("Company Name (extract): " + (companyName != null ? companyName : "(not found)")); + System.out.println(" Confidence: " + (companyNameField.getConfidence() != null + ? String.format("%.2f", companyNameField.getConfidence()) + : "N/A")); + System.out.println(" Source: " + (companyNameField.getSources() != null + ? ContentSource.toRawString(companyNameField.getSources()) + : "N/A")); + List spans = companyNameField.getSpans(); + if (spans != null && !spans.isEmpty()) { + ContentSpan span = spans.get(0); + System.out.println( + " Position in markdown: offset=" + span.getOffset() + ", length=" + span.getLength()); + } + } + + // Extract field (literal text extraction) + ContentField totalAmountField + = content.getFields() != null ? content.getFields().get("total_amount") : null; + if (totalAmountField instanceof NumberField) { + NumberField nf = (NumberField) totalAmountField; + Double totalAmount = nf.getValueNumber(); + System.out.println("Total Amount (extract): " + + (totalAmount != null ? String.format("%.2f", totalAmount) : "(not found)")); + System.out.println(" Confidence: " + (totalAmountField.getConfidence() != null + ? String.format("%.2f", totalAmountField.getConfidence()) + : "N/A")); + System.out.println(" Source: " + (totalAmountField.getSources() != null + ? ContentSource.toRawString(totalAmountField.getSources()) + : "N/A")); + List spans = totalAmountField.getSpans(); + if (spans != null && !spans.isEmpty()) { + ContentSpan span = spans.get(0); + System.out.println( + " Position in markdown: offset=" + span.getOffset() + ", length=" + span.getLength()); + } + } + + // Generate field (AI-generated value) + ContentField summaryField + = content.getFields() != null ? content.getFields().get("document_summary") : null; + if (summaryField instanceof StringField) { + StringField sf = (StringField) summaryField; + String summary = sf.getValueString(); + System.out.println("Document Summary (generate): " + (summary != null ? summary : "(not found)")); + System.out.println(" Confidence: " + (summaryField.getConfidence() != null + ? String.format("%.2f", summaryField.getConfidence()) + : "N/A")); + // Note: Generated fields may not have source information + if (summaryField.getSources() != null && !summaryField.getSources().isEmpty()) { + System.out.println(" Source: " + ContentSource.toRawString(summaryField.getSources())); + } + } + + // Classify field (classification against predefined categories) + ContentField documentTypeField + = content.getFields() != null ? content.getFields().get("document_type") : null; + if (documentTypeField instanceof StringField) { + StringField sf = (StringField) documentTypeField; + String documentType = sf.getValueString(); + System.out + .println("Document Type (classify): " + (documentType != null ? documentType : "(not found)")); + System.out.println(" Confidence: " + (documentTypeField.getConfidence() != null + ? String.format("%.2f", documentTypeField.getConfidence()) + : "N/A")); + // Note: Classified fields may not have source information + if (documentTypeField.getSources() != null && !documentTypeField.getSources().isEmpty()) { + System.out.println(" Source: " + ContentSource.toRawString(documentTypeField.getSources())); + } + } + } + // END:ContentUnderstandingUseCustomAnalyzer + + // BEGIN:Assertion_ContentUnderstandingUseCustomAnalyzer + assertNotNull(documentUrl, "Document URL should not be null"); + assertNotNull(analyzeOperation, "Analyze operation should not be null"); + assertTrue(analyzeOperation.waitForCompletion().getStatus().isComplete(), "Operation should be completed"); + System.out.println("Analyze operation properties verified"); + + assertNotNull(AnalysisResult, "Analyze result should not be null"); + assertNotNull(AnalysisResult.getContents(), "Result should contain contents"); + assertTrue(AnalysisResult.getContents().size() > 0, "Result should have at least one content"); + assertEquals(1, AnalysisResult.getContents().size(), "Result should have exactly one content element"); + System.out.println("Analysis result contains " + AnalysisResult.getContents().size() + " content(s)"); + + DocumentContent documentContent = AnalysisResult.getContents().get(0) instanceof DocumentContent + ? (DocumentContent) AnalysisResult.getContents().get(0) + : null; + assertNotNull(documentContent, "Content should be DocumentContent"); + assertNotNull(documentContent.getFields(), "Document content should have fields"); + System.out.println("Document content has custom fields"); + + // Verify company_name field (Extract method) + ContentField companyNameFieldAssert + = documentContent.getFields() != null ? documentContent.getFields().get("company_name") : null; + if (companyNameFieldAssert != null) { + System.out.println("company_name field found"); + assertTrue(companyNameFieldAssert instanceof StringField, "company_name should be a StringField"); + + if (companyNameFieldAssert instanceof StringField) { + StringField cnf = (StringField) companyNameFieldAssert; + if (cnf.getValueString() != null && !cnf.getValueString().trim().isEmpty()) { + System.out.println(" Value: " + cnf.getValueString()); + } + } + + if (companyNameFieldAssert.getConfidence() != null) { + assertTrue( + companyNameFieldAssert.getConfidence() >= 0 && companyNameFieldAssert.getConfidence() <= 1, + "company_name confidence should be between 0 and 1, but was " + + companyNameFieldAssert.getConfidence()); + System.out + .println(" Confidence: " + String.format("%.2f", companyNameFieldAssert.getConfidence())); + } + + if (companyNameFieldAssert.getSources() != null && !companyNameFieldAssert.getSources().isEmpty()) { + assertTrue(ContentSource.toRawString(companyNameFieldAssert.getSources()).startsWith("D("), + "Source should start with 'D(' for extracted fields"); + System.out.println(" Source: " + ContentSource.toRawString(companyNameFieldAssert.getSources())); + } + + List spans = companyNameFieldAssert.getSpans(); + if (spans != null && !spans.isEmpty()) { + assertTrue(spans.size() > 0, "Spans should not be empty when not null"); + for (ContentSpan span : spans) { + assertTrue(span.getOffset() >= 0, "Span offset should be >= 0, but was " + span.getOffset()); + assertTrue(span.getLength() > 0, "Span length should be > 0, but was " + span.getLength()); + } + System.out.println(" Spans: " + spans.size() + " span(s)"); + } + } else { + System.out.println("⚠️ company_name field not found"); + } + + System.out.println("All custom analyzer usage properties validated successfully"); + // END:Assertion_ContentUnderstandingUseCustomAnalyzer + } finally { + // Cleanup is handled by @AfterEach + } + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample04_CreateAnalyzerAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample04_CreateAnalyzerAsync.java new file mode 100644 index 000000000000..90084e34f5b2 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample04_CreateAnalyzerAsync.java @@ -0,0 +1,500 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.AnalysisInput; +import com.azure.ai.contentunderstanding.models.AnalysisResult; +import com.azure.ai.contentunderstanding.models.ContentAnalyzer; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerConfig; + +import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentFieldDefinition; +import com.azure.ai.contentunderstanding.models.ContentFieldSchema; +import com.azure.ai.contentunderstanding.models.ContentFieldType; +import com.azure.ai.contentunderstanding.models.DocumentContent; +import com.azure.ai.contentunderstanding.models.ContentField; +import com.azure.ai.contentunderstanding.models.ContentSource; +import com.azure.ai.contentunderstanding.models.ContentSpan; +import com.azure.ai.contentunderstanding.models.GenerationMethod; +import com.azure.ai.contentunderstanding.models.NumberField; +import com.azure.ai.contentunderstanding.models.StringField; +import com.azure.core.util.polling.PollerFlux; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Async sample demonstrating how to create a custom analyzer with field schema. + * This sample shows: + * 1. Defining a field schema with custom fields + * 2. Demonstrating three extraction methods: Extract, Generate, Classify + * 3. Creating a custom analyzer with configuration asynchronously + * 4. Using the custom analyzer to analyze documents + */ +public class Sample04_CreateAnalyzerAsync extends ContentUnderstandingClientTestBase { + + private String createdAnalyzerId; + + @AfterEach + public void cleanup() { + if (createdAnalyzerId != null) { + try { + contentUnderstandingAsyncClient.deleteAnalyzer(createdAnalyzerId).block(); + System.out.println("Analyzer '" + createdAnalyzerId + "' deleted successfully."); + } catch (Exception e) { + // Ignore cleanup errors + } + } + } + + @Test + public void testCreateAnalyzerAsync() { + + // BEGIN:ContentUnderstandingCreateAnalyzerAsync + // Generate a unique analyzer ID + String analyzerId = testResourceNamer.randomName("my_custom_analyzer_", 50); + + // Define field schema with custom fields + // This example demonstrates three extraction methods: + // - extract: Literal text extraction (requires estimateSourceAndConfidence) + // - generate: AI-generated values based on content interpretation + // - classify: Classification against predefined categories + Map fields = new HashMap<>(); + + ContentFieldDefinition companyNameDef = new ContentFieldDefinition(); + companyNameDef.setType(ContentFieldType.STRING); + companyNameDef.setMethod(GenerationMethod.EXTRACT); + companyNameDef.setDescription("Name of the company"); + fields.put("company_name", companyNameDef); + + ContentFieldDefinition totalAmountDef = new ContentFieldDefinition(); + totalAmountDef.setType(ContentFieldType.NUMBER); + totalAmountDef.setMethod(GenerationMethod.EXTRACT); + totalAmountDef.setDescription("Total amount on the document"); + fields.put("total_amount", totalAmountDef); + + ContentFieldDefinition summaryDef = new ContentFieldDefinition(); + summaryDef.setType(ContentFieldType.STRING); + summaryDef.setMethod(GenerationMethod.GENERATE); + summaryDef.setDescription("A brief summary of the document content"); + fields.put("document_summary", summaryDef); + + ContentFieldDefinition documentTypeDef = new ContentFieldDefinition(); + documentTypeDef.setType(ContentFieldType.STRING); + documentTypeDef.setMethod(GenerationMethod.CLASSIFY); + documentTypeDef.setDescription("Type of document"); + documentTypeDef.setEnumProperty(Arrays.asList("invoice", "receipt", "contract", "report", "other")); + fields.put("document_type", documentTypeDef); + + ContentFieldSchema fieldSchema = new ContentFieldSchema(); + fieldSchema.setName("company_schema"); + fieldSchema.setDescription("Schema for extracting company information"); + fieldSchema.setFields(fields); + + // Create the custom analyzer with configuration + Map models = new HashMap<>(); + models.put("completion", "gpt-4.1"); + models.put("embedding", "text-embedding-3-large"); + + ContentAnalyzer customAnalyzer = new ContentAnalyzer().setBaseAnalyzerId("prebuilt-document") + .setDescription("Custom analyzer for extracting company information") + .setConfig(new ContentAnalyzerConfig().setOcrEnabled(true) + .setLayoutEnabled(true) + .setFormulaEnabled(true) + .setEstimateFieldSourceAndConfidence(true) + .setReturnDetails(true)) + .setFieldSchema(fieldSchema) + .setModels(models); + + // Create the analyzer + PollerFlux operation + = contentUnderstandingAsyncClient.beginCreateAnalyzer(analyzerId, customAnalyzer, true); + + // Use reactive pattern: chain operations using flatMap + // In a real application, you would use subscribe() instead of block() + ContentAnalyzer 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())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + + System.out.println("Analyzer '" + analyzerId + "' created successfully!"); + if (result.getDescription() != null && !result.getDescription().trim().isEmpty()) { + System.out.println(" Description: " + result.getDescription()); + } + + if (result.getFieldSchema() != null && result.getFieldSchema().getFields() != null) { + System.out.println(" Fields (" + result.getFieldSchema().getFields().size() + "):"); + result.getFieldSchema().getFields().forEach((fieldName, fieldDef) -> { + String method = fieldDef.getMethod() != null ? fieldDef.getMethod().toString() : "auto"; + String type = fieldDef.getType() != null ? fieldDef.getType().toString() : "unknown"; + System.out.println(" - " + fieldName + ": " + type + " (" + method + ")"); + }); + } + // END:ContentUnderstandingCreateAnalyzerAsync + + createdAnalyzerId = analyzerId; // Track for cleanup + + // BEGIN:Assertion_ContentUnderstandingCreateAnalyzerAsync + assertNotNull(analyzerId, "Analyzer ID should not be null"); + assertFalse(analyzerId.trim().isEmpty(), "Analyzer ID should not be empty"); + assertNotNull(fieldSchema, "Field schema should not be null"); + assertNotNull(customAnalyzer, "Custom analyzer should not be null"); + assertNotNull(operation, "Create analyzer operation should not be null"); + assertNotNull(result, "Analyzer result should not be null"); + System.out.println("Create analyzer operation properties verified"); + System.out.println("Analyzer '" + analyzerId + "' created successfully"); + + // Verify base analyzer + assertNotNull(result.getBaseAnalyzerId(), "Base analyzer ID should not be null"); + assertEquals("prebuilt-document", result.getBaseAnalyzerId(), "Base analyzer ID should match"); + System.out.println("Base analyzer ID verified: " + result.getBaseAnalyzerId()); + + // Verify analyzer config + assertNotNull(result.getConfig(), "Analyzer config should not be null"); + assertTrue(result.getConfig().isFormulaEnabled(), "EnableFormula should be true"); + assertTrue(result.getConfig().isLayoutEnabled(), "EnableLayout should be true"); + assertTrue(result.getConfig().isOcrEnabled(), "EnableOcr should be true"); + assertTrue(result.getConfig().isEstimateFieldSourceAndConfidence(), + "EstimateFieldSourceAndConfidence should be true"); + assertTrue(result.getConfig().isReturnDetails(), "ReturnDetails should be true"); + System.out.println("Analyzer config verified"); + + // Verify field schema + assertNotNull(result.getFieldSchema(), "Field schema should not be null"); + assertFalse(result.getFieldSchema().getName().trim().isEmpty(), "Field schema name should not be empty"); + assertEquals("company_schema", result.getFieldSchema().getName(), "Field schema name should match"); + assertFalse(result.getFieldSchema().getDescription().trim().isEmpty(), + "Field schema description should not be empty"); + System.out.println("Field schema verified: " + result.getFieldSchema().getName()); + + // Verify field schema fields + assertNotNull(result.getFieldSchema().getFields(), "Field schema fields should not be null"); + assertEquals(4, result.getFieldSchema().getFields().size(), "Should have 4 custom fields"); + System.out.println("Field schema contains " + result.getFieldSchema().getFields().size() + " fields"); + + // Verify company_name field + assertTrue(result.getFieldSchema().getFields().containsKey("company_name"), + "Should contain company_name field"); + ContentFieldDefinition companyNameDefResult = result.getFieldSchema().getFields().get("company_name"); + assertEquals(ContentFieldType.STRING, companyNameDefResult.getType(), "company_name should be String type"); + assertEquals(GenerationMethod.EXTRACT, companyNameDefResult.getMethod(), + "company_name should use Extract method"); + assertFalse(companyNameDefResult.getDescription().trim().isEmpty(), "company_name should have description"); + System.out.println(" company_name field verified (String, Extract)"); + + // Verify total_amount field + assertTrue(result.getFieldSchema().getFields().containsKey("total_amount"), + "Should contain total_amount field"); + ContentFieldDefinition totalAmountDefResult = result.getFieldSchema().getFields().get("total_amount"); + assertEquals(ContentFieldType.NUMBER, totalAmountDefResult.getType(), "total_amount should be Number type"); + assertEquals(GenerationMethod.EXTRACT, totalAmountDefResult.getMethod(), + "total_amount should use Extract method"); + assertFalse(totalAmountDefResult.getDescription().trim().isEmpty(), "total_amount should have description"); + System.out.println(" total_amount field verified (Number, Extract)"); + + // Verify document_summary field + assertTrue(result.getFieldSchema().getFields().containsKey("document_summary"), + "Should contain document_summary field"); + ContentFieldDefinition summaryDefResult = result.getFieldSchema().getFields().get("document_summary"); + assertEquals(ContentFieldType.STRING, summaryDefResult.getType(), "document_summary should be String type"); + assertEquals(GenerationMethod.GENERATE, summaryDefResult.getMethod(), + "document_summary should use Generate method"); + assertFalse(summaryDefResult.getDescription().trim().isEmpty(), "document_summary should have description"); + System.out.println(" document_summary field verified (String, Generate)"); + + // Verify document_type field + assertTrue(result.getFieldSchema().getFields().containsKey("document_type"), + "Should contain document_type field"); + ContentFieldDefinition documentTypeDefResult = result.getFieldSchema().getFields().get("document_type"); + assertEquals(ContentFieldType.STRING, documentTypeDefResult.getType(), "document_type should be String type"); + assertEquals(GenerationMethod.CLASSIFY, documentTypeDefResult.getMethod(), + "document_type should use Classify method"); + assertFalse(documentTypeDefResult.getDescription().trim().isEmpty(), "document_type should have description"); + assertNotNull(documentTypeDefResult.getEnumProperty(), "document_type should have enum values"); + assertEquals(5, documentTypeDefResult.getEnumProperty().size(), "document_type should have 5 enum values"); + assertTrue(documentTypeDefResult.getEnumProperty().contains("invoice"), + "document_type enum should contain 'invoice'"); + assertTrue(documentTypeDefResult.getEnumProperty().contains("receipt"), + "document_type enum should contain 'receipt'"); + assertTrue(documentTypeDefResult.getEnumProperty().contains("contract"), + "document_type enum should contain 'contract'"); + assertTrue(documentTypeDefResult.getEnumProperty().contains("report"), + "document_type enum should contain 'report'"); + assertTrue(documentTypeDefResult.getEnumProperty().contains("other"), + "document_type enum should contain 'other'"); + System.out.println(" document_type field verified (String, Classify, 5 enum values)"); + + // Verify models + assertNotNull(result.getModels(), "Models should not be null"); + assertTrue(result.getModels().size() >= 2, "Should have at least 2 model mappings"); + assertTrue(result.getModels().containsKey("completion"), "Should contain 'completion' model mapping"); + assertTrue(result.getModels().containsKey("embedding"), "Should contain 'embedding' model mapping"); + assertEquals("gpt-4.1", result.getModels().get("completion"), "Completion model should be 'gpt-4.1'"); + assertEquals("text-embedding-3-large", result.getModels().get("embedding"), + "Embedding model should be 'text-embedding-3-large'"); + System.out.println("Model mappings verified: " + result.getModels().size() + " model(s)"); + + // Verify description + if (result.getDescription() != null && !result.getDescription().trim().isEmpty()) { + System.out.println("Analyzer description: " + result.getDescription()); + } + + System.out.println("All analyzer creation properties validated successfully"); + // END:Assertion_ContentUnderstandingCreateAnalyzerAsync + } + + @Test + public void testUseCustomAnalyzerAsync() { + // First create an analyzer + String analyzerId = testResourceNamer.randomName("test_analyzer_", 50); + + Map fields = new HashMap<>(); + + ContentFieldDefinition companyNameDef = new ContentFieldDefinition(); + companyNameDef.setType(ContentFieldType.STRING); + companyNameDef.setMethod(GenerationMethod.EXTRACT); + companyNameDef.setDescription("Name of the company"); + fields.put("company_name", companyNameDef); + + ContentFieldDefinition totalAmountDef = new ContentFieldDefinition(); + totalAmountDef.setType(ContentFieldType.NUMBER); + totalAmountDef.setMethod(GenerationMethod.EXTRACT); + totalAmountDef.setDescription("Total amount on the document"); + fields.put("total_amount", totalAmountDef); + + ContentFieldDefinition summaryDef = new ContentFieldDefinition(); + summaryDef.setType(ContentFieldType.STRING); + summaryDef.setMethod(GenerationMethod.GENERATE); + summaryDef.setDescription("A brief summary of the document content"); + fields.put("document_summary", summaryDef); + + ContentFieldDefinition documentTypeDef = new ContentFieldDefinition(); + documentTypeDef.setType(ContentFieldType.STRING); + documentTypeDef.setMethod(GenerationMethod.CLASSIFY); + documentTypeDef.setDescription("Type of document"); + documentTypeDef.setEnumProperty(Arrays.asList("invoice", "receipt", "contract", "report", "other")); + fields.put("document_type", documentTypeDef); + + ContentFieldSchema fieldSchema = new ContentFieldSchema(); + fieldSchema.setName("company_schema"); + fieldSchema.setDescription("Schema for extracting company information"); + fieldSchema.setFields(fields); + + ContentAnalyzerConfig config = new ContentAnalyzerConfig(); + config.setFormulaEnabled(true); + config.setLayoutEnabled(true); + config.setOcrEnabled(true); + + ContentAnalyzer customAnalyzer = new ContentAnalyzer(); + customAnalyzer.setBaseAnalyzerId("prebuilt-document"); + customAnalyzer.setDescription("Custom analyzer for extracting company information"); + customAnalyzer.setConfig(config); + customAnalyzer.setFieldSchema(fieldSchema); + + Map models = new HashMap<>(); + models.put("completion", "gpt-4.1"); + models.put("embedding", "text-embedding-3-large"); + customAnalyzer.setModels(models); + + // Use reactive pattern: chain operations using flatMap + // In a real application, you would use subscribe() instead of block() + contentUnderstandingAsyncClient.beginCreateAnalyzer(analyzerId, customAnalyzer).last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + createdAnalyzerId = analyzerId; // Track for cleanup + + try { + // BEGIN:ContentUnderstandingUseCustomAnalyzerAsync + // Using a publicly accessible sample file from Azure-Samples GitHub repository + String documentUrl + = "https://raw.githubusercontent.com/Azure-Samples/azure-ai-content-understanding-dotnet/main/ContentUnderstanding.Common/data/invoice.pdf"; + + AnalysisInput input = new AnalysisInput(); + input.setUrl(documentUrl); + + // Analyze a document using the custom analyzer + PollerFlux analyzeOperation + = contentUnderstandingAsyncClient.beginAnalyze(analyzerId, Arrays.asList(input)); + + // Use reactive pattern: chain operations using flatMap + // In a real application, you would use subscribe() instead of block() + AnalysisResult AnalysisResult = analyzeOperation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + + // Extract custom fields from the result + // Since EstimateFieldSourceAndConfidence is enabled, we can access confidence scores and source information + if (AnalysisResult.getContents() != null + && !AnalysisResult.getContents().isEmpty() + && AnalysisResult.getContents().get(0) instanceof DocumentContent) { + DocumentContent content = (DocumentContent) AnalysisResult.getContents().get(0); + + // Extract field (literal text extraction) + ContentField companyNameField + = content.getFields() != null ? content.getFields().get("company_name") : null; + if (companyNameField instanceof StringField) { + StringField sf = (StringField) companyNameField; + String companyName = sf.getValueString(); + System.out + .println("Company Name (extract): " + (companyName != null ? companyName : "(not found)")); + System.out.println(" Confidence: " + (companyNameField.getConfidence() != null + ? String.format("%.2f", companyNameField.getConfidence()) + : "N/A")); + System.out.println(" Source: " + (companyNameField.getSources() != null + ? ContentSource.toRawString(companyNameField.getSources()) + : "N/A")); + List spans = companyNameField.getSpans(); + if (spans != null && !spans.isEmpty()) { + ContentSpan span = spans.get(0); + System.out.println( + " Position in markdown: offset=" + span.getOffset() + ", length=" + span.getLength()); + } + } + + // Extract field (literal text extraction) + ContentField totalAmountField + = content.getFields() != null ? content.getFields().get("total_amount") : null; + if (totalAmountField instanceof NumberField) { + NumberField nf = (NumberField) totalAmountField; + Double totalAmount = nf.getValueNumber(); + System.out.println("Total Amount (extract): " + + (totalAmount != null ? String.format("%.2f", totalAmount) : "(not found)")); + System.out.println(" Confidence: " + (totalAmountField.getConfidence() != null + ? String.format("%.2f", totalAmountField.getConfidence()) + : "N/A")); + System.out.println(" Source: " + (totalAmountField.getSources() != null + ? ContentSource.toRawString(totalAmountField.getSources()) + : "N/A")); + List spans = totalAmountField.getSpans(); + if (spans != null && !spans.isEmpty()) { + ContentSpan span = spans.get(0); + System.out.println( + " Position in markdown: offset=" + span.getOffset() + ", length=" + span.getLength()); + } + } + + // Generate field (AI-generated value) + ContentField summaryField + = content.getFields() != null ? content.getFields().get("document_summary") : null; + if (summaryField instanceof StringField) { + StringField sf = (StringField) summaryField; + String summary = sf.getValueString(); + System.out.println("Document Summary (generate): " + (summary != null ? summary : "(not found)")); + System.out.println(" Confidence: " + (summaryField.getConfidence() != null + ? String.format("%.2f", summaryField.getConfidence()) + : "N/A")); + // Note: Generated fields may not have source information + if (summaryField.getSources() != null && !summaryField.getSources().isEmpty()) { + System.out.println(" Source: " + ContentSource.toRawString(summaryField.getSources())); + } + } + + // Classify field (classification against predefined categories) + ContentField documentTypeField + = content.getFields() != null ? content.getFields().get("document_type") : null; + if (documentTypeField instanceof StringField) { + StringField sf = (StringField) documentTypeField; + String documentType = sf.getValueString(); + System.out + .println("Document Type (classify): " + (documentType != null ? documentType : "(not found)")); + System.out.println(" Confidence: " + (documentTypeField.getConfidence() != null + ? String.format("%.2f", documentTypeField.getConfidence()) + : "N/A")); + // Note: Classified fields may not have source information + if (documentTypeField.getSources() != null && !documentTypeField.getSources().isEmpty()) { + System.out.println(" Source: " + ContentSource.toRawString(documentTypeField.getSources())); + } + } + } + // END:ContentUnderstandingUseCustomAnalyzerAsync + + // BEGIN:Assertion_ContentUnderstandingUseCustomAnalyzerAsync + assertNotNull(documentUrl, "Document URL should not be null"); + assertNotNull(analyzeOperation, "Analyze operation should not be null"); + assertNotNull(AnalysisResult, "Analyze result should not be null"); + assertNotNull(AnalysisResult.getContents(), "Result should contain contents"); + assertTrue(AnalysisResult.getContents().size() > 0, "Result should have at least one content"); + assertEquals(1, AnalysisResult.getContents().size(), "Result should have exactly one content element"); + System.out.println("Analyze operation properties verified"); + System.out.println("Analysis result contains " + AnalysisResult.getContents().size() + " content(s)"); + + DocumentContent documentContent = AnalysisResult.getContents().get(0) instanceof DocumentContent + ? (DocumentContent) AnalysisResult.getContents().get(0) + : null; + assertNotNull(documentContent, "Content should be DocumentContent"); + assertNotNull(documentContent.getFields(), "Document content should have fields"); + System.out.println("Document content has custom fields"); + + // Verify company_name field (Extract method) + ContentField companyNameFieldAssert + = documentContent.getFields() != null ? documentContent.getFields().get("company_name") : null; + if (companyNameFieldAssert != null) { + System.out.println("company_name field found"); + assertTrue(companyNameFieldAssert instanceof StringField, "company_name should be a StringField"); + + if (companyNameFieldAssert instanceof StringField) { + StringField cnf = (StringField) companyNameFieldAssert; + if (cnf.getValueString() != null && !cnf.getValueString().trim().isEmpty()) { + System.out.println(" Value: " + cnf.getValueString()); + } + } + + if (companyNameFieldAssert.getConfidence() != null) { + assertTrue( + companyNameFieldAssert.getConfidence() >= 0 && companyNameFieldAssert.getConfidence() <= 1, + "company_name confidence should be between 0 and 1, but was " + + companyNameFieldAssert.getConfidence()); + System.out + .println(" Confidence: " + String.format("%.2f", companyNameFieldAssert.getConfidence())); + } + + if (companyNameFieldAssert.getSources() != null && !companyNameFieldAssert.getSources().isEmpty()) { + assertTrue(ContentSource.toRawString(companyNameFieldAssert.getSources()).startsWith("D("), + "Source should start with 'D(' for extracted fields"); + System.out.println(" Source: " + ContentSource.toRawString(companyNameFieldAssert.getSources())); + } + + List spans = companyNameFieldAssert.getSpans(); + if (spans != null && !spans.isEmpty()) { + assertTrue(spans.size() > 0, "Spans should not be empty when not null"); + for (ContentSpan span : spans) { + assertTrue(span.getOffset() >= 0, "Span offset should be >= 0, but was " + span.getOffset()); + assertTrue(span.getLength() > 0, "Span length should be > 0, but was " + span.getLength()); + } + System.out.println(" Spans: " + spans.size() + " span(s)"); + } + } else { + System.out.println("⚠️ company_name field not found"); + } + + System.out.println("All custom analyzer usage properties validated successfully"); + // END:Assertion_ContentUnderstandingUseCustomAnalyzerAsync + } finally { + // Cleanup is handled by @AfterEach + } + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample04_CreateAnalyzerAsyncTest.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample04_CreateAnalyzerAsyncTest.java index 65756d680b01..28ed684e9487 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample04_CreateAnalyzerAsyncTest.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample04_CreateAnalyzerAsyncTest.java @@ -340,7 +340,7 @@ public void testUseCustomAnalyzerAsync() { // Use reactive pattern: chain operations using flatMap // In a real application, you would use subscribe() instead of block() - AnalysisResult analyzeResult = analyzeOperation.last().flatMap(pollResponse -> { + AnalysisResult AnalysisResult = analyzeOperation.last().flatMap(pollResponse -> { if (pollResponse.getStatus().isComplete()) { return pollResponse.getFinalResult(); } else { @@ -351,10 +351,10 @@ public void testUseCustomAnalyzerAsync() { // Extract custom fields from the result // Since EstimateFieldSourceAndConfidence is enabled, we can access confidence scores and source information - if (analyzeResult.getContents() != null - && !analyzeResult.getContents().isEmpty() - && analyzeResult.getContents().get(0) instanceof DocumentContent) { - DocumentContent content = (DocumentContent) analyzeResult.getContents().get(0); + if (AnalysisResult.getContents() != null + && !AnalysisResult.getContents().isEmpty() + && AnalysisResult.getContents().get(0) instanceof DocumentContent) { + DocumentContent content = (DocumentContent) AnalysisResult.getContents().get(0); // Extract field (literal text extraction) ContentField companyNameField @@ -436,15 +436,15 @@ public void testUseCustomAnalyzerAsync() { // BEGIN:Assertion_ContentUnderstandingUseCustomAnalyzerAsync assertNotNull(documentUrl, "Document URL should not be null"); assertNotNull(analyzeOperation, "Analyze operation should not be null"); - assertNotNull(analyzeResult, "Analyze result should not be null"); - assertNotNull(analyzeResult.getContents(), "Result should contain contents"); - assertTrue(analyzeResult.getContents().size() > 0, "Result should have at least one content"); - assertEquals(1, analyzeResult.getContents().size(), "Result should have exactly one content element"); + assertNotNull(AnalysisResult, "Analyze result should not be null"); + assertNotNull(AnalysisResult.getContents(), "Result should contain contents"); + assertTrue(AnalysisResult.getContents().size() > 0, "Result should have at least one content"); + assertEquals(1, AnalysisResult.getContents().size(), "Result should have exactly one content element"); System.out.println("Analyze operation properties verified"); - System.out.println("Analysis result contains " + analyzeResult.getContents().size() + " content(s)"); + System.out.println("Analysis result contains " + AnalysisResult.getContents().size() + " content(s)"); - DocumentContent documentContent = analyzeResult.getContents().get(0) instanceof DocumentContent - ? (DocumentContent) analyzeResult.getContents().get(0) + DocumentContent documentContent = AnalysisResult.getContents().get(0) instanceof DocumentContent + ? (DocumentContent) AnalysisResult.getContents().get(0) : null; assertNotNull(documentContent, "Content should be DocumentContent"); assertNotNull(documentContent.getFields(), "Document content should have fields"); diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample04_CreateAnalyzerTest.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample04_CreateAnalyzerTest.java index 2922b96671b0..2777674eb130 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample04_CreateAnalyzerTest.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample04_CreateAnalyzerTest.java @@ -321,14 +321,14 @@ public void testUseCustomAnalyzer() { SyncPoller analyzeOperation = contentUnderstandingClient.beginAnalyze(analyzerId, Arrays.asList(input)); - AnalysisResult analyzeResult = analyzeOperation.getFinalResult(); + AnalysisResult AnalysisResult = analyzeOperation.getFinalResult(); // Extract custom fields from the result // Since EstimateFieldSourceAndConfidence is enabled, we can access confidence scores and source information - if (analyzeResult.getContents() != null - && !analyzeResult.getContents().isEmpty() - && analyzeResult.getContents().get(0) instanceof DocumentContent) { - DocumentContent content = (DocumentContent) analyzeResult.getContents().get(0); + if (AnalysisResult.getContents() != null + && !AnalysisResult.getContents().isEmpty() + && AnalysisResult.getContents().get(0) instanceof DocumentContent) { + DocumentContent content = (DocumentContent) AnalysisResult.getContents().get(0); // Extract field (literal text extraction) ContentField companyNameField @@ -413,14 +413,14 @@ public void testUseCustomAnalyzer() { assertTrue(analyzeOperation.waitForCompletion().getStatus().isComplete(), "Operation should be completed"); System.out.println("Analyze operation properties verified"); - assertNotNull(analyzeResult, "Analyze result should not be null"); - assertNotNull(analyzeResult.getContents(), "Result should contain contents"); - assertTrue(analyzeResult.getContents().size() > 0, "Result should have at least one content"); - assertEquals(1, analyzeResult.getContents().size(), "Result should have exactly one content element"); - System.out.println("Analysis result contains " + analyzeResult.getContents().size() + " content(s)"); + assertNotNull(AnalysisResult, "Analyze result should not be null"); + assertNotNull(AnalysisResult.getContents(), "Result should contain contents"); + assertTrue(AnalysisResult.getContents().size() > 0, "Result should have at least one content"); + assertEquals(1, AnalysisResult.getContents().size(), "Result should have exactly one content element"); + System.out.println("Analysis result contains " + AnalysisResult.getContents().size() + " content(s)"); - DocumentContent documentContent = analyzeResult.getContents().get(0) instanceof DocumentContent - ? (DocumentContent) analyzeResult.getContents().get(0) + DocumentContent documentContent = AnalysisResult.getContents().get(0) instanceof DocumentContent + ? (DocumentContent) AnalysisResult.getContents().get(0) : null; assertNotNull(documentContent, "Content should be DocumentContent"); assertNotNull(documentContent.getFields(), "Document content should have fields"); diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample05_CreateClassifier.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample05_CreateClassifier.java new file mode 100644 index 000000000000..9a14c71589b9 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample05_CreateClassifier.java @@ -0,0 +1,181 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.ContentAnalyzer; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerConfig; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentCategoryDefinition; +import com.azure.core.util.polling.SyncPoller; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * Sample demonstrating how to create a classifier analyzer. + * + * This sample shows how to create a classifier that categorizes documents into predefined + * custom categories using ContentCategories. Classifiers are useful for: + * - Content organization: Organize large document collections by type through categorization + * - Data routing (optional): Route data to specific custom analyzers based on category + * - Multi-document processing: Process files containing multiple document types by automatically + * segmenting them + */ +public class Sample05_CreateClassifier extends ContentUnderstandingClientTestBase { + + private String createdAnalyzerId; + + @AfterEach + public void cleanup() { + if (createdAnalyzerId != null) { + try { + contentUnderstandingClient.deleteAnalyzer(createdAnalyzerId); + System.out.println("Classifier analyzer '" + createdAnalyzerId + "' deleted successfully."); + } catch (Exception e) { + // Ignore cleanup errors + } + } + } + + @Test + public void testCreateClassifier() { + + // BEGIN:ContentUnderstandingCreateClassifier + // Generate a unique classifier analyzer ID + String analyzerId = testResourceNamer.randomName("document_classifier_", 50); + + System.out.println("Creating classifier analyzer '" + analyzerId + "'..."); + + // Define content categories for classification + // Each category has a description that helps the AI model understand what documents belong to it + Map categories = new HashMap<>(); + + categories.put("Loan_Application", + new ContentCategoryDefinition() + .setDescription("Documents submitted by individuals or businesses to request funding, " + + "typically including personal or business details, financial history, loan amount, " + + "purpose, and supporting documentation.")); + + categories.put("Invoice", + new ContentCategoryDefinition() + .setDescription("Billing documents issued by sellers or service providers to request payment " + + "for goods or services, detailing items, prices, taxes, totals, and payment terms.")); + + categories.put("Bank_Statement", + new ContentCategoryDefinition() + .setDescription("Official statements issued by banks that summarize account activity over a period, " + + "including deposits, withdrawals, fees, and balances.")); + + // Create analyzer configuration with content categories + ContentAnalyzerConfig config = new ContentAnalyzerConfig().setReturnDetails(true) + .setSegmentEnabled(true) // Enable automatic segmentation by category + .setContentCategories(categories); + + // Create the classifier analyzer + // Note: models are specified using model names, not deployment names + Map models = new HashMap<>(); + models.put("completion", "gpt-4.1"); + + ContentAnalyzer classifier = new ContentAnalyzer().setBaseAnalyzerId("prebuilt-document") + .setDescription("Custom classifier for financial document categorization") + .setConfig(config) + .setModels(models); + + // Create the classifier + SyncPoller operation + = contentUnderstandingClient.beginCreateAnalyzer(analyzerId, classifier, true); + + ContentAnalyzer result = operation.getFinalResult(); + System.out.println("Classifier '" + analyzerId + "' created successfully!"); + // END:ContentUnderstandingCreateClassifier + + createdAnalyzerId = analyzerId; // Track for cleanup + + // BEGIN:Assertion_ContentUnderstandingCreateClassifier + // Verify basic properties + assertNotNull(analyzerId, "Analyzer ID should not be null"); + assertFalse(analyzerId.trim().isEmpty(), "Analyzer ID should not be empty"); + assertNotNull(operation, "Create analyzer operation should not be null"); + assertTrue(operation.waitForCompletion().getStatus().isComplete(), "Operation should be completed"); + System.out.println("✓ Create classifier operation completed successfully"); + + assertNotNull(result, "Analyzer result should not be null"); + System.out.println("✓ Classifier analyzer created: " + analyzerId); + + // Verify base analyzer + assertNotNull(result.getBaseAnalyzerId(), "Base analyzer ID should not be null"); + assertEquals("prebuilt-document", result.getBaseAnalyzerId(), "Base analyzer ID should match"); + System.out.println("✓ Base analyzer ID verified: " + result.getBaseAnalyzerId()); + + // Verify description + assertNotNull(result.getDescription(), "Description should not be null"); + assertEquals("Custom classifier for financial document categorization", result.getDescription(), + "Description should match"); + System.out.println("✓ Description verified: " + result.getDescription()); + + // Verify analyzer config + assertNotNull(result.getConfig(), "Analyzer config should not be null"); + System.out.println("✓ Analyzer config present"); + + // Verify content categories + assertNotNull(result.getConfig().getContentCategories(), "Content categories should not be null"); + assertEquals(3, result.getConfig().getContentCategories().size(), "Should have 3 content categories"); + System.out.println("✓ Content categories count verified: " + result.getConfig().getContentCategories().size()); + + // Verify Loan_Application category + assertTrue(result.getConfig().getContentCategories().containsKey("Loan_Application"), + "Should contain Loan_Application category"); + ContentCategoryDefinition loanAppCategory = result.getConfig().getContentCategories().get("Loan_Application"); + assertNotNull(loanAppCategory.getDescription(), "Loan_Application description should not be null"); + assertTrue(loanAppCategory.getDescription().contains("funding"), + "Loan_Application description should mention funding"); + System.out.println(" ✓ Loan_Application category verified"); + + // Verify Invoice category + assertTrue(result.getConfig().getContentCategories().containsKey("Invoice"), "Should contain Invoice category"); + ContentCategoryDefinition invoiceCategory = result.getConfig().getContentCategories().get("Invoice"); + assertNotNull(invoiceCategory.getDescription(), "Invoice description should not be null"); + assertTrue(invoiceCategory.getDescription().contains("payment"), "Invoice description should mention payment"); + System.out.println(" ✓ Invoice category verified"); + + // Verify Bank_Statement category + assertTrue(result.getConfig().getContentCategories().containsKey("Bank_Statement"), + "Should contain Bank_Statement category"); + ContentCategoryDefinition bankCategory = result.getConfig().getContentCategories().get("Bank_Statement"); + assertNotNull(bankCategory.getDescription(), "Bank_Statement description should not be null"); + assertTrue(bankCategory.getDescription().contains("account activity"), + "Bank_Statement description should mention account activity"); + System.out.println(" ✓ Bank_Statement category verified"); + + // Verify SegmentEnabled is set + assertNotNull(result.getConfig().isSegmentEnabled(), "SegmentEnabled should not be null"); + assertTrue(result.getConfig().isSegmentEnabled(), "SegmentEnabled should be true"); + System.out.println("✓ SegmentEnabled verified: " + result.getConfig().isSegmentEnabled()); + + // Verify returnDetails is set + assertNotNull(result.getConfig().isReturnDetails(), "ReturnDetails should not be null"); + assertTrue(result.getConfig().isReturnDetails(), "ReturnDetails should be true"); + System.out.println("✓ ReturnDetails verified: " + result.getConfig().isReturnDetails()); + + // Verify models + assertNotNull(result.getModels(), "Models should not be null"); + assertTrue(result.getModels().containsKey("completion"), "Should contain 'completion' model mapping"); + System.out.println("✓ Model mappings verified: " + result.getModels().size() + " model(s)"); + + System.out.println("\n════════════════════════════════════════════════════════════"); + System.out.println("✓ CLASSIFIER CREATION VERIFIED SUCCESSFULLY"); + System.out.println("════════════════════════════════════════════════════════════"); + System.out.println(" Analyzer ID: " + analyzerId); + System.out.println(" Base Analyzer: " + result.getBaseAnalyzerId()); + System.out.println(" Categories: " + result.getConfig().getContentCategories().size()); + System.out.println(" Segmentation: " + result.getConfig().isSegmentEnabled()); + System.out.println("════════════════════════════════════════════════════════════"); + // END:Assertion_ContentUnderstandingCreateClassifier + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample05_CreateClassifierAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample05_CreateClassifierAsync.java new file mode 100644 index 000000000000..611fac2d7864 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample05_CreateClassifierAsync.java @@ -0,0 +1,190 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.ContentAnalyzer; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerConfig; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentCategoryDefinition; +import com.azure.core.util.polling.PollerFlux; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * Async sample demonstrating how to create a classifier analyzer. + * + * This sample shows how to create a classifier that categorizes documents into predefined + * custom categories using ContentCategories asynchronously. Classifiers are useful for: + * - Content organization: Organize large document collections by type through categorization + * - Data routing (optional): Route data to specific custom analyzers based on category + * - Multi-document processing: Process files containing multiple document types by automatically + * segmenting them + */ +public class Sample05_CreateClassifierAsync extends ContentUnderstandingClientTestBase { + + private String createdAnalyzerId; + + @AfterEach + public void cleanup() { + if (createdAnalyzerId != null) { + try { + contentUnderstandingAsyncClient.deleteAnalyzer(createdAnalyzerId).block(); + System.out.println("Classifier analyzer '" + createdAnalyzerId + "' deleted successfully."); + } catch (Exception e) { + // Ignore cleanup errors + } + } + } + + @Test + public void testCreateClassifierAsync() { + + // BEGIN:ContentUnderstandingCreateClassifierAsync + // Generate a unique classifier analyzer ID + String analyzerId = testResourceNamer.randomName("document_classifier_", 50); + + System.out.println("Creating classifier analyzer '" + analyzerId + "'..."); + + // Define content categories for classification + // Each category has a description that helps the AI model understand what documents belong to it + Map categories = new HashMap<>(); + + categories.put("Loan_Application", + new ContentCategoryDefinition() + .setDescription("Documents submitted by individuals or businesses to request funding, " + + "typically including personal or business details, financial history, loan amount, " + + "purpose, and supporting documentation.")); + + categories.put("Invoice", + new ContentCategoryDefinition() + .setDescription("Billing documents issued by sellers or service providers to request payment " + + "for goods or services, detailing items, prices, taxes, totals, and payment terms.")); + + categories.put("Bank_Statement", + new ContentCategoryDefinition() + .setDescription("Official statements issued by banks that summarize account activity over a period, " + + "including deposits, withdrawals, fees, and balances.")); + + // Create analyzer configuration with content categories + ContentAnalyzerConfig config = new ContentAnalyzerConfig().setReturnDetails(true) + .setSegmentEnabled(true) // Enable automatic segmentation by category + .setContentCategories(categories); + + // Create the classifier analyzer + // Note: models are specified using model names, not deployment names + Map models = new HashMap<>(); + models.put("completion", "gpt-4.1"); + + ContentAnalyzer classifier = new ContentAnalyzer().setBaseAnalyzerId("prebuilt-document") + .setDescription("Custom classifier for financial document categorization") + .setConfig(config) + .setModels(models); + + // Create the classifier + PollerFlux operation + = contentUnderstandingAsyncClient.beginCreateAnalyzer(analyzerId, classifier, true); + + // Use reactive pattern: chain operations using flatMap + // In a real application, you would use subscribe() instead of block() + ContentAnalyzer 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())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + + System.out.println("Classifier '" + analyzerId + "' created successfully!"); + // END:ContentUnderstandingCreateClassifierAsync + + createdAnalyzerId = analyzerId; // Track for cleanup + + // BEGIN:Assertion_ContentUnderstandingCreateClassifierAsync + // Verify basic properties + assertNotNull(analyzerId, "Analyzer ID should not be null"); + assertFalse(analyzerId.trim().isEmpty(), "Analyzer ID should not be empty"); + assertNotNull(operation, "Create analyzer operation should not be null"); + assertNotNull(result, "Analyzer result should not be null"); + System.out.println("✓ Create classifier operation completed successfully"); + System.out.println("✓ Classifier analyzer created: " + analyzerId); + + // Verify base analyzer + assertNotNull(result.getBaseAnalyzerId(), "Base analyzer ID should not be null"); + assertEquals("prebuilt-document", result.getBaseAnalyzerId(), "Base analyzer ID should match"); + System.out.println("✓ Base analyzer ID verified: " + result.getBaseAnalyzerId()); + + // Verify description + assertNotNull(result.getDescription(), "Description should not be null"); + assertEquals("Custom classifier for financial document categorization", result.getDescription(), + "Description should match"); + System.out.println("✓ Description verified: " + result.getDescription()); + + // Verify analyzer config + assertNotNull(result.getConfig(), "Analyzer config should not be null"); + System.out.println("✓ Analyzer config present"); + + // Verify content categories + assertNotNull(result.getConfig().getContentCategories(), "Content categories should not be null"); + assertEquals(3, result.getConfig().getContentCategories().size(), "Should have 3 content categories"); + System.out.println("✓ Content categories count verified: " + result.getConfig().getContentCategories().size()); + + // Verify Loan_Application category + assertTrue(result.getConfig().getContentCategories().containsKey("Loan_Application"), + "Should contain Loan_Application category"); + ContentCategoryDefinition loanAppCategory = result.getConfig().getContentCategories().get("Loan_Application"); + assertNotNull(loanAppCategory.getDescription(), "Loan_Application description should not be null"); + assertTrue(loanAppCategory.getDescription().contains("funding"), + "Loan_Application description should mention funding"); + System.out.println(" ✓ Loan_Application category verified"); + + // Verify Invoice category + assertTrue(result.getConfig().getContentCategories().containsKey("Invoice"), "Should contain Invoice category"); + ContentCategoryDefinition invoiceCategory = result.getConfig().getContentCategories().get("Invoice"); + assertNotNull(invoiceCategory.getDescription(), "Invoice description should not be null"); + assertTrue(invoiceCategory.getDescription().contains("payment"), "Invoice description should mention payment"); + System.out.println(" ✓ Invoice category verified"); + + // Verify Bank_Statement category + assertTrue(result.getConfig().getContentCategories().containsKey("Bank_Statement"), + "Should contain Bank_Statement category"); + ContentCategoryDefinition bankCategory = result.getConfig().getContentCategories().get("Bank_Statement"); + assertNotNull(bankCategory.getDescription(), "Bank_Statement description should not be null"); + assertTrue(bankCategory.getDescription().contains("account activity"), + "Bank_Statement description should mention account activity"); + System.out.println(" ✓ Bank_Statement category verified"); + + // Verify SegmentEnabled is set + assertNotNull(result.getConfig().isSegmentEnabled(), "SegmentEnabled should not be null"); + assertTrue(result.getConfig().isSegmentEnabled(), "SegmentEnabled should be true"); + System.out.println("✓ SegmentEnabled verified: " + result.getConfig().isSegmentEnabled()); + + // Verify returnDetails is set + assertNotNull(result.getConfig().isReturnDetails(), "ReturnDetails should not be null"); + assertTrue(result.getConfig().isReturnDetails(), "ReturnDetails should be true"); + System.out.println("✓ ReturnDetails verified: " + result.getConfig().isReturnDetails()); + + // Verify models + assertNotNull(result.getModels(), "Models should not be null"); + assertTrue(result.getModels().containsKey("completion"), "Should contain 'completion' model mapping"); + System.out.println("✓ Model mappings verified: " + result.getModels().size() + " model(s)"); + + System.out.println("\n════════════════════════════════════════════════════════════"); + System.out.println("✓ CLASSIFIER CREATION VERIFIED SUCCESSFULLY"); + System.out.println("════════════════════════════════════════════════════════════"); + System.out.println(" Analyzer ID: " + analyzerId); + System.out.println(" Base Analyzer: " + result.getBaseAnalyzerId()); + System.out.println(" Categories: " + result.getConfig().getContentCategories().size()); + System.out.println(" Segmentation: " + result.getConfig().isSegmentEnabled()); + System.out.println("════════════════════════════════════════════════════════════"); + // END:Assertion_ContentUnderstandingCreateClassifierAsync + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample06_GetAnalyzer.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample06_GetAnalyzer.java new file mode 100644 index 000000000000..6899f3c43087 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample06_GetAnalyzer.java @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.ContentAnalyzer; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Sample demonstrating how to get analyzer information. + * This sample shows: + * 1. Retrieving analyzer details by ID + * 2. Accessing analyzer configuration + * 3. Inspecting field schema definitions + * 4. Getting prebuilt analyzer information + */ +public class Sample06_GetAnalyzer extends ContentUnderstandingClientTestBase { + + @Test + public void testGetAnalyzer() { + + // BEGIN:ContentUnderstandingGetAnalyzer + // Get a prebuilt analyzer (these are always available) + String analyzerId = "prebuilt-invoice"; + + ContentAnalyzer analyzer = contentUnderstandingClient.getAnalyzer(analyzerId); + + System.out.println("Analyzer ID: " + analyzer.getAnalyzerId()); + System.out.println( + "Base Analyzer ID: " + (analyzer.getBaseAnalyzerId() != null ? analyzer.getBaseAnalyzerId() : "N/A")); + System.out.println("Description: " + (analyzer.getDescription() != null ? analyzer.getDescription() : "N/A")); + + // Display configuration + if (analyzer.getConfig() != null) { + System.out.println("\nAnalyzer Configuration:"); + System.out.println(" Enable OCR: " + analyzer.getConfig().isOcrEnabled()); + System.out.println(" Enable Layout: " + analyzer.getConfig().isLayoutEnabled()); + System.out.println(" Enable Formula: " + analyzer.getConfig().isFormulaEnabled()); + System.out.println( + " Estimate Field Source and Confidence: " + analyzer.getConfig().isEstimateFieldSourceAndConfidence()); + System.out.println(" Return Details: " + analyzer.getConfig().isReturnDetails()); + } + + // Display field schema if available + if (analyzer.getFieldSchema() != null) { + System.out.println("\nField Schema:"); + System.out.println(" Name: " + analyzer.getFieldSchema().getName()); + System.out.println(" Description: " + (analyzer.getFieldSchema().getDescription() != null + ? analyzer.getFieldSchema().getDescription() + : "N/A")); + if (analyzer.getFieldSchema().getFields() != null) { + System.out.println(" Number of fields: " + analyzer.getFieldSchema().getFields().size()); + System.out.println(" Fields:"); + analyzer.getFieldSchema().getFields().forEach((fieldName, fieldDef) -> { + System.out.println(" - " + fieldName + " (" + fieldDef.getType() + ", Method: " + + (fieldDef.getMethod() != null ? fieldDef.getMethod() : "N/A") + ")"); + if (fieldDef.getDescription() != null && !fieldDef.getDescription().trim().isEmpty()) { + System.out.println(" Description: " + fieldDef.getDescription()); + } + }); + } + } + + // Display models if available + if (analyzer.getModels() != null && !analyzer.getModels().isEmpty()) { + System.out.println("\nModel Mappings:"); + analyzer.getModels().forEach((modelKey, modelValue) -> { + System.out.println(" " + modelKey + ": " + modelValue); + }); + } + + // Display status if available + if (analyzer.getStatus() != null) { + System.out.println("\nAnalyzer Status: " + analyzer.getStatus()); + } + + // Display created/updated timestamps if available + if (analyzer.getCreatedAt() != null) { + System.out.println("Created: " + analyzer.getCreatedAt()); + } + if (analyzer.getLastModifiedAt() != null) { + System.out.println("Updated: " + analyzer.getLastModifiedAt()); + } + // END:ContentUnderstandingGetAnalyzer + + // BEGIN:Assertion_ContentUnderstandingGetAnalyzer + assertNotNull(analyzerId, "Analyzer ID should not be null"); + assertNotNull(analyzer, "Analyzer should not be null"); + System.out.println("\nAnalyzer retrieved successfully"); + + // Verify analyzer ID + assertNotNull(analyzer.getAnalyzerId(), "Analyzer ID should not be null"); + assertEquals(analyzerId, analyzer.getAnalyzerId(), "Analyzer ID should match requested ID"); + System.out.println("Analyzer ID verified: " + analyzer.getAnalyzerId()); + + // Verify analyzer has configuration + assertNotNull(analyzer.getConfig(), "Analyzer config should not be null"); + System.out.println("Analyzer configuration verified"); + + // For prebuilt analyzers, verify they have field schema + if (analyzer.getFieldSchema() != null) { + assertNotNull(analyzer.getFieldSchema().getName(), "Field schema name should not be null"); + assertFalse(analyzer.getFieldSchema().getName().trim().isEmpty(), "Field schema name should not be empty"); + System.out.println("Field schema verified: " + analyzer.getFieldSchema().getName()); + + if (analyzer.getFieldSchema().getFields() != null) { + assertTrue(analyzer.getFieldSchema().getFields().size() > 0, + "Field schema should have at least one field"); + System.out.println("Field schema contains " + analyzer.getFieldSchema().getFields().size() + " fields"); + } + } + + System.out.println("All analyzer properties validated successfully"); + // END:Assertion_ContentUnderstandingGetAnalyzer + } + + @Test + public void testGetAnalyzerNotFound() { + // Test getting another prebuilt analyzer + String analyzerId = "prebuilt-document"; + + ContentAnalyzer analyzer = contentUnderstandingClient.getAnalyzer(analyzerId); + + System.out.println("\nRetrieving prebuilt-document analyzer..."); + System.out.println("Analyzer ID: " + analyzer.getAnalyzerId()); + System.out.println("Description: " + (analyzer.getDescription() != null ? analyzer.getDescription() : "N/A")); + + // Verify the analyzer + assertNotNull(analyzer, "Analyzer should not be null"); + assertEquals(analyzerId, analyzer.getAnalyzerId(), "Analyzer ID should match"); + assertNotNull(analyzer.getConfig(), "Analyzer config should not be null"); + System.out.println("Prebuilt-document analyzer verified successfully"); + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample06_GetAnalyzerAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample06_GetAnalyzerAsync.java new file mode 100644 index 000000000000..abe9c237cbbc --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample06_GetAnalyzerAsync.java @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.ContentAnalyzer; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Async sample demonstrating how to get analyzer information. + * This sample shows: + * 1. Retrieving analyzer details by ID + * 2. Accessing analyzer configuration + * 3. Inspecting field schema definitions + * 4. Getting prebuilt analyzer information + */ +public class Sample06_GetAnalyzerAsync extends ContentUnderstandingClientTestBase { + + @Test + public void testGetAnalyzerAsync() { + + // BEGIN:ContentUnderstandingGetAnalyzerAsync + // Get a prebuilt analyzer (these are always available) + String analyzerId = "prebuilt-invoice"; + + ContentAnalyzer analyzer = contentUnderstandingAsyncClient.getAnalyzer(analyzerId).block(); + + System.out.println("Analyzer ID: " + analyzer.getAnalyzerId()); + System.out.println( + "Base Analyzer ID: " + (analyzer.getBaseAnalyzerId() != null ? analyzer.getBaseAnalyzerId() : "N/A")); + System.out.println("Description: " + (analyzer.getDescription() != null ? analyzer.getDescription() : "N/A")); + + // Display configuration + if (analyzer.getConfig() != null) { + System.out.println("\nAnalyzer Configuration:"); + System.out.println(" Enable OCR: " + analyzer.getConfig().isOcrEnabled()); + System.out.println(" Enable Layout: " + analyzer.getConfig().isLayoutEnabled()); + System.out.println(" Enable Formula: " + analyzer.getConfig().isFormulaEnabled()); + System.out.println( + " Estimate Field Source and Confidence: " + analyzer.getConfig().isEstimateFieldSourceAndConfidence()); + System.out.println(" Return Details: " + analyzer.getConfig().isReturnDetails()); + } + + // Display field schema if available + if (analyzer.getFieldSchema() != null) { + System.out.println("\nField Schema:"); + System.out.println(" Name: " + analyzer.getFieldSchema().getName()); + System.out.println(" Description: " + (analyzer.getFieldSchema().getDescription() != null + ? analyzer.getFieldSchema().getDescription() + : "N/A")); + if (analyzer.getFieldSchema().getFields() != null) { + System.out.println(" Number of fields: " + analyzer.getFieldSchema().getFields().size()); + System.out.println(" Fields:"); + analyzer.getFieldSchema().getFields().forEach((fieldName, fieldDef) -> { + System.out.println(" - " + fieldName + " (" + fieldDef.getType() + ", Method: " + + (fieldDef.getMethod() != null ? fieldDef.getMethod() : "N/A") + ")"); + if (fieldDef.getDescription() != null && !fieldDef.getDescription().trim().isEmpty()) { + System.out.println(" Description: " + fieldDef.getDescription()); + } + }); + } + } + + // Display models if available + if (analyzer.getModels() != null && !analyzer.getModels().isEmpty()) { + System.out.println("\nModel Mappings:"); + analyzer.getModels().forEach((modelKey, modelValue) -> { + System.out.println(" " + modelKey + ": " + modelValue); + }); + } + + // Display status if available + if (analyzer.getStatus() != null) { + System.out.println("\nAnalyzer Status: " + analyzer.getStatus()); + } + + // Display created/updated timestamps if available + if (analyzer.getCreatedAt() != null) { + System.out.println("Created: " + analyzer.getCreatedAt()); + } + if (analyzer.getLastModifiedAt() != null) { + System.out.println("Updated: " + analyzer.getLastModifiedAt()); + } + // END:ContentUnderstandingGetAnalyzerAsync + + // BEGIN:Assertion_ContentUnderstandingGetAnalyzerAsync + assertNotNull(analyzerId, "Analyzer ID should not be null"); + assertNotNull(analyzer, "Analyzer should not be null"); + System.out.println("\nAnalyzer retrieved successfully"); + + // Verify analyzer ID + assertNotNull(analyzer.getAnalyzerId(), "Analyzer ID should not be null"); + assertEquals(analyzerId, analyzer.getAnalyzerId(), "Analyzer ID should match requested ID"); + System.out.println("Analyzer ID verified: " + analyzer.getAnalyzerId()); + + // Verify analyzer has configuration + assertNotNull(analyzer.getConfig(), "Analyzer config should not be null"); + assertNotNull(analyzer.getConfig(), "Analyzer config should not be null"); + System.out.println("Analyzer configuration verified"); + + // For prebuilt analyzers, verify they have field schema + if (analyzer.getFieldSchema() != null) { + assertNotNull(analyzer.getFieldSchema().getName(), "Field schema name should not be null"); + assertFalse(analyzer.getFieldSchema().getName().trim().isEmpty(), "Field schema name should not be empty"); + System.out.println("Field schema verified: " + analyzer.getFieldSchema().getName()); + + if (analyzer.getFieldSchema().getFields() != null) { + assertTrue(analyzer.getFieldSchema().getFields().size() > 0, + "Field schema should have at least one field"); + System.out.println("Field schema contains " + analyzer.getFieldSchema().getFields().size() + " fields"); + } + } + + System.out.println("All analyzer properties validated successfully"); + // END:Assertion_ContentUnderstandingGetAnalyzerAsync + } + + @Test + public void testGetAnalyzerNotFoundAsync() { + // Test getting another prebuilt analyzer + String analyzerId = "prebuilt-document"; + + ContentAnalyzer analyzer = contentUnderstandingAsyncClient.getAnalyzer(analyzerId).block(); + + System.out.println("\nRetrieving prebuilt-document analyzer..."); + System.out.println("Analyzer ID: " + analyzer.getAnalyzerId()); + System.out.println("Description: " + (analyzer.getDescription() != null ? analyzer.getDescription() : "N/A")); + + // Verify the analyzer + assertNotNull(analyzer, "Analyzer should not be null"); + assertEquals(analyzerId, analyzer.getAnalyzerId(), "Analyzer ID should match"); + assertNotNull(analyzer.getConfig(), "Analyzer config should not be null"); + System.out.println("Prebuilt-document analyzer verified successfully"); + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample07_ListAnalyzers.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample07_ListAnalyzers.java new file mode 100644 index 000000000000..aea764306956 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample07_ListAnalyzers.java @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.ContentAnalyzer; +import com.azure.core.http.rest.PagedIterable; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Sample demonstrating how to list all analyzers. + * This sample shows: + * 1. Listing all available analyzers (both prebuilt and custom) + * 2. Filtering analyzers by status + * 3. Iterating through paginated results + * 4. Displaying analyzer properties + */ +public class Sample07_ListAnalyzers extends ContentUnderstandingClientTestBase { + + @Test + public void testListAnalyzers() { + + // BEGIN:ContentUnderstandingListAnalyzers + // List all analyzers + PagedIterable analyzers = contentUnderstandingClient.listAnalyzers(); + + System.out.println("Listing all analyzers:"); + System.out.println("======================"); + + int count = 0; + int prebuiltCount = 0; + int customCount = 0; + + for (ContentAnalyzer analyzer : analyzers) { + count++; + + // Determine if this is a prebuilt or custom analyzer + boolean isPrebuilt = analyzer.getAnalyzerId().startsWith("prebuilt-"); + if (isPrebuilt) { + prebuiltCount++; + } else { + customCount++; + } + + System.out.println("\nAnalyzer #" + count + ":"); + System.out.println(" ID: " + analyzer.getAnalyzerId()); + System.out.println(" Type: " + (isPrebuilt ? "Prebuilt" : "Custom")); + + if (analyzer.getDescription() != null && !analyzer.getDescription().trim().isEmpty()) { + System.out.println(" Description: " + analyzer.getDescription()); + } + + if (analyzer.getBaseAnalyzerId() != null) { + System.out.println(" Base Analyzer: " + analyzer.getBaseAnalyzerId()); + } + + if (analyzer.getStatus() != null) { + System.out.println(" Status: " + analyzer.getStatus()); + } + + if (analyzer.getCreatedAt() != null) { + System.out.println(" Created: " + analyzer.getCreatedAt()); + } + + if (analyzer.getLastModifiedAt() != null) { + System.out.println(" Last Modified: " + analyzer.getLastModifiedAt()); + } + + // Display field schema summary if available + if (analyzer.getFieldSchema() != null && analyzer.getFieldSchema().getFields() != null) { + System.out.println(" Fields: " + analyzer.getFieldSchema().getFields().size() + " field(s) defined"); + } + + // Display tags if available + if (analyzer.getTags() != null && !analyzer.getTags().isEmpty()) { + System.out.println(" Tags: " + analyzer.getTags().size() + " tag(s)"); + } + } + + System.out.println("\n======================"); + System.out.println("Total analyzers: " + count); + System.out.println(" Prebuilt: " + prebuiltCount); + System.out.println(" Custom: " + customCount); + // END:ContentUnderstandingListAnalyzers + + // BEGIN:Assertion_ContentUnderstandingListAnalyzers + assertNotNull(analyzers, "Analyzers list should not be null"); + System.out.println("\nAnalyzers list retrieved successfully"); + + // Verify we have at least the prebuilt analyzers + assertTrue(count > 0, "Should have at least one analyzer"); + assertTrue(prebuiltCount > 0, "Should have at least one prebuilt analyzer"); + System.out.println("Verified: Found " + count + " total analyzer(s)"); + System.out.println("Verified: Found " + prebuiltCount + " prebuilt analyzer(s)"); + if (customCount > 0) { + System.out.println("Verified: Found " + customCount + " custom analyzer(s)"); + } + + // Verify each analyzer has required properties + int validatedCount = 0; + for (ContentAnalyzer analyzer : analyzers) { + assertNotNull(analyzer.getAnalyzerId(), "Analyzer ID should not be null"); + assertFalse(analyzer.getAnalyzerId().trim().isEmpty(), "Analyzer ID should not be empty"); + assertNotNull(analyzer.getStatus(), "Analyzer status should not be null"); + validatedCount++; + + // Only validate first few to avoid excessive output + if (validatedCount >= 5) { + break; + } + } + + System.out.println("All analyzer list properties validated successfully"); + // END:Assertion_ContentUnderstandingListAnalyzers + } + + @Test + public void testListAnalyzersWithMaxResults() { + // List all analyzers and filter for ready ones + PagedIterable analyzers = contentUnderstandingClient.listAnalyzers(); + + System.out.println("\nListing ready analyzers:"); + System.out.println("========================"); + + int readyCount = 0; + for (ContentAnalyzer analyzer : analyzers) { + if (analyzer.getStatus() != null && "ready".equalsIgnoreCase(analyzer.getStatus().toString())) { + readyCount++; + System.out.println("\nReady Analyzer #" + readyCount + ":"); + System.out.println(" ID: " + analyzer.getAnalyzerId()); + if (analyzer.getDescription() != null) { + System.out.println(" Description: " + analyzer.getDescription()); + } + } + } + + System.out.println("\n========================"); + System.out.println("Total ready analyzers: " + readyCount); + + // Verify + assertTrue(readyCount > 0, "Should have at least one ready analyzer"); + System.out.println("Verified: Found " + readyCount + " ready analyzer(s)"); + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample07_ListAnalyzersAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample07_ListAnalyzersAsync.java new file mode 100644 index 000000000000..6e0c6eb32957 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample07_ListAnalyzersAsync.java @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.ContentAnalyzer; +import com.azure.core.http.rest.PagedFlux; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Async sample demonstrating how to list all analyzers. + * This sample shows: + * 1. Listing all available analyzers (both prebuilt and custom) + * 2. Filtering analyzers by status + * 3. Iterating through paginated results + * 4. Displaying analyzer properties + */ +public class Sample07_ListAnalyzersAsync extends ContentUnderstandingClientTestBase { + + @Test + public void testListAnalyzersAsync() { + + // BEGIN:ContentUnderstandingListAnalyzersAsync + // List all analyzers + PagedFlux analyzers = contentUnderstandingAsyncClient.listAnalyzers(); + + System.out.println("Listing all analyzers:"); + System.out.println("======================"); + + final int[] count = { 0 }; + final int[] prebuiltCount = { 0 }; + final int[] customCount = { 0 }; + + analyzers.toIterable().forEach(analyzer -> { + count[0]++; + + // Determine if this is a prebuilt or custom analyzer + boolean isPrebuilt = analyzer.getAnalyzerId().startsWith("prebuilt-"); + if (isPrebuilt) { + prebuiltCount[0]++; + } else { + customCount[0]++; + } + + System.out.println("\nAnalyzer #" + count[0] + ":"); + System.out.println(" ID: " + analyzer.getAnalyzerId()); + System.out.println(" Type: " + (isPrebuilt ? "Prebuilt" : "Custom")); + + if (analyzer.getDescription() != null && !analyzer.getDescription().trim().isEmpty()) { + System.out.println(" Description: " + analyzer.getDescription()); + } + + if (analyzer.getBaseAnalyzerId() != null) { + System.out.println(" Base Analyzer: " + analyzer.getBaseAnalyzerId()); + } + + if (analyzer.getStatus() != null) { + System.out.println(" Status: " + analyzer.getStatus()); + } + + if (analyzer.getCreatedAt() != null) { + System.out.println(" Created: " + analyzer.getCreatedAt()); + } + + if (analyzer.getLastModifiedAt() != null) { + System.out.println(" Last Modified: " + analyzer.getLastModifiedAt()); + } + + // Display field schema summary if available + if (analyzer.getFieldSchema() != null && analyzer.getFieldSchema().getFields() != null) { + System.out.println(" Fields: " + analyzer.getFieldSchema().getFields().size() + " field(s) defined"); + } + + // Display tags if available + if (analyzer.getTags() != null && !analyzer.getTags().isEmpty()) { + System.out.println(" Tags: " + analyzer.getTags().size() + " tag(s)"); + } + }); + + System.out.println("\n======================"); + System.out.println("Total analyzers: " + count[0]); + System.out.println(" Prebuilt: " + prebuiltCount[0]); + System.out.println(" Custom: " + customCount[0]); + // END:ContentUnderstandingListAnalyzersAsync + + // BEGIN:Assertion_ContentUnderstandingListAnalyzersAsync + assertNotNull(analyzers, "Analyzers list should not be null"); + System.out.println("\nAnalyzers list retrieved successfully"); + + // Verify we have at least the prebuilt analyzers + assertTrue(count[0] > 0, "Should have at least one analyzer"); + assertTrue(prebuiltCount[0] > 0, "Should have at least one prebuilt analyzer"); + System.out.println("Verified: Found " + count[0] + " total analyzer(s)"); + System.out.println("Verified: Found " + prebuiltCount[0] + " prebuilt analyzer(s)"); + if (customCount[0] > 0) { + System.out.println("Verified: Found " + customCount[0] + " custom analyzer(s)"); + } + + // Verify each analyzer has required properties + final int[] validatedCount = { 0 }; + analyzers.toIterable().forEach(analyzer -> { + if (validatedCount[0] < 5) { + assertNotNull(analyzer.getAnalyzerId(), "Analyzer ID should not be null"); + assertFalse(analyzer.getAnalyzerId().trim().isEmpty(), "Analyzer ID should not be empty"); + assertNotNull(analyzer.getStatus(), "Analyzer status should not be null"); + validatedCount[0]++; + } + }); + + System.out.println("All analyzer list properties validated successfully"); + // END:Assertion_ContentUnderstandingListAnalyzersAsync + } + + @Test + public void testListAnalyzersWithMaxResultsAsync() { + // List all analyzers and filter for ready ones + PagedFlux analyzers = contentUnderstandingAsyncClient.listAnalyzers(); + + System.out.println("\nListing ready analyzers:"); + System.out.println("========================"); + + final int[] readyCount = { 0 }; + analyzers.toIterable().forEach(analyzer -> { + if (analyzer.getStatus() != null && "ready".equalsIgnoreCase(analyzer.getStatus().toString())) { + readyCount[0]++; + System.out.println("\nReady Analyzer #" + readyCount[0] + ":"); + System.out.println(" ID: " + analyzer.getAnalyzerId()); + if (analyzer.getDescription() != null) { + System.out.println(" Description: " + analyzer.getDescription()); + } + } + }); + + System.out.println("\n========================"); + System.out.println("Total ready analyzers: " + readyCount[0]); + + // Verify + assertTrue(readyCount[0] > 0, "Should have at least one ready analyzer"); + System.out.println("Verified: Found " + readyCount[0] + " ready analyzer(s)"); + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample08_UpdateAnalyzer.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample08_UpdateAnalyzer.java new file mode 100644 index 000000000000..071e8c836154 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample08_UpdateAnalyzer.java @@ -0,0 +1,148 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.ContentAnalyzer; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerConfig; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentFieldDefinition; +import com.azure.ai.contentunderstanding.models.ContentFieldSchema; +import com.azure.ai.contentunderstanding.models.ContentFieldType; +import com.azure.ai.contentunderstanding.models.GenerationMethod; +import com.azure.core.util.polling.SyncPoller; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * Sample demonstrating how to update an existing analyzer. + * This sample shows: + * 1. Creating an analyzer + * 2. Updating analyzer description + * 3. Updating analyzer configuration + * 4. Updating field schema + */ +public class Sample08_UpdateAnalyzer extends ContentUnderstandingClientTestBase { + + private String analyzerId; + + @BeforeEach + public void setup() { + // Create an analyzer for testing + analyzerId = testResourceNamer.randomName("update_test_analyzer_", 50); + + Map fields = new HashMap<>(); + ContentFieldDefinition titleDef = new ContentFieldDefinition(); + titleDef.setType(ContentFieldType.STRING); + titleDef.setMethod(GenerationMethod.EXTRACT); + titleDef.setDescription("Document title"); + fields.put("title", titleDef); + + ContentFieldSchema fieldSchema = new ContentFieldSchema(); + fieldSchema.setName("basic_schema"); + fieldSchema.setDescription("Basic document schema"); + fieldSchema.setFields(fields); + + Map models = new HashMap<>(); + models.put("completion", "gpt-4.1"); + models.put("embedding", "text-embedding-3-large"); + + ContentAnalyzer analyzer = new ContentAnalyzer().setBaseAnalyzerId("prebuilt-document") + .setDescription("Original analyzer for update testing") + .setConfig(new ContentAnalyzerConfig().setOcrEnabled(true).setLayoutEnabled(true)) + .setFieldSchema(fieldSchema) + .setModels(models); + + contentUnderstandingClient.beginCreateAnalyzer(analyzerId, analyzer).getFinalResult(); + System.out.println("Test analyzer created: " + analyzerId); + } + + @AfterEach + public void cleanup() { + if (analyzerId != null) { + try { + contentUnderstandingClient.deleteAnalyzer(analyzerId); + System.out.println("Test analyzer deleted: " + analyzerId); + } catch (Exception e) { + // Ignore cleanup errors + } + } + } + + @Test + public void testUpdateAnalyzer() { + // BEGIN:ContentUnderstandingUpdateAnalyzer + // Get the current analyzer + ContentAnalyzer currentAnalyzer = contentUnderstandingClient.getAnalyzer(analyzerId); + System.out.println("Current description: " + currentAnalyzer.getDescription()); + + // Update the analyzer with new configuration + Map updatedFields = new HashMap<>(); + + // Keep the original field + ContentFieldDefinition titleDef = new ContentFieldDefinition(); + titleDef.setType(ContentFieldType.STRING); + titleDef.setMethod(GenerationMethod.EXTRACT); + titleDef.setDescription("Document title"); + updatedFields.put("title", titleDef); + + // Add a new field + ContentFieldDefinition authorDef = new ContentFieldDefinition(); + authorDef.setType(ContentFieldType.STRING); + authorDef.setMethod(GenerationMethod.EXTRACT); + authorDef.setDescription("Document author"); + updatedFields.put("author", authorDef); + + ContentFieldSchema updatedFieldSchema = new ContentFieldSchema(); + updatedFieldSchema.setName("enhanced_schema"); + updatedFieldSchema.setDescription("Enhanced document schema with author"); + updatedFieldSchema.setFields(updatedFields); + + Map updatedModels = new HashMap<>(); + updatedModels.put("completion", "gpt-4.1"); + updatedModels.put("embedding", "text-embedding-3-large"); + + ContentAnalyzer updatedAnalyzer = new ContentAnalyzer().setBaseAnalyzerId("prebuilt-document") + .setDescription("Updated analyzer with enhanced schema") + .setConfig(new ContentAnalyzerConfig().setOcrEnabled(true).setLayoutEnabled(true).setFormulaEnabled(true)) // Enable formula extraction + .setFieldSchema(updatedFieldSchema) + .setModels(updatedModels); + + // Update the analyzer using the convenience method + // This method accepts a ContentAnalyzer object directly instead of BinaryData + ContentAnalyzer result = contentUnderstandingClient.updateAnalyzer(analyzerId, updatedAnalyzer); + + System.out.println("Analyzer updated successfully!"); + System.out.println("New description: " + result.getDescription()); + // END:ContentUnderstandingUpdateAnalyzer + + // BEGIN:Assertion_ContentUnderstandingUpdateAnalyzer + assertNotNull(result, "Updated analyzer should not be null"); + assertEquals(analyzerId, result.getAnalyzerId(), "Analyzer ID should match"); + assertEquals("Updated analyzer with enhanced schema", result.getDescription(), "Description should be updated"); + System.out.println("Analyzer description verified"); + + // Verify field schema was updated + assertNotNull(result.getFieldSchema(), "Field schema should not be null"); + assertEquals("enhanced_schema", result.getFieldSchema().getName(), "Field schema name should be updated"); + assertEquals(2, result.getFieldSchema().getFields().size(), "Should have 2 fields after update"); + assertTrue(result.getFieldSchema().getFields().containsKey("title"), "Should still contain title field"); + assertTrue(result.getFieldSchema().getFields().containsKey("author"), "Should contain new author field"); + System.out.println("Field schema update verified: " + result.getFieldSchema().getFields().size() + " fields"); + + // Verify config was updated + assertNotNull(result.getConfig(), "Config should not be null"); + assertTrue(result.getConfig().isFormulaEnabled(), "EnableFormula should now be true"); + System.out.println("Config update verified"); + + System.out.println("All analyzer update properties validated successfully"); + // END:Assertion_ContentUnderstandingUpdateAnalyzer + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample08_UpdateAnalyzerAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample08_UpdateAnalyzerAsync.java new file mode 100644 index 000000000000..8bb19dbd88ec --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample08_UpdateAnalyzerAsync.java @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.ContentAnalyzer; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerConfig; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentFieldDefinition; +import com.azure.ai.contentunderstanding.models.ContentFieldSchema; +import com.azure.ai.contentunderstanding.models.ContentFieldType; +import com.azure.ai.contentunderstanding.models.GenerationMethod; +import com.azure.core.util.polling.PollerFlux; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * Async sample demonstrating how to update an existing analyzer. + * This sample shows: + * 1. Creating an analyzer + * 2. Updating analyzer description + * 3. Updating analyzer configuration + * 4. Updating field schema + */ +public class Sample08_UpdateAnalyzerAsync extends ContentUnderstandingClientTestBase { + + private String analyzerId; + + @BeforeEach + public void setup() { + // Create an analyzer for testing + analyzerId = testResourceNamer.randomName("update_test_analyzer_", 50); + + Map fields = new HashMap<>(); + ContentFieldDefinition titleDef = new ContentFieldDefinition(); + titleDef.setType(ContentFieldType.STRING); + titleDef.setMethod(GenerationMethod.EXTRACT); + titleDef.setDescription("Document title"); + fields.put("title", titleDef); + + ContentFieldSchema fieldSchema = new ContentFieldSchema(); + fieldSchema.setName("basic_schema"); + fieldSchema.setDescription("Basic document schema"); + fieldSchema.setFields(fields); + + Map models = new HashMap<>(); + models.put("completion", "gpt-4.1"); + models.put("embedding", "text-embedding-3-large"); + + ContentAnalyzer analyzer = new ContentAnalyzer().setBaseAnalyzerId("prebuilt-document") + .setDescription("Original analyzer for update testing") + .setConfig(new ContentAnalyzerConfig().setOcrEnabled(true).setLayoutEnabled(true)) + .setFieldSchema(fieldSchema) + .setModels(models); + + contentUnderstandingAsyncClient.beginCreateAnalyzer(analyzerId, analyzer).last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + System.out.println("Test analyzer created: " + analyzerId); + } + + @AfterEach + public void cleanup() { + if (analyzerId != null) { + try { + contentUnderstandingAsyncClient.deleteAnalyzer(analyzerId).block(); + System.out.println("Test analyzer deleted: " + analyzerId); + } catch (Exception e) { + // Ignore cleanup errors + } + } + } + + @Test + public void testUpdateAnalyzerAsync() { + // BEGIN:ContentUnderstandingUpdateAnalyzerAsync + // Get the current analyzer + ContentAnalyzer currentAnalyzer = contentUnderstandingAsyncClient.getAnalyzer(analyzerId).block(); + System.out.println("Current description: " + currentAnalyzer.getDescription()); + + // Update the analyzer with new configuration + Map updatedFields = new HashMap<>(); + + // Keep the original field + ContentFieldDefinition titleDef = new ContentFieldDefinition(); + titleDef.setType(ContentFieldType.STRING); + titleDef.setMethod(GenerationMethod.EXTRACT); + titleDef.setDescription("Document title"); + updatedFields.put("title", titleDef); + + // Add a new field + ContentFieldDefinition authorDef = new ContentFieldDefinition(); + authorDef.setType(ContentFieldType.STRING); + authorDef.setMethod(GenerationMethod.EXTRACT); + authorDef.setDescription("Document author"); + updatedFields.put("author", authorDef); + + ContentFieldSchema updatedFieldSchema = new ContentFieldSchema(); + updatedFieldSchema.setName("enhanced_schema"); + updatedFieldSchema.setDescription("Enhanced document schema with author"); + updatedFieldSchema.setFields(updatedFields); + + Map updatedModels = new HashMap<>(); + updatedModels.put("completion", "gpt-4.1"); + updatedModels.put("embedding", "text-embedding-3-large"); + + ContentAnalyzer updatedAnalyzer = new ContentAnalyzer().setBaseAnalyzerId("prebuilt-document") + .setDescription("Updated analyzer with enhanced schema") + .setConfig(new ContentAnalyzerConfig().setOcrEnabled(true).setLayoutEnabled(true).setFormulaEnabled(true)) // Enable formula extraction + .setFieldSchema(updatedFieldSchema) + .setModels(updatedModels); + + // Update the analyzer using the convenience method + // This method accepts a ContentAnalyzer object directly instead of BinaryData + ContentAnalyzer result = contentUnderstandingAsyncClient.updateAnalyzer(analyzerId, updatedAnalyzer).block(); + + System.out.println("Analyzer updated successfully!"); + System.out.println("New description: " + result.getDescription()); + // END:ContentUnderstandingUpdateAnalyzerAsync + + // BEGIN:Assertion_ContentUnderstandingUpdateAnalyzerAsync + assertNotNull(result, "Updated analyzer should not be null"); + assertEquals(analyzerId, result.getAnalyzerId(), "Analyzer ID should match"); + assertEquals("Updated analyzer with enhanced schema", result.getDescription(), "Description should be updated"); + System.out.println("Analyzer description verified"); + + // Verify field schema was updated + assertNotNull(result.getFieldSchema(), "Field schema should not be null"); + assertEquals("enhanced_schema", result.getFieldSchema().getName(), "Field schema name should be updated"); + assertEquals(2, result.getFieldSchema().getFields().size(), "Should have 2 fields after update"); + assertTrue(result.getFieldSchema().getFields().containsKey("title"), "Should still contain title field"); + assertTrue(result.getFieldSchema().getFields().containsKey("author"), "Should contain new author field"); + System.out.println("Field schema update verified: " + result.getFieldSchema().getFields().size() + " fields"); + + // Verify config was updated + assertNotNull(result.getConfig(), "Config should not be null"); + assertTrue(result.getConfig().isFormulaEnabled(), "EnableFormula should now be true"); + System.out.println("Config update verified"); + + System.out.println("All analyzer update properties validated successfully"); + // END:Assertion_ContentUnderstandingUpdateAnalyzerAsync + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample09_DeleteAnalyzer.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample09_DeleteAnalyzer.java new file mode 100644 index 000000000000..9de10d804c5a --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample09_DeleteAnalyzer.java @@ -0,0 +1,115 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.ContentAnalyzer; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerConfig; +import com.azure.ai.contentunderstanding.models.ContentFieldDefinition; +import com.azure.ai.contentunderstanding.models.ContentFieldSchema; +import com.azure.ai.contentunderstanding.models.ContentFieldType; +import com.azure.ai.contentunderstanding.models.GenerationMethod; +import com.azure.core.exception.ResourceNotFoundException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * Sample demonstrating how to delete an analyzer. + * This sample shows: + * 1. Creating a temporary analyzer + * 2. Verifying the analyzer exists + * 3. Deleting the analyzer + * 4. Verifying the analyzer no longer exists + */ +public class Sample09_DeleteAnalyzer extends ContentUnderstandingClientTestBase { + + @Test + public void testDeleteAnalyzer() { + + // BEGIN:ContentUnderstandingDeleteAnalyzer + // First, create a temporary analyzer to delete + String analyzerId = testResourceNamer.randomName("analyzer_to_delete_", 50); + + Map fields = new HashMap<>(); + ContentFieldDefinition titleDef = new ContentFieldDefinition(); + titleDef.setType(ContentFieldType.STRING); + titleDef.setMethod(GenerationMethod.EXTRACT); + titleDef.setDescription("Document title"); + fields.put("title", titleDef); + + ContentFieldSchema fieldSchema = new ContentFieldSchema(); + fieldSchema.setName("temp_schema"); + fieldSchema.setDescription("Temporary schema for deletion demo"); + fieldSchema.setFields(fields); + + Map models = new HashMap<>(); + models.put("completion", "gpt-4.1"); + models.put("embedding", "text-embedding-3-large"); + + ContentAnalyzer analyzer = new ContentAnalyzer().setBaseAnalyzerId("prebuilt-document") + .setDescription("Temporary analyzer for deletion demo") + .setConfig(new ContentAnalyzerConfig().setOcrEnabled(true).setLayoutEnabled(true)) + .setFieldSchema(fieldSchema) + .setModels(models); + + contentUnderstandingClient.beginCreateAnalyzer(analyzerId, analyzer).getFinalResult(); + System.out.println("Temporary analyzer created: " + analyzerId); + + // Verify the analyzer exists + ContentAnalyzer retrievedAnalyzer = contentUnderstandingClient.getAnalyzer(analyzerId); + System.out.println("Verified analyzer exists with ID: " + retrievedAnalyzer.getAnalyzerId()); + + // Delete the analyzer + contentUnderstandingClient.deleteAnalyzer(analyzerId); + System.out.println("Analyzer deleted successfully: " + analyzerId); + + // Verify the analyzer no longer exists + boolean analyzerDeleted = false; + try { + contentUnderstandingClient.getAnalyzer(analyzerId); + } catch (ResourceNotFoundException e) { + analyzerDeleted = true; + System.out.println("Confirmed: Analyzer no longer exists"); + } + // END:ContentUnderstandingDeleteAnalyzer + + // BEGIN:Assertion_ContentUnderstandingDeleteAnalyzer + assertNotNull(analyzerId, "Analyzer ID should not be null"); + assertFalse(analyzerId.trim().isEmpty(), "Analyzer ID should not be empty"); + System.out.println("Analyzer ID verified: " + analyzerId); + + assertNotNull(retrievedAnalyzer, "Retrieved analyzer should not be null before deletion"); + assertEquals(analyzerId, retrievedAnalyzer.getAnalyzerId(), "Retrieved analyzer ID should match"); + System.out.println("Analyzer existence verified before deletion"); + + assertTrue(analyzerDeleted, "Analyzer should be deleted and not retrievable"); + System.out.println("Analyzer deletion verified"); + + System.out.println("All analyzer deletion properties validated successfully"); + // END:Assertion_ContentUnderstandingDeleteAnalyzer + } + + @Test + public void testDeleteNonexistentAnalyzer() { + // Try to delete a non-existent analyzer + String nonExistentId = testResourceNamer.randomName("non_existent_analyzer_", 50); + + System.out.println("\nAttempting to delete non-existent analyzer: " + nonExistentId); + + // Note: The SDK allows deleting non-existent analyzers without throwing an exception + // This is a valid behavior (idempotent delete operation) + try { + contentUnderstandingClient.deleteAnalyzer(nonExistentId); + System.out.println("Delete operation completed (idempotent - no error for non-existent resource)"); + } catch (ResourceNotFoundException e) { + System.out.println("ResourceNotFoundException caught (alternative behavior): " + e.getMessage()); + } + + System.out.println("Non-existent analyzer deletion behavior verified (SDK allows idempotent deletes)"); + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample09_DeleteAnalyzerAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample09_DeleteAnalyzerAsync.java new file mode 100644 index 000000000000..b3b033faffff --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample09_DeleteAnalyzerAsync.java @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.ContentAnalyzer; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerConfig; +import com.azure.ai.contentunderstanding.models.ContentFieldDefinition; +import com.azure.ai.contentunderstanding.models.ContentFieldSchema; +import com.azure.ai.contentunderstanding.models.ContentFieldType; +import com.azure.ai.contentunderstanding.models.GenerationMethod; +import com.azure.core.exception.ResourceNotFoundException; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.HashMap; +import java.util.Map; + +/** + * Async sample demonstrating how to delete an analyzer. + * This sample shows: + * 1. Creating a temporary analyzer + * 2. Verifying the analyzer exists + * 3. Deleting the analyzer + * 4. Verifying the analyzer no longer exists + */ +public class Sample09_DeleteAnalyzerAsync extends ContentUnderstandingClientTestBase { + + @Test + public void testDeleteAnalyzerAsync() { + + // BEGIN:ContentUnderstandingDeleteAnalyzerAsync + // First, create a temporary analyzer to delete + String analyzerId = testResourceNamer.randomName("analyzer_to_delete_", 50); + + Map fields = new HashMap<>(); + ContentFieldDefinition titleDef = new ContentFieldDefinition(); + titleDef.setType(ContentFieldType.STRING); + titleDef.setMethod(GenerationMethod.EXTRACT); + titleDef.setDescription("Document title"); + fields.put("title", titleDef); + + ContentFieldSchema fieldSchema = new ContentFieldSchema(); + fieldSchema.setName("temp_schema"); + fieldSchema.setDescription("Temporary schema for deletion demo"); + fieldSchema.setFields(fields); + + Map models = new HashMap<>(); + models.put("completion", "gpt-4.1"); + models.put("embedding", "text-embedding-3-large"); + + ContentAnalyzer analyzer = new ContentAnalyzer().setBaseAnalyzerId("prebuilt-document") + .setDescription("Temporary analyzer for deletion demo") + .setConfig(new ContentAnalyzerConfig().setOcrEnabled(true).setLayoutEnabled(true)) + .setFieldSchema(fieldSchema) + .setModels(models); + + contentUnderstandingAsyncClient.beginCreateAnalyzer(analyzerId, analyzer).last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); + System.out.println("Temporary analyzer created: " + analyzerId); + + // Verify the analyzer exists + ContentAnalyzer retrievedAnalyzer = contentUnderstandingAsyncClient.getAnalyzer(analyzerId).block(); + System.out.println("Verified analyzer exists with ID: " + retrievedAnalyzer.getAnalyzerId()); + + // Delete the analyzer + contentUnderstandingAsyncClient.deleteAnalyzer(analyzerId).block(); + System.out.println("Analyzer deleted successfully: " + analyzerId); + + // Verify the analyzer no longer exists + boolean analyzerDeleted = false; + try { + contentUnderstandingAsyncClient.getAnalyzer(analyzerId).block(); + } catch (ResourceNotFoundException e) { + analyzerDeleted = true; + System.out.println("Confirmed: Analyzer no longer exists"); + } + // END:ContentUnderstandingDeleteAnalyzerAsync + + // BEGIN:Assertion_ContentUnderstandingDeleteAnalyzerAsync + assertNotNull(analyzerId, "Analyzer ID should not be null"); + assertFalse(analyzerId.trim().isEmpty(), "Analyzer ID should not be empty"); + System.out.println("Analyzer ID verified: " + analyzerId); + + assertNotNull(retrievedAnalyzer, "Retrieved analyzer should not be null before deletion"); + assertEquals(analyzerId, retrievedAnalyzer.getAnalyzerId(), "Retrieved analyzer ID should match"); + System.out.println("Analyzer existence verified before deletion"); + + assertTrue(analyzerDeleted, "Analyzer should be deleted and not retrievable"); + System.out.println("Analyzer deletion verified"); + + System.out.println("All analyzer deletion properties validated successfully"); + // END:Assertion_ContentUnderstandingDeleteAnalyzerAsync + } + + @Test + public void testDeleteNonexistentAnalyzerAsync() { + // Try to delete a non-existent analyzer + String nonExistentId = testResourceNamer.randomName("non_existent_analyzer_", 50); + + System.out.println("\nAttempting to delete non-existent analyzer: " + nonExistentId); + + // Note: The SDK allows deleting non-existent analyzers without throwing an exception + // This is a valid behavior (idempotent delete operation) + try { + contentUnderstandingAsyncClient.deleteAnalyzer(nonExistentId).block(); + System.out.println("Delete operation completed (idempotent - no error for non-existent resource)"); + } catch (ResourceNotFoundException e) { + System.out.println("ResourceNotFoundException caught (alternative behavior): " + e.getMessage()); + } + + System.out.println("Non-existent analyzer deletion behavior verified (SDK allows idempotent deletes)"); + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample10_AnalyzeConfigs.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample10_AnalyzeConfigs.java new file mode 100644 index 000000000000..fadd32e08dab --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample10_AnalyzeConfigs.java @@ -0,0 +1,185 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.AnalysisResult; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.DocumentAnnotation; +import com.azure.ai.contentunderstanding.models.DocumentChartFigure; +import com.azure.ai.contentunderstanding.models.DocumentContent; +import com.azure.ai.contentunderstanding.models.DocumentFormula; +import com.azure.ai.contentunderstanding.models.DocumentHyperlink; +import com.azure.core.util.BinaryData; +import com.azure.core.util.polling.SyncPoller; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Sample demonstrating how to analyze documents with advanced configs using prebuilt-documentSearch. + * This sample shows: + * 1. Using prebuilt-documentSearch analyzer which has formulas, layout, and OCR enabled + * 2. Extracting charts from documents + * 3. Extracting hyperlinks from documents + * 4. Extracting formulas from document pages + * 5. Extracting annotations from documents + */ +public class Sample10_AnalyzeConfigs extends ContentUnderstandingClientTestBase { + + @Test + public void testAnalyzeConfigs() throws IOException { + + // BEGIN:ContentUnderstandingAnalyzeWithConfigs + // Load local sample file + Path filePath = Paths.get("src/samples/resources/sample_document_features.pdf"); + byte[] fileBytes = Files.readAllBytes(filePath); + BinaryData binaryData = BinaryData.fromBytes(fileBytes); + + System.out.println("Analyzing " + filePath + " with prebuilt-documentSearch..."); + System.out.println("Note: prebuilt-documentSearch has formulas, layout, and OCR enabled by default."); + + // Analyze with prebuilt-documentSearch which has formulas, layout, and OCR enabled + // These configs enable extraction of charts, annotations, hyperlinks, and formulas + SyncPoller operation + = contentUnderstandingClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData); + + AnalysisResult result = operation.getFinalResult(); + // END:ContentUnderstandingAnalyzeWithConfigs + + // BEGIN:Assertion_ContentUnderstandingAnalyzeWithConfigs + assertNotNull(operation, "Analysis operation should not be null"); + assertTrue(operation.waitForCompletion().getStatus().isComplete(), "Operation should be completed"); + System.out.println("Analysis operation properties verified"); + + assertNotNull(result, "Analysis result should not be null"); + assertNotNull(result.getContents(), "Result should contain contents"); + assertTrue(result.getContents().size() > 0, "Result should have at least one content"); + assertEquals(1, result.getContents().size(), "PDF file should have exactly one content element"); + System.out.println("Analysis result contains " + result.getContents().size() + " content(s)"); + + // Verify document content type + DocumentContent firstDocContent = result.getContents().get(0) instanceof DocumentContent + ? (DocumentContent) result.getContents().get(0) + : null; + assertNotNull(firstDocContent, "Content should be DocumentContent"); + assertTrue(firstDocContent.getStartPageNumber() >= 1, "Start page should be >= 1"); + assertTrue(firstDocContent.getEndPageNumber() >= firstDocContent.getStartPageNumber(), + "End page should be >= start page"); + int totalPages = firstDocContent.getEndPageNumber() - firstDocContent.getStartPageNumber() + 1; + System.out.println("Document has " + totalPages + " page(s) from " + firstDocContent.getStartPageNumber() + + " to " + firstDocContent.getEndPageNumber()); + System.out.println("Document features analysis with configs completed successfully"); + // END:Assertion_ContentUnderstandingAnalyzeWithConfigs + + // BEGIN:ContentUnderstandingExtractCharts + // Extract charts from document content + if (result.getContents().get(0) instanceof DocumentContent) { + DocumentContent documentContent = (DocumentContent) result.getContents().get(0); + + if (documentContent.getFigures() != null && !documentContent.getFigures().isEmpty()) { + List chartFigures = documentContent.getFigures() + .stream() + .filter(f -> f instanceof DocumentChartFigure) + .map(f -> (DocumentChartFigure) f) + .collect(Collectors.toList()); + + System.out.println("Found " + chartFigures.size() + " chart(s)"); + for (DocumentChartFigure chart : chartFigures) { + System.out.println(" Chart ID: " + chart.getId()); + if (chart.getDescription() != null && !chart.getDescription().isEmpty()) { + System.out.println(" Description: " + chart.getDescription()); + } + if (chart.getCaption() != null + && chart.getCaption().getContent() != null + && !chart.getCaption().getContent().isEmpty()) { + System.out.println(" Caption: " + chart.getCaption().getContent()); + } + } + } + } + // END:ContentUnderstandingExtractCharts + + // BEGIN:ContentUnderstandingExtractHyperlinks + // Extract hyperlinks from document content + if (result.getContents().get(0) instanceof DocumentContent) { + DocumentContent docContent = (DocumentContent) result.getContents().get(0); + + if (docContent.getHyperlinks() != null && !docContent.getHyperlinks().isEmpty()) { + System.out.println("\nFound " + docContent.getHyperlinks().size() + " hyperlink(s)"); + for (DocumentHyperlink hyperlink : docContent.getHyperlinks()) { + System.out + .println(" URL: " + (hyperlink.getUrl() != null ? hyperlink.getUrl() : "(not available)")); + System.out.println(" Content: " + + (hyperlink.getContent() != null ? hyperlink.getContent() : "(not available)")); + } + } + } + // END:ContentUnderstandingExtractHyperlinks + + // BEGIN:ContentUnderstandingExtractFormulas + // Extract formulas from document pages + if (result.getContents().get(0) instanceof DocumentContent) { + DocumentContent content = (DocumentContent) result.getContents().get(0); + + if (content.getPages() != null) { + int formulaCount = 0; + for (com.azure.ai.contentunderstanding.models.DocumentPage page : content.getPages()) { + if (page.getFormulas() != null) { + formulaCount += page.getFormulas().size(); + } + } + + if (formulaCount > 0) { + System.out.println("\nFound " + formulaCount + " formula(s)"); + for (com.azure.ai.contentunderstanding.models.DocumentPage page : content.getPages()) { + if (page.getFormulas() != null) { + for (DocumentFormula formula : page.getFormulas()) { + System.out.println(" Formula Kind: " + formula.getKind()); + System.out.println(" LaTeX: " + + (formula.getValue() != null ? formula.getValue() : "(not available)")); + if (formula.getConfidence() != null) { + System.out.println(String.format(" Confidence: %.2f", formula.getConfidence())); + } + } + } + } + } + } + } + // END:ContentUnderstandingExtractFormulas + + // BEGIN:ContentUnderstandingExtractAnnotations + // Extract annotations from document content + if (result.getContents().get(0) instanceof DocumentContent) { + DocumentContent document = (DocumentContent) result.getContents().get(0); + + if (document.getAnnotations() != null && !document.getAnnotations().isEmpty()) { + System.out.println("\nFound " + document.getAnnotations().size() + " annotation(s)"); + for (DocumentAnnotation annotation : document.getAnnotations()) { + System.out.println(" Annotation ID: " + annotation.getId()); + System.out.println(" Kind: " + annotation.getKind()); + if (annotation.getAuthor() != null && !annotation.getAuthor().isEmpty()) { + System.out.println(" Author: " + annotation.getAuthor()); + } + if (annotation.getComments() != null && !annotation.getComments().isEmpty()) { + System.out.println(" Comments: " + annotation.getComments().size()); + for (com.azure.ai.contentunderstanding.models.DocumentAnnotationComment comment : annotation + .getComments()) { + System.out.println(" - " + comment.getMessage()); + } + } + } + } + } + // END:ContentUnderstandingExtractAnnotations + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample10_AnalyzeConfigsAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample10_AnalyzeConfigsAsync.java new file mode 100644 index 000000000000..b07bdcd6ef71 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample10_AnalyzeConfigsAsync.java @@ -0,0 +1,193 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.AnalysisResult; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.DocumentAnnotation; +import com.azure.ai.contentunderstanding.models.DocumentChartFigure; +import com.azure.ai.contentunderstanding.models.DocumentContent; +import com.azure.ai.contentunderstanding.models.DocumentFormula; +import com.azure.ai.contentunderstanding.models.DocumentHyperlink; +import com.azure.core.util.BinaryData; +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.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Async sample demonstrating how to analyze documents with advanced configs using prebuilt-documentSearch. + * This sample shows: + * 1. Using prebuilt-documentSearch analyzer which has formulas, layout, and OCR enabled + * 2. Extracting charts from documents + * 3. Extracting hyperlinks from documents + * 4. Extracting formulas from document pages + * 5. Extracting annotations from documents + */ +public class Sample10_AnalyzeConfigsAsync extends ContentUnderstandingClientTestBase { + + @Test + public void testAnalyzeConfigsAsync() throws IOException { + + // BEGIN:ContentUnderstandingAnalyzeWithConfigsAsync + // Load local sample file + Path filePath = Paths.get("src/samples/resources/sample_document_features.pdf"); + byte[] fileBytes = Files.readAllBytes(filePath); + BinaryData binaryData = BinaryData.fromBytes(fileBytes); + + System.out.println("Analyzing " + filePath + " with prebuilt-documentSearch..."); + System.out.println("Note: prebuilt-documentSearch has formulas, layout, and OCR enabled by default."); + + // Analyze with prebuilt-documentSearch which has formulas, layout, and OCR enabled + // These configs enable extraction of charts, annotations, hyperlinks, and formulas + PollerFlux operation + = contentUnderstandingAsyncClient.beginAnalyzeBinary("prebuilt-documentSearch", binaryData); + + // Use reactive pattern: chain operations using flatMap + // 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())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + // END:ContentUnderstandingAnalyzeWithConfigsAsync + + // BEGIN:Assertion_ContentUnderstandingAnalyzeWithConfigsAsync + assertNotNull(operation, "Analysis operation should not be null"); + assertNotNull(result, "Analysis result should not be null"); + assertNotNull(result.getContents(), "Result should contain contents"); + assertTrue(result.getContents().size() > 0, "Result should have at least one content"); + assertEquals(1, result.getContents().size(), "PDF file should have exactly one content element"); + System.out.println("Analysis operation properties verified"); + System.out.println("Analysis result contains " + result.getContents().size() + " content(s)"); + + // Verify document content type + DocumentContent firstDocContent = result.getContents().get(0) instanceof DocumentContent + ? (DocumentContent) result.getContents().get(0) + : null; + assertNotNull(firstDocContent, "Content should be DocumentContent"); + assertTrue(firstDocContent.getStartPageNumber() >= 1, "Start page should be >= 1"); + assertTrue(firstDocContent.getEndPageNumber() >= firstDocContent.getStartPageNumber(), + "End page should be >= start page"); + int totalPages = firstDocContent.getEndPageNumber() - firstDocContent.getStartPageNumber() + 1; + System.out.println("Document has " + totalPages + " page(s) from " + firstDocContent.getStartPageNumber() + + " to " + firstDocContent.getEndPageNumber()); + System.out.println("Document features analysis with configs completed successfully"); + // END:Assertion_ContentUnderstandingAnalyzeWithConfigsAsync + + // BEGIN:ContentUnderstandingExtractChartsAsync + // Extract charts from document content + if (result.getContents().get(0) instanceof DocumentContent) { + DocumentContent documentContent = (DocumentContent) result.getContents().get(0); + + if (documentContent.getFigures() != null && !documentContent.getFigures().isEmpty()) { + List chartFigures = documentContent.getFigures() + .stream() + .filter(f -> f instanceof DocumentChartFigure) + .map(f -> (DocumentChartFigure) f) + .collect(Collectors.toList()); + + System.out.println("Found " + chartFigures.size() + " chart(s)"); + for (DocumentChartFigure chart : chartFigures) { + System.out.println(" Chart ID: " + chart.getId()); + if (chart.getDescription() != null && !chart.getDescription().isEmpty()) { + System.out.println(" Description: " + chart.getDescription()); + } + if (chart.getCaption() != null + && chart.getCaption().getContent() != null + && !chart.getCaption().getContent().isEmpty()) { + System.out.println(" Caption: " + chart.getCaption().getContent()); + } + } + } + } + // END:ContentUnderstandingExtractChartsAsync + + // BEGIN:ContentUnderstandingExtractHyperlinksAsync + // Extract hyperlinks from document content + if (result.getContents().get(0) instanceof DocumentContent) { + DocumentContent docContent = (DocumentContent) result.getContents().get(0); + + if (docContent.getHyperlinks() != null && !docContent.getHyperlinks().isEmpty()) { + System.out.println("\nFound " + docContent.getHyperlinks().size() + " hyperlink(s)"); + for (DocumentHyperlink hyperlink : docContent.getHyperlinks()) { + System.out + .println(" URL: " + (hyperlink.getUrl() != null ? hyperlink.getUrl() : "(not available)")); + System.out.println(" Content: " + + (hyperlink.getContent() != null ? hyperlink.getContent() : "(not available)")); + } + } + } + // END:ContentUnderstandingExtractHyperlinksAsync + + // BEGIN:ContentUnderstandingExtractFormulasAsync + // Extract formulas from document pages + if (result.getContents().get(0) instanceof DocumentContent) { + DocumentContent content = (DocumentContent) result.getContents().get(0); + + if (content.getPages() != null) { + int formulaCount = 0; + for (com.azure.ai.contentunderstanding.models.DocumentPage page : content.getPages()) { + if (page.getFormulas() != null) { + formulaCount += page.getFormulas().size(); + } + } + + if (formulaCount > 0) { + System.out.println("\nFound " + formulaCount + " formula(s)"); + for (com.azure.ai.contentunderstanding.models.DocumentPage page : content.getPages()) { + if (page.getFormulas() != null) { + for (DocumentFormula formula : page.getFormulas()) { + System.out.println(" Formula Kind: " + formula.getKind()); + System.out.println(" LaTeX: " + + (formula.getValue() != null ? formula.getValue() : "(not available)")); + if (formula.getConfidence() != null) { + System.out.println(String.format(" Confidence: %.2f", formula.getConfidence())); + } + } + } + } + } + } + } + // END:ContentUnderstandingExtractFormulasAsync + + // BEGIN:ContentUnderstandingExtractAnnotationsAsync + // Extract annotations from document content + if (result.getContents().get(0) instanceof DocumentContent) { + DocumentContent document = (DocumentContent) result.getContents().get(0); + + if (document.getAnnotations() != null && !document.getAnnotations().isEmpty()) { + System.out.println("\nFound " + document.getAnnotations().size() + " annotation(s)"); + for (DocumentAnnotation annotation : document.getAnnotations()) { + System.out.println(" Annotation ID: " + annotation.getId()); + System.out.println(" Kind: " + annotation.getKind()); + if (annotation.getAuthor() != null && !annotation.getAuthor().isEmpty()) { + System.out.println(" Author: " + annotation.getAuthor()); + } + if (annotation.getComments() != null && !annotation.getComments().isEmpty()) { + System.out.println(" Comments: " + annotation.getComments().size()); + for (com.azure.ai.contentunderstanding.models.DocumentAnnotationComment comment : annotation + .getComments()) { + System.out.println(" - " + comment.getMessage()); + } + } + } + } + } + // END:ContentUnderstandingExtractAnnotationsAsync + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample11_AnalyzeReturnRawJson.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample11_AnalyzeReturnRawJson.java new file mode 100644 index 000000000000..0cadf0472548 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample11_AnalyzeReturnRawJson.java @@ -0,0 +1,155 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.core.http.rest.RequestOptions; +import com.azure.core.util.BinaryData; +import com.azure.core.util.polling.SyncPoller; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * Sample demonstrating how to analyze documents and get raw JSON response using protocol methods. + * This sample shows: + * 1. Using protocol method to get raw JSON response instead of strongly-typed objects + * 2. Parsing raw JSON response + * 3. Pretty-printing and saving JSON to file + * + * Note: For production use, prefer the object model approach (beginAnalyzeBinary with typed parameters) + * which returns AnalysisResult objects that are easier to work with. + */ +public class Sample11_AnalyzeReturnRawJson extends ContentUnderstandingClientTestBase { + + @Test + public void testAnalyzeReturnRawJson() throws IOException { + + // BEGIN:ContentUnderstandingAnalyzeReturnRawJson + // Load local test file + Path filePath = Paths.get("src/samples/resources/sample_invoice.pdf"); + byte[] fileBytes = Files.readAllBytes(filePath); + + // Prepare request body with binary data using JSON format + // Note: The API expects a JSON request with "inputs" array containing document data + String base64Data = java.util.Base64.getEncoder().encodeToString(fileBytes); + String requestJson = String.format("{\"inputs\": [{\"data\": \"%s\"}]}", base64Data); + BinaryData requestBody = BinaryData.fromString(requestJson); + + // Use protocol method to get raw JSON response + // Note: For production use, prefer the object model approach (beginAnalyze with typed parameters) + // which returns AnalysisResult objects that are easier to work with + SyncPoller operation + = contentUnderstandingClient.beginAnalyze("prebuilt-documentSearch", requestBody, new RequestOptions()); + + BinaryData responseData = operation.getFinalResult(); + // END:ContentUnderstandingAnalyzeReturnRawJson + + // BEGIN:Assertion_ContentUnderstandingAnalyzeReturnRawJson + assertTrue(Files.exists(filePath), "Sample file should exist at " + filePath); + assertTrue(fileBytes.length > 0, "File should not be empty"); + System.out.println("File loaded: " + filePath + " (" + String.format("%,d", fileBytes.length) + " bytes)"); + + assertNotNull(operation, "Analysis operation should not be null"); + assertTrue(operation.waitForCompletion().getStatus().isComplete(), "Operation should be completed"); + System.out.println("Analysis operation completed with status: " + operation.poll().getStatus()); + + assertNotNull(responseData, "Response data should not be null"); + assertTrue(responseData.toBytes().length > 0, "Response data should not be empty"); + System.out.println("Response data size: " + String.format("%,d", responseData.toBytes().length) + " bytes"); + + // Verify response data can be converted to string + String responseString = responseData.toString(); + assertNotNull(responseString, "Response string should not be null"); + assertTrue(responseString.length() > 0, "Response string should not be empty"); + System.out.println("Response string length: " + String.format("%,d", responseString.length()) + " characters"); + + // Verify response is valid JSON format + try { + ObjectMapper mapper = new ObjectMapper(); + JsonNode jsonNode = mapper.readTree(responseData.toBytes()); + assertNotNull(jsonNode, "Response should be valid JSON"); + System.out.println("Response is valid JSON format"); + } catch (Exception ex) { + fail("Response data is not valid JSON: " + ex.getMessage()); + } + + System.out.println("Raw JSON analysis operation completed successfully"); + // END:Assertion_ContentUnderstandingAnalyzeReturnRawJson + + // BEGIN:ContentUnderstandingParseRawJson + // Parse the raw JSON response + ObjectMapper mapper = new ObjectMapper(); + JsonNode jsonNode = mapper.readTree(responseData.toBytes()); + + // Pretty-print the JSON + String prettyJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode); + + // Create output directory if it doesn't exist + Path outputDir = Paths.get("target/sample_output"); + Files.createDirectories(outputDir); + + // Save to file + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); + String outputFileName = "analyze_result_" + timestamp + ".json"; + Path outputPath = outputDir.resolve(outputFileName); + Files.write(outputPath, prettyJson.getBytes(java.nio.charset.StandardCharsets.UTF_8)); + + System.out.println("Raw JSON response saved to: " + outputPath); + System.out.println("File size: " + String.format("%,d", prettyJson.length()) + " characters"); + // END:ContentUnderstandingParseRawJson + + // BEGIN:Assertion_ContentUnderstandingParseRawJson + assertNotNull(jsonNode, "JSON node should not be null"); + System.out.println("JSON document parsed successfully"); + + assertNotNull(prettyJson, "Pretty JSON string should not be null"); + assertTrue(prettyJson.length() > 0, "Pretty JSON should not be empty"); + assertTrue(prettyJson.length() >= responseData.toString().length(), + "Pretty JSON should be same size or larger than original (due to indentation)"); + System.out.println("Pretty JSON generated: " + String.format("%,d", prettyJson.length()) + " characters"); + + // Verify JSON is properly indented + assertTrue(prettyJson.contains("\n"), "Pretty JSON should contain line breaks"); + assertTrue(prettyJson.contains(" "), "Pretty JSON should contain indentation"); + System.out.println("JSON is properly formatted with indentation"); + + // Verify output directory + assertNotNull(outputDir, "Output directory path should not be null"); + assertTrue(Files.exists(outputDir), "Output directory should exist at " + outputDir); + System.out.println("Output directory verified: " + outputDir); + + // Verify output file name format + assertNotNull(outputFileName, "Output file name should not be null"); + assertTrue(outputFileName.startsWith("analyze_result_"), + "Output file name should start with 'analyze_result_'"); + assertTrue(outputFileName.endsWith(".json"), "Output file name should end with '.json'"); + System.out.println("Output file name: " + outputFileName); + + // Verify output file path + assertNotNull(outputPath, "Output file path should not be null"); + assertTrue(outputPath.toString().contains(outputDir.toString()), "Output path should contain output directory"); + assertTrue(outputPath.toString().endsWith(".json"), "Output path should end with '.json'"); + assertTrue(Files.exists(outputPath), "Output file should exist at " + outputPath); + System.out.println("Output file created: " + outputPath); + + // Verify file content size + long fileSize = Files.size(outputPath); + assertTrue(fileSize > 0, "Output file should not be empty"); + assertEquals(prettyJson.length(), fileSize, "File size should match pretty JSON length"); + System.out.println("Output file size verified: " + String.format("%,d", fileSize) + " bytes"); + + System.out.println("Raw JSON parsing and saving completed successfully"); + // END:Assertion_ContentUnderstandingParseRawJson + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample11_AnalyzeReturnRawJsonAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample11_AnalyzeReturnRawJsonAsync.java new file mode 100644 index 000000000000..b126ee26a5ff --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample11_AnalyzeReturnRawJsonAsync.java @@ -0,0 +1,162 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.core.http.rest.RequestOptions; +import com.azure.core.util.BinaryData; +import com.azure.core.util.polling.PollerFlux; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * Async sample demonstrating how to analyze documents and get raw JSON response using protocol methods. + * This sample shows: + * 1. Using protocol method to get raw JSON response instead of strongly-typed objects + * 2. Parsing raw JSON response + * 3. Pretty-printing and saving JSON to file + * + * Note: For production use, prefer the object model approach (beginAnalyzeBinary with typed parameters) + * which returns AnalysisResult objects that are easier to work with. + */ +public class Sample11_AnalyzeReturnRawJsonAsync extends ContentUnderstandingClientTestBase { + + @Test + public void testAnalyzeReturnRawJsonAsync() throws IOException { + + // BEGIN:ContentUnderstandingAnalyzeReturnRawJsonAsync + // Load local test file + Path filePath = Paths.get("src/samples/resources/sample_invoice.pdf"); + byte[] fileBytes = Files.readAllBytes(filePath); + + // Prepare request body with binary data using JSON format + // Note: The API expects a JSON request with "inputs" array containing document data + String base64Data = java.util.Base64.getEncoder().encodeToString(fileBytes); + String requestJson = String.format("{\"inputs\": [{\"data\": \"%s\"}]}", base64Data); + BinaryData requestBody = BinaryData.fromString(requestJson); + + // Use protocol method to get raw JSON response + // Note: For production use, prefer the object model approach (beginAnalyze with typed parameters) + // which returns AnalysisResult objects that are easier to work with + PollerFlux operation = contentUnderstandingAsyncClient + .beginAnalyze("prebuilt-documentSearch", requestBody, new RequestOptions()); + + // Use reactive pattern: chain operations using flatMap + // In a real application, you would use subscribe() instead of block() + BinaryData responseData = operation.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + // END:ContentUnderstandingAnalyzeReturnRawJsonAsync + + // BEGIN:Assertion_ContentUnderstandingAnalyzeReturnRawJsonAsync + assertTrue(Files.exists(filePath), "Sample file should exist at " + filePath); + assertTrue(fileBytes.length > 0, "File should not be empty"); + System.out.println("File loaded: " + filePath + " (" + String.format("%,d", fileBytes.length) + " bytes)"); + + assertNotNull(operation, "Analysis operation should not be null"); + assertNotNull(responseData, "Response data should not be null"); + assertTrue(responseData.toBytes().length > 0, "Response data should not be empty"); + System.out.println("Response data size: " + String.format("%,d", responseData.toBytes().length) + " bytes"); + + // Verify response data can be converted to string + String responseString = responseData.toString(); + assertNotNull(responseString, "Response string should not be null"); + assertTrue(responseString.length() > 0, "Response string should not be empty"); + System.out.println("Response string length: " + String.format("%,d", responseString.length()) + " characters"); + + // Verify response is valid JSON format + try { + ObjectMapper mapper = new ObjectMapper(); + JsonNode jsonNode = mapper.readTree(responseData.toBytes()); + assertNotNull(jsonNode, "Response should be valid JSON"); + System.out.println("Response is valid JSON format"); + } catch (Exception ex) { + fail("Response data is not valid JSON: " + ex.getMessage()); + } + + System.out.println("Raw JSON analysis operation completed successfully"); + // END:Assertion_ContentUnderstandingAnalyzeReturnRawJsonAsync + + // BEGIN:ContentUnderstandingParseRawJsonAsync + // Parse the raw JSON response + ObjectMapper mapper = new ObjectMapper(); + JsonNode jsonNode = mapper.readTree(responseData.toBytes()); + + // Pretty-print the JSON + String prettyJson = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(jsonNode); + + // Create output directory if it doesn't exist + Path outputDir = Paths.get("target/sample_output"); + Files.createDirectories(outputDir); + + // Save to file + String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); + String outputFileName = "analyze_result_" + timestamp + ".json"; + Path outputPath = outputDir.resolve(outputFileName); + Files.write(outputPath, prettyJson.getBytes(java.nio.charset.StandardCharsets.UTF_8)); + + System.out.println("Raw JSON response saved to: " + outputPath); + System.out.println("File size: " + String.format("%,d", prettyJson.length()) + " characters"); + // END:ContentUnderstandingParseRawJsonAsync + + // BEGIN:Assertion_ContentUnderstandingParseRawJsonAsync + assertNotNull(jsonNode, "JSON node should not be null"); + System.out.println("JSON document parsed successfully"); + + assertNotNull(prettyJson, "Pretty JSON string should not be null"); + assertTrue(prettyJson.length() > 0, "Pretty JSON should not be empty"); + assertTrue(prettyJson.length() >= responseData.toString().length(), + "Pretty JSON should be same size or larger than original (due to indentation)"); + System.out.println("Pretty JSON generated: " + String.format("%,d", prettyJson.length()) + " characters"); + + // Verify JSON is properly indented + assertTrue(prettyJson.contains("\n"), "Pretty JSON should contain line breaks"); + assertTrue(prettyJson.contains(" "), "Pretty JSON should contain indentation"); + System.out.println("JSON is properly formatted with indentation"); + + // Verify output directory + assertNotNull(outputDir, "Output directory path should not be null"); + assertTrue(Files.exists(outputDir), "Output directory should exist at " + outputDir); + System.out.println("Output directory verified: " + outputDir); + + // Verify output file name format + assertNotNull(outputFileName, "Output file name should not be null"); + assertTrue(outputFileName.startsWith("analyze_result_"), + "Output file name should start with 'analyze_result_'"); + assertTrue(outputFileName.endsWith(".json"), "Output file name should end with '.json'"); + System.out.println("Output file name: " + outputFileName); + + // Verify output file path + assertNotNull(outputPath, "Output file path should not be null"); + assertTrue(outputPath.toString().contains(outputDir.toString()), "Output path should contain output directory"); + assertTrue(outputPath.toString().endsWith(".json"), "Output path should end with '.json'"); + assertTrue(Files.exists(outputPath), "Output file should exist at " + outputPath); + System.out.println("Output file created: " + outputPath); + + // Verify file content size + long fileSize = Files.size(outputPath); + assertTrue(fileSize > 0, "Output file should not be empty"); + assertEquals(prettyJson.length(), fileSize, "File size should match pretty JSON length"); + System.out.println("Output file size verified: " + String.format("%,d", fileSize) + " bytes"); + + System.out.println("Raw JSON parsing and saving completed successfully"); + // END:Assertion_ContentUnderstandingParseRawJsonAsync + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample12_GetResultFile.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample12_GetResultFile.java new file mode 100644 index 000000000000..02dc9d752876 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample12_GetResultFile.java @@ -0,0 +1,297 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.AnalysisInput; +import com.azure.ai.contentunderstanding.models.AnalysisResult; +import com.azure.ai.contentunderstanding.models.AudioVisualContent; +import com.azure.ai.contentunderstanding.models.DocumentContent; +import com.azure.core.util.BinaryData; +import com.azure.core.util.polling.SyncPoller; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Sample demonstrates how to retrieve result files (like keyframe images) from video analysis operations. + */ +public class Sample12_GetResultFile extends ContentUnderstandingClientTestBase { + + /** + * Synchronous sample for getting result files from a completed analysis operation. + *

    + * Note: The Azure Content Understanding service requires extended time after analysis + * completion for keyframe result files to become available. This test uses retry logic + * to handle the delay. + */ + @Test + public void testGetResultFile() throws IOException { + + // BEGIN: com.azure.ai.contentunderstanding.getResultFile + // For video analysis, use a video URL to get keyframes + String videoUrl + = "https://github.com/Azure-Samples/azure-ai-content-understanding-assets/raw/refs/heads/main/videos/sdk_samples/FlightSimulator.mp4"; + + // Step 1: Start the video analysis operation + AnalysisInput input = new AnalysisInput(); + input.setUrl(videoUrl); + + SyncPoller poller + = contentUnderstandingClient.beginAnalyze("prebuilt-videoSearch", Arrays.asList(input)); + + System.out.println("Started analysis operation"); + + // Wait for completion + AnalysisResult result = poller.getFinalResult(); + System.out.println("Analysis completed successfully!"); + + // Get the operation ID from the polling result using the getId() convenience method + // The operation ID is extracted from the Operation-Location header and can be used with + // getResultFile() and deleteResult() APIs + String operationId = poller.poll().getValue().getId(); + System.out.println("Operation ID: " + operationId); + + // END: com.azure.ai.contentunderstanding.getResultFile + + // Verify operation started and completed + assertNotNull(videoUrl, "Video URL should not be null"); + System.out.println("Video URL: " + videoUrl); + + assertNotNull(operationId, "Operation ID should not be null"); + assertFalse(operationId.trim().isEmpty(), "Operation ID should not be empty"); + assertTrue(operationId.length() > 0, "Operation ID should have length > 0"); + assertFalse(operationId.contains(" "), "Operation ID should not contain spaces"); + System.out.println("Operation ID obtained: " + operationId); + System.out.println(" Length: " + operationId.length() + " characters"); + + // Verify result + assertNotNull(result, "Analysis result should not be null"); + assertNotNull(result.getContents(), "Result should contain contents"); + assertTrue(result.getContents().size() > 0, "Result should have at least one content"); + System.out.println("Analysis result contains " + result.getContents().size() + " content(s)"); + + // BEGIN: com.azure.ai.contentunderstanding.getResultFile.keyframes + // Step 2: Get keyframes from video analysis result + AudioVisualContent videoContent = null; + for (Object content : result.getContents()) { + if (content instanceof AudioVisualContent) { + videoContent = (AudioVisualContent) content; + break; + } + } + + if (videoContent != null + && videoContent.getKeyFrameTimes() != null + && !videoContent.getKeyFrameTimes().isEmpty()) { + List keyFrameTimes + = videoContent.getKeyFrameTimes().stream().map(Duration::toMillis).collect(Collectors.toList()); + System.out.println("Total keyframes: " + keyFrameTimes.size()); + + // Get the first keyframe + long firstFrameTimeMs = keyFrameTimes.get(0); + System.out.println("First keyframe time: " + firstFrameTimeMs + " ms"); + + // Construct the keyframe path + String framePath = "keyframes/" + firstFrameTimeMs; + System.out.println("Getting result file: " + framePath); + + // Retrieve the keyframe image using convenience method with retry logic + // Result files may not be immediately available after analysis completion + BinaryData fileData = null; + int maxRetries = 12; + int retryDelayMs = 10000; + for (int attempt = 1; attempt <= maxRetries; attempt++) { + try { + fileData = contentUnderstandingClient.getResultFile(operationId, framePath); + break; // Success, exit retry loop + } catch (Exception e) { + if (attempt == maxRetries) { + throw e; // Re-throw on final attempt + } + System.out.println("Attempt " + attempt + " failed: " + e.getMessage()); + System.out.println("Waiting " + (retryDelayMs / 1000) + " seconds before retry..."); + try { + Thread.sleep(retryDelayMs); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while waiting for retry", ie); + } + } + } + byte[] imageBytes = fileData.toBytes(); + System.out.println("Retrieved keyframe image (" + String.format("%,d", imageBytes.length) + " bytes)"); + + // Save the keyframe image + Path outputDir = Paths.get("target", "sample_output"); + Files.createDirectories(outputDir); + String outputFileName = "keyframe_" + firstFrameTimeMs + ".jpg"; + Path outputPath = outputDir.resolve(outputFileName); + Files.write(outputPath, imageBytes); + + System.out.println("Keyframe image saved to: " + outputPath.toAbsolutePath()); + // END: com.azure.ai.contentunderstanding.getResultFile.keyframes + + // Verify video content + assertNotNull(videoContent, "Video content should not be null"); + assertNotNull(keyFrameTimes, "KeyFrameTimesMs should not be null"); + assertTrue(keyFrameTimes.size() > 0, "Should have at least one keyframe"); + System.out.println("\n🎬 Keyframe Information:"); + System.out.println("Total keyframes: " + keyFrameTimes.size()); + + // Verify keyframe times are valid + for (long frameTime : keyFrameTimes) { + assertTrue(frameTime >= 0, "Keyframe time should be non-negative, but was " + frameTime); + } + + // Get keyframe statistics + long lastFrameTimeMs = keyFrameTimes.get(keyFrameTimes.size() - 1); + double avgFrameInterval = keyFrameTimes.size() > 1 + ? (double) (lastFrameTimeMs - firstFrameTimeMs) / (keyFrameTimes.size() - 1) + : 0; + + assertTrue(firstFrameTimeMs >= 0, "First keyframe time should be >= 0"); + assertTrue(lastFrameTimeMs >= firstFrameTimeMs, "Last keyframe time should be >= first keyframe time"); + + System.out.println(" First keyframe: " + firstFrameTimeMs + " ms (" + + String.format("%.2f", firstFrameTimeMs / 1000.0) + " seconds)"); + System.out.println(" Last keyframe: " + lastFrameTimeMs + " ms (" + + String.format("%.2f", lastFrameTimeMs / 1000.0) + " seconds)"); + if (keyFrameTimes.size() > 1) { + System.out.println(" Average interval: " + String.format("%.2f", avgFrameInterval) + " ms"); + } + + // Verify file data + System.out.println("\n📥 File Data Verification:"); + assertNotNull(fileData, "File data should not be null"); + + // Verify image data + System.out.println("\nVerifying image data..."); + assertNotNull(imageBytes, "Image bytes should not be null"); + assertTrue(imageBytes.length > 0, "Image should have content"); + assertTrue(imageBytes.length >= 100, "Image should have reasonable size (>= 100 bytes)"); + System.out.println("Image size: " + String.format("%,d", imageBytes.length) + " bytes (" + + String.format("%.2f", imageBytes.length / 1024.0) + " KB)"); + + // Verify image format + String imageFormat = detectImageFormat(imageBytes); + System.out.println("Detected image format: " + imageFormat); + assertNotEquals("Unknown", imageFormat, "Image format should be recognized"); + + // Verify saved file + System.out.println("\n💾 Saved File Verification:"); + assertTrue(Files.exists(outputPath), "Saved file should exist"); + long fileSize = Files.size(outputPath); + assertTrue(fileSize > 0, "Saved file should have content"); + assertEquals(imageBytes.length, fileSize, "Saved file size should match image size"); + System.out.println("File saved: " + outputPath.toAbsolutePath()); + System.out.println("File size verified: " + String.format("%,d", fileSize) + " bytes"); + + // Verify file can be read back + byte[] readBackBytes = Files.readAllBytes(outputPath); + assertEquals(imageBytes.length, readBackBytes.length, "Read back file size should match original"); + System.out.println("File content verified (read back matches original)"); + + // Test additional keyframes if available + if (keyFrameTimes.size() > 1) { + System.out + .println("\nTesting additional keyframes (" + (keyFrameTimes.size() - 1) + " more available)..."); + int middleIndex = keyFrameTimes.size() / 2; + long middleFrameTimeMs = keyFrameTimes.get(middleIndex); + String middleFramePath = "keyframes/" + middleFrameTimeMs; + + BinaryData middleFileData = contentUnderstandingClient.getResultFile(operationId, middleFramePath); + assertNotNull(middleFileData, "Middle keyframe data should not be null"); + assertTrue(middleFileData.toBytes().length > 0, "Middle keyframe should have content"); + System.out.println( + "Successfully retrieved keyframe at index " + middleIndex + " (" + middleFrameTimeMs + " ms)"); + System.out.println(" Size: " + String.format("%,d", middleFileData.toBytes().length) + " bytes"); + } + + // Summary + System.out.println("\n✅ Keyframe retrieval verification completed successfully:"); + System.out.println(" Operation ID: " + operationId); + System.out.println(" Total keyframes: " + keyFrameTimes.size()); + System.out.println(" First keyframe time: " + firstFrameTimeMs + " ms"); + System.out.println(" Image format: " + imageFormat); + System.out.println(" Image size: " + String.format("%,d", imageBytes.length) + " bytes"); + System.out.println(" Saved to: " + outputPath.toAbsolutePath()); + System.out.println(" File verified: Yes"); + } else { + // No video content (expected for document analysis) + System.out.println("\n📚 GetResultFile API Usage Example:"); + System.out.println(" For video analysis with keyframes:"); + System.out.println(" 1. Analyze video with prebuilt-videoSearch"); + System.out.println(" 2. Get keyframe times from AudioVisualContent.getKeyFrameTimesMs()"); + System.out.println(" 3. Retrieve keyframes using getResultFile():"); + System.out.println(" BinaryData fileData = contentUnderstandingClient.getResultFile(\"" + operationId + + "\", \"keyframes/1000\");"); + System.out.println(" 4. Save or process the keyframe image"); + + // Verify content type + if (result.getContents().get(0) instanceof DocumentContent) { + DocumentContent docContent = (DocumentContent) result.getContents().get(0); + System.out.println("\nContent type: DocumentContent (as expected)"); + System.out.println(" MIME type: " + + (docContent.getMimeType() != null ? docContent.getMimeType() : "(not specified)")); + System.out + .println(" Pages: " + docContent.getStartPageNumber() + " - " + docContent.getEndPageNumber()); + } + + assertNotNull(operationId, "Operation ID should be available for GetResultFile API"); + assertFalse(operationId.trim().isEmpty(), "Operation ID should not be empty"); + System.out.println("Operation ID available for GetResultFile API: " + operationId); + } + } + + /** + * Detect image format from magic bytes. + */ + private String detectImageFormat(byte[] imageBytes) { + if (imageBytes.length < 2) { + return "Unknown"; + } + + // Check JPEG magic bytes (FF D8) + if (imageBytes[0] == (byte) 0xFF && imageBytes[1] == (byte) 0xD8) { + return "JPEG"; + } + + // Check PNG magic bytes (89 50 4E 47) + if (imageBytes.length >= 4 + && imageBytes[0] == (byte) 0x89 + && imageBytes[1] == 0x50 + && imageBytes[2] == 0x4E + && imageBytes[3] == 0x47) { + return "PNG"; + } + + // Check GIF magic bytes (47 49 46) + if (imageBytes.length >= 3 && imageBytes[0] == 0x47 && imageBytes[1] == 0x49 && imageBytes[2] == 0x46) { + return "GIF"; + } + + // Check WebP magic bytes (52 49 46 46 ... 57 45 42 50) + if (imageBytes.length >= 12 + && imageBytes[0] == 0x52 + && imageBytes[1] == 0x49 + && imageBytes[8] == 0x57 + && imageBytes[9] == 0x45 + && imageBytes[10] == 0x42 + && imageBytes[11] == 0x50) { + return "WebP"; + } + + return "Unknown"; + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample12_GetResultFileAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample12_GetResultFileAsync.java new file mode 100644 index 000000000000..f7844fdeb7a0 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample12_GetResultFileAsync.java @@ -0,0 +1,310 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.AnalysisInput; +import com.azure.ai.contentunderstanding.models.AnalysisResult; +import com.azure.ai.contentunderstanding.models.AudioVisualContent; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.DocumentContent; +import com.azure.core.util.BinaryData; +import com.azure.core.util.polling.PollerFlux; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Async sample demonstrates how to retrieve result files (like keyframe images) from video analysis operations. + */ +public class Sample12_GetResultFileAsync extends ContentUnderstandingClientTestBase { + + /** + * Asynchronous sample for getting result files from a completed analysis operation. + *

    + * Note: The Azure Content Understanding service requires extended time after analysis + * completion for keyframe result files to become available. This test uses retry logic + * to handle the delay. + */ + @Test + public void testGetResultFileAsync() throws IOException { + + // BEGIN: com.azure.ai.contentunderstanding.getResultFileAsync + // For video analysis, use a video URL to get keyframes + String videoUrl + = "https://github.com/Azure-Samples/azure-ai-content-understanding-assets/raw/refs/heads/main/videos/sdk_samples/FlightSimulator.mp4"; + + // Step 1: Start the video analysis operation + AnalysisInput input = new AnalysisInput(); + input.setUrl(videoUrl); + + PollerFlux poller + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-videoSearch", Arrays.asList(input)); + + System.out.println("Started analysis operation"); + + // Use reactive pattern: chain operations using flatMap + // In a real application, you would use subscribe() instead of block() + // Use AtomicReference to capture the operation ID from the polling response + AtomicReference operationIdRef = new AtomicReference<>(); + AnalysisResult result = poller.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + // Capture the operation ID for later use with getResultFile() + operationIdRef.set(pollResponse.getValue().getId()); + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + + System.out.println("Analysis completed successfully!"); + String operationId = operationIdRef.get(); + System.out.println("Operation ID: " + operationId); + + // END: com.azure.ai.contentunderstanding.getResultFileAsync + + // Verify operation started and completed + assertNotNull(videoUrl, "Video URL should not be null"); + System.out.println("Video URL: " + videoUrl); + + assertNotNull(operationId, "Operation ID should not be null"); + assertFalse(operationId.trim().isEmpty(), "Operation ID should not be empty"); + assertTrue(operationId.length() > 0, "Operation ID should have length > 0"); + assertFalse(operationId.contains(" "), "Operation ID should not contain spaces"); + System.out.println("Operation ID obtained: " + operationId); + System.out.println(" Length: " + operationId.length() + " characters"); + + // Verify result + assertNotNull(result, "Analysis result should not be null"); + assertNotNull(result.getContents(), "Result should contain contents"); + assertTrue(result.getContents().size() > 0, "Result should have at least one content"); + System.out.println("Analysis result contains " + result.getContents().size() + " content(s)"); + + // BEGIN: com.azure.ai.contentunderstanding.getResultFileAsync.keyframes + // Step 2: Get keyframes from video analysis result + AudioVisualContent videoContent = null; + for (Object content : result.getContents()) { + if (content instanceof AudioVisualContent) { + videoContent = (AudioVisualContent) content; + break; + } + } + + if (videoContent != null + && videoContent.getKeyFrameTimes() != null + && !videoContent.getKeyFrameTimes().isEmpty()) { + List keyFrameTimes + = videoContent.getKeyFrameTimes().stream().map(Duration::toMillis).collect(Collectors.toList()); + System.out.println("Total keyframes: " + keyFrameTimes.size()); + + // Get the first keyframe + long firstFrameTimeMs = keyFrameTimes.get(0); + System.out.println("First keyframe time: " + firstFrameTimeMs + " ms"); + + // Construct the keyframe path + String framePath = "keyframes/" + firstFrameTimeMs; + System.out.println("Getting result file: " + framePath); + + // Retrieve the keyframe image using convenience method with retry logic + // Result files may not be immediately available after analysis completion + BinaryData fileData = null; + int maxRetries = 12; + int retryDelayMs = 10000; + for (int attempt = 1; attempt <= maxRetries; attempt++) { + try { + fileData = contentUnderstandingAsyncClient.getResultFile(operationId, framePath).block(); + break; // Success, exit retry loop + } catch (Exception e) { + if (attempt == maxRetries) { + throw e; // Re-throw on final attempt + } + System.out.println("Attempt " + attempt + " failed: " + e.getMessage()); + System.out.println("Waiting " + (retryDelayMs / 1000) + " seconds before retry..."); + try { + Thread.sleep(retryDelayMs); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + throw new RuntimeException("Interrupted while waiting for retry", ie); + } + } + } + byte[] imageBytes = fileData.toBytes(); + System.out.println("Retrieved keyframe image (" + String.format("%,d", imageBytes.length) + " bytes)"); + + // Save the keyframe image + Path outputDir = Paths.get("target", "sample_output"); + Files.createDirectories(outputDir); + String outputFileName = "keyframe_" + firstFrameTimeMs + ".jpg"; + Path outputPath = outputDir.resolve(outputFileName); + Files.write(outputPath, imageBytes); + + System.out.println("Keyframe image saved to: " + outputPath.toAbsolutePath()); + // END: com.azure.ai.contentunderstanding.getResultFileAsync.keyframes + + // Verify video content + assertNotNull(videoContent, "Video content should not be null"); + assertNotNull(keyFrameTimes, "KeyFrameTimesMs should not be null"); + assertTrue(keyFrameTimes.size() > 0, "Should have at least one keyframe"); + System.out.println("\n🎬 Keyframe Information:"); + System.out.println("Total keyframes: " + keyFrameTimes.size()); + + // Verify keyframe times are valid + for (long frameTime : keyFrameTimes) { + assertTrue(frameTime >= 0, "Keyframe time should be non-negative, but was " + frameTime); + } + + // Get keyframe statistics + long lastFrameTimeMs = keyFrameTimes.get(keyFrameTimes.size() - 1); + double avgFrameInterval = keyFrameTimes.size() > 1 + ? (double) (lastFrameTimeMs - firstFrameTimeMs) / (keyFrameTimes.size() - 1) + : 0; + + assertTrue(firstFrameTimeMs >= 0, "First keyframe time should be >= 0"); + assertTrue(lastFrameTimeMs >= firstFrameTimeMs, "Last keyframe time should be >= first keyframe time"); + + System.out.println(" First keyframe: " + firstFrameTimeMs + " ms (" + + String.format("%.2f", firstFrameTimeMs / 1000.0) + " seconds)"); + System.out.println(" Last keyframe: " + lastFrameTimeMs + " ms (" + + String.format("%.2f", lastFrameTimeMs / 1000.0) + " seconds)"); + if (keyFrameTimes.size() > 1) { + System.out.println(" Average interval: " + String.format("%.2f", avgFrameInterval) + " ms"); + } + + // Verify file data + System.out.println("\n📥 File Data Verification:"); + assertNotNull(fileData, "File data should not be null"); + + // Verify image data + System.out.println("\nVerifying image data..."); + assertNotNull(imageBytes, "Image bytes should not be null"); + assertTrue(imageBytes.length > 0, "Image should have content"); + assertTrue(imageBytes.length >= 100, "Image should have reasonable size (>= 100 bytes)"); + System.out.println("Image size: " + String.format("%,d", imageBytes.length) + " bytes (" + + String.format("%.2f", imageBytes.length / 1024.0) + " KB)"); + + // Verify image format + String imageFormat = detectImageFormat(imageBytes); + System.out.println("Detected image format: " + imageFormat); + assertNotEquals("Unknown", imageFormat, "Image format should be recognized"); + + // Verify saved file + System.out.println("\n💾 Saved File Verification:"); + assertTrue(Files.exists(outputPath), "Saved file should exist"); + long fileSize = Files.size(outputPath); + assertTrue(fileSize > 0, "Saved file should have content"); + assertEquals(imageBytes.length, fileSize, "Saved file size should match image size"); + System.out.println("File saved: " + outputPath.toAbsolutePath()); + System.out.println("File size verified: " + String.format("%,d", fileSize) + " bytes"); + + // Verify file can be read back + byte[] readBackBytes = Files.readAllBytes(outputPath); + assertEquals(imageBytes.length, readBackBytes.length, "Read back file size should match original"); + System.out.println("File content verified (read back matches original)"); + + // Test additional keyframes if available + if (keyFrameTimes.size() > 1) { + System.out + .println("\nTesting additional keyframes (" + (keyFrameTimes.size() - 1) + " more available)..."); + int middleIndex = keyFrameTimes.size() / 2; + long middleFrameTimeMs = keyFrameTimes.get(middleIndex); + String middleFramePath = "keyframes/" + middleFrameTimeMs; + + BinaryData middleFileData + = contentUnderstandingAsyncClient.getResultFile(operationId, middleFramePath).block(); + assertNotNull(middleFileData, "Middle keyframe data should not be null"); + assertTrue(middleFileData.toBytes().length > 0, "Middle keyframe should have content"); + System.out.println( + "Successfully retrieved keyframe at index " + middleIndex + " (" + middleFrameTimeMs + " ms)"); + System.out.println(" Size: " + String.format("%,d", middleFileData.toBytes().length) + " bytes"); + } + + // Summary + System.out.println("\n✅ Keyframe retrieval verification completed successfully:"); + System.out.println(" Operation ID: " + operationId); + System.out.println(" Total keyframes: " + keyFrameTimes.size()); + System.out.println(" First keyframe time: " + firstFrameTimeMs + " ms"); + System.out.println(" Image format: " + imageFormat); + System.out.println(" Image size: " + String.format("%,d", imageBytes.length) + " bytes"); + System.out.println(" Saved to: " + outputPath.toAbsolutePath()); + System.out.println(" File verified: Yes"); + } else { + // No video content (expected for document analysis) + System.out.println("\n📚 GetResultFile API Usage Example:"); + System.out.println(" For video analysis with keyframes:"); + System.out.println(" 1. Analyze video with prebuilt-videoSearch"); + System.out.println(" 2. Get keyframe times from AudioVisualContent.getKeyFrameTimesMs()"); + System.out.println(" 3. Retrieve keyframes using getResultFile():"); + System.out.println(" BinaryData fileData = contentUnderstandingAsyncClient.getResultFile(\"" + + operationId + "\", \"keyframes/1000\").block();"); + System.out.println(" 4. Save or process the keyframe image"); + + // Verify content type + if (result.getContents().get(0) instanceof DocumentContent) { + DocumentContent docContent = (DocumentContent) result.getContents().get(0); + System.out.println("\nContent type: DocumentContent (as expected)"); + System.out.println(" MIME type: " + + (docContent.getMimeType() != null ? docContent.getMimeType() : "(not specified)")); + System.out + .println(" Pages: " + docContent.getStartPageNumber() + " - " + docContent.getEndPageNumber()); + } + + assertNotNull(operationId, "Operation ID should be available for GetResultFile API"); + assertFalse(operationId.trim().isEmpty(), "Operation ID should not be empty"); + System.out.println("Operation ID available for GetResultFile API: " + operationId); + } + } + + /** + * Detect image format from magic bytes. + */ + private String detectImageFormat(byte[] imageBytes) { + if (imageBytes.length < 2) { + return "Unknown"; + } + + // Check JPEG magic bytes (FF D8) + if (imageBytes[0] == (byte) 0xFF && imageBytes[1] == (byte) 0xD8) { + return "JPEG"; + } + + // Check PNG magic bytes (89 50 4E 47) + if (imageBytes.length >= 4 + && imageBytes[0] == (byte) 0x89 + && imageBytes[1] == 0x50 + && imageBytes[2] == 0x4E + && imageBytes[3] == 0x47) { + return "PNG"; + } + + // Check GIF magic bytes (47 49 46) + if (imageBytes.length >= 3 && imageBytes[0] == 0x47 && imageBytes[1] == 0x49 && imageBytes[2] == 0x46) { + return "GIF"; + } + + // Check WebP magic bytes (52 49 46 46 ... 57 45 42 50) + if (imageBytes.length >= 12 + && imageBytes[0] == 0x52 + && imageBytes[1] == 0x49 + && imageBytes[8] == 0x57 + && imageBytes[9] == 0x45 + && imageBytes[10] == 0x42 + && imageBytes[11] == 0x50) { + return "WebP"; + } + + return "Unknown"; + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample13_DeleteResult.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample13_DeleteResult.java new file mode 100644 index 000000000000..5b0c853cc9dd --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample13_DeleteResult.java @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.AnalysisInput; +import com.azure.ai.contentunderstanding.models.AnalysisResult; +import com.azure.ai.contentunderstanding.models.ContentField; +import com.azure.ai.contentunderstanding.models.DocumentContent; +import com.azure.core.util.polling.SyncPoller; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Sample demonstrates how to delete analysis results after they are no longer needed. + */ +public class Sample13_DeleteResult extends ContentUnderstandingClientTestBase { + + /** + * Synchronous sample for analyzing a document and then deleting the result. + */ + @Test + public void testDeleteResult() { + + // BEGIN: com.azure.ai.contentunderstanding.deleteResult + // Step 1: Analyze a document + String documentUrl + = "https://github.com/Azure-Samples/cognitive-services-REST-api-samples/raw/master/curl/form-recognizer/sample-invoice.pdf"; + + AnalysisInput input = new AnalysisInput(); + input.setUrl(documentUrl); + + SyncPoller poller + = contentUnderstandingClient.beginAnalyze("prebuilt-invoice", Arrays.asList(input)); + + // Wait for operation to complete to get a result ID + System.out.println("Started analysis operation"); + + // Wait for completion + AnalysisResult result = poller.getFinalResult(); + System.out.println("Analysis completed successfully!"); + + // Get the operation ID using the getId() convenience method + // This ID is extracted from the Operation-Location header and is needed for deleteResult() + String operationId = poller.poll().getValue().getId(); + System.out.println("Operation ID: " + operationId); + + // Display some sample results using getValue() convenience method + if (result.getContents() != null && !result.getContents().isEmpty()) { + Object firstContent = result.getContents().get(0); + if (firstContent instanceof DocumentContent) { + DocumentContent docContent = (DocumentContent) firstContent; + Map fields = docContent.getFields(); + if (fields != null) { + System.out.println("Total fields extracted: " + fields.size()); + ContentField customerNameField = fields.get("CustomerName"); + if (customerNameField != null) { + // Use getValue() instead of casting to StringField + String customerName = (String) customerNameField.getValue(); + System.out.println("Customer Name: " + (customerName != null ? customerName : "(not found)")); + } + } + } + } + + // Step 2: Delete the analysis result using the operation ID + // This cleans up the server-side resources (including keyframe images for video analysis) + contentUnderstandingClient.deleteResult(operationId); + System.out.println("Analysis result deleted successfully!"); + // END: com.azure.ai.contentunderstanding.deleteResult + + // Verify operation + System.out.println("\n📋 Analysis Operation Verification:"); + assertNotNull(documentUrl, "Document URL should not be null"); + System.out.println("Document URL: " + documentUrl); + System.out.println("Analysis operation completed successfully"); + + // Verify result + assertNotNull(result, "Analysis result should not be null"); + assertNotNull(result.getContents(), "Result should contain contents"); + assertTrue(result.getContents().size() > 0, "Result should have at least one content"); + assertEquals(1, result.getContents().size(), "Invoice should have exactly one content element"); + System.out.println("Analysis result contains " + result.getContents().size() + " content(s)"); + + // Verify document content + Object firstContent = result.getContents().get(0); + assertTrue(firstContent instanceof DocumentContent, "Content should be DocumentContent"); + DocumentContent documentContent = (DocumentContent) firstContent; + assertNotNull(documentContent.getFields(), "Document content should have fields"); + System.out.println("Document content has " + documentContent.getFields().size() + " field(s)"); + + // API Pattern Demo + System.out.println("\n🗑️ Result Deletion API Pattern:"); + System.out.println(" contentUnderstandingClient.deleteResultWithResponse(resultId, requestOptions)"); + System.out.println(" Use the result ID from the analysis operation for cleanup"); + + // Summary + System.out.println("\n✅ DeleteResult API pattern demonstrated:"); + System.out.println(" Analysis: Completed successfully"); + System.out.println(" Fields extracted: " + documentContent.getFields().size()); + System.out.println(" API: deleteResultWithResponse available for cleanup"); + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample13_DeleteResultAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample13_DeleteResultAsync.java new file mode 100644 index 000000000000..926e9002b883 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample13_DeleteResultAsync.java @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.AnalysisInput; +import com.azure.ai.contentunderstanding.models.AnalysisResult; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerAnalyzeOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentField; +import com.azure.ai.contentunderstanding.models.DocumentContent; +import com.azure.core.util.polling.PollerFlux; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Async sample demonstrates how to delete analysis results after they are no longer needed. + */ +public class Sample13_DeleteResultAsync extends ContentUnderstandingClientTestBase { + + /** + * Asynchronous sample for analyzing a document and then deleting the result. + */ + @Test + public void testDeleteResultAsync() { + + // BEGIN: com.azure.ai.contentunderstanding.deleteResultAsync + // Step 1: Analyze a document + String documentUrl + = "https://github.com/Azure-Samples/cognitive-services-REST-api-samples/raw/master/curl/form-recognizer/sample-invoice.pdf"; + + AnalysisInput input = new AnalysisInput(); + input.setUrl(documentUrl); + + PollerFlux poller + = contentUnderstandingAsyncClient.beginAnalyze("prebuilt-invoice", Arrays.asList(input)); + + // Wait for operation to complete to get a result ID + System.out.println("Started analysis operation"); + + // Use reactive pattern: chain operations using flatMap + // In a real application, you would use subscribe() instead of block() + // Use AtomicReference to capture the operation ID from the polling response + AtomicReference operationIdRef = new AtomicReference<>(); + AnalysisResult result = poller.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + // Capture the operation ID for later use with deleteResult() + operationIdRef.set(pollResponse.getValue().getId()); + return pollResponse.getFinalResult(); + } else { + return Mono.error( + new RuntimeException("Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + + System.out.println("Analysis completed successfully!"); + String operationId = operationIdRef.get(); + System.out.println("Operation ID: " + operationId); + + // Display some sample results using getValue() convenience method + if (result.getContents() != null && !result.getContents().isEmpty()) { + Object firstContent = result.getContents().get(0); + if (firstContent instanceof DocumentContent) { + DocumentContent docContent = (DocumentContent) firstContent; + Map fields = docContent.getFields(); + if (fields != null) { + System.out.println("Total fields extracted: " + fields.size()); + ContentField customerNameField = fields.get("CustomerName"); + if (customerNameField != null) { + // Use getValue() instead of casting to StringField + String customerName = (String) customerNameField.getValue(); + System.out.println("Customer Name: " + (customerName != null ? customerName : "(not found)")); + } + } + } + } + + // Step 2: Delete the analysis result using the operation ID + // This cleans up the server-side resources (including keyframe images for video analysis) + contentUnderstandingAsyncClient.deleteResult(operationId).block(); + System.out.println("Analysis result deleted successfully!"); + // END: com.azure.ai.contentunderstanding.deleteResultAsync + + // Verify operation + System.out.println("\n📋 Analysis Operation Verification:"); + assertNotNull(documentUrl, "Document URL should not be null"); + System.out.println("Document URL: " + documentUrl); + System.out.println("Analysis operation completed successfully"); + + // Verify result + assertNotNull(result, "Analysis result should not be null"); + assertNotNull(result.getContents(), "Result should contain contents"); + assertTrue(result.getContents().size() > 0, "Result should have at least one content"); + assertEquals(1, result.getContents().size(), "Invoice should have exactly one content element"); + System.out.println("Analysis result contains " + result.getContents().size() + " content(s)"); + + // Verify document content + Object firstContent = result.getContents().get(0); + assertTrue(firstContent instanceof DocumentContent, "Content should be DocumentContent"); + DocumentContent documentContent = (DocumentContent) firstContent; + assertNotNull(documentContent.getFields(), "Document content should have fields"); + System.out.println("Document content has " + documentContent.getFields().size() + " field(s)"); + + // API Pattern Demo + System.out.println("\n🗑️ Result Deletion API Pattern:"); + System.out.println(" contentUnderstandingAsyncClient.deleteResult(resultId).block()"); + System.out.println(" Use the result ID from the analysis operation for cleanup"); + + // Summary + System.out.println("\n✅ DeleteResult API pattern demonstrated:"); + System.out.println(" Analysis: Completed successfully"); + System.out.println(" Fields extracted: " + documentContent.getFields().size()); + System.out.println(" API: deleteResult available for cleanup"); + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample14_CopyAnalyzer.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample14_CopyAnalyzer.java new file mode 100644 index 000000000000..dd2f03d17880 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample14_CopyAnalyzer.java @@ -0,0 +1,370 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.ContentAnalyzer; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerConfig; +import com.azure.ai.contentunderstanding.models.ContentFieldDefinition; +import com.azure.ai.contentunderstanding.models.ContentFieldSchema; +import com.azure.ai.contentunderstanding.models.ContentFieldType; +import com.azure.ai.contentunderstanding.models.GenerationMethod; +import com.azure.core.util.polling.SyncPoller; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Sample demonstrates how to copy an analyzer within the same resource. + * For cross-resource copying, see Sample15_GrantCopyAuth. + */ +public class Sample14_CopyAnalyzer extends ContentUnderstandingClientTestBase { + + /** + * Synchronous sample for copying an analyzer. + */ + @Test + public void testCopyAnalyzer() { + System.out.println("✓ Client initialized successfully"); + + // Generate unique analyzer IDs for this test + String sourceAnalyzerId = testResourceNamer.randomName("test_analyzer_source_", 50); + String targetAnalyzerId = testResourceNamer.randomName("test_analyzer_target_", 50); + + try { + // BEGIN: com.azure.ai.contentunderstanding.copyAnalyzer + // Step 1: Create the source analyzer + ContentAnalyzerConfig sourceConfig = new ContentAnalyzerConfig(); + sourceConfig.setFormulaEnabled(false); + sourceConfig.setLayoutEnabled(true); + sourceConfig.setOcrEnabled(true); + sourceConfig.setEstimateFieldSourceAndConfidence(true); + sourceConfig.setReturnDetails(true); + + Map fields = new HashMap<>(); + + ContentFieldDefinition companyNameField = new ContentFieldDefinition(); + companyNameField.setType(ContentFieldType.STRING); + companyNameField.setMethod(GenerationMethod.EXTRACT); + companyNameField.setDescription("Name of the company"); + fields.put("company_name", companyNameField); + + ContentFieldDefinition totalAmountField = new ContentFieldDefinition(); + totalAmountField.setType(ContentFieldType.NUMBER); + totalAmountField.setMethod(GenerationMethod.EXTRACT); + totalAmountField.setDescription("Total amount on the document"); + fields.put("total_amount", totalAmountField); + + ContentFieldSchema sourceFieldSchema = new ContentFieldSchema(); + sourceFieldSchema.setName("company_schema"); + sourceFieldSchema.setDescription("Schema for extracting company information"); + sourceFieldSchema.setFields(fields); + + ContentAnalyzer sourceAnalyzer = new ContentAnalyzer(); + sourceAnalyzer.setBaseAnalyzerId("prebuilt-document"); + sourceAnalyzer.setDescription("Source analyzer for copying"); + sourceAnalyzer.setConfig(sourceConfig); + sourceAnalyzer.setFieldSchema(sourceFieldSchema); + + Map models = new HashMap<>(); + models.put("completion", "gpt-4.1"); + sourceAnalyzer.setModels(models); + + Map tags = new HashMap<>(); + tags.put("modelType", "in_development"); + sourceAnalyzer.setTags(tags); + + // Create source analyzer + SyncPoller createPoller + = contentUnderstandingClient.beginCreateAnalyzer(sourceAnalyzerId, sourceAnalyzer); + ContentAnalyzer sourceResult = createPoller.getFinalResult(); + System.out.println("Source analyzer '" + sourceAnalyzerId + "' created successfully!"); + + // Verify source analyzer is available before copying (ensure it's fully provisioned) + ContentAnalyzer verifiedSource = contentUnderstandingClient.getAnalyzer(sourceAnalyzerId); + System.out.println("Source analyzer verified: " + verifiedSource.getDescription()); + + // Step 2: Copy the source analyzer to target + // Note: This copies within the same resource using the simplified 2-parameter method. + ContentAnalyzer copiedAnalyzer = null; + try { + SyncPoller copyPoller + = contentUnderstandingClient.beginCopyAnalyzer(targetAnalyzerId, sourceAnalyzerId); + copiedAnalyzer = copyPoller.getFinalResult(); + System.out.println("Analyzer copied to '" + targetAnalyzerId + "' successfully!"); + // END: com.azure.ai.contentunderstanding.copyAnalyzer + } catch (com.azure.core.exception.ResourceNotFoundException e) { + // Some Content Understanding endpoints may not support same-resource copy operations + // This is a service-side configuration, not a SDK bug + System.out.println("⚠️ Copy operation not supported on this endpoint."); + System.out.println(" Error: " + e.getMessage()); + System.out.println(" Note: For cross-resource copying, use Sample15_GrantCopyAuth."); + System.out.println("\n📋 CopyAnalyzer API Pattern Demonstrated:"); + System.out.println(" contentUnderstandingClient.beginCopyAnalyzer(targetId, sourceId);"); + System.out.println( + " For cross-resource: beginCopyAnalyzer(targetId, sourceId, allowReplace, sourceResourceId, sourceRegion);"); + return; // Skip the rest of the test + } + + // ========== VERIFICATION: Source Analyzer Creation ========== + System.out.println("\n📋 Source Analyzer Creation Verification:"); + + // Verify analyzer IDs + assertNotNull(sourceAnalyzerId, "Source analyzer ID should not be null"); + assertFalse(sourceAnalyzerId.trim().isEmpty(), "Source analyzer ID should not be empty"); + assertNotNull(targetAnalyzerId, "Target analyzer ID should not be null"); + assertFalse(targetAnalyzerId.trim().isEmpty(), "Target analyzer ID should not be empty"); + assertNotEquals(sourceAnalyzerId, targetAnalyzerId, "Source and target IDs should be different"); + System.out.println(" ✓ Analyzer IDs validated"); + System.out.println(" Source: " + sourceAnalyzerId); + System.out.println(" Target: " + targetAnalyzerId); + + // Verify source config + assertNotNull(sourceConfig, "Source config should not be null"); + assertEquals(false, sourceConfig.isFormulaEnabled(), "FormulaEnabled should be false"); + assertEquals(true, sourceConfig.isLayoutEnabled(), "LayoutEnabled should be true"); + assertEquals(true, sourceConfig.isOcrEnabled(), "OcrEnabled should be true"); + assertEquals(true, sourceConfig.isEstimateFieldSourceAndConfidence(), + "EstimateFieldSourceAndConfidence should be true"); + assertEquals(true, sourceConfig.isReturnDetails(), "ReturnDetails should be true"); + System.out.println(" ✓ Source config verified"); + + // Verify source field schema + assertNotNull(sourceFieldSchema, "Source field schema should not be null"); + assertEquals("company_schema", sourceFieldSchema.getName(), "Field schema name should match"); + assertEquals("Schema for extracting company information", sourceFieldSchema.getDescription(), + "Field schema description should match"); + assertEquals(2, sourceFieldSchema.getFields().size(), "Should have 2 fields"); + System.out.println(" ✓ Source field schema verified: " + sourceFieldSchema.getName()); + + // Verify individual field definitions + assertTrue(sourceFieldSchema.getFields().containsKey("company_name"), "Should contain company_name field"); + ContentFieldDefinition companyField = sourceFieldSchema.getFields().get("company_name"); + assertEquals(ContentFieldType.STRING, companyField.getType(), "company_name should be STRING type"); + assertEquals(GenerationMethod.EXTRACT, companyField.getMethod(), "company_name should use EXTRACT method"); + assertEquals("Name of the company", companyField.getDescription(), "company_name description should match"); + System.out.println(" ✓ company_name field verified"); + + assertTrue(sourceFieldSchema.getFields().containsKey("total_amount"), "Should contain total_amount field"); + ContentFieldDefinition amountField = sourceFieldSchema.getFields().get("total_amount"); + assertEquals(ContentFieldType.NUMBER, amountField.getType(), "total_amount should be NUMBER type"); + assertEquals(GenerationMethod.EXTRACT, amountField.getMethod(), "total_amount should use EXTRACT method"); + assertEquals("Total amount on the document", amountField.getDescription(), + "total_amount description should match"); + System.out.println(" ✓ total_amount field verified"); + + // Verify source analyzer object + assertNotNull(sourceAnalyzer, "Source analyzer object should not be null"); + assertEquals("prebuilt-document", sourceAnalyzer.getBaseAnalyzerId(), "Base analyzer ID should match"); + assertEquals("Source analyzer for copying", sourceAnalyzer.getDescription(), "Description should match"); + assertTrue(sourceAnalyzer.getModels().containsKey("completion"), "Should have completion model"); + assertEquals("gpt-4.1", sourceAnalyzer.getModels().get("completion"), "Completion model should be gpt-4.1"); + assertTrue(sourceAnalyzer.getTags().containsKey("modelType"), "Should have modelType tag"); + assertEquals("in_development", sourceAnalyzer.getTags().get("modelType"), + "modelType tag should be in_development"); + System.out.println(" ✓ Source analyzer object verified"); + + // Verify creation result + assertNotNull(sourceResult, "Source analyzer result should not be null"); + assertEquals("prebuilt-document", sourceResult.getBaseAnalyzerId(), "Base analyzer ID should match"); + assertEquals("Source analyzer for copying", sourceResult.getDescription(), "Description should match"); + System.out.println(" ✓ Source analyzer created: " + sourceAnalyzerId); + + // Verify config in result + assertNotNull(sourceResult.getConfig(), "Config should not be null in result"); + assertEquals(false, sourceResult.getConfig().isFormulaEnabled(), "FormulaEnabled should be preserved"); + assertEquals(true, sourceResult.getConfig().isLayoutEnabled(), "LayoutEnabled should be preserved"); + assertEquals(true, sourceResult.getConfig().isOcrEnabled(), "OcrEnabled should be preserved"); + System.out.println(" ✓ Config preserved in result"); + + // Verify field schema in result + assertNotNull(sourceResult.getFieldSchema(), "Field schema should not be null in result"); + assertEquals("company_schema", sourceResult.getFieldSchema().getName(), + "Field schema name should be preserved"); + assertEquals(2, sourceResult.getFieldSchema().getFields().size(), "Should have 2 fields in result"); + assertTrue(sourceResult.getFieldSchema().getFields().containsKey("company_name"), + "Should contain company_name in result"); + assertTrue(sourceResult.getFieldSchema().getFields().containsKey("total_amount"), + "Should contain total_amount in result"); + System.out + .println(" ✓ Field schema preserved: " + sourceResult.getFieldSchema().getFields().size() + " fields"); + + // Verify tags in result + assertNotNull(sourceResult.getTags(), "Tags should not be null in result"); + assertTrue(sourceResult.getTags().containsKey("modelType"), "Should contain modelType tag in result"); + assertEquals("in_development", sourceResult.getTags().get("modelType"), + "modelType tag should be preserved"); + System.out.println(" ✓ Tags preserved: " + sourceResult.getTags().size() + " tag(s)"); + + // Verify models in result + assertNotNull(sourceResult.getModels(), "Models should not be null in result"); + assertTrue(sourceResult.getModels().containsKey("completion"), "Should have completion model in result"); + assertEquals("gpt-4.1", sourceResult.getModels().get("completion"), "Completion model should be preserved"); + System.out.println(" ✓ Models preserved: " + sourceResult.getModels().size() + " model(s)"); + + System.out.println("\n✅ Source analyzer creation completed:"); + System.out.println(" ID: " + sourceAnalyzerId); + System.out.println(" Base: " + sourceResult.getBaseAnalyzerId()); + System.out.println(" Fields: " + sourceResult.getFieldSchema().getFields().size()); + System.out.println(" Tags: " + sourceResult.getTags().size()); + System.out.println(" Models: " + sourceResult.getModels().size()); + + // Get the source analyzer to verify retrieval + ContentAnalyzer sourceAnalyzerInfo = contentUnderstandingClient.getAnalyzer(sourceAnalyzerId); + + System.out.println("\n📋 Source Analyzer Retrieval Verification:"); + assertNotNull(sourceAnalyzerInfo, "Source analyzer info should not be null"); + assertEquals(sourceResult.getBaseAnalyzerId(), sourceAnalyzerInfo.getBaseAnalyzerId(), + "Base analyzer should match"); + assertEquals(sourceResult.getDescription(), sourceAnalyzerInfo.getDescription(), + "Description should match"); + System.out.println(" ✓ Source analyzer retrieved successfully"); + System.out.println(" Description: " + sourceAnalyzerInfo.getDescription()); + System.out.println(" Tags: " + String.join(", ", + sourceAnalyzerInfo.getTags() + .entrySet() + .stream() + .map(e -> e.getKey() + "=" + e.getValue()) + .toArray(String[]::new))); + + // ========== VERIFICATION: Analyzer Copy Operation ========== + System.out.println("\n📋 Analyzer Copy Verification:"); + assertNotNull(copiedAnalyzer, "Copied analyzer should not be null"); + System.out.println(" ✓ Copy operation completed"); + + // Verify base properties match source + assertEquals(sourceResult.getBaseAnalyzerId(), copiedAnalyzer.getBaseAnalyzerId(), + "Copied analyzer should have same base analyzer ID"); + assertEquals(sourceResult.getDescription(), copiedAnalyzer.getDescription(), + "Copied analyzer should have same description"); + System.out.println(" ✓ Base properties preserved"); + System.out.println(" Base analyzer ID: " + copiedAnalyzer.getBaseAnalyzerId()); + System.out.println(" Description: '" + copiedAnalyzer.getDescription() + "'"); + + // Verify field schema structure + assertNotNull(copiedAnalyzer.getFieldSchema(), "Copied analyzer should have field schema"); + assertEquals(sourceResult.getFieldSchema().getName(), copiedAnalyzer.getFieldSchema().getName(), + "Field schema name should match"); + assertEquals(sourceResult.getFieldSchema().getDescription(), + copiedAnalyzer.getFieldSchema().getDescription(), "Field schema description should match"); + assertEquals(sourceResult.getFieldSchema().getFields().size(), + copiedAnalyzer.getFieldSchema().getFields().size(), "Field count should match"); + System.out.println(" ✓ Field schema structure preserved"); + System.out.println(" Schema: " + copiedAnalyzer.getFieldSchema().getName()); + System.out.println(" Fields: " + copiedAnalyzer.getFieldSchema().getFields().size()); + + // Verify individual field definitions were copied correctly + assertTrue(copiedAnalyzer.getFieldSchema().getFields().containsKey("company_name"), + "Copied analyzer should contain company_name field"); + ContentFieldDefinition copiedCompanyField = copiedAnalyzer.getFieldSchema().getFields().get("company_name"); + assertEquals(ContentFieldType.STRING, copiedCompanyField.getType(), + "company_name type should be preserved"); + assertEquals(GenerationMethod.EXTRACT, copiedCompanyField.getMethod(), + "company_name method should be preserved"); + System.out.println( + " ✓ company_name field: " + copiedCompanyField.getType() + " / " + copiedCompanyField.getMethod()); + + assertTrue(copiedAnalyzer.getFieldSchema().getFields().containsKey("total_amount"), + "Copied analyzer should contain total_amount field"); + ContentFieldDefinition copiedAmountField = copiedAnalyzer.getFieldSchema().getFields().get("total_amount"); + assertEquals(ContentFieldType.NUMBER, copiedAmountField.getType(), "total_amount type should be preserved"); + assertEquals(GenerationMethod.EXTRACT, copiedAmountField.getMethod(), + "total_amount method should be preserved"); + System.out.println( + " ✓ total_amount field: " + copiedAmountField.getType() + " / " + copiedAmountField.getMethod()); + + // Verify tags were copied + assertNotNull(copiedAnalyzer.getTags(), "Copied analyzer should have tags"); + assertEquals(sourceResult.getTags().size(), copiedAnalyzer.getTags().size(), "Tag count should match"); + assertTrue(copiedAnalyzer.getTags().containsKey("modelType"), + "Copied analyzer should contain modelType tag"); + assertEquals("in_development", copiedAnalyzer.getTags().get("modelType"), + "Copied analyzer should have same tag value"); + System.out.println(" ✓ Tags preserved: " + copiedAnalyzer.getTags().size() + " tag(s)"); + System.out.println(" modelType=" + copiedAnalyzer.getTags().get("modelType")); + + // Verify config was copied + assertNotNull(copiedAnalyzer.getConfig(), "Copied analyzer should have config"); + assertEquals(sourceResult.getConfig().isFormulaEnabled(), copiedAnalyzer.getConfig().isFormulaEnabled(), + "FormulaEnabled should match"); + assertEquals(sourceResult.getConfig().isLayoutEnabled(), copiedAnalyzer.getConfig().isLayoutEnabled(), + "LayoutEnabled should match"); + assertEquals(sourceResult.getConfig().isOcrEnabled(), copiedAnalyzer.getConfig().isOcrEnabled(), + "OcrEnabled should match"); + assertEquals(sourceResult.getConfig().isEstimateFieldSourceAndConfidence(), + copiedAnalyzer.getConfig().isEstimateFieldSourceAndConfidence(), + "EstimateFieldSourceAndConfidence should match"); + assertEquals(sourceResult.getConfig().isReturnDetails(), copiedAnalyzer.getConfig().isReturnDetails(), + "ReturnDetails should match"); + System.out.println(" ✓ Config preserved"); + System.out.println(" LayoutEnabled: " + copiedAnalyzer.getConfig().isLayoutEnabled()); + System.out.println(" OcrEnabled: " + copiedAnalyzer.getConfig().isOcrEnabled()); + + // Verify models were copied + assertNotNull(copiedAnalyzer.getModels(), "Copied analyzer should have models"); + assertEquals(sourceResult.getModels().size(), copiedAnalyzer.getModels().size(), + "Model count should match"); + if (copiedAnalyzer.getModels().containsKey("completion")) { + assertEquals("gpt-4.1", copiedAnalyzer.getModels().get("completion"), "Completion model should match"); + System.out.println(" ✓ Models preserved: " + copiedAnalyzer.getModels().size() + " model(s)"); + System.out.println(" completion=" + copiedAnalyzer.getModels().get("completion")); + } + + // Verify the copied analyzer via Get operation + ContentAnalyzer verifiedCopy = contentUnderstandingClient.getAnalyzer(targetAnalyzerId); + + System.out.println("\n📋 Copied Analyzer Retrieval Verification:"); + assertNotNull(verifiedCopy, "Retrieved copied analyzer should not be null"); + assertEquals(copiedAnalyzer.getBaseAnalyzerId(), verifiedCopy.getBaseAnalyzerId(), + "Retrieved analyzer should match copied analyzer"); + assertEquals(copiedAnalyzer.getDescription(), verifiedCopy.getDescription(), + "Retrieved description should match"); + assertEquals(copiedAnalyzer.getFieldSchema().getFields().size(), + verifiedCopy.getFieldSchema().getFields().size(), "Retrieved field count should match"); + System.out.println(" ✓ Copied analyzer verified via retrieval"); + + // Summary + String separator = new String(new char[60]).replace("\0", "═"); + System.out.println("\n" + separator); + System.out.println("✅ ANALYZER COPY VERIFICATION COMPLETED SUCCESSFULLY"); + System.out.println(separator); + System.out.println("Source Analyzer:"); + System.out.println(" ID: " + sourceAnalyzerId); + System.out.println(" Base: " + sourceResult.getBaseAnalyzerId()); + System.out.println(" Description: " + sourceResult.getDescription()); + System.out.println(" Fields: " + sourceResult.getFieldSchema().getFields().size()); + System.out.println(" Tags: " + sourceResult.getTags().size()); + System.out.println(" Models: " + sourceResult.getModels().size()); + System.out.println("\nTarget Analyzer (Copied):"); + System.out.println(" ID: " + targetAnalyzerId); + System.out.println(" Base: " + copiedAnalyzer.getBaseAnalyzerId()); + System.out.println(" Description: " + copiedAnalyzer.getDescription()); + System.out.println(" Fields: " + copiedAnalyzer.getFieldSchema().getFields().size()); + System.out.println(" Tags: " + copiedAnalyzer.getTags().size()); + System.out.println(" Models: " + copiedAnalyzer.getModels().size()); + System.out.println("\n✅ All properties successfully copied and verified!"); + System.out.println(separator); + + } finally { + // Cleanup: Delete the analyzers + try { + contentUnderstandingClient.deleteAnalyzer(sourceAnalyzerId); + System.out.println("\nSource analyzer deleted: " + sourceAnalyzerId); + } catch (Exception e) { + System.out.println("Note: Failed to delete source analyzer (may not exist): " + e.getMessage()); + } + + try { + contentUnderstandingClient.deleteAnalyzer(targetAnalyzerId); + System.out.println("Target analyzer deleted: " + targetAnalyzerId); + } catch (Exception e) { + System.out.println("Note: Failed to delete target analyzer (may not exist): " + e.getMessage()); + } + } + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample14_CopyAnalyzerAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample14_CopyAnalyzerAsync.java new file mode 100644 index 000000000000..6d661bcdfe75 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample14_CopyAnalyzerAsync.java @@ -0,0 +1,392 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.ContentAnalyzer; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerConfig; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentFieldDefinition; +import com.azure.ai.contentunderstanding.models.ContentFieldSchema; +import com.azure.ai.contentunderstanding.models.ContentFieldType; +import com.azure.ai.contentunderstanding.models.GenerationMethod; +import com.azure.core.util.polling.PollerFlux; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Async sample demonstrates how to copy an analyzer within the same resource. + * For cross-resource copying, see Sample15_GrantCopyAuthAsync. + */ +public class Sample14_CopyAnalyzerAsync extends ContentUnderstandingClientTestBase { + + /** + * Asynchronous sample for copying an analyzer. + */ + @Test + public void testCopyAnalyzerAsync() { + System.out.println("✓ Client initialized successfully"); + + // Generate unique analyzer IDs for this test + String sourceAnalyzerId = testResourceNamer.randomName("test_analyzer_source_", 50); + String targetAnalyzerId = testResourceNamer.randomName("test_analyzer_target_", 50); + + try { + // BEGIN: com.azure.ai.contentunderstanding.copyAnalyzerAsync + // Step 1: Create the source analyzer + ContentAnalyzerConfig sourceConfig = new ContentAnalyzerConfig(); + sourceConfig.setFormulaEnabled(false); + sourceConfig.setLayoutEnabled(true); + sourceConfig.setOcrEnabled(true); + sourceConfig.setEstimateFieldSourceAndConfidence(true); + sourceConfig.setReturnDetails(true); + + Map fields = new HashMap<>(); + + ContentFieldDefinition companyNameField = new ContentFieldDefinition(); + companyNameField.setType(ContentFieldType.STRING); + companyNameField.setMethod(GenerationMethod.EXTRACT); + companyNameField.setDescription("Name of the company"); + fields.put("company_name", companyNameField); + + ContentFieldDefinition totalAmountField = new ContentFieldDefinition(); + totalAmountField.setType(ContentFieldType.NUMBER); + totalAmountField.setMethod(GenerationMethod.EXTRACT); + totalAmountField.setDescription("Total amount on the document"); + fields.put("total_amount", totalAmountField); + + ContentFieldSchema sourceFieldSchema = new ContentFieldSchema(); + sourceFieldSchema.setName("company_schema"); + sourceFieldSchema.setDescription("Schema for extracting company information"); + sourceFieldSchema.setFields(fields); + + ContentAnalyzer sourceAnalyzer = new ContentAnalyzer(); + sourceAnalyzer.setBaseAnalyzerId("prebuilt-document"); + sourceAnalyzer.setDescription("Source analyzer for copying"); + sourceAnalyzer.setConfig(sourceConfig); + sourceAnalyzer.setFieldSchema(sourceFieldSchema); + + Map models = new HashMap<>(); + models.put("completion", "gpt-4.1"); + sourceAnalyzer.setModels(models); + + Map tags = new HashMap<>(); + tags.put("modelType", "in_development"); + sourceAnalyzer.setTags(tags); + + // Create source analyzer + PollerFlux createPoller + = contentUnderstandingAsyncClient.beginCreateAnalyzer(sourceAnalyzerId, sourceAnalyzer); + + // Use reactive pattern: chain operations using flatMap + // In a real application, you would use subscribe() instead of block() + ContentAnalyzer sourceResult = createPoller.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + + System.out.println("Source analyzer '" + sourceAnalyzerId + "' created successfully!"); + + // Verify source analyzer is available before copying (ensure it's fully provisioned) + ContentAnalyzer verifiedSource = contentUnderstandingAsyncClient.getAnalyzer(sourceAnalyzerId).block(); + System.out.println("Source analyzer verified: " + verifiedSource.getDescription()); + + // Step 2: Copy the source analyzer to target + // Note: This copies within the same resource using the simplified 2-parameter method. + ContentAnalyzer copiedAnalyzer = null; + try { + PollerFlux copyPoller + = contentUnderstandingAsyncClient.beginCopyAnalyzer(targetAnalyzerId, sourceAnalyzerId); + + // Use reactive pattern for copy operation as well + copiedAnalyzer = copyPoller.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + + System.out.println("Analyzer copied to '" + targetAnalyzerId + "' successfully!"); + // END: com.azure.ai.contentunderstanding.copyAnalyzerAsync + } catch (com.azure.core.exception.ResourceNotFoundException e) { + // Some Content Understanding endpoints may not support same-resource copy operations + // This is a service-side configuration, not a SDK bug + System.out.println("⚠️ Copy operation not supported on this endpoint."); + System.out.println(" Error: " + e.getMessage()); + System.out.println(" Note: For cross-resource copying, use Sample15_GrantCopyAuthAsync."); + System.out.println("\n📋 CopyAnalyzer API Pattern Demonstrated:"); + System.out.println(" contentUnderstandingAsyncClient.beginCopyAnalyzer(targetId, sourceId);"); + System.out.println( + " For cross-resource: beginCopyAnalyzer(targetId, sourceId, allowReplace, sourceResourceId, sourceRegion);"); + return; // Skip the rest of the test + } + + // ========== VERIFICATION: Source Analyzer Creation ========== + System.out.println("\n📋 Source Analyzer Creation Verification:"); + + // Verify analyzer IDs + assertNotNull(sourceAnalyzerId, "Source analyzer ID should not be null"); + assertFalse(sourceAnalyzerId.trim().isEmpty(), "Source analyzer ID should not be empty"); + assertNotNull(targetAnalyzerId, "Target analyzer ID should not be null"); + assertFalse(targetAnalyzerId.trim().isEmpty(), "Target analyzer ID should not be empty"); + assertNotEquals(sourceAnalyzerId, targetAnalyzerId, "Source and target IDs should be different"); + System.out.println(" ✓ Analyzer IDs validated"); + System.out.println(" Source: " + sourceAnalyzerId); + System.out.println(" Target: " + targetAnalyzerId); + + // Verify source config + assertNotNull(sourceConfig, "Source config should not be null"); + assertEquals(false, sourceConfig.isFormulaEnabled(), "FormulaEnabled should be false"); + assertEquals(true, sourceConfig.isLayoutEnabled(), "LayoutEnabled should be true"); + assertEquals(true, sourceConfig.isOcrEnabled(), "OcrEnabled should be true"); + assertEquals(true, sourceConfig.isEstimateFieldSourceAndConfidence(), + "EstimateFieldSourceAndConfidence should be true"); + assertEquals(true, sourceConfig.isReturnDetails(), "ReturnDetails should be true"); + System.out.println(" ✓ Source config verified"); + + // Verify source field schema + assertNotNull(sourceFieldSchema, "Source field schema should not be null"); + assertEquals("company_schema", sourceFieldSchema.getName(), "Field schema name should match"); + assertEquals("Schema for extracting company information", sourceFieldSchema.getDescription(), + "Field schema description should match"); + assertEquals(2, sourceFieldSchema.getFields().size(), "Should have 2 fields"); + System.out.println(" ✓ Source field schema verified: " + sourceFieldSchema.getName()); + + // Verify individual field definitions + assertTrue(sourceFieldSchema.getFields().containsKey("company_name"), "Should contain company_name field"); + ContentFieldDefinition companyField = sourceFieldSchema.getFields().get("company_name"); + assertEquals(ContentFieldType.STRING, companyField.getType(), "company_name should be STRING type"); + assertEquals(GenerationMethod.EXTRACT, companyField.getMethod(), "company_name should use EXTRACT method"); + assertEquals("Name of the company", companyField.getDescription(), "company_name description should match"); + System.out.println(" ✓ company_name field verified"); + + assertTrue(sourceFieldSchema.getFields().containsKey("total_amount"), "Should contain total_amount field"); + ContentFieldDefinition amountField = sourceFieldSchema.getFields().get("total_amount"); + assertEquals(ContentFieldType.NUMBER, amountField.getType(), "total_amount should be NUMBER type"); + assertEquals(GenerationMethod.EXTRACT, amountField.getMethod(), "total_amount should use EXTRACT method"); + assertEquals("Total amount on the document", amountField.getDescription(), + "total_amount description should match"); + System.out.println(" ✓ total_amount field verified"); + + // Verify source analyzer object + assertNotNull(sourceAnalyzer, "Source analyzer object should not be null"); + assertEquals("prebuilt-document", sourceAnalyzer.getBaseAnalyzerId(), "Base analyzer ID should match"); + assertEquals("Source analyzer for copying", sourceAnalyzer.getDescription(), "Description should match"); + assertTrue(sourceAnalyzer.getModels().containsKey("completion"), "Should have completion model"); + assertEquals("gpt-4.1", sourceAnalyzer.getModels().get("completion"), "Completion model should be gpt-4.1"); + assertTrue(sourceAnalyzer.getTags().containsKey("modelType"), "Should have modelType tag"); + assertEquals("in_development", sourceAnalyzer.getTags().get("modelType"), + "modelType tag should be in_development"); + System.out.println(" ✓ Source analyzer object verified"); + + // Verify creation result + assertNotNull(sourceResult, "Source analyzer result should not be null"); + assertEquals("prebuilt-document", sourceResult.getBaseAnalyzerId(), "Base analyzer ID should match"); + assertEquals("Source analyzer for copying", sourceResult.getDescription(), "Description should match"); + System.out.println(" ✓ Source analyzer created: " + sourceAnalyzerId); + + // Verify config in result + assertNotNull(sourceResult.getConfig(), "Config should not be null in result"); + assertEquals(false, sourceResult.getConfig().isFormulaEnabled(), "FormulaEnabled should be preserved"); + assertEquals(true, sourceResult.getConfig().isLayoutEnabled(), "LayoutEnabled should be preserved"); + assertEquals(true, sourceResult.getConfig().isOcrEnabled(), "OcrEnabled should be preserved"); + System.out.println(" ✓ Config preserved in result"); + + // Verify field schema in result + assertNotNull(sourceResult.getFieldSchema(), "Field schema should not be null in result"); + assertEquals("company_schema", sourceResult.getFieldSchema().getName(), + "Field schema name should be preserved"); + assertEquals(2, sourceResult.getFieldSchema().getFields().size(), "Should have 2 fields in result"); + assertTrue(sourceResult.getFieldSchema().getFields().containsKey("company_name"), + "Should contain company_name in result"); + assertTrue(sourceResult.getFieldSchema().getFields().containsKey("total_amount"), + "Should contain total_amount in result"); + System.out + .println(" ✓ Field schema preserved: " + sourceResult.getFieldSchema().getFields().size() + " fields"); + + // Verify tags in result + assertNotNull(sourceResult.getTags(), "Tags should not be null in result"); + assertTrue(sourceResult.getTags().containsKey("modelType"), "Should contain modelType tag in result"); + assertEquals("in_development", sourceResult.getTags().get("modelType"), + "modelType tag should be preserved"); + System.out.println(" ✓ Tags preserved: " + sourceResult.getTags().size() + " tag(s)"); + + // Verify models in result + assertNotNull(sourceResult.getModels(), "Models should not be null in result"); + assertTrue(sourceResult.getModels().containsKey("completion"), "Should have completion model in result"); + assertEquals("gpt-4.1", sourceResult.getModels().get("completion"), "Completion model should be preserved"); + System.out.println(" ✓ Models preserved: " + sourceResult.getModels().size() + " model(s)"); + + System.out.println("\n✅ Source analyzer creation completed:"); + System.out.println(" ID: " + sourceAnalyzerId); + System.out.println(" Base: " + sourceResult.getBaseAnalyzerId()); + System.out.println(" Fields: " + sourceResult.getFieldSchema().getFields().size()); + System.out.println(" Tags: " + sourceResult.getTags().size()); + System.out.println(" Models: " + sourceResult.getModels().size()); + + // Get the source analyzer to verify retrieval + ContentAnalyzer sourceAnalyzerInfo = contentUnderstandingAsyncClient.getAnalyzer(sourceAnalyzerId).block(); + + System.out.println("\n📋 Source Analyzer Retrieval Verification:"); + assertNotNull(sourceAnalyzerInfo, "Source analyzer info should not be null"); + assertEquals(sourceResult.getBaseAnalyzerId(), sourceAnalyzerInfo.getBaseAnalyzerId(), + "Base analyzer should match"); + assertEquals(sourceResult.getDescription(), sourceAnalyzerInfo.getDescription(), + "Description should match"); + System.out.println(" ✓ Source analyzer retrieved successfully"); + System.out.println(" Description: " + sourceAnalyzerInfo.getDescription()); + System.out.println(" Tags: " + String.join(", ", + sourceAnalyzerInfo.getTags() + .entrySet() + .stream() + .map(e -> e.getKey() + "=" + e.getValue()) + .toArray(String[]::new))); + + // ========== VERIFICATION: Analyzer Copy Operation ========== + System.out.println("\n📋 Analyzer Copy Verification:"); + assertNotNull(copiedAnalyzer, "Copied analyzer should not be null"); + System.out.println(" ✓ Copy operation completed"); + + // Verify base properties match source + assertEquals(sourceResult.getBaseAnalyzerId(), copiedAnalyzer.getBaseAnalyzerId(), + "Copied analyzer should have same base analyzer ID"); + assertEquals(sourceResult.getDescription(), copiedAnalyzer.getDescription(), + "Copied analyzer should have same description"); + System.out.println(" ✓ Base properties preserved"); + System.out.println(" Base analyzer ID: " + copiedAnalyzer.getBaseAnalyzerId()); + System.out.println(" Description: '" + copiedAnalyzer.getDescription() + "'"); + + // Verify field schema structure + assertNotNull(copiedAnalyzer.getFieldSchema(), "Copied analyzer should have field schema"); + assertEquals(sourceResult.getFieldSchema().getName(), copiedAnalyzer.getFieldSchema().getName(), + "Field schema name should match"); + assertEquals(sourceResult.getFieldSchema().getDescription(), + copiedAnalyzer.getFieldSchema().getDescription(), "Field schema description should match"); + assertEquals(sourceResult.getFieldSchema().getFields().size(), + copiedAnalyzer.getFieldSchema().getFields().size(), "Field count should match"); + System.out.println(" ✓ Field schema structure preserved"); + System.out.println(" Schema: " + copiedAnalyzer.getFieldSchema().getName()); + System.out.println(" Fields: " + copiedAnalyzer.getFieldSchema().getFields().size()); + + // Verify individual field definitions were copied correctly + assertTrue(copiedAnalyzer.getFieldSchema().getFields().containsKey("company_name"), + "Copied analyzer should contain company_name field"); + ContentFieldDefinition copiedCompanyField = copiedAnalyzer.getFieldSchema().getFields().get("company_name"); + assertEquals(ContentFieldType.STRING, copiedCompanyField.getType(), + "company_name type should be preserved"); + assertEquals(GenerationMethod.EXTRACT, copiedCompanyField.getMethod(), + "company_name method should be preserved"); + System.out.println( + " ✓ company_name field: " + copiedCompanyField.getType() + " / " + copiedCompanyField.getMethod()); + + assertTrue(copiedAnalyzer.getFieldSchema().getFields().containsKey("total_amount"), + "Copied analyzer should contain total_amount field"); + ContentFieldDefinition copiedAmountField = copiedAnalyzer.getFieldSchema().getFields().get("total_amount"); + assertEquals(ContentFieldType.NUMBER, copiedAmountField.getType(), "total_amount type should be preserved"); + assertEquals(GenerationMethod.EXTRACT, copiedAmountField.getMethod(), + "total_amount method should be preserved"); + System.out.println( + " ✓ total_amount field: " + copiedAmountField.getType() + " / " + copiedAmountField.getMethod()); + + // Verify tags were copied + assertNotNull(copiedAnalyzer.getTags(), "Copied analyzer should have tags"); + assertEquals(sourceResult.getTags().size(), copiedAnalyzer.getTags().size(), "Tag count should match"); + assertTrue(copiedAnalyzer.getTags().containsKey("modelType"), + "Copied analyzer should contain modelType tag"); + assertEquals("in_development", copiedAnalyzer.getTags().get("modelType"), + "Copied analyzer should have same tag value"); + System.out.println(" ✓ Tags preserved: " + copiedAnalyzer.getTags().size() + " tag(s)"); + System.out.println(" modelType=" + copiedAnalyzer.getTags().get("modelType")); + + // Verify config was copied + assertNotNull(copiedAnalyzer.getConfig(), "Copied analyzer should have config"); + assertEquals(sourceResult.getConfig().isFormulaEnabled(), copiedAnalyzer.getConfig().isFormulaEnabled(), + "FormulaEnabled should match"); + assertEquals(sourceResult.getConfig().isLayoutEnabled(), copiedAnalyzer.getConfig().isLayoutEnabled(), + "LayoutEnabled should match"); + assertEquals(sourceResult.getConfig().isOcrEnabled(), copiedAnalyzer.getConfig().isOcrEnabled(), + "OcrEnabled should match"); + assertEquals(sourceResult.getConfig().isEstimateFieldSourceAndConfidence(), + copiedAnalyzer.getConfig().isEstimateFieldSourceAndConfidence(), + "EstimateFieldSourceAndConfidence should match"); + assertEquals(sourceResult.getConfig().isReturnDetails(), copiedAnalyzer.getConfig().isReturnDetails(), + "ReturnDetails should match"); + System.out.println(" ✓ Config preserved"); + System.out.println(" LayoutEnabled: " + copiedAnalyzer.getConfig().isLayoutEnabled()); + System.out.println(" OcrEnabled: " + copiedAnalyzer.getConfig().isOcrEnabled()); + + // Verify models were copied + assertNotNull(copiedAnalyzer.getModels(), "Copied analyzer should have models"); + assertEquals(sourceResult.getModels().size(), copiedAnalyzer.getModels().size(), + "Model count should match"); + if (copiedAnalyzer.getModels().containsKey("completion")) { + assertEquals("gpt-4.1", copiedAnalyzer.getModels().get("completion"), "Completion model should match"); + System.out.println(" ✓ Models preserved: " + copiedAnalyzer.getModels().size() + " model(s)"); + System.out.println(" completion=" + copiedAnalyzer.getModels().get("completion")); + } + + // Verify the copied analyzer via Get operation + ContentAnalyzer verifiedCopy = contentUnderstandingAsyncClient.getAnalyzer(targetAnalyzerId).block(); + + System.out.println("\n📋 Copied Analyzer Retrieval Verification:"); + assertNotNull(verifiedCopy, "Retrieved copied analyzer should not be null"); + assertEquals(copiedAnalyzer.getBaseAnalyzerId(), verifiedCopy.getBaseAnalyzerId(), + "Retrieved analyzer should match copied analyzer"); + assertEquals(copiedAnalyzer.getDescription(), verifiedCopy.getDescription(), + "Retrieved description should match"); + assertEquals(copiedAnalyzer.getFieldSchema().getFields().size(), + verifiedCopy.getFieldSchema().getFields().size(), "Retrieved field count should match"); + System.out.println(" ✓ Copied analyzer verified via retrieval"); + + // Summary + String separator = new String(new char[60]).replace("\0", "═"); + System.out.println("\n" + separator); + System.out.println("✅ ANALYZER COPY VERIFICATION COMPLETED SUCCESSFULLY"); + System.out.println(separator); + System.out.println("Source Analyzer:"); + System.out.println(" ID: " + sourceAnalyzerId); + System.out.println(" Base: " + sourceResult.getBaseAnalyzerId()); + System.out.println(" Description: " + sourceResult.getDescription()); + System.out.println(" Fields: " + sourceResult.getFieldSchema().getFields().size()); + System.out.println(" Tags: " + sourceResult.getTags().size()); + System.out.println(" Models: " + sourceResult.getModels().size()); + System.out.println("\nTarget Analyzer (Copied):"); + System.out.println(" ID: " + targetAnalyzerId); + System.out.println(" Base: " + copiedAnalyzer.getBaseAnalyzerId()); + System.out.println(" Description: " + copiedAnalyzer.getDescription()); + System.out.println(" Fields: " + copiedAnalyzer.getFieldSchema().getFields().size()); + System.out.println(" Tags: " + copiedAnalyzer.getTags().size()); + System.out.println(" Models: " + copiedAnalyzer.getModels().size()); + System.out.println("\n✅ All properties successfully copied and verified!"); + System.out.println(separator); + + } finally { + // Cleanup: Delete the analyzers + try { + contentUnderstandingAsyncClient.deleteAnalyzer(sourceAnalyzerId).block(); + System.out.println("\nSource analyzer deleted: " + sourceAnalyzerId); + } catch (Exception e) { + System.out.println("Note: Failed to delete source analyzer (may not exist): " + e.getMessage()); + } + + try { + contentUnderstandingAsyncClient.deleteAnalyzer(targetAnalyzerId).block(); + System.out.println("Target analyzer deleted: " + targetAnalyzerId); + } catch (Exception e) { + System.out.println("Note: Failed to delete target analyzer (may not exist): " + e.getMessage()); + } + } + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample14_CopyAnalyzerAsyncTest.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample14_CopyAnalyzerAsyncTest.java index cd09a2df14d5..b109cbf4ee78 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample14_CopyAnalyzerAsyncTest.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample14_CopyAnalyzerAsyncTest.java @@ -152,9 +152,9 @@ public void testCopyAnalyzerAsync() { // Verify source config assertNotNull(sourceConfig, "Source config should not be null"); - assertEquals(false, sourceConfig.isFormulaEnabled(), "EnableFormula should be false"); - assertEquals(true, sourceConfig.isLayoutEnabled(), "EnableLayout should be true"); - assertEquals(true, sourceConfig.isOcrEnabled(), "EnableOcr should be true"); + assertEquals(false, sourceConfig.isFormulaEnabled(), "FormulaEnabled should be false"); + assertEquals(true, sourceConfig.isLayoutEnabled(), "LayoutEnabled should be true"); + assertEquals(true, sourceConfig.isOcrEnabled(), "OcrEnabled should be true"); assertEquals(true, sourceConfig.isEstimateFieldSourceAndConfidence(), "EstimateFieldSourceAndConfidence should be true"); assertEquals(true, sourceConfig.isReturnDetails(), "ReturnDetails should be true"); @@ -203,9 +203,9 @@ public void testCopyAnalyzerAsync() { // Verify config in result assertNotNull(sourceResult.getConfig(), "Config should not be null in result"); - assertEquals(false, sourceResult.getConfig().isFormulaEnabled(), "EnableFormula should be preserved"); - assertEquals(true, sourceResult.getConfig().isLayoutEnabled(), "EnableLayout should be preserved"); - assertEquals(true, sourceResult.getConfig().isOcrEnabled(), "EnableOcr should be preserved"); + assertEquals(false, sourceResult.getConfig().isFormulaEnabled(), "FormulaEnabled should be preserved"); + assertEquals(true, sourceResult.getConfig().isLayoutEnabled(), "LayoutEnabled should be preserved"); + assertEquals(true, sourceResult.getConfig().isOcrEnabled(), "OcrEnabled should be preserved"); System.out.println(" ✓ Config preserved in result"); // Verify field schema in result @@ -317,19 +317,19 @@ public void testCopyAnalyzerAsync() { // Verify config was copied assertNotNull(copiedAnalyzer.getConfig(), "Copied analyzer should have config"); assertEquals(sourceResult.getConfig().isFormulaEnabled(), copiedAnalyzer.getConfig().isFormulaEnabled(), - "EnableFormula should match"); + "FormulaEnabled should match"); assertEquals(sourceResult.getConfig().isLayoutEnabled(), copiedAnalyzer.getConfig().isLayoutEnabled(), - "EnableLayout should match"); + "LayoutEnabled should match"); assertEquals(sourceResult.getConfig().isOcrEnabled(), copiedAnalyzer.getConfig().isOcrEnabled(), - "EnableOcr should match"); + "OcrEnabled should match"); assertEquals(sourceResult.getConfig().isEstimateFieldSourceAndConfidence(), copiedAnalyzer.getConfig().isEstimateFieldSourceAndConfidence(), "EstimateFieldSourceAndConfidence should match"); assertEquals(sourceResult.getConfig().isReturnDetails(), copiedAnalyzer.getConfig().isReturnDetails(), "ReturnDetails should match"); System.out.println(" ✓ Config preserved"); - System.out.println(" EnableLayout: " + copiedAnalyzer.getConfig().isLayoutEnabled()); - System.out.println(" EnableOcr: " + copiedAnalyzer.getConfig().isOcrEnabled()); + System.out.println(" LayoutEnabled: " + copiedAnalyzer.getConfig().isLayoutEnabled()); + System.out.println(" OcrEnabled: " + copiedAnalyzer.getConfig().isOcrEnabled()); // Verify models were copied assertNotNull(copiedAnalyzer.getModels(), "Copied analyzer should have models"); diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample14_CopyAnalyzerTest.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample14_CopyAnalyzerTest.java index f9378d52c5fa..427cda69515c 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample14_CopyAnalyzerTest.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample14_CopyAnalyzerTest.java @@ -130,9 +130,9 @@ public void testCopyAnalyzer() { // Verify source config assertNotNull(sourceConfig, "Source config should not be null"); - assertEquals(false, sourceConfig.isFormulaEnabled(), "EnableFormula should be false"); - assertEquals(true, sourceConfig.isLayoutEnabled(), "EnableLayout should be true"); - assertEquals(true, sourceConfig.isOcrEnabled(), "EnableOcr should be true"); + assertEquals(false, sourceConfig.isFormulaEnabled(), "FormulaEnabled should be false"); + assertEquals(true, sourceConfig.isLayoutEnabled(), "LayoutEnabled should be true"); + assertEquals(true, sourceConfig.isOcrEnabled(), "OcrEnabled should be true"); assertEquals(true, sourceConfig.isEstimateFieldSourceAndConfidence(), "EstimateFieldSourceAndConfidence should be true"); assertEquals(true, sourceConfig.isReturnDetails(), "ReturnDetails should be true"); @@ -181,9 +181,9 @@ public void testCopyAnalyzer() { // Verify config in result assertNotNull(sourceResult.getConfig(), "Config should not be null in result"); - assertEquals(false, sourceResult.getConfig().isFormulaEnabled(), "EnableFormula should be preserved"); - assertEquals(true, sourceResult.getConfig().isLayoutEnabled(), "EnableLayout should be preserved"); - assertEquals(true, sourceResult.getConfig().isOcrEnabled(), "EnableOcr should be preserved"); + assertEquals(false, sourceResult.getConfig().isFormulaEnabled(), "FormulaEnabled should be preserved"); + assertEquals(true, sourceResult.getConfig().isLayoutEnabled(), "LayoutEnabled should be preserved"); + assertEquals(true, sourceResult.getConfig().isOcrEnabled(), "OcrEnabled should be preserved"); System.out.println(" ✓ Config preserved in result"); // Verify field schema in result @@ -295,19 +295,19 @@ public void testCopyAnalyzer() { // Verify config was copied assertNotNull(copiedAnalyzer.getConfig(), "Copied analyzer should have config"); assertEquals(sourceResult.getConfig().isFormulaEnabled(), copiedAnalyzer.getConfig().isFormulaEnabled(), - "EnableFormula should match"); + "FormulaEnabled should match"); assertEquals(sourceResult.getConfig().isLayoutEnabled(), copiedAnalyzer.getConfig().isLayoutEnabled(), - "EnableLayout should match"); + "LayoutEnabled should match"); assertEquals(sourceResult.getConfig().isOcrEnabled(), copiedAnalyzer.getConfig().isOcrEnabled(), - "EnableOcr should match"); + "OcrEnabled should match"); assertEquals(sourceResult.getConfig().isEstimateFieldSourceAndConfidence(), copiedAnalyzer.getConfig().isEstimateFieldSourceAndConfidence(), "EstimateFieldSourceAndConfidence should match"); assertEquals(sourceResult.getConfig().isReturnDetails(), copiedAnalyzer.getConfig().isReturnDetails(), "ReturnDetails should match"); System.out.println(" ✓ Config preserved"); - System.out.println(" EnableLayout: " + copiedAnalyzer.getConfig().isLayoutEnabled()); - System.out.println(" EnableOcr: " + copiedAnalyzer.getConfig().isOcrEnabled()); + System.out.println(" LayoutEnabled: " + copiedAnalyzer.getConfig().isLayoutEnabled()); + System.out.println(" OcrEnabled: " + copiedAnalyzer.getConfig().isOcrEnabled()); // Verify models were copied assertNotNull(copiedAnalyzer.getModels(), "Copied analyzer should have models"); diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample15_GrantCopyAuth.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample15_GrantCopyAuth.java new file mode 100644 index 000000000000..cbdf5258378b --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample15_GrantCopyAuth.java @@ -0,0 +1,172 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.ContentUnderstandingClient; +import com.azure.ai.contentunderstanding.ContentUnderstandingClientBuilder; +import com.azure.ai.contentunderstanding.models.ContentAnalyzer; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerConfig; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentFieldDefinition; +import com.azure.ai.contentunderstanding.models.CopyAuthorization; +import com.azure.ai.contentunderstanding.models.ContentFieldSchema; +import com.azure.ai.contentunderstanding.models.ContentFieldType; +import com.azure.ai.contentunderstanding.models.GenerationMethod; +import com.azure.core.credential.AzureKeyCredential; +import com.azure.core.test.annotation.LiveOnly; +import com.azure.core.util.polling.SyncPoller; +import com.azure.identity.DefaultAzureCredentialBuilder; +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Sample demonstrates how to grant copy authorization and copy an analyzer from a source + * Microsoft Foundry resource to a target Microsoft Foundry resource (cross-resource copying). + * + *

    For same-resource copying, see Sample14_CopyAnalyzer.

    + * + *

    Required environment variables for cross-resource copying:

    + *
      + *
    • SOURCE_RESOURCE_ID: Azure resource ID of the source resource
    • + *
    • SOURCE_REGION: Region of the source resource
    • + *
    • TARGET_ENDPOINT: Endpoint of the target resource
    • + *
    • TARGET_KEY (optional): API key for target resource
    • + *
    • TARGET_RESOURCE_ID: Azure resource ID of the target resource
    • + *
    • TARGET_REGION: Region of the target resource
    • + *
    + * + *

    Note: If API key is not provided, DefaultAzureCredential will be used. + * Cross-resource copying with DefaultAzureCredential requires 'Cognitive Services User' role + * on both source and target resources.

    + */ +public class Sample15_GrantCopyAuth extends ContentUnderstandingClientTestBase { + + /** + * Demonstrates cross-resource copying with actual resource information. + * + * This test is marked as LiveOnly because it requires connecting to two separate + * Azure resources, which cannot be reliably replayed in PLAYBACK mode. + */ + @LiveOnly + @Test + public void testCrossResourceCopy() { + // Check for required environment variables (matching samples naming convention) + String sourceResourceId = System.getenv("SOURCE_RESOURCE_ID"); + String sourceRegion = System.getenv("SOURCE_REGION"); + String targetEndpoint = System.getenv("TARGET_ENDPOINT"); + String targetKey = System.getenv("TARGET_KEY"); + String targetResourceId = System.getenv("TARGET_RESOURCE_ID"); + String targetRegion = System.getenv("TARGET_REGION"); + + if (sourceResourceId == null + || sourceRegion == null + || targetEndpoint == null + || targetResourceId == null + || targetRegion == null) { + System.out.println("⚠️ Cross-resource copying requires environment variables:"); + System.out.println(" SOURCE_RESOURCE_ID, SOURCE_REGION"); + System.out.println(" TARGET_ENDPOINT, TARGET_KEY (optional), TARGET_RESOURCE_ID, TARGET_REGION"); + System.out.println(" Skipping cross-resource copy test."); + return; + } + + // Build target client with appropriate authentication + ContentUnderstandingClientBuilder targetBuilder + = new ContentUnderstandingClientBuilder().endpoint(targetEndpoint); + ContentUnderstandingClient targetClient; + if (targetKey != null && !targetKey.trim().isEmpty()) { + targetClient = targetBuilder.credential(new AzureKeyCredential(targetKey)).buildClient(); + } else { + targetClient = targetBuilder.credential(new DefaultAzureCredentialBuilder().build()).buildClient(); + } + + String sourceAnalyzerId = testResourceNamer.randomName("test_cross_resource_source_", 50); + String targetAnalyzerId = testResourceNamer.randomName("test_cross_resource_target_", 50); + + try { + // Step 1: Create source analyzer + ContentAnalyzerConfig config = new ContentAnalyzerConfig(); + config.setLayoutEnabled(true); + config.setOcrEnabled(true); + + Map fields = new HashMap<>(); + ContentFieldDefinition companyNameField = new ContentFieldDefinition(); + companyNameField.setType(ContentFieldType.STRING); + companyNameField.setMethod(GenerationMethod.EXTRACT); + companyNameField.setDescription("Name of the company"); + fields.put("company_name", companyNameField); + + ContentFieldDefinition totalAmountField = new ContentFieldDefinition(); + totalAmountField.setType(ContentFieldType.NUMBER); + totalAmountField.setMethod(GenerationMethod.EXTRACT); + totalAmountField.setDescription("Total amount on the document"); + fields.put("total_amount", totalAmountField); + + ContentFieldSchema fieldSchema = new ContentFieldSchema(); + fieldSchema.setName("company_schema"); + fieldSchema.setDescription("Schema for extracting company information"); + fieldSchema.setFields(fields); + + ContentAnalyzer sourceAnalyzer = new ContentAnalyzer(); + sourceAnalyzer.setBaseAnalyzerId("prebuilt-document"); + sourceAnalyzer.setDescription("Source analyzer for cross-resource copying"); + sourceAnalyzer.setConfig(config); + sourceAnalyzer.setFieldSchema(fieldSchema); + + Map models = new HashMap<>(); + models.put("completion", "gpt-4.1"); + sourceAnalyzer.setModels(models); + + SyncPoller createPoller + = contentUnderstandingClient.beginCreateAnalyzer(sourceAnalyzerId, sourceAnalyzer); + ContentAnalyzer sourceResult = createPoller.getFinalResult(); + System.out.println("Source analyzer '" + sourceAnalyzerId + "' created successfully!"); + + // Step 2: Grant copy authorization using convenience method + CopyAuthorization copyAuth + = contentUnderstandingClient.grantCopyAuthorization(sourceAnalyzerId, targetResourceId, targetRegion); + + assertNotNull(copyAuth, "Copy authorization should not be null"); + System.out.println("Copy authorization granted!"); + System.out.println(" Target Azure Resource ID: " + copyAuth.getTargetAzureResourceId()); + System.out.println(" Expires at: " + copyAuth.getExpiresAt()); + + // Step 3: Copy analyzer to target resource using convenience method + SyncPoller copyPoller = targetClient + .beginCopyAnalyzer(targetAnalyzerId, sourceAnalyzerId, false, sourceResourceId, sourceRegion); + ContentAnalyzer targetResult = copyPoller.getFinalResult(); + + System.out.println("Target analyzer '" + targetAnalyzerId + "' copied successfully!"); + System.out.println(" Description: " + targetResult.getDescription()); + + // Verify copied analyzer + ContentAnalyzer copiedAnalyzer = targetClient.getAnalyzer(targetAnalyzerId); + assertNotNull(copiedAnalyzer, "Copied analyzer should not be null"); + assertEquals(sourceResult.getBaseAnalyzerId(), copiedAnalyzer.getBaseAnalyzerId()); + assertEquals(sourceResult.getDescription(), copiedAnalyzer.getDescription()); + System.out.println("Cross-resource copy verification completed"); + + } finally { + // Cleanup: delete both analyzers + try { + contentUnderstandingClient.deleteAnalyzer(sourceAnalyzerId); + System.out.println("Source analyzer '" + sourceAnalyzerId + "' deleted."); + } catch (Exception e) { + // Ignore cleanup errors + } + + try { + targetClient.deleteAnalyzer(targetAnalyzerId); + System.out.println("Target analyzer '" + targetAnalyzerId + "' deleted."); + } catch (Exception e) { + // Ignore cleanup errors + } + } + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample15_GrantCopyAuthAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample15_GrantCopyAuthAsync.java new file mode 100644 index 000000000000..9b7a861edb0a --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample15_GrantCopyAuthAsync.java @@ -0,0 +1,195 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.ContentUnderstandingAsyncClient; +import com.azure.ai.contentunderstanding.ContentUnderstandingClientBuilder; +import com.azure.ai.contentunderstanding.models.ContentAnalyzer; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerConfig; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerOperationStatus; +import com.azure.ai.contentunderstanding.models.ContentFieldDefinition; +import com.azure.ai.contentunderstanding.models.CopyAuthorization; +import com.azure.ai.contentunderstanding.models.ContentFieldSchema; +import com.azure.ai.contentunderstanding.models.ContentFieldType; +import com.azure.ai.contentunderstanding.models.GenerationMethod; +import com.azure.core.credential.AzureKeyCredential; +import com.azure.core.test.annotation.LiveOnly; +import com.azure.core.util.polling.PollerFlux; +import com.azure.identity.DefaultAzureCredentialBuilder; +import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Async sample demonstrates how to grant copy authorization and copy an analyzer from a source + * Microsoft Foundry resource to a target Microsoft Foundry resource (cross-resource copying). + * + *

    For same-resource copying, see Sample14_CopyAnalyzerAsync.

    + * + *

    Required environment variables for cross-resource copying:

    + *
      + *
    • SOURCE_RESOURCE_ID: Azure resource ID of the source resource
    • + *
    • SOURCE_REGION: Region of the source resource
    • + *
    • TARGET_ENDPOINT: Endpoint of the target resource
    • + *
    • TARGET_KEY (optional): API key for target resource
    • + *
    • TARGET_RESOURCE_ID: Azure resource ID of the target resource
    • + *
    • TARGET_REGION: Region of the target resource
    • + *
    + * + *

    Note: If API key is not provided, DefaultAzureCredential will be used. + * Cross-resource copying with DefaultAzureCredential requires 'Cognitive Services User' role + * on both source and target resources.

    + */ +public class Sample15_GrantCopyAuthAsync extends ContentUnderstandingClientTestBase { + + /** + * Demonstrates cross-resource copying with actual resource information. + * + * This test is marked as LiveOnly because it requires connecting to two separate + * Azure resources, which cannot be reliably replayed in PLAYBACK mode. + */ + @LiveOnly + @Test + public void testCrossResourceCopyAsync() { + // Check for required environment variables (matching samples naming convention) + String sourceResourceId = System.getenv("SOURCE_RESOURCE_ID"); + String sourceRegion = System.getenv("SOURCE_REGION"); + String targetEndpoint = System.getenv("TARGET_ENDPOINT"); + String targetKey = System.getenv("TARGET_KEY"); + String targetResourceId = System.getenv("TARGET_RESOURCE_ID"); + String targetRegion = System.getenv("TARGET_REGION"); + + if (sourceResourceId == null + || sourceRegion == null + || targetEndpoint == null + || targetResourceId == null + || targetRegion == null) { + System.out.println("⚠️ Cross-resource copying requires environment variables:"); + System.out.println(" SOURCE_RESOURCE_ID, SOURCE_REGION"); + System.out.println(" TARGET_ENDPOINT, TARGET_KEY (optional), TARGET_RESOURCE_ID, TARGET_REGION"); + System.out.println(" Skipping cross-resource copy test."); + return; + } + + // Build target client with appropriate authentication + ContentUnderstandingClientBuilder targetBuilder + = new ContentUnderstandingClientBuilder().endpoint(targetEndpoint); + ContentUnderstandingAsyncClient targetAsyncClient; + if (targetKey != null && !targetKey.trim().isEmpty()) { + targetAsyncClient = targetBuilder.credential(new AzureKeyCredential(targetKey)).buildAsyncClient(); + } else { + targetAsyncClient + = targetBuilder.credential(new DefaultAzureCredentialBuilder().build()).buildAsyncClient(); + } + + String sourceAnalyzerId = testResourceNamer.randomName("test_cross_resource_source_", 50); + String targetAnalyzerId = testResourceNamer.randomName("test_cross_resource_target_", 50); + + try { + // Step 1: Create source analyzer + ContentAnalyzerConfig config = new ContentAnalyzerConfig(); + config.setLayoutEnabled(true); + config.setOcrEnabled(true); + + Map fields = new HashMap<>(); + ContentFieldDefinition companyNameField = new ContentFieldDefinition(); + companyNameField.setType(ContentFieldType.STRING); + companyNameField.setMethod(GenerationMethod.EXTRACT); + companyNameField.setDescription("Name of the company"); + fields.put("company_name", companyNameField); + + ContentFieldDefinition totalAmountField = new ContentFieldDefinition(); + totalAmountField.setType(ContentFieldType.NUMBER); + totalAmountField.setMethod(GenerationMethod.EXTRACT); + totalAmountField.setDescription("Total amount on the document"); + fields.put("total_amount", totalAmountField); + + ContentFieldSchema fieldSchema = new ContentFieldSchema(); + fieldSchema.setName("company_schema"); + fieldSchema.setDescription("Schema for extracting company information"); + fieldSchema.setFields(fields); + + ContentAnalyzer sourceAnalyzer = new ContentAnalyzer(); + sourceAnalyzer.setBaseAnalyzerId("prebuilt-document"); + sourceAnalyzer.setDescription("Source analyzer for cross-resource copying"); + sourceAnalyzer.setConfig(config); + sourceAnalyzer.setFieldSchema(fieldSchema); + + Map models = new HashMap<>(); + models.put("completion", "gpt-4.1"); + sourceAnalyzer.setModels(models); + + PollerFlux createPoller + = contentUnderstandingAsyncClient.beginCreateAnalyzer(sourceAnalyzerId, sourceAnalyzer); + + // Use reactive pattern: chain operations using flatMap + // In a real application, you would use subscribe() instead of block() + ContentAnalyzer sourceResult = createPoller.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + + System.out.println("Source analyzer '" + sourceAnalyzerId + "' created successfully!"); + + // Step 2: Grant copy authorization using convenience method + CopyAuthorization copyAuth = contentUnderstandingAsyncClient + .grantCopyAuthorization(sourceAnalyzerId, targetResourceId, targetRegion) + .block(); + + assertNotNull(copyAuth, "Copy authorization should not be null"); + System.out.println("Copy authorization granted!"); + System.out.println(" Target Azure Resource ID: " + copyAuth.getTargetAzureResourceId()); + System.out.println(" Expires at: " + copyAuth.getExpiresAt()); + + // Step 3: Copy analyzer to target resource using convenience method + PollerFlux copyPoller = targetAsyncClient + .beginCopyAnalyzer(targetAnalyzerId, sourceAnalyzerId, false, sourceResourceId, sourceRegion); + + // Use reactive pattern for copy operation as well + ContentAnalyzer targetResult = copyPoller.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + + System.out.println("Target analyzer '" + targetAnalyzerId + "' copied successfully!"); + System.out.println(" Description: " + targetResult.getDescription()); + + // Verify copied analyzer + ContentAnalyzer copiedAnalyzer = targetAsyncClient.getAnalyzer(targetAnalyzerId).block(); + assertNotNull(copiedAnalyzer, "Copied analyzer should not be null"); + assertEquals(sourceResult.getBaseAnalyzerId(), copiedAnalyzer.getBaseAnalyzerId()); + assertEquals(sourceResult.getDescription(), copiedAnalyzer.getDescription()); + System.out.println("Cross-resource copy verification completed"); + + } finally { + // Cleanup: delete both analyzers + try { + contentUnderstandingAsyncClient.deleteAnalyzer(sourceAnalyzerId).block(); + System.out.println("Source analyzer '" + sourceAnalyzerId + "' deleted."); + } catch (Exception e) { + // Ignore cleanup errors + } + + try { + targetAsyncClient.deleteAnalyzer(targetAnalyzerId).block(); + System.out.println("Target analyzer '" + targetAnalyzerId + "' deleted."); + } catch (Exception e) { + // Ignore cleanup errors + } + } + } +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample16_CreateAnalyzerWithLabels.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample16_CreateAnalyzerWithLabels.java new file mode 100644 index 000000000000..26dfc76e9926 --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample16_CreateAnalyzerWithLabels.java @@ -0,0 +1,329 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.AnalysisInput; +import com.azure.ai.contentunderstanding.models.AnalysisResult; +import com.azure.ai.contentunderstanding.models.ContentAnalyzer; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerConfig; +import com.azure.ai.contentunderstanding.models.ContentField; +import com.azure.ai.contentunderstanding.models.ContentFieldDefinition; +import com.azure.ai.contentunderstanding.models.ContentFieldSchema; +import com.azure.ai.contentunderstanding.models.ContentFieldType; +import com.azure.ai.contentunderstanding.models.DocumentContent; +import com.azure.ai.contentunderstanding.models.GenerationMethod; +import com.azure.ai.contentunderstanding.models.KnowledgeSource; +import com.azure.ai.contentunderstanding.models.LabeledDataKnowledgeSource; +import com.azure.core.credential.TokenCredential; +import com.azure.core.util.polling.SyncPoller; +import com.azure.identity.DefaultAzureCredentialBuilder; +import org.junit.jupiter.api.Test; + +import com.azure.core.test.TestMode; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Sample demonstrates how to build analyzers with training labels (labeled data from Azure Blob Storage). + * + * This sample is mainly to show the API pattern for creating an analyzer with labeled training data. + * For an easier labeling workflow, use Azure AI Content Understanding Studio at + * https://contentunderstanding.ai.azure.com/ + * + * Labeled receipt data is available in this repo at {@code src/samples/resources/receipt_labels} + * (images and corresponding .labels.json files). To use it for training: + * + *

    Manual instructions to upload labels into Azure Blob Storage:

    + *
      + *
    1. Create an Azure Blob Storage container (or use an existing one).
    2. + *
    3. Upload the contents of {@code src/samples/resources/receipt_labels} into the container. + * You may upload into the container root or into a subfolder (e.g., "receipt_labels/").
    4. + *
    5. Generate a SAS (Shared Access Signature) URL for the container with at least List and Read + * permissions. In Azure Portal: Storage account → Containers → your container → Shared access + * token; set expiry and permissions, then generate the SAS URL.
    6. + *
    7. Set {@code CONTENTUNDERSTANDING_TRAINING_DATA_SAS_URL} to the full SAS URL + * (e.g., {@code https://.blob.core.windows.net/?sv=...&se=...}).
    8. + *
    9. If you uploaded into a subfolder, set {@code CONTENTUNDERSTANDING_TRAINING_DATA_PREFIX} to + * that path (e.g., "receipt_labels/"). If files are at the container root, omit the prefix + * or leave it unset.
    10. + *
    + * + *

    Each labeled document in the training folder includes:

    + *
      + *
    • The original file (e.g., PDF or image).
    • + *
    • A corresponding .labels.json file with labeled fields.
    • + *
    • A corresponding .result.json file with OCR results (optional).
    • + *
    + * + *

    Required environment variables:

    + *
      + *
    • {@code CONTENTUNDERSTANDING_ENDPOINT} – Azure Content Understanding endpoint URL
    • + *
    + * + *

    Optional environment variables (for labeled training data):

    + *
      + *
    • {@code CONTENTUNDERSTANDING_TRAINING_DATA_SAS_URL} – SAS URL for the Azure Blob container + * with labeled training data. If set, the analyzer is created with a labeled-data knowledge + * source; otherwise, created without training data.
    • + *
    • {@code CONTENTUNDERSTANDING_TRAINING_DATA_PREFIX} – Path prefix within the container + * (e.g., "receipt_labels/" or "CreateAnalyzerWithLabels/"). Omit or leave unset if files + * are at the container root.
    • + *
    • {@code CONTENTUNDERSTANDING_TRAINING_DATA_STORAGE_ACCOUNT} – Storage account name for + * auto-upload (Option B). Used when SAS URL is not set.
    • + *
    • {@code CONTENTUNDERSTANDING_TRAINING_DATA_CONTAINER} – Container name for auto-upload + * (Option B). Used when SAS URL is not set.
    • + *
    + */ +public class Sample16_CreateAnalyzerWithLabels extends ContentUnderstandingClientTestBase { + + /** + * Demonstrates creating an analyzer with labeled training data. + * + * This test creates an analyzer with field schema. If TRAINING_DATA_SAS_URL is provided, + * labeled training data will be used; otherwise falls back to auto-upload if storage account + * and container are configured, or demonstrates the API pattern without actual training data. + */ + @Test + public void testCreateAnalyzerWithLabels() { + + String analyzerId = testResourceNamer.randomName("test_receipt_analyzer_", 50); + // In PLAYBACK mode, use a placeholder URL to ensure consistent test behavior + String trainingDataSasUrl = getTestMode() == TestMode.PLAYBACK + ? "https://placeholder.blob.core.windows.net/container?sv=placeholder" + : System.getenv("CONTENTUNDERSTANDING_TRAINING_DATA_SAS_URL"); + // Save prefix in test proxy variable during RECORD, load back during PLAYBACK so request bodies match. + String trainingDataPrefix; + if (getTestMode() == TestMode.PLAYBACK) { + String recorded = interceptorManager.getProxyVariableSupplier().get(); + trainingDataPrefix = (recorded == null || recorded.isEmpty()) ? null : recorded; + } else if (getTestMode() == TestMode.RECORD) { + trainingDataPrefix = System.getenv("CONTENTUNDERSTANDING_TRAINING_DATA_PREFIX"); + interceptorManager.getProxyVariableConsumer().accept(trainingDataPrefix != null ? trainingDataPrefix : ""); + } else { + trainingDataPrefix = System.getenv("CONTENTUNDERSTANDING_TRAINING_DATA_PREFIX"); + } + + // Option B fallback: upload local label files and generate SAS URL + if ((trainingDataSasUrl == null || trainingDataSasUrl.trim().isEmpty()) && getTestMode() != TestMode.PLAYBACK) { + String storageAccount = System.getenv("CONTENTUNDERSTANDING_TRAINING_DATA_STORAGE_ACCOUNT"); + String container = System.getenv("CONTENTUNDERSTANDING_TRAINING_DATA_CONTAINER"); + if (storageAccount != null + && !storageAccount.trim().isEmpty() + && container != null + && !container.trim().isEmpty()) { + TokenCredential credential = new DefaultAzureCredentialBuilder().build(); + String localDir = new File("src/samples/resources/receipt_labels").getAbsolutePath(); + uploadTrainingData(storageAccount, container, credential, localDir, trainingDataPrefix); + trainingDataSasUrl = generateUserDelegationSasUrl(storageAccount, container, credential); + } + } + + try { + // BEGIN: com.azure.ai.contentunderstanding.createAnalyzerWithLabels + // Step 1: Define field schema for receipt extraction + Map fields = new HashMap<>(); + + // MerchantName field + ContentFieldDefinition merchantNameField = new ContentFieldDefinition(); + merchantNameField.setType(ContentFieldType.STRING); + merchantNameField.setMethod(GenerationMethod.EXTRACT); + merchantNameField.setDescription("Name of the merchant"); + fields.put("MerchantName", merchantNameField); + + // Items array field - define item structure + ContentFieldDefinition itemDefinition = new ContentFieldDefinition(); + itemDefinition.setType(ContentFieldType.OBJECT); + itemDefinition.setMethod(GenerationMethod.EXTRACT); + itemDefinition.setDescription("Individual item details"); + + Map itemProperties = new HashMap<>(); + + ContentFieldDefinition quantityField = new ContentFieldDefinition(); + quantityField.setType(ContentFieldType.STRING); + quantityField.setMethod(GenerationMethod.EXTRACT); + quantityField.setDescription("Quantity of the item"); + itemProperties.put("Quantity", quantityField); + + ContentFieldDefinition nameField = new ContentFieldDefinition(); + nameField.setType(ContentFieldType.STRING); + nameField.setMethod(GenerationMethod.EXTRACT); + nameField.setDescription("Name of the item"); + itemProperties.put("Name", nameField); + + ContentFieldDefinition priceField = new ContentFieldDefinition(); + priceField.setType(ContentFieldType.STRING); + priceField.setMethod(GenerationMethod.EXTRACT); + priceField.setDescription("Price of the item"); + itemProperties.put("Price", priceField); + + itemDefinition.setProperties(itemProperties); + + // Items array field + ContentFieldDefinition itemsField = new ContentFieldDefinition(); + itemsField.setType(ContentFieldType.ARRAY); + itemsField.setMethod(GenerationMethod.GENERATE); + itemsField.setDescription("List of items purchased"); + itemsField.setItemDefinition(itemDefinition); + fields.put("Items", itemsField); + + // TotalPrice field + ContentFieldDefinition totalPriceField = new ContentFieldDefinition(); + totalPriceField.setType(ContentFieldType.STRING); + totalPriceField.setMethod(GenerationMethod.EXTRACT); + totalPriceField.setDescription("Total amount"); + fields.put("TotalPrice", totalPriceField); + + ContentFieldSchema fieldSchema = new ContentFieldSchema(); + fieldSchema.setName("receipt_schema"); + fieldSchema.setDescription("Schema for receipt extraction with items"); + fieldSchema.setFields(fields); + + // Step 2: Create labeled data knowledge source (optional, based on environment variable) + List knowledgeSources = new ArrayList<>(); + if (trainingDataSasUrl != null && !trainingDataSasUrl.trim().isEmpty()) { + LabeledDataKnowledgeSource knowledgeSource + = new LabeledDataKnowledgeSource().setContainerUrl(trainingDataSasUrl); + if (trainingDataPrefix != null && !trainingDataPrefix.trim().isEmpty()) { + knowledgeSource.setPrefix(trainingDataPrefix); + } + knowledgeSources.add(knowledgeSource); + System.out.println("Using labeled training data from: " + + trainingDataSasUrl.substring(0, Math.min(50, trainingDataSasUrl.length())) + "..."); + } else { + System.out.println("No TRAINING_DATA_SAS_URL set, creating analyzer without labeled training data"); + } + + // Step 3: Create analyzer (with or without labeled data) + Map models = new HashMap<>(); + models.put("completion", "gpt-4.1"); + models.put("embedding", "text-embedding-3-large"); + + ContentAnalyzer analyzer = new ContentAnalyzer().setBaseAnalyzerId("prebuilt-document") + .setDescription("Receipt analyzer with labeled training data") + .setConfig(new ContentAnalyzerConfig().setLayoutEnabled(true).setOcrEnabled(true)) + .setFieldSchema(fieldSchema) + .setModels(models); + + if (!knowledgeSources.isEmpty()) { + analyzer.setKnowledgeSources(knowledgeSources); + } + + SyncPoller createPoller + = contentUnderstandingClient.beginCreateAnalyzer(analyzerId, analyzer, true); + ContentAnalyzer result = createPoller.getFinalResult(); + + System.out.println("Analyzer created: " + analyzerId); + System.out.println(" Description: " + result.getDescription()); + System.out.println(" Base analyzer: " + result.getBaseAnalyzerId()); + System.out.println(" Fields: " + result.getFieldSchema().getFields().size()); + System.out.println(" Knowledge srcs: " + + (result.getKnowledgeSources() != null ? result.getKnowledgeSources().size() : 0)); + // END: com.azure.ai.contentunderstanding.createAnalyzerWithLabels + + // BEGIN: Assertion_ContentUnderstandingCreateAnalyzerWithLabels + // Verify analyzer creation + System.out.println("\nAnalyzer Creation Verification:"); + assertNotNull(result, "Analyzer should not be null"); + assertEquals("prebuilt-document", result.getBaseAnalyzerId()); + assertEquals("Receipt analyzer with labeled training data", result.getDescription()); + assertNotNull(result.getFieldSchema()); + assertEquals("receipt_schema", result.getFieldSchema().getName()); + assertEquals(3, result.getFieldSchema().getFields().size()); + System.out.println("Analyzer created successfully"); + + // Verify field schema + Map resultFields = result.getFieldSchema().getFields(); + assertTrue(resultFields.containsKey("MerchantName"), "Should have MerchantName field"); + assertTrue(resultFields.containsKey("Items"), "Should have Items field"); + assertTrue(resultFields.containsKey("TotalPrice"), "Should have TotalPrice field"); + + ContentFieldDefinition itemsFieldResult = resultFields.get("Items"); + assertEquals(ContentFieldType.ARRAY, itemsFieldResult.getType()); + assertNotNull(itemsFieldResult.getItemDefinition()); + assertEquals(ContentFieldType.OBJECT, itemsFieldResult.getItemDefinition().getType()); + assertEquals(3, itemsFieldResult.getItemDefinition().getProperties().size()); + System.out.println("Field schema verified:"); + System.out.println(" MerchantName: String (Extract)"); + System.out.println(" Items: Array of Objects (Generate)"); + System.out.println(" - Quantity, Name, Price"); + System.out.println(" TotalPrice: String (Extract)"); + // END: Assertion_ContentUnderstandingCreateAnalyzerWithLabels + + // If training data was provided, test the analyzer with a sample document + if (trainingDataSasUrl != null && !trainingDataSasUrl.trim().isEmpty()) { + System.out.println("\nTesting analyzer with sample document..."); + String testDocUrl + = "https://github.com/Azure-Samples/cognitive-services-REST-api-samples/raw/master/curl/form-recognizer/sample-invoice.pdf"; + + AnalysisInput input = new AnalysisInput(); + input.setUrl(testDocUrl); + + AnalysisResult AnalysisResult + = contentUnderstandingClient.beginAnalyze(analyzerId, Arrays.asList(input)).getFinalResult(); + + System.out.println("Analysis completed!"); + assertNotNull(AnalysisResult); + assertNotNull(AnalysisResult.getContents()); + assertTrue(AnalysisResult.getContents().size() > 0); + + if (AnalysisResult.getContents().get(0) instanceof DocumentContent) { + DocumentContent docContent = (DocumentContent) AnalysisResult.getContents().get(0); + System.out.println("Extracted fields: " + docContent.getFields().size()); + + // Display extracted values + if (docContent.getFields().containsKey("MerchantName")) { + ContentField merchantField = docContent.getFields().get("MerchantName"); + if (merchantField != null) { + String merchantName = (String) merchantField.getValue(); + System.out.println(" MerchantName: " + merchantName); + } + } + if (docContent.getFields().containsKey("TotalPrice")) { + ContentField totalPriceFieldValue = docContent.getFields().get("TotalPrice"); + if (totalPriceFieldValue != null) { + String totalPrice = (String) totalPriceFieldValue.getValue(); + System.out.println(" TotalPrice: " + totalPrice); + } + } + } + } + + // Display API pattern information + System.out.println("\nCreateAnalyzerWithLabels API Pattern:"); + System.out.println(" 1. Define field schema with nested structures (arrays, objects)"); + System.out.println(" 2. Upload training data to Azure Blob Storage:"); + System.out.println(" - Documents: receipt1.jpg, receipt2.jpg, ..."); + System.out.println(" - Labels: receipt1.jpg.labels.json, receipt2.jpg.labels.json, ..."); + System.out.println(" - OCR: receipt1.jpg.result.json, receipt2.jpg.result.json, ..."); + System.out.println(" 3. Create LabeledDataKnowledgeSource with storage SAS URL"); + System.out.println(" 4. Create analyzer with field schema and knowledge sources"); + System.out.println(" 5. Use analyzer for document analysis"); + + System.out.println("\nCreateAnalyzerWithLabels pattern demonstration completed"); + if (trainingDataSasUrl == null || trainingDataSasUrl.trim().isEmpty()) { + System.out.println(" Note: This sample demonstrates the API pattern."); + System.out.println( + " For actual training, provide CONTENTUNDERSTANDING_TRAINING_DATA_SAS_URL with labeled data."); + } + + } finally { + // Cleanup + try { + contentUnderstandingClient.deleteAnalyzer(analyzerId); + System.out.println("\nAnalyzer deleted: " + analyzerId); + } catch (Exception e) { + System.out.println("Note: Failed to delete analyzer: " + e.getMessage()); + } + } + } + +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample16_CreateAnalyzerWithLabelsAsync.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample16_CreateAnalyzerWithLabelsAsync.java new file mode 100644 index 000000000000..a81793f1f0dc --- /dev/null +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample16_CreateAnalyzerWithLabelsAsync.java @@ -0,0 +1,350 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// Code generated by Microsoft (R) TypeSpec Code Generator. + +package com.azure.ai.contentunderstanding.tests.samples; + +import com.azure.ai.contentunderstanding.models.AnalysisInput; +import com.azure.ai.contentunderstanding.models.AnalysisResult; +import com.azure.ai.contentunderstanding.models.ContentAnalyzer; +import com.azure.ai.contentunderstanding.models.ContentAnalyzerConfig; +import com.azure.ai.contentunderstanding.models.ContentField; +import com.azure.ai.contentunderstanding.models.ContentFieldDefinition; +import com.azure.ai.contentunderstanding.models.ContentFieldSchema; +import com.azure.ai.contentunderstanding.models.ContentFieldType; +import com.azure.ai.contentunderstanding.models.DocumentContent; +import com.azure.ai.contentunderstanding.models.GenerationMethod; +import com.azure.ai.contentunderstanding.models.KnowledgeSource; +import com.azure.ai.contentunderstanding.models.LabeledDataKnowledgeSource; +import com.azure.core.credential.TokenCredential; +import com.azure.core.util.polling.PollerFlux; +import com.azure.identity.DefaultAzureCredentialBuilder; +import reactor.core.publisher.Mono; +import org.junit.jupiter.api.Test; + +import com.azure.core.test.TestMode; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Async sample demonstrates how to build analyzers with training labels (labeled data from Azure Blob Storage). + * + * This sample is mainly to show the API pattern for creating an analyzer with labeled training data. + * For an easier labeling workflow, use Azure AI Content Understanding Studio at + * https://contentunderstanding.ai.azure.com/ + * + * Labeled receipt data is available in this repo at {@code src/samples/resources/receipt_labels} + * (images and corresponding .labels.json files). To use it for training: + * + *

    Manual instructions to upload labels into Azure Blob Storage:

    + *
      + *
    1. Create an Azure Blob Storage container (or use an existing one).
    2. + *
    3. Upload the contents of {@code src/samples/resources/receipt_labels} into the container. + * You may upload into the container root or into a subfolder (e.g., "receipt_labels/").
    4. + *
    5. Generate a SAS (Shared Access Signature) URL for the container with at least List and Read + * permissions. In Azure Portal: Storage account → Containers → your container → Shared access + * token; set expiry and permissions, then generate the SAS URL.
    6. + *
    7. Set {@code CONTENTUNDERSTANDING_TRAINING_DATA_SAS_URL} to the full SAS URL + * (e.g., {@code https://.blob.core.windows.net/?sv=...&se=...}).
    8. + *
    9. If you uploaded into a subfolder, set {@code CONTENTUNDERSTANDING_TRAINING_DATA_PREFIX} to + * that path (e.g., "receipt_labels/"). If files are at the container root, omit the prefix + * or leave it unset.
    10. + *
    + * + *

    Each labeled document in the training folder includes:

    + *
      + *
    • The original file (e.g., PDF or image).
    • + *
    • A corresponding .labels.json file with labeled fields.
    • + *
    • A corresponding .result.json file with OCR results (optional).
    • + *
    + * + *

    Required environment variables:

    + *
      + *
    • {@code CONTENTUNDERSTANDING_ENDPOINT} – Azure Content Understanding endpoint URL
    • + *
    + * + *

    Optional environment variables (for labeled training data):

    + *
      + *
    • {@code CONTENTUNDERSTANDING_TRAINING_DATA_SAS_URL} – SAS URL for the Azure Blob container + * with labeled training data. If set, the analyzer is created with a labeled-data knowledge + * source; otherwise, created without training data.
    • + *
    • {@code CONTENTUNDERSTANDING_TRAINING_DATA_PREFIX} – Path prefix within the container + * (e.g., "receipt_labels/" or "CreateAnalyzerWithLabels/"). Omit or leave unset if files + * are at the container root.
    • + *
    • {@code CONTENTUNDERSTANDING_TRAINING_DATA_STORAGE_ACCOUNT} – Storage account name for + * auto-upload (Option B). Used when SAS URL is not set.
    • + *
    • {@code CONTENTUNDERSTANDING_TRAINING_DATA_CONTAINER} – Container name for auto-upload + * (Option B). Used when SAS URL is not set.
    • + *
    + */ +public class Sample16_CreateAnalyzerWithLabelsAsync extends ContentUnderstandingClientTestBase { + + /** + * Demonstrates creating an analyzer with labeled training data. + * + * This test creates an analyzer with field schema. If TRAINING_DATA_SAS_URL is provided, + * labeled training data will be used; otherwise falls back to auto-upload if storage account + * and container are configured, or demonstrates the API pattern without actual training data. + */ + @Test + public void testCreateAnalyzerWithLabelsAsync() { + + String analyzerId = testResourceNamer.randomName("test_receipt_analyzer_", 50); + // In PLAYBACK mode, use a placeholder URL to ensure consistent test behavior + String trainingDataSasUrl = getTestMode() == TestMode.PLAYBACK + ? "https://placeholder.blob.core.windows.net/container?sv=placeholder" + : System.getenv("CONTENTUNDERSTANDING_TRAINING_DATA_SAS_URL"); + // Save prefix in test proxy variable during RECORD, load back during PLAYBACK so request bodies match. + String trainingDataPrefix; + if (getTestMode() == TestMode.PLAYBACK) { + String recorded = interceptorManager.getProxyVariableSupplier().get(); + trainingDataPrefix = (recorded == null || recorded.isEmpty()) ? null : recorded; + } else if (getTestMode() == TestMode.RECORD) { + trainingDataPrefix = System.getenv("CONTENTUNDERSTANDING_TRAINING_DATA_PREFIX"); + interceptorManager.getProxyVariableConsumer().accept(trainingDataPrefix != null ? trainingDataPrefix : ""); + } else { + trainingDataPrefix = System.getenv("CONTENTUNDERSTANDING_TRAINING_DATA_PREFIX"); + } + + // Option B fallback: upload local label files and generate SAS URL + if ((trainingDataSasUrl == null || trainingDataSasUrl.trim().isEmpty()) && getTestMode() != TestMode.PLAYBACK) { + String storageAccount = System.getenv("CONTENTUNDERSTANDING_TRAINING_DATA_STORAGE_ACCOUNT"); + String container = System.getenv("CONTENTUNDERSTANDING_TRAINING_DATA_CONTAINER"); + if (storageAccount != null + && !storageAccount.trim().isEmpty() + && container != null + && !container.trim().isEmpty()) { + TokenCredential credential = new DefaultAzureCredentialBuilder().build(); + String localDir = new File("src/samples/resources/receipt_labels").getAbsolutePath(); + uploadTrainingData(storageAccount, container, credential, localDir, trainingDataPrefix); + trainingDataSasUrl = generateUserDelegationSasUrl(storageAccount, container, credential); + } + } + + try { + // BEGIN: com.azure.ai.contentunderstanding.createAnalyzerWithLabelsAsync + // Step 1: Define field schema for receipt extraction + Map fields = new HashMap<>(); + + // MerchantName field + ContentFieldDefinition merchantNameField = new ContentFieldDefinition(); + merchantNameField.setType(ContentFieldType.STRING); + merchantNameField.setMethod(GenerationMethod.EXTRACT); + merchantNameField.setDescription("Name of the merchant"); + fields.put("MerchantName", merchantNameField); + + // Items array field - define item structure + ContentFieldDefinition itemDefinition = new ContentFieldDefinition(); + itemDefinition.setType(ContentFieldType.OBJECT); + itemDefinition.setMethod(GenerationMethod.EXTRACT); + itemDefinition.setDescription("Individual item details"); + + Map itemProperties = new HashMap<>(); + + ContentFieldDefinition quantityField = new ContentFieldDefinition(); + quantityField.setType(ContentFieldType.STRING); + quantityField.setMethod(GenerationMethod.EXTRACT); + quantityField.setDescription("Quantity of the item"); + itemProperties.put("Quantity", quantityField); + + ContentFieldDefinition nameField = new ContentFieldDefinition(); + nameField.setType(ContentFieldType.STRING); + nameField.setMethod(GenerationMethod.EXTRACT); + nameField.setDescription("Name of the item"); + itemProperties.put("Name", nameField); + + ContentFieldDefinition priceField = new ContentFieldDefinition(); + priceField.setType(ContentFieldType.STRING); + priceField.setMethod(GenerationMethod.EXTRACT); + priceField.setDescription("Price of the item"); + itemProperties.put("Price", priceField); + + itemDefinition.setProperties(itemProperties); + + // Items array field + ContentFieldDefinition itemsField = new ContentFieldDefinition(); + itemsField.setType(ContentFieldType.ARRAY); + itemsField.setMethod(GenerationMethod.GENERATE); + itemsField.setDescription("List of items purchased"); + itemsField.setItemDefinition(itemDefinition); + fields.put("Items", itemsField); + + // TotalPrice field + ContentFieldDefinition totalPriceField = new ContentFieldDefinition(); + totalPriceField.setType(ContentFieldType.STRING); + totalPriceField.setMethod(GenerationMethod.EXTRACT); + totalPriceField.setDescription("Total amount"); + fields.put("TotalPrice", totalPriceField); + + ContentFieldSchema fieldSchema = new ContentFieldSchema(); + fieldSchema.setName("receipt_schema"); + fieldSchema.setDescription("Schema for receipt extraction with items"); + fieldSchema.setFields(fields); + + // Step 2: Create labeled data knowledge source (optional, based on environment variable) + List knowledgeSources = new ArrayList<>(); + if (trainingDataSasUrl != null && !trainingDataSasUrl.trim().isEmpty()) { + LabeledDataKnowledgeSource knowledgeSource + = new LabeledDataKnowledgeSource().setContainerUrl(trainingDataSasUrl); + if (trainingDataPrefix != null && !trainingDataPrefix.trim().isEmpty()) { + knowledgeSource.setPrefix(trainingDataPrefix); + } + knowledgeSources.add(knowledgeSource); + System.out.println("Using labeled training data from: " + + trainingDataSasUrl.substring(0, Math.min(50, trainingDataSasUrl.length())) + "..."); + } else { + System.out.println("No TRAINING_DATA_SAS_URL set, creating analyzer without labeled training data"); + } + + // Step 3: Create analyzer (with or without labeled data) + Map models = new HashMap<>(); + models.put("completion", "gpt-4.1"); + models.put("embedding", "text-embedding-3-large"); + + ContentAnalyzer analyzer = new ContentAnalyzer().setBaseAnalyzerId("prebuilt-document") + .setDescription("Receipt analyzer with labeled training data") + .setConfig(new ContentAnalyzerConfig().setLayoutEnabled(true).setOcrEnabled(true)) + .setFieldSchema(fieldSchema) + .setModels(models); + + if (!knowledgeSources.isEmpty()) { + analyzer.setKnowledgeSources(knowledgeSources); + } + + PollerFlux createPoller + = contentUnderstandingAsyncClient.beginCreateAnalyzer(analyzerId, analyzer, true); + + // Use reactive pattern: chain operations using flatMap + // In a real application, you would use subscribe() instead of block() + ContentAnalyzer result = createPoller.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + + System.out.println("Analyzer created: " + analyzerId); + System.out.println(" Description: " + result.getDescription()); + System.out.println(" Base analyzer: " + result.getBaseAnalyzerId()); + System.out.println(" Fields: " + result.getFieldSchema().getFields().size()); + System.out.println(" Knowledge srcs: " + + (result.getKnowledgeSources() != null ? result.getKnowledgeSources().size() : 0)); + // END: com.azure.ai.contentunderstanding.createAnalyzerWithLabelsAsync + + // BEGIN: Assertion_ContentUnderstandingCreateAnalyzerWithLabelsAsync + // Verify analyzer creation + System.out.println("\nAnalyzer Creation Verification:"); + assertNotNull(result, "Analyzer should not be null"); + assertEquals("prebuilt-document", result.getBaseAnalyzerId()); + assertEquals("Receipt analyzer with labeled training data", result.getDescription()); + assertNotNull(result.getFieldSchema()); + assertEquals("receipt_schema", result.getFieldSchema().getName()); + assertEquals(3, result.getFieldSchema().getFields().size()); + System.out.println("Analyzer created successfully"); + + // Verify field schema + Map resultFields = result.getFieldSchema().getFields(); + assertTrue(resultFields.containsKey("MerchantName"), "Should have MerchantName field"); + assertTrue(resultFields.containsKey("Items"), "Should have Items field"); + assertTrue(resultFields.containsKey("TotalPrice"), "Should have TotalPrice field"); + + ContentFieldDefinition itemsFieldResult = resultFields.get("Items"); + assertEquals(ContentFieldType.ARRAY, itemsFieldResult.getType()); + assertNotNull(itemsFieldResult.getItemDefinition()); + assertEquals(ContentFieldType.OBJECT, itemsFieldResult.getItemDefinition().getType()); + assertEquals(3, itemsFieldResult.getItemDefinition().getProperties().size()); + System.out.println("Field schema verified:"); + System.out.println(" MerchantName: String (Extract)"); + System.out.println(" Items: Array of Objects (Generate)"); + System.out.println(" - Quantity, Name, Price"); + System.out.println(" TotalPrice: String (Extract)"); + // END: Assertion_ContentUnderstandingCreateAnalyzerWithLabelsAsync + + // If training data was provided, test the analyzer with a sample document + if (trainingDataSasUrl != null && !trainingDataSasUrl.trim().isEmpty()) { + System.out.println("\nTesting analyzer with sample document..."); + String testDocUrl + = "https://github.com/Azure-Samples/cognitive-services-REST-api-samples/raw/master/curl/form-recognizer/sample-invoice.pdf"; + + AnalysisInput input = new AnalysisInput(); + input.setUrl(testDocUrl); + + PollerFlux analyzePoller + = contentUnderstandingAsyncClient.beginAnalyze(analyzerId, Arrays.asList(input)); + + // Use reactive pattern for analyze operation + AnalysisResult AnalysisResult = analyzePoller.last().flatMap(pollResponse -> { + if (pollResponse.getStatus().isComplete()) { + return pollResponse.getFinalResult(); + } else { + return Mono.error(new RuntimeException( + "Polling completed unsuccessfully with status: " + pollResponse.getStatus())); + } + }).block(); // block() is used here for testing; in production, use subscribe() + + System.out.println("Analysis completed!"); + assertNotNull(AnalysisResult); + assertNotNull(AnalysisResult.getContents()); + assertTrue(AnalysisResult.getContents().size() > 0); + + if (AnalysisResult.getContents().get(0) instanceof DocumentContent) { + DocumentContent docContent = (DocumentContent) AnalysisResult.getContents().get(0); + System.out.println("Extracted fields: " + docContent.getFields().size()); + + // Display extracted values + if (docContent.getFields().containsKey("MerchantName")) { + ContentField merchantField = docContent.getFields().get("MerchantName"); + if (merchantField != null) { + String merchantName = (String) merchantField.getValue(); + System.out.println(" MerchantName: " + merchantName); + } + } + if (docContent.getFields().containsKey("TotalPrice")) { + ContentField totalPriceFieldValue = docContent.getFields().get("TotalPrice"); + if (totalPriceFieldValue != null) { + String totalPrice = (String) totalPriceFieldValue.getValue(); + System.out.println(" TotalPrice: " + totalPrice); + } + } + } + } + + // Display API pattern information + System.out.println("\nCreateAnalyzerWithLabels API Pattern:"); + System.out.println(" 1. Define field schema with nested structures (arrays, objects)"); + System.out.println(" 2. Upload training data to Azure Blob Storage:"); + System.out.println(" - Documents: receipt1.jpg, receipt2.jpg, ..."); + System.out.println(" - Labels: receipt1.jpg.labels.json, receipt2.jpg.labels.json, ..."); + System.out.println(" - OCR: receipt1.jpg.result.json, receipt2.jpg.result.json, ..."); + System.out.println(" 3. Create LabeledDataKnowledgeSource with storage SAS URL"); + System.out.println(" 4. Create analyzer with field schema and knowledge sources"); + System.out.println(" 5. Use analyzer for document analysis"); + + System.out.println("\nCreateAnalyzerWithLabels pattern demonstration completed"); + if (trainingDataSasUrl == null || trainingDataSasUrl.trim().isEmpty()) { + System.out.println(" Note: This sample demonstrates the API pattern."); + System.out.println( + " For actual training, provide CONTENTUNDERSTANDING_TRAINING_DATA_SAS_URL with labeled data."); + } + + } finally { + // Cleanup + try { + contentUnderstandingAsyncClient.deleteAnalyzer(analyzerId).block(); + System.out.println("\nAnalyzer deleted: " + analyzerId); + } catch (Exception e) { + System.out.println("Note: Failed to delete analyzer: " + e.getMessage()); + } + } + } + +} diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample16_CreateAnalyzerWithLabelsAsyncTest.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample16_CreateAnalyzerWithLabelsAsyncTest.java index a8ff69adff65..d53f550aa4b9 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample16_CreateAnalyzerWithLabelsAsyncTest.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample16_CreateAnalyzerWithLabelsAsyncTest.java @@ -239,7 +239,7 @@ public void testCreateAnalyzerWithLabelsAsync() { = contentUnderstandingAsyncClient.beginAnalyze(analyzerId, Arrays.asList(input)); // Use reactive pattern for analyze operation - AnalysisResult analyzeResult = analyzePoller.last().flatMap(pollResponse -> { + AnalysisResult AnalysisResult = analyzePoller.last().flatMap(pollResponse -> { if (pollResponse.getStatus().isComplete()) { return pollResponse.getFinalResult(); } else { @@ -249,12 +249,12 @@ public void testCreateAnalyzerWithLabelsAsync() { }).block(); // block() is used here for testing; in production, use subscribe() System.out.println("Analysis completed!"); - assertNotNull(analyzeResult); - assertNotNull(analyzeResult.getContents()); - assertTrue(analyzeResult.getContents().size() > 0); + assertNotNull(AnalysisResult); + assertNotNull(AnalysisResult.getContents()); + assertTrue(AnalysisResult.getContents().size() > 0); - if (analyzeResult.getContents().get(0) instanceof DocumentContent) { - DocumentContent docContent = (DocumentContent) analyzeResult.getContents().get(0); + if (AnalysisResult.getContents().get(0) instanceof DocumentContent) { + DocumentContent docContent = (DocumentContent) AnalysisResult.getContents().get(0); System.out.println("Extracted fields: " + docContent.getFields().size()); // Display extracted values diff --git a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample16_CreateAnalyzerWithLabelsTest.java b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample16_CreateAnalyzerWithLabelsTest.java index 9a7813712e7c..0393e49ae812 100644 --- a/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample16_CreateAnalyzerWithLabelsTest.java +++ b/sdk/contentunderstanding/azure-ai-contentunderstanding/src/test/java/com/azure/ai/contentunderstanding/tests/samples/Sample16_CreateAnalyzerWithLabelsTest.java @@ -224,16 +224,16 @@ public void testCreateAnalyzerWithLabels() { AnalysisInput input = new AnalysisInput(); input.setUrl(testDocUrl); - AnalysisResult analyzeResult + AnalysisResult AnalysisResult = contentUnderstandingClient.beginAnalyze(analyzerId, Arrays.asList(input)).getFinalResult(); System.out.println("Analysis completed!"); - assertNotNull(analyzeResult); - assertNotNull(analyzeResult.getContents()); - assertTrue(analyzeResult.getContents().size() > 0); + assertNotNull(AnalysisResult); + assertNotNull(AnalysisResult.getContents()); + assertTrue(AnalysisResult.getContents().size() > 0); - if (analyzeResult.getContents().get(0) instanceof DocumentContent) { - DocumentContent docContent = (DocumentContent) analyzeResult.getContents().get(0); + if (AnalysisResult.getContents().get(0) instanceof DocumentContent) { + DocumentContent docContent = (DocumentContent) AnalysisResult.getContents().get(0); System.out.println("Extracted fields: " + docContent.getFields().size()); // Display extracted values