Skip to content

Commit

Permalink
Merge branch 'androidimport' into androidimport-resourcemanager_compute
Browse files Browse the repository at this point in the history
  • Loading branch information
TristanBecker authored Oct 17, 2023
2 parents 727d1b7 + c8ae5ad commit 191b9cf
Show file tree
Hide file tree
Showing 266 changed files with 7,780 additions and 654 deletions.
99 changes: 98 additions & 1 deletion sdk/android/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,109 @@ The following libraries have known issues with Android:
- `azure-core`'s `ReflectionSerializable` class also requires an external StAX dependency.

## Dependency management

- Dependency version management should use the azure BOM
- Must use OkHttp instead of Netty for http

- Recommend `azure-core` version `1.44.0` or greater. This adds behaviour to ReflectionUtils that improves Android compatibility.
- Recommend `com.fasterxml.jackson:jackson-core`, `com.fasterxml.jackson:jackson-databind`, `com.fasterxml.jackson:jackson-dataformat-xml`, `com.fasterxml.jackson:jackson-datatype-jsr310` version `2.15.0` or greater for transitive dependencies. An example of how to do this is found in the build.gradle.kts file in the android-samples app.
- The use of Jackson also requires an external StAX dependency.


### Overriding dependencies
An example [build.gradle.kts](sdk/android/azure-samples/build.gradle.kts) is included.

#### Include the BOM file

Include the `azure-sdk-bom` in your project to take a dependency on the stable version of the library. In the following snippet, replace the `{bom_version_to_target}` placeholder with the version number. To learn more about the BOM, see the [Azure SDK BOM README](https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/boms/azure-sdk-bom/README.md).

```
dependencies{
implementation(project.dependencies.platform("com.azure:azure-sdk-bom:{bom_version_to_target}"))
}
```

Then include the direct dependency in the `dependencies` section without the version tag:

```
dependencies{
implementation("com.azure:azure-storage")
}
```

#### Include direct dependency

To take dependency on a particular version of the library that isn't present in the BOM, add the direct dependency to your project as follows:

