Skip to content

Commit

Permalink
add audio endpoint
Browse files Browse the repository at this point in the history
refactor string enum conversion factory
added a way to enable debugging of json payload content
  • Loading branch information
StephenHodgson committed Nov 7, 2023
1 parent e69beca commit 34b0d8f
Show file tree
Hide file tree
Showing 22 changed files with 268 additions and 193 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -339,3 +339,4 @@ ASALocalRun/
# BeatPulse healthcheck temp database
healthchecksdb
.vscode
OpenAI-DotNet-Tests/Assets/HelloWorld.mp3
20 changes: 18 additions & 2 deletions OpenAI-DotNet-Tests/TestFixture_07_Audio.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ internal class TestFixture_07_Audio : AbstractTestFixture
public async Task Test_1_Transcription()
{
Assert.IsNotNull(OpenAIClient.AudioEndpoint);
var transcriptionAudio = Path.GetFullPath("..\\..\\..\\Assets\\T3mt39YrlyLoq8laHSdf.mp3");
var transcriptionAudio = Path.GetFullPath(@"..\..\..\Assets\T3mt39YrlyLoq8laHSdf.mp3");
using var request = new AudioTranscriptionRequest(transcriptionAudio, temperature: 0.1f, language: "en");
var result = await OpenAIClient.AudioEndpoint.CreateTranscriptionAsync(request);
Assert.IsNotNull(result);
Expand All @@ -23,11 +23,27 @@ public async Task Test_1_Transcription()
public async Task Test_2_Translation()
{
Assert.IsNotNull(OpenAIClient.AudioEndpoint);
var translationAudio = Path.GetFullPath("..\\..\\..\\Assets\\Ja-botchan_1-1_1-2.mp3");
var translationAudio = Path.GetFullPath(@"..\..\..\Assets\Ja-botchan_1-1_1-2.mp3");
using var request = new AudioTranslationRequest(Path.GetFullPath(translationAudio));
var result = await OpenAIClient.AudioEndpoint.CreateTranslationAsync(request);
Assert.IsNotNull(result);
Console.WriteLine(result);
}

[Test]
public async Task Test_3_Speech()
{
Assert.IsNotNull(OpenAIClient.AudioEndpoint);
var request = new SpeechRequest("Hello World!");
async Task ChunkCallback(ReadOnlyMemory<byte> chunkCallback)
{
Assert.IsFalse(chunkCallback.IsEmpty);
await Task.CompletedTask;
}

var result = await OpenAIClient.AudioEndpoint.CreateSpeechAsync(request, ChunkCallback);
Assert.IsFalse(result.IsEmpty);
await File.WriteAllBytesAsync(@"..\..\..\Assets\HelloWorld.mp3", result.ToArray());
}
}
}
43 changes: 42 additions & 1 deletion OpenAI-DotNet/Audio/AudioEndpoint.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using OpenAI.Extensions;
using System;
using OpenAI.Extensions;
using System.IO;
using System.Net.Http;
using System.Text.Json;
Expand Down Expand Up @@ -31,6 +32,46 @@ public AudioEndpoint(OpenAIClient api) : base(api) { }
/// <inheritdoc />
protected override string Root => "audio";

/// <summary>
/// Generates audio from the input text.
/// </summary>
/// <param name="request"><see cref="SpeechRequest"/>.</param>
/// <param name="chunkCallback">Optional, partial chunk <see cref="ReadOnlyMemory{T}"/> callback to stream audio as it arrives.</param>
/// <param name="cancellationToken">Optional, <see cref="CancellationToken"/>.</param>
/// <returns><see cref="ReadOnlyMemory{T}"/></returns>
public async Task<ReadOnlyMemory<byte>> CreateSpeechAsync(SpeechRequest request, Func<ReadOnlyMemory<byte>, Task> chunkCallback = null, CancellationToken cancellationToken = default)
{
var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
var response = await Api.Client.PostAsync(GetUrl("/speech"), jsonContent, cancellationToken).ConfigureAwait(false);
await response.CheckResponseAsync(cancellationToken).ConfigureAwait(false);
await using var responseStream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
await using var memoryStream = new MemoryStream();
int bytesRead;
var totalBytesRead = 0;
var buffer = new byte[8192];

while ((bytesRead = await responseStream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) > 0)
{
await memoryStream.WriteAsync(new ReadOnlyMemory<byte>(buffer, 0, bytesRead), cancellationToken).ConfigureAwait(false);

if (chunkCallback != null)
{
try
{
await chunkCallback(new ReadOnlyMemory<byte>(memoryStream.GetBuffer(), totalBytesRead, bytesRead)).ConfigureAwait(false);
}
catch (Exception e)
{
Console.WriteLine(e);
}
}

totalBytesRead += bytesRead;
}

return new ReadOnlyMemory<byte>(memoryStream.GetBuffer(), 0, totalBytesRead);
}

