Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@
</PropertyGroup>

<ItemGroup>
<Compile Remove="Core\ClientCore.AudioToText.cs" />
<Compile Remove="Core\ClientCore.TextToAudio.cs" />
<Compile Remove="Services\AzureOpenAIAudioToTextService.cs" />
<Compile Remove="Services\AzureOpenAITextToAudioService.cs" />
</ItemGroup>

Expand All @@ -31,7 +33,9 @@
</ItemGroup>

<ItemGroup>
<None Include="Core\ClientCore.AudioToText.cs" />
<None Include="Core\ClientCore.TextToAudio.cs" />
<None Include="Services\AzureOpenAIAudioToTextService.cs" />
<None Include="Services\AzureOpenAITextToAudioService.cs" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright (c) Microsoft. All rights reserved.

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using OpenAI.Audio;

namespace Microsoft.SemanticKernel.Connectors.AzureOpenAI;

/// <summary>
/// Base class for AI clients that provides common functionality for interacting with Azure OpenAI services.
/// </summary>
internal partial class ClientCore
{
/// <summary>
/// Generates an image with the provided configuration.
/// </summary>
/// <param name="input">Input audio to generate the text</param>
/// <param name="executionSettings">Audio-to-text execution settings for the prompt</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests. The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>Url of the generated image</returns>
internal async Task<IReadOnlyList<TextContent>> GetTextFromAudioContentsAsync(
AudioContent input,
PromptExecutionSettings? executionSettings,
CancellationToken cancellationToken)
{
if (!input.CanRead)
{
throw new ArgumentException("The input audio content is not readable.", nameof(input));
}

OpenAIAudioToTextExecutionSettings audioExecutionSettings = OpenAIAudioToTextExecutionSettings.FromExecutionSettings(executionSettings)!;
AudioTranscriptionOptions? audioOptions = AudioOptionsFromExecutionSettings(audioExecutionSettings);

Verify.ValidFilename(audioExecutionSettings?.Filename);

using var memoryStream = new MemoryStream(input.Data!.Value.ToArray());

AudioTranscription responseData = (await RunRequestAsync(() => this.Client.GetAudioClient(this.ModelId).TranscribeAudioAsync(memoryStream, audioExecutionSettings?.Filename, audioOptions)).ConfigureAwait(false)).Value;

return [new(responseData.Text, this.ModelId, metadata: GetResponseMetadata(responseData))];
}

/// <summary>
/// Converts <see cref="PromptExecutionSettings"/> to <see cref="AudioTranscriptionOptions"/> type.
/// </summary>
/// <param name="executionSettings">Instance of <see cref="PromptExecutionSettings"/>.</param>
/// <returns>Instance of <see cref="AudioTranscriptionOptions"/>.</returns>
private static AudioTranscriptionOptions? AudioOptionsFromExecutionSettings(OpenAIAudioToTextExecutionSettings executionSettings)
=> new()
{
Granularities = ConvertToAudioTimestampGranularities(executionSettings!.Granularities),
Language = executionSettings.Language,
Prompt = executionSettings.Prompt,
Temperature = executionSettings.Temperature
};

private static AudioTimestampGranularities ConvertToAudioTimestampGranularities(IEnumerable<OpenAIAudioToTextExecutionSettings.TimeStampGranularities>? granularities)
{
AudioTimestampGranularities result = AudioTimestampGranularities.Default;

if (granularities is not null)
{
foreach (var granularity in granularities)
{
var openAIGranularity = granularity switch
{
OpenAIAudioToTextExecutionSettings.TimeStampGranularities.Word => AudioTimestampGranularities.Word,
OpenAIAudioToTextExecutionSettings.TimeStampGranularities.Segment => AudioTimestampGranularities.Segment,
_ => AudioTimestampGranularities.Default
};

result |= openAIGranularity;
}
}

return result;
}

private static Dictionary<string, object?> GetResponseMetadata(AudioTranscription audioTranscription)
=> new(3)
{
[nameof(audioTranscription.Language)] = audioTranscription.Language,
[nameof(audioTranscription.Duration)] = audioTranscription.Duration,
[nameof(audioTranscription.Segments)] = audioTranscription.Segments
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// 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 Azure.AI.OpenAI;
using Azure.Core;
using Microsoft.Extensions.Logging;
using Microsoft.SemanticKernel.AudioToText;
using Microsoft.SemanticKernel.Services;

namespace Microsoft.SemanticKernel.Connectors.AzureOpenAI;

/// <summary>
/// Azure OpenAI audio-to-text service.
/// </summary>
[Experimental("SKEXP0001")]
public sealed class AzureOpenAIAudioToTextService : IAudioToTextService
{
/// <summary>Core implementation shared by Azure OpenAI services.</summary>
private readonly AzureOpenAIClientCore _core;

/// <inheritdoc/>
public IReadOnlyDictionary<string, object?> Attributes => this._core.Attributes;

/// <summary>
/// Creates an instance of the <see cref="AzureOpenAIAudioToTextService"/> with API key auth.
/// </summary>
/// <param name="deploymentName">Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource</param>
/// <param name="endpoint">Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart</param>
/// <param name="apiKey">Azure OpenAI API key, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart</param>
/// <param name="modelId">Azure OpenAI model id, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource</param>
/// <param name="httpClient">Custom <see cref="HttpClient"/> for HTTP requests.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> to use for logging. If null, no logging will be performed.</param>
public AzureOpenAIAudioToTextService(
string deploymentName,
string endpoint,
string apiKey,
string? modelId = null,
HttpClient? httpClient = null,
ILoggerFactory? loggerFactory = null)
{
this._core = new(deploymentName, endpoint, apiKey, httpClient, loggerFactory?.CreateLogger(typeof(AzureOpenAIAudioToTextService)));
this._core.AddAttribute(AIServiceExtensions.ModelIdKey, modelId);
}

/// <summary>
/// Creates an instance of the <see cref="AzureOpenAIAudioToTextService"/> with AAD auth.
/// </summary>
/// <param name="deploymentName">Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource</param>
/// <param name="endpoint">Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart</param>
/// <param name="credentials">Token credentials, e.g. DefaultAzureCredential, ManagedIdentityCredential, EnvironmentCredential, etc.</param>
/// <param name="modelId">Azure OpenAI model id, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource</param>
/// <param name="httpClient">Custom <see cref="HttpClient"/> for HTTP requests.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> to use for logging. If null, no logging will be performed.</param>
public AzureOpenAIAudioToTextService(
string deploymentName,
string endpoint,
TokenCredential credentials,
string? modelId = null,
HttpClient? httpClient = null,
ILoggerFactory? loggerFactory = null)
{
this._core = new(deploymentName, endpoint, credentials, httpClient, loggerFactory?.CreateLogger(typeof(AzureOpenAIAudioToTextService)));
this._core.AddAttribute(AIServiceExtensions.ModelIdKey, modelId);
}

/// <summary>
/// Creates an instance of the <see cref="AzureOpenAIAudioToTextService"/> using the specified <see cref="OpenAIClient"/>.
/// </summary>
/// <param name="deploymentName">Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource</param>
/// <param name="openAIClient">Custom <see cref="OpenAIClient"/>.</param>
/// <param name="modelId">Azure OpenAI model id, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> to use for logging. If null, no logging will be performed.</param>
public AzureOpenAIAudioToTextService(
string deploymentName,
OpenAIClient openAIClient,
string? modelId = null,
ILoggerFactory? loggerFactory = null)
{
this._core = new(deploymentName, openAIClient, loggerFactory?.CreateLogger(typeof(AzureOpenAIAudioToTextService)));
this._core.AddAttribute(AIServiceExtensions.ModelIdKey, modelId);
}

/// <inheritdoc/>
public Task<IReadOnlyList<TextContent>> GetTextContentsAsync(
AudioContent content,
PromptExecutionSettings? executionSettings = null,
Kernel? kernel = null,
CancellationToken cancellationToken = default)
=> this._core.GetTextContentFromAudioAsync(content, executionSettings, cancellationToken);
}