[//]: # ({x-version-update-start;com.azure:azure-core;dependency})
```
dependencies{
// azure core -- specifically declare 1.44.0 to get updated reflectionutils
implementation("com.azure:azure-core:1.44.0")
}
```
[//]: # ({x-version-update-end})

#### Overriding transitive dependencies

in build.gradle use the following structure to override jackson version
```
dependencies{
constraints {
implementation("com.fasterxml.jackson:jackson-bom") {
version {
require("2.15.2")
}
because("earlier versions declared as transitive dependencies use android non-compatible factory method")
}
implementation("com.fasterxml.jackson:jackson-core") {
version {
require("2.15.2")
}
because("earlier versions declared as transitive dependencies for azure are not android compatible")
}
implementation("com.fasterxml.jackson:jackson-databind") {
version {
require("2.15.2")
}
because("earlier versions declared as transitive dependencies for azure are not android compatible")
}
implementation("com.fasterxml.jackson:jackson-dataformat-xml") {
version {
require("2.15.2")
}
because("earlier versions declared as transitive dependencies for azure are not android compatible")
}
implementation("com.fasterxml.jackson:jackson-datatype-jsr310") {
version {
require("2.15.2")
}
because("earlier versions declared as transitive dependencies for azure are not android compatible")
}
}
}
```
#### Replacing Netty dependency with OkHttp

Exclude the netty dependency as shown in the following snippet.
```
configurations{
implementation{
exclude(group = "com.azure", module = "azure-core-http-netty")
}
}
```
Then include OkHttp in the dependencies.
```
dependencies{
implementation("com.azure:azure-core-http-okhttp")
}
```

## Credential management on Android
- The method used in the samples to pass credentials from System Environment Variables to the sample app on a device or emulator via the BuildConfig class is not suitable for production or use in real apps. There is a risk of keys being exposed, as data in BuildConfig is stored in plaintext in the APK on the device.
- The method used in the samples to pass credentials from System Environment Variables to the sample app on a device or emulator via the BuildConfig class is not suitable for production or use in real apps. There is a risk of keys being exposed, as data in BuildConfig is stored in plaintext in the APK on the device.
- Recommend using active directory as documented here:
https://learn.microsoft.com/en-us/azure/active-directory-b2c/configure-authentication-sample-android-app?tabs=kotlin

## Reporting and troubleshooting errors when using the SDK
If you encounter an error caused by the SDK that occurs in Android only, it is best to make an issue in the Azure SDK for Java repository, beginning the issue name with [ANDROID] to help distinguish it.
Expand Down
5 changes: 3 additions & 2 deletions sdk/android/azure-samples/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ dependencies {
// azure_messaging_eventhubs
implementation("com.azure:azure-messaging-eventhubs")


//azure_resourcemanager
implementation("com.azure:azure-core-management")
implementation("com.azure.resourcemanager:azure-resourcemanager-compute:2.31.0")
Expand All @@ -88,8 +89,8 @@ dependencies {
//For testing issue 35756, https://github.com/Azure/azure-sdk-for-java/issues/35756
implementation("com.azure:azure-ai-translation-text:1.0.0-beta.1")

//For testing issue 35719, https://github.com/Azure/azure-sdk-for-java/issues/35719
implementation("com.azure:azure-ai-openai:1.0.0-beta.2")
//For testing OpenAI and issue 35719, https://github.com/Azure/azure-sdk-for-java/issues/35719
implementation("com.azure:azure-ai-openai:1.0.0-beta.5")

// SL4J provides more verbose logging
/*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
package com.azure.android;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import android.util.Log;

import androidx.test.ext.junit.runners.AndroidJUnit4;

import com.azure.ai.openai.OpenAIClient;
import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.ai.openai.models.AudioTranscription;
import com.azure.ai.openai.models.AudioTranscriptionFormat;
import com.azure.ai.openai.models.AudioTranscriptionOptions;
import com.azure.ai.openai.models.AudioTranslation;
import com.azure.ai.openai.models.AudioTranslationFormat;
import com.azure.ai.openai.models.AudioTranslationOptions;
import com.azure.ai.openai.models.ChatChoice;
import com.azure.ai.openai.models.ChatCompletions;
import com.azure.ai.openai.models.ChatCompletionsOptions;
import com.azure.ai.openai.models.ChatMessage;
import com.azure.ai.openai.models.ChatRole;
import com.azure.ai.openai.models.Choice;
import com.azure.ai.openai.models.Completions;
import com.azure.ai.openai.models.CompletionsOptions;
import com.azure.ai.openai.models.CompletionsUsage;
import com.azure.ai.openai.models.EmbeddingItem;
import com.azure.ai.openai.models.Embeddings;
import com.azure.ai.openai.models.EmbeddingsOptions;
import com.azure.ai.openai.models.EmbeddingsUsage;
import com.azure.ai.openai.models.ImageGenerationOptions;
import com.azure.ai.openai.models.ImageLocation;
import com.azure.ai.openai.models.ImageResponse;
import com.azure.core.credential.AzureKeyCredential;
import com.azure.core.models.ResponseError;
import com.azure.core.util.BinaryData;

import org.junit.Test;
import org.junit.runner.RunWith;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

@RunWith(AndroidJUnit4.class)
public class OpenAISampleTests {
/**
* The following tests are Open AI samples ported directly, with assertions added to ensure
* that they will fail instrumented tests rather than simply logging errors and passing tests.
* Currently it still contains log statements in order to help verify that the samples are working, and the
* transcription and translation samples at the bottom will probably need to have their file paths changed.
*/
private final String azureOpenaiKey = "{azure-open-ai-key}";
private final String endpoint = "{azure-open-ai-endpoint}";
private final String deploymentOrModelId = "{azure-open-ai-deployment-model-id}";
OpenAIClient client = new OpenAIClientBuilder()
.endpoint(endpoint)
.credential(new AzureKeyCredential(azureOpenaiKey))
.buildClient();

@Test
public void GetImagesSampleTest() {
ImageGenerationOptions imageGenerationOptions = new ImageGenerationOptions(
"A drawing of the Seattle skyline in the style of Van Gogh");
ImageResponse images = client.getImages(imageGenerationOptions);

for (ImageLocation imageLocation : images.getData()) {
ResponseError error = imageLocation.getError();
if (error != null) {
fail(String.format("Image generation operation failed. Error code: %s, error message: %s.%n",
error.getCode(), error.getMessage()));
} else {
assertNotNull(imageLocation.getUrl());
}
}
}

@Test
public void GetEmbeddingsSampleTest() {
String TAG = "GetEmbeddingsSample";
EmbeddingsOptions embeddingsOptions = new EmbeddingsOptions(Arrays.asList("Your text string goes here"));

Embeddings embeddings = client.getEmbeddings(deploymentOrModelId, embeddingsOptions);

for (EmbeddingItem item : embeddings.getData()) {
Log.i(TAG, String.format("Index: %d.%n", item.getPromptIndex()));
for (Double embedding : item.getEmbedding()) {
assertNotNull(embedding);
}
}

EmbeddingsUsage usage = embeddings.getUsage();
assertTrue(usage.getTotalTokens() > 0);
Log.i(TAG, String.format(
"Usage: number of prompt token is %d and number of total tokens in request and response is %d.%n",
usage.getPromptTokens(), usage.getTotalTokens()));
}

@Test
public void GetCompletionsSampleTest() {
String TAG = "GetCompletionsSample";
List<String> prompt = new ArrayList<>();
prompt.add("Why did the eagles not carry Frodo Baggins to Mordor?");

Completions completions = client.getCompletions(deploymentOrModelId, new CompletionsOptions(prompt));

assertNotNull(completions.getId());
assertNotNull(completions.getCreatedAt());
Log.i(TAG, String.format("Model ID=%s is created at %s.%n", completions.getId(), completions.getCreatedAt()));
for (Choice choice : completions.getChoices()) {
assertNotNull(choice.getText());
Log.i(TAG, String.format("Index: %d, Text: %s.%n", choice.getIndex(), choice.getText()));
}

CompletionsUsage usage = completions.getUsage();
assertTrue(usage.getTotalTokens() > 0);
assertEquals(usage.getCompletionTokens() + usage.getPromptTokens(), usage.getTotalTokens());
Log.i(TAG, String.format("Usage: number of prompt token is %d, "
+ "number of completion token is %d, and number of total tokens in request and response is %d.%n",
usage.getPromptTokens(), usage.getCompletionTokens(), usage.getTotalTokens()));
}

@Test
public void GetChatCompletionsSampleTest() {
String TAG = "GetChatCompletionsSample";
List<ChatMessage> chatMessages = new ArrayList<>();
chatMessages.add(new ChatMessage(ChatRole.SYSTEM, "You are a helpful assistant. You will talk like a pirate."));
chatMessages.add(new ChatMessage(ChatRole.USER, "Can you help me?"));
chatMessages.add(new ChatMessage(ChatRole.ASSISTANT, "Of course, me hearty! What can I do for ye?"));
chatMessages.add(new ChatMessage(ChatRole.USER, "What's the best way to train a parrot?"));

ChatCompletions chatCompletions = client.getChatCompletions(deploymentOrModelId, new ChatCompletionsOptions(chatMessages));

Log.i(TAG, String.format("Model ID=%s is created at %s.%n", chatCompletions.getId(), chatCompletions.getCreatedAt()));
assertNotNull(chatCompletions.getId());
for (ChatChoice choice : chatCompletions.getChoices()) {
ChatMessage message = choice.getMessage();
assertNotNull(message);
Log.i(TAG, String.format("Index: %d, Chat Role: %s.%n", choice.getIndex(), message.getRole()));
Log.i(TAG, String.format("Message: %s", message.getContent()));
}

CompletionsUsage usage = chatCompletions.getUsage();
assertEquals(usage.getCompletionTokens() + usage.getPromptTokens(), usage.getTotalTokens());
Log.i(TAG, String.format("Usage: number of prompt token is %d, "
+ "number of completion token is %d, and number of total tokens in request and response is %d.%n",
usage.getPromptTokens(), usage.getCompletionTokens(), usage.getTotalTokens()));
}

@Test
public void AudioTranscriptionSampleTest() {
String TAG = "AudioTranscriptionSample";
String fileName = "batman.wav";
Path filePath = Paths.get("src/samples/java/com/azure/ai/openai/resources/" + fileName);

byte[] file = BinaryData.fromFile(filePath).toBytes();
AudioTranscriptionOptions transcriptionOptions = new AudioTranscriptionOptions(file)
.setResponseFormat(AudioTranscriptionFormat.JSON);

AudioTranscription transcription = client.getAudioTranscription(deploymentOrModelId, fileName, transcriptionOptions);
assertNotNull(transcription);
Log.i(TAG, "Transcription: " + transcription.getText());
}

@Test
public void AudioTranslationSampleTest() {
String TAG = "AudioTranslationSample";
String fileName = "JP_it_is_rainy_today.wav";
Path filePath = Paths.get("src/samples/java/com/azure/ai/openai/resources/" + fileName);

byte[] file = BinaryData.fromFile(filePath).toBytes();
AudioTranslationOptions translationOptions = new AudioTranslationOptions(file)
.setResponseFormat(AudioTranslationFormat.JSON);

AudioTranslation translation = client.getAudioTranslation(deploymentOrModelId, fileName, translationOptions);
assertNotNull(translation);
Log.i(TAG, "Translation: " + translation.getText());
}
}
Loading

0 comments on commit 191b9cf

Please sign in to comment.