Skip to content
Merged
Show file tree
Hide file tree
Changes from 58 commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
721a035
Early code generation from topic branch for whisper
jpalvarezl Aug 31, 2023
bce544c
Added simplest test
jpalvarezl Aug 31, 2023
98b0587
Regened with correct paths
jpalvarezl Aug 31, 2023
3e7c30f
Fixed name of method in the test
jpalvarezl Aug 31, 2023
35384fb
Added test file for translations
jpalvarezl Aug 31, 2023
8c0087e
[OpenAI] BYO Multipart form request support (#36621)
jpalvarezl Sep 7, 2023
bfd2bd8
Code regen and adjustments to new methods
jpalvarezl Sep 7, 2023
bd7f7b8
Using latest commit
jpalvarezl Sep 7, 2023
9f141ec
plain text works
jpalvarezl Sep 7, 2023
6a09621
Code gen works
jpalvarezl Sep 7, 2023
042a688
Code regen with looser types, no hooks for content-type nor length
jpalvarezl Sep 11, 2023
372fbc9
Migrated multiform implementation over from the strongly typed branch
jpalvarezl Sep 12, 2023
430ce3e
Added headers
jpalvarezl Sep 12, 2023
f86b515
Added classes
jpalvarezl Sep 12, 2023
5246435
reran code gen
jpalvarezl Sep 13, 2023
05d703e
Compiles with modded tsp defintion, including content-type
jpalvarezl Sep 13, 2023
b338c6e
Corrected wrong value passed for content-length
jpalvarezl Sep 13, 2023
b4fce21
It works!
jpalvarezl Sep 13, 2023
dd075cd
Removed pattern instanceof for older compatibility version
jpalvarezl Sep 13, 2023
cc9736c
Refactored the MultipartHelper to be testable
jpalvarezl Sep 13, 2023
d5be9d4
Added test definition for MultipartDataHelper class
jpalvarezl Sep 13, 2023
f19d690
Added happy path test and model to the list to be serialized
jpalvarezl Sep 13, 2023
b21db2b
Added tests for the MultipartDataHelper class
jpalvarezl Sep 13, 2023
d78ebb3
Refactored audio translation tests to use testRunners
jpalvarezl Sep 13, 2023
1b0b188
Added tests for miused formats
jpalvarezl Sep 13, 2023
e5b3612
Added more negative tests for wrong formats
jpalvarezl Sep 13, 2023
1417214
Renamed tests
jpalvarezl Sep 13, 2023
6ba8260
Finished Azure OAI sync test suite
jpalvarezl Sep 14, 2023
c9c4a51
Added support for nonAzure translations
jpalvarezl Sep 14, 2023
2860ce9
Added Async translation methods
jpalvarezl Sep 14, 2023
d1a7aea
Added tests and async functionality for translations
jpalvarezl Sep 14, 2023
262cdde
Async translation tests for non-Azure
jpalvarezl Sep 14, 2023
8d6bfc6
Extracted audioTranscription assertion statements to method
jpalvarezl Sep 14, 2023
9b842de
Added sync transcription functionality and AOAI tests
jpalvarezl Sep 14, 2023
5f49b14
Added license to source files
jpalvarezl Sep 14, 2023
a5ad09a
Added todo markers where docs are missing
jpalvarezl Sep 14, 2023
15ddabd
Added async implementation and minimal testing for transcription
jpalvarezl Sep 14, 2023
f299a3a
Added tests for nonAzure OAI
jpalvarezl Sep 14, 2023
1f7161b
Code regen
jpalvarezl Sep 15, 2023
1974d1e
Corrected content type for bodyParam nonAzure
jpalvarezl Sep 15, 2023
846cd6c
Added remaing transcription tests for AOAI sync case
jpalvarezl Sep 15, 2023
4d8eea3
Added tests for async AOAI
jpalvarezl Sep 15, 2023
a6af7ea
Added transcription tests for nonAzure OAI sync API
jpalvarezl Sep 15, 2023
fac7601
Added tests for nonAzure OAI async API
jpalvarezl Sep 15, 2023
5e39c7d
Commited whisper session-record changes
jpalvarezl Sep 15, 2023
70bbc20
Inlined methods
jpalvarezl Sep 15, 2023
5b75b7e
Added documentation to sync/async client for translation and transcri…
jpalvarezl Sep 15, 2023
88406cd
Added documentation to multipart helper classes
jpalvarezl Sep 15, 2023
fbbaea7
Replaced start imports with single class imports
jpalvarezl Sep 15, 2023
38078fb
Simplified tests and added logger to async client
jpalvarezl Sep 18, 2023
2bc1976
Added missing asset
jpalvarezl Sep 18, 2023
1c48b63
Added recordings for nonAzure tests
jpalvarezl Sep 18, 2023
4383d7a
Style checks
jpalvarezl Sep 18, 2023
3578e4c
Style check
jpalvarezl Sep 18, 2023
76f640e
Style check
jpalvarezl Sep 18, 2023
691080d
Style check done
jpalvarezl Sep 18, 2023
398b702
Changelog update and static bug analysis issues addressed
jpalvarezl Sep 18, 2023
ae71931
Last 2 replacement of monoError
jpalvarezl Sep 18, 2023
47e345b
[OpenAI] Added sample and updated READMEs (#36806)
mssfang Sep 18, 2023
6306060
suppression spotbugs for allowing external mutation on the bytep[ (#3…
mssfang Sep 18, 2023
9b6fdd1
Merge branch 'main' into jpalvarezl/whisper_support_looser_types
mssfang Sep 18, 2023
40dcaff
fixed unknown cspell error, 'mpga'
mssfang Sep 19, 2023
4af1ba5
fixed sample broken links
mssfang Sep 19, 2023
65e4ee4
regenerated, no changes but only indents alignment
mssfang Sep 19, 2023
c1fb921
Hardcoded boundary value for multipart requests
jpalvarezl Sep 19, 2023
a5f32d9
Updated test records for nonAzure
jpalvarezl Sep 19, 2023
bcb6fae
Most test passing with latest service version
jpalvarezl Sep 19, 2023
04a3201
Rolled back test records for regressed tests
jpalvarezl Sep 19, 2023
8773677
Removed unused import
jpalvarezl Sep 19, 2023
6bafc2e
Re-ordered method parameters. Options bag is last
jpalvarezl Sep 19, 2023
8f4e36a
Readme update
jpalvarezl Sep 19, 2023
5a9b295
[OpenAI] Improve JavaDoc and compatible with JDK 21 (#36846)
mssfang Sep 19, 2023
8b6fca8
removed export implementaion/model
mssfang Sep 19, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions sdk/openai/azure-ai-openai/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

### Features Added

- Support for `Whisper` endpoints was added.
- Translation and Transcription of audio files is available
- The above features are available both in Azure and non-Azure OpenAI

### Breaking Changes

### Bugs Fixed
Expand Down
2 changes: 1 addition & 1 deletion sdk/openai/azure-ai-openai/assets.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "java",
"TagPrefix": "java/openai/azure-ai-openai",
"Tag": "java/openai/azure-ai-openai_57107e7a09"
"Tag": "java/openai/azure-ai-openai_298c639caa"
}

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ public enum OpenAIServiceVersion implements ServiceVersion {
V2023_07_01_PREVIEW("2023-07-01-preview"),

/** Enum value 2023-08-01-preview. */
V2023_08_01_PREVIEW("2023-08-01-preview");
V2023_08_01_PREVIEW("2023-08-01-preview"),

/** Enum value 2023-09-01-preview. */
V2023_09_01_PREVIEW("2023-09-01-preview");

private final String version;

Expand All @@ -41,6 +44,6 @@ public String getVersion() {
* @return The latest {@link OpenAIServiceVersion}.
*/
public static OpenAIServiceVersion getLatest() {
return V2023_08_01_PREVIEW;
return V2023_09_01_PREVIEW;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.ai.openai.implementation;

/**
* Interface implemented by classes that would generate a boundary string to use in multipart type of requests.
* The main purpose of this class is to allow to mock behaviour for tests
*/
public interface MultipartBoundaryGenerator {

/**
* Generates a new multipart boundary value each time the method is called
* @return a {@link String} value containing a boundary to be used in HTTP multipart requests
*/
String generateBoundary();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.ai.openai.implementation;

import com.azure.ai.openai.models.AudioTranscriptionOptions;
import com.azure.ai.openai.models.AudioTranslationOptions;
import com.azure.core.util.BinaryData;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
* Helper class for marshaling {@link AudioTranscriptionOptions} and {@link AudioTranslationOptions} objects to be used
* in multipart HTTP requests according to RFC7578.
*/
public class MultipartDataHelper {

/**
* Value to be used as part of the divider for the multipart requests.
*/
private final String boundary;

/**
* The actual part separator in the request. This is obtained by prepending "--" to the "boundary".
*/
private final String partSeparator;

/**
* The marker for the ending of a multipart request. This is obtained by post-pending "--" to the "partSeparator".
*/
private final String endMarker;

/**
* Charset used for encoding the multipart HTTP request.
*/
private final Charset encoderCharset = StandardCharsets.UTF_8;

/**
* Line separator for the multipart HTTP request.
*/
private static final String CRLF = "\r\n";

/**
* Default constructor used in the code. The boundary is a random value.
*/
public MultipartDataHelper() {
this(() -> UUID.randomUUID().toString().substring(0, 16));
}

/**
* Constructor accepting a boundary generator. Used for testing.
* @param boundaryGenerator Generates the value for "boundary".
*/
public MultipartDataHelper(MultipartBoundaryGenerator boundaryGenerator) {
this.boundary = boundaryGenerator.generateBoundary();
partSeparator = "--" + boundary;
endMarker = partSeparator + "--";
}

/**
*
* @return the "boundary" value.
*/
public String getBoundary() {
return boundary;
}

/**
* This methods marshals the passed request into ready to be sent
* @param requestOptions object to be marshalled for the multipart HTTP request
* @param fileName the name of the file that is being sent as a part of this request
* @return the marshalled data and its length
* @param <T> {@link AudioTranscriptionOptions} and {@link AudioTranslationOptions} are the only types supported.
* This represents the type information of the request object.
*/
public <T> MultipartDataSerializationResult serializeRequest(T requestOptions, String fileName) {
if (requestOptions instanceof AudioTranslationOptions) {
AudioTranslationOptions audioTranslationOptions = (AudioTranslationOptions) requestOptions;
byte[] file = audioTranslationOptions.getFile();
List<MultipartField> fields = formatAudioTranslationOptions(audioTranslationOptions);
return serializeRequestFields(file, fields, fileName);
} else if (requestOptions instanceof AudioTranscriptionOptions) {
AudioTranscriptionOptions audioTranscriptionOptions = (AudioTranscriptionOptions) requestOptions;
byte[] file = audioTranscriptionOptions.getFile();
List<MultipartField> fields = formatAudioTranscriptionOptions(audioTranscriptionOptions);
return serializeRequestFields(file, fields, fileName);
} else {
throw new IllegalArgumentException("Only AudioTranslationOptions and AudioTranscriptionOptions currently supported");
}
}

/**
*
* @param file is the byte[] representation of the file in the request object.
* @param fields a list of the members other than the file in the request object.
* @param fileName the name of the file passed in the "file" field of the request object.
* @return a structure containing the marshalled data and its length.
*/
private MultipartDataSerializationResult serializeRequestFields(byte[] file, List<MultipartField> fields, String fileName) {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

// Multipart preamble
String fileFieldPreamble = partSeparator
+ CRLF + "Content-Disposition: form-data; name=\"file\"; filename=\""
+ fileName + "\""
+ CRLF + "Content-Type: application/octet-stream" + CRLF + CRLF;
try {
// Writing the file into the request as a byte stream
byteArrayOutputStream.write(fileFieldPreamble.getBytes(encoderCharset));
byteArrayOutputStream.write(file);

// Adding other fields to the request
for (MultipartField field : fields) {
byteArrayOutputStream.write(serializeField(field));
}
byteArrayOutputStream.write((CRLF + endMarker).getBytes(encoderCharset));
} catch (IOException e) {
throw new RuntimeException(e);
}

byte[] totalData = byteArrayOutputStream.toByteArray();
return new MultipartDataSerializationResult(BinaryData.fromBytes(totalData), totalData.length);
}

/**
* Adds member fields apart from the file to the multipart HTTP request
* @param audioTranslationOptions request object
* @return a list of the fields in the request (except for "file")
*/
private List<MultipartField> formatAudioTranslationOptions(AudioTranslationOptions audioTranslationOptions) {
List<MultipartField> fields = new ArrayList<>();
if (audioTranslationOptions.getResponseFormat() != null) {
fields.add(new MultipartField(
"response_format",
audioTranslationOptions.getResponseFormat().toString()));
}
if (audioTranslationOptions.getModel() != null) {
fields.add(new MultipartField("model",
audioTranslationOptions.getModel()
));
}
if (audioTranslationOptions.getPrompt() != null) {
fields.add(new MultipartField("prompt",
audioTranslationOptions.getPrompt()));
}
if (audioTranslationOptions.getTemperature() != null) {
fields.add(new MultipartField("temperature",
String.valueOf(audioTranslationOptions.getTemperature())));
}
return fields;
}

/**
* Adds member fields apart from the file to the multipart HTTP request
* @param audioTranscriptionOptions request object
* @return a list of the fields in the request (except for "file")
*/
private List<MultipartField> formatAudioTranscriptionOptions(AudioTranscriptionOptions audioTranscriptionOptions) {
List<MultipartField> fields = new ArrayList<>();
if (audioTranscriptionOptions.getResponseFormat() != null) {
fields.add(new MultipartField("response_format",
audioTranscriptionOptions.getResponseFormat().toString()));
}
if (audioTranscriptionOptions.getModel() != null) {
fields.add(new MultipartField("model",
audioTranscriptionOptions.getModel()
));
}
if (audioTranscriptionOptions.getPrompt() != null) {
fields.add(new MultipartField("prompt",
audioTranscriptionOptions.getPrompt()));
}
if (audioTranscriptionOptions.getTemperature() != null) {
fields.add(new MultipartField("temperature",
String.valueOf(audioTranscriptionOptions.getTemperature())));
}
if (audioTranscriptionOptions.getLanguage() != null) {
fields.add(new MultipartField("language",
audioTranscriptionOptions.getLanguage()));
}
return fields;
}

/**
* This method formats a field for a multipart HTTP request and returns its byte[] representation
* @param field the field of the request to be marshalled
* @return byte[] representation of a field for a multipart HTTP request
*/
private byte[] serializeField(MultipartField field) {
String serialized = CRLF + partSeparator
+ CRLF + "Content-Disposition: form-data; name=\""
+ field.getWireName() + "\"" + CRLF + CRLF
+ field.getValue();

return serialized.getBytes(encoderCharset);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.ai.openai.implementation;

import com.azure.core.util.BinaryData;

/**
* This class is used as a stand-in representation of marshalled data to be used in an HTTP multipart request.
*/
public class MultipartDataSerializationResult {

/**
* Represents the length of the content of this request. The value is to be used for the "Content-Length" header
* of the HTTP request
*/
private final long dataLength;

/**
* The multipart form data of the request.
*/
private final BinaryData data;

/**
* Constructor bundling both data and its length
* @param data the multipart form data of the request
* @param contentLength the length of the multipart form data of the request
*/
public MultipartDataSerializationResult(BinaryData data, long contentLength) {
this.dataLength = contentLength;
this.data = data;
}

/**
*
* @return the result of marshaling a multipart HTTP request
*/
public BinaryData getData() {
return data;
}

/**
*
* @return the length of a multipart HTTP request data
*/
public long getDataLength() {
return dataLength;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.ai.openai.implementation;

/**
* A field of a request for a multipart HTTP request.
*/
public class MultipartField {

/**
* The JSON key name of this field.
*/
private final String wireName;

/**
* The JSON value of this field.
*/
private final String value;

/**
*
* @param wireName The JSON key name of this field.
* @param value The JSON value of this field.
*/
public MultipartField(String wireName, String value) {
this.wireName = wireName;
this.value = value;
}

/**
*
* @return The JSON key name of this field.
*/
public String getWireName() {
return wireName;
}

/**
*
* @return The JSON value of this field.
*/
public String getValue() {
return value;
}
}
Loading