/// <summary>
/// Transcribes audio into the input language.
/// </summary>
Expand Down
43 changes: 43 additions & 0 deletions OpenAI-DotNet/Audio/SpeechRequest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Text.Json.Serialization;
using OpenAI.Extensions;
using OpenAI.Models;

namespace OpenAI.Audio
{
public sealed class SpeechRequest
{
/// <summary>
/// Constructor.
/// </summary>
/// <param name="input">The text to generate audio for. The maximum length is 4096 characters.</param>
/// <param name="model">One of the available TTS models. Defaults to tts-1.</param>
/// <param name="voice">The voice to use when generating the audio.</param>
/// <param name="responseFormat">The format to audio in. Supported formats are mp3, opus, aac, and flac.</param>
/// <param name="speed">The speed of the generated audio. Select a value from 0.25 to 4.0. 1.0 is the default.</param>
public SpeechRequest(string input, Model model = null, SpeechVoice voice = SpeechVoice.Alloy, SpeechResponseFormat responseFormat = SpeechResponseFormat.MP3, float? speed = null)
{
Input = input;
Model = string.IsNullOrWhiteSpace(model?.Id) ? Models.Model.TTS_1 : model;
Voice = voice;
ResponseFormat = responseFormat;
Speed = speed;
}

[JsonPropertyName("model")]
public string Model { get; }

[JsonPropertyName("input")]
public string Input { get; }

[JsonPropertyName("voice")]
public SpeechVoice Voice { get; }

[JsonPropertyName("response_format")]
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
[JsonConverter(typeof(JsonStringEnumConverter<SpeechResponseFormat>))]
public SpeechResponseFormat ResponseFormat { get; }

[JsonPropertyName("speed")]
public float? Speed { get; }
}
}
16 changes: 16 additions & 0 deletions OpenAI-DotNet/Audio/SpeechResponseFormat.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Runtime.Serialization;

