diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI/Connectors.AzureOpenAI.csproj b/dotnet/src/Connectors/Connectors.AzureOpenAI/Connectors.AzureOpenAI.csproj index 35c31788610d..73bba3cb28f6 100644 --- a/dotnet/src/Connectors/Connectors.AzureOpenAI/Connectors.AzureOpenAI.csproj +++ b/dotnet/src/Connectors/Connectors.AzureOpenAI/Connectors.AzureOpenAI.csproj @@ -21,10 +21,20 @@ Semantic Kernel connectors for Azure OpenAI. Contains clients for text generation, chat completion, embedding and DALL-E text to image. + + + + + + + + + + diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI/Core/ClientCore.TextToAudio.cs b/dotnet/src/Connectors/Connectors.AzureOpenAI/Core/ClientCore.TextToAudio.cs new file mode 100644 index 000000000000..d11b5ce81a26 --- /dev/null +++ b/dotnet/src/Connectors/Connectors.AzureOpenAI/Core/ClientCore.TextToAudio.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.ClientModel; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using OpenAI.Audio; + +namespace Microsoft.SemanticKernel.Connectors.AzureOpenAI; + +/// +/// Base class for AI clients that provides common functionality for interacting with Azure OpenAI services. +/// +internal partial class ClientCore +{ + /// + /// Generates an image with the provided configuration. + /// + /// Prompt to generate the image + /// Text to Audio execution settings for the prompt + /// The to monitor for cancellation requests. The default is . + /// Url of the generated image + internal async Task> GetAudioContentsAsync( + string prompt, + PromptExecutionSettings? executionSettings, + CancellationToken cancellationToken) + { + Verify.NotNullOrWhiteSpace(prompt); + + OpenAITextToAudioExecutionSettings? audioExecutionSettings = OpenAITextToAudioExecutionSettings.FromExecutionSettings(executionSettings); + var (responseFormat, mimeType) = GetGeneratedSpeechFormatAndMimeType(audioExecutionSettings?.ResponseFormat); + SpeechGenerationOptions options = new() + { + ResponseFormat = responseFormat, + Speed = audioExecutionSettings?.Speed, + }; + + ClientResult response = await RunRequestAsync(() => this.Client.GetAudioClient(this.ModelId).GenerateSpeechFromTextAsync(prompt, GetGeneratedSpeechVoice(audioExecutionSettings?.Voice), options, cancellationToken)).ConfigureAwait(false); + + return [new AudioContent(response.Value.ToArray(), mimeType)]; + } + + private static GeneratedSpeechVoice GetGeneratedSpeechVoice(string? voice) + => voice?.ToUpperInvariant() switch + { + "ALLOY" => GeneratedSpeechVoice.Alloy, + "ECHO" => GeneratedSpeechVoice.Echo, + "FABLE" => GeneratedSpeechVoice.Fable, + "ONYX" => GeneratedSpeechVoice.Onyx, + "NOVA" => GeneratedSpeechVoice.Nova, + "SHIMMER" => GeneratedSpeechVoice.Shimmer, + _ => throw new NotSupportedException($"The voice '{voice}' is not supported."), + }; + + private static (GeneratedSpeechFormat Format, string MimeType) GetGeneratedSpeechFormatAndMimeType(string? format) + => format?.ToUpperInvariant() switch + { + "WAV" => (GeneratedSpeechFormat.Wav, "audio/wav"), + "MP3" => (GeneratedSpeechFormat.Mp3, "audio/mpeg"), + "OPUS" => (GeneratedSpeechFormat.Opus, "audio/opus"), + "FLAC" => (GeneratedSpeechFormat.Flac, "audio/flac"), + "AAC" => (GeneratedSpeechFormat.Aac, "audio/aac"), + "PCM" => (GeneratedSpeechFormat.Pcm, "audio/l16"), + _ => throw new NotSupportedException($"The format '{format}' is not supported.") + }; +} diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI/Services/AzureOpenAITextToAudioService.cs b/dotnet/src/Connectors/Connectors.AzureOpenAI/Services/AzureOpenAITextToAudioService.cs new file mode 100644 index 000000000000..5dd70f6fd38f --- /dev/null +++ b/dotnet/src/Connectors/Connectors.AzureOpenAI/Services/AzureOpenAITextToAudioService.cs @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.SemanticKernel.Services; +using Microsoft.SemanticKernel.TextToAudio; + +namespace Microsoft.SemanticKernel.Connectors.AzureOpenAI; + +/// +/// Azure OpenAI text-to-audio service. +/// +[Experimental("SKEXP0001")] +public sealed class AzureOpenAITextToAudioService : ITextToAudioService +{ + /// + /// Azure OpenAI text-to-audio client for HTTP operations. + /// + private readonly AzureOpenAITextToAudioClient _client; + + /// + public IReadOnlyDictionary Attributes => this._client.Attributes; + + /// + /// Gets the key used to store the deployment name in the dictionary. + /// + public static string DeploymentNameKey => "DeploymentName"; + + /// + /// Creates an instance of the connector with API key auth. + /// + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// Azure OpenAI API key, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// Azure OpenAI model id, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Custom for HTTP requests. + /// The to use for logging. If null, no logging will be performed. + public AzureOpenAITextToAudioService( + string deploymentName, + string endpoint, + string apiKey, + string? modelId = null, + HttpClient? httpClient = null, + ILoggerFactory? loggerFactory = null) + { + this._client = new(deploymentName, endpoint, apiKey, modelId, httpClient, loggerFactory?.CreateLogger(typeof(AzureOpenAITextToAudioService))); + + this._client.AddAttribute(DeploymentNameKey, deploymentName); + this._client.AddAttribute(AIServiceExtensions.ModelIdKey, modelId); + } + + /// + public Task> GetAudioContentsAsync( + string text, + PromptExecutionSettings? executionSettings = null, + Kernel? kernel = null, + CancellationToken cancellationToken = default) + => this._client.GetAudioContentsAsync(text, executionSettings, cancellationToken); +}