namespace OpenAI.Audio
{
public enum SpeechResponseFormat
{
[EnumMember(Value = "mp3")]
MP3,
[EnumMember(Value = "opus")]
Opus,
[EnumMember(Value = "aac")]
AAC,
[EnumMember(Value = "flac")]
Flac
}
}
12 changes: 12 additions & 0 deletions OpenAI-DotNet/Audio/SpeechVoice.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace OpenAI.Audio
{
public enum SpeechVoice
{
Alloy = 0,
Echo,
Fable,
Onyx,
Nova,
Shimmer
}
}
6 changes: 3 additions & 3 deletions OpenAI-DotNet/Chat/ChatEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public ChatEndpoint(OpenAIClient api) : base(api) { }
/// <returns><see cref="ChatResponse"/>.</returns>
public async Task<ChatResponse> GetCompletionAsync(ChatRequest chatRequest, CancellationToken cancellationToken = default)
{
var jsonContent = JsonSerializer.Serialize(chatRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent();
var jsonContent = JsonSerializer.Serialize(chatRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
var response = await Api.Client.PostAsync(GetUrl("/completions"), jsonContent, cancellationToken).ConfigureAwait(false);
var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
return response.DeserializeResponse<ChatResponse>(responseAsString, OpenAIClient.JsonSerializationOptions);
Expand All @@ -47,7 +47,7 @@ public async Task<ChatResponse> GetCompletionAsync(ChatRequest chatRequest, Canc
public async Task<ChatResponse> StreamCompletionAsync(ChatRequest chatRequest, Action<ChatResponse> resultHandler, CancellationToken cancellationToken = default)
{
chatRequest.Stream = true;
var jsonContent = JsonSerializer.Serialize(chatRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent();
var jsonContent = JsonSerializer.Serialize(chatRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
using var request = new HttpRequestMessage(HttpMethod.Post, GetUrl("/completions"));
request.Content = jsonContent;
var response = await Api.Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -98,7 +98,7 @@ public async Task<ChatResponse> StreamCompletionAsync(ChatRequest chatRequest, A
public async IAsyncEnumerable<ChatResponse> StreamCompletionEnumerableAsync(ChatRequest chatRequest, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
chatRequest.Stream = true;
var jsonContent = JsonSerializer.Serialize(chatRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent();
var jsonContent = JsonSerializer.Serialize(chatRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
using var request = new HttpRequestMessage(HttpMethod.Post, GetUrl("/completions"));
request.Content = jsonContent;
var response = await Api.Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
Expand Down
12 changes: 3 additions & 9 deletions OpenAI-DotNet/Chat/Role.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
using System.Runtime.Serialization;

namespace OpenAI.Chat
{
public enum Role
{
[EnumMember(Value = "system")]
System = 1,
[EnumMember(Value = "assistant")]
Assistant = 2,
[EnumMember(Value = "user")]
User = 3,
[EnumMember(Value = "function")]
Function = 4,
Assistant,
User,
Function,
}
}
6 changes: 3 additions & 3 deletions OpenAI-DotNet/Completions/CompletionsEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public async Task<CompletionResult> CreateCompletionAsync(
public async Task<CompletionResult> CreateCompletionAsync(CompletionRequest completionRequest, CancellationToken cancellationToken = default)
{
completionRequest.Stream = false;
var jsonContent = JsonSerializer.Serialize(completionRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent();
var jsonContent = JsonSerializer.Serialize(completionRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
var response = await Api.Client.PostAsync(GetUrl(), jsonContent, cancellationToken).ConfigureAwait(false);
var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
return response.DeserializeResponse<CompletionResult>(responseAsString, OpenAIClient.JsonSerializationOptions);
Expand Down Expand Up @@ -198,7 +198,7 @@ public async Task StreamCompletionAsync(
public async Task StreamCompletionAsync(CompletionRequest completionRequest, Action<CompletionResult> resultHandler, CancellationToken cancellationToken = default)
{
completionRequest.Stream = true;
var jsonContent = JsonSerializer.Serialize(completionRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent();
var jsonContent = JsonSerializer.Serialize(completionRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
using var request = new HttpRequestMessage(HttpMethod.Post, GetUrl());
request.Content = jsonContent;
var response = await Api.Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -306,7 +306,7 @@ public IAsyncEnumerable<CompletionResult> StreamCompletionEnumerableAsync(
public async IAsyncEnumerable<CompletionResult> StreamCompletionEnumerableAsync(CompletionRequest completionRequest, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
completionRequest.Stream = true;
var jsonContent = JsonSerializer.Serialize(completionRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent();
var jsonContent = JsonSerializer.Serialize(completionRequest, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
using var request = new HttpRequestMessage(HttpMethod.Post, GetUrl());
request.Content = jsonContent;
var response = await Api.Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
Expand Down
2 changes: 1 addition & 1 deletion OpenAI-DotNet/Edits/EditsEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public async Task<string> CreateEditAsync(
/// <returns><see cref="EditResponse"/></returns>
public async Task<EditResponse> CreateEditAsync(EditRequest request, CancellationToken cancellationToken = default)
{
var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent();
var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
var response = await Api.Client.PostAsync(GetUrl(), jsonContent, cancellationToken).ConfigureAwait(false);
var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
return response.DeserializeResponse<EditResponse>(responseAsString, OpenAIClient.JsonSerializationOptions);
Expand Down
2 changes: 1 addition & 1 deletion OpenAI-DotNet/Embeddings/EmbeddingsEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public async Task<EmbeddingsResponse> CreateEmbeddingAsync(IEnumerable<string> i
/// <returns><see cref="EmbeddingsResponse"/></returns>
public async Task<EmbeddingsResponse> CreateEmbeddingAsync(EmbeddingsRequest request, CancellationToken cancellationToken = default)
{
var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent();
var jsonContent = JsonSerializer.Serialize(request, OpenAIClient.JsonSerializationOptions).ToJsonStringContent(EnableDebug);
var response = await Api.Client.PostAsync(GetUrl(), jsonContent, cancellationToken).ConfigureAwait(false);
var responseAsString = await response.ReadAsStringAsync(EnableDebug, cancellationToken).ConfigureAwait(false);
return response.DeserializeResponse<EmbeddingsResponse>(responseAsString, OpenAIClient.JsonSerializationOptions);
Expand Down
125 changes: 0 additions & 125 deletions OpenAI-DotNet/Extensions/CustomEnumConverter.cs

This file was deleted.

Loading

0 comments on commit 34b0d8f

Please sign in to comment.