Skip to content
Open
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 @@ -1936,6 +1936,16 @@ public async Task OnAutoFunctionInvocationAsync(AutoFunctionInvocationContext co
{ "{\"voice\":\"echo\",\"format\":\"opus\"}", "{\"voice\":\"echo\",\"format\":\"opus\"}" },
};

public static TheoryData<ChatOutputAudioFormat, string> AudioMimeTypeMappingData => new()
{
{ ChatOutputAudioFormat.Wav, "audio/wav" },
{ ChatOutputAudioFormat.Aac, "audio/aac" },
{ ChatOutputAudioFormat.Mp3, "audio/mp3" },
{ ChatOutputAudioFormat.Opus, "audio/opus" },
{ ChatOutputAudioFormat.Flac, "audio/flac" },
{ ChatOutputAudioFormat.Pcm16, "audio/pcm16" },
};

#pragma warning disable CS8618, CA1812
private sealed class MathReasoning
{
Expand Down Expand Up @@ -2146,6 +2156,56 @@ public async Task ItHandlesAudioContentWithMetadataInResponseAsync()
// The ExpiresAt value is converted to a DateTime object, so we can't directly compare it to the Unix timestamp
}

[Theory]
[MemberData(nameof(AudioMimeTypeMappingData))]
public async Task ItMapsAudioOutputFormatToCorrectMimeTypeAsync(ChatOutputAudioFormat format, string expectedMimeType)
{
// Arrange
var chatCompletion = new OpenAIChatCompletionService(modelId: "gpt-4o", apiKey: "NOKEY", httpClient: this._httpClient);

var responseJson = """
{
"model": "gpt-4o",
"choices": [
{
"message": {
"role": "assistant",
"content": "This is the text response.",
"audio": {
"data": "AQIDBA=="
}
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 20,
"total_tokens": 30
}
}
""";

this._messageHandlerStub.ResponseToReturn = new HttpResponseMessage(HttpStatusCode.OK)
{ Content = new StringContent(responseJson) };

var settings = new OpenAIPromptExecutionSettings
{
Modalities = ChatResponseModalities.Text | ChatResponseModalities.Audio,
Audio = new ChatAudioOptions(ChatOutputAudioVoice.Alloy, format)
};

// Act
var result = await chatCompletion.GetChatMessageContentAsync(this._chatHistoryForTest, settings);

// Assert
Assert.NotNull(result);
Assert.Equal(2, result.Items.Count);
var audioContent = result.Items[1] as AudioContent;
Assert.NotNull(audioContent);
Assert.Equal(expectedMimeType, audioContent.MimeType);
}

[Fact]
public async Task GetChatMessageContentsThrowsExceptionWithEmptyBinaryContentAsync()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -999,9 +999,9 @@ private OpenAIChatMessageContent CreateChatMessageContent(OAIChat.ChatCompletion
return "audio/opus";
}

if (audioOptions.OutputAudioFormat == ChatOutputAudioFormat.Wav)
if (audioOptions.OutputAudioFormat == ChatOutputAudioFormat.Aac)
{
return "audio/wav";
return "audio/aac";
}

if (audioOptions.OutputAudioFormat == ChatOutputAudioFormat.Flac)
Expand All @@ -1014,7 +1014,7 @@ private OpenAIChatMessageContent CreateChatMessageContent(OAIChat.ChatCompletion
return "audio/pcm16";
}

throw new NotSupportedException($"Unsupported audio output format '{audioOptions.OutputAudioFormat}'. Supported formats are 'wav', 'mp3', 'opus', 'flac' and 'pcm16'.");
throw new NotSupportedException($"Unsupported audio output format '{audioOptions.OutputAudioFormat}'. Supported formats are 'wav', 'mp3', 'opus', 'aac', 'flac' and 'pcm16'.");
}

private OpenAIChatMessageContent CreateChatMessageContent(ChatMessageRole chatRole, string content, ChatToolCall[] toolCalls, FunctionCallContent[]? functionCalls, IReadOnlyDictionary<string, object?>? metadata, string? authorName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.SemanticKernel.Process.Internal;
using Microsoft.SemanticKernel.Process.Models;

Expand Down Expand Up @@ -50,5 +51,9 @@ public KernelProcess(KernelProcessState state, IList<KernelProcessStepInfo> step
Verify.NotNullOrWhiteSpace(state.Name);

this.Steps = [.. steps];
if (threads is not null)
{
this.Threads = threads.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
Comment thread
rogerbarreto marked this conversation as resolved.
}
}
60 changes: 60 additions & 0 deletions dotnet/src/Experimental/Process.UnitTests/KernelProcessTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) Microsoft. All rights reserved.

using System.Collections.Generic;
using Xunit;

namespace Microsoft.SemanticKernel.Process.UnitTests;

/// <summary>
/// Unit tests for <see cref="KernelProcess"/>.
/// </summary>
public class KernelProcessTests
{
/// <summary>
/// Verifies that the <see cref="KernelProcess"/> constructor assigns the supplied
/// <c>threads</c> dictionary to the <see cref="KernelProcess.Threads"/> property
/// instead of silently discarding it.
/// </summary>
[Fact]
public void Constructor_AssignsThreadsParameter_WhenProvided()
{
// Arrange
var state = new KernelProcessState(name: "TestProcess", version: "v1", id: "p1");
var steps = new List<KernelProcessStepInfo>();
var threads = new Dictionary<string, KernelProcessAgentThread>
{
["main"] = new KernelProcessAgentThread { ThreadName = "main", ThreadId = "t-1" },
["aux"] = new KernelProcessAgentThread { ThreadName = "aux", ThreadId = "t-2" },
};

// Act
var process = new KernelProcess(state, steps, edges: null, threads: threads);

// Assert
Assert.Equal(2, process.Threads.Count);
Assert.True(process.Threads.ContainsKey("main"));
Assert.True(process.Threads.ContainsKey("aux"));
Assert.Equal("t-1", process.Threads["main"].ThreadId);
Assert.Equal("t-2", process.Threads["aux"].ThreadId);
}

/// <summary>
/// Verifies that the <see cref="KernelProcess"/> constructor leaves
/// <see cref="KernelProcess.Threads"/> as an empty dictionary when the
/// <c>threads</c> argument is null.
/// </summary>
[Fact]
public void Constructor_DefaultsThreadsToEmptyDictionary_WhenNull()
{
// Arrange
var state = new KernelProcessState(name: "TestProcess", version: "v1", id: "p2");
var steps = new List<KernelProcessStepInfo>();

// Act
var process = new KernelProcess(state, steps, edges: null, threads: null);

// Assert
Assert.NotNull(process.Threads);
Assert.Empty(process.Threads);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -179,15 +179,17 @@ public static class PromptExecutionSettingsExtensions
options.ToolMode = ChatToolMode.Auto;
options.AllowMultipleToolCalls = autoChoiceBehavior.Options?.AllowParallelCalls;
}
else if (settings.FunctionChoiceBehavior is NoneFunctionChoiceBehavior noneFunctionChoiceBehavior)
{
options.ToolMode = ChatToolMode.None;
}
else if (settings.FunctionChoiceBehavior is RequiredFunctionChoiceBehavior requiredFunctionChoiceBehavior)
{
options.ToolMode = ChatToolMode.RequireAny;
options.AllowMultipleToolCalls = requiredFunctionChoiceBehavior.Options?.AllowParallelCalls;
}
else
if (settings.FunctionChoiceBehavior is NoneFunctionChoiceBehavior noneFunctionChoiceBehavior)
{
options.ToolMode = ChatToolMode.None;
}
else
if (settings.FunctionChoiceBehavior is RequiredFunctionChoiceBehavior requiredFunctionChoiceBehavior)
{
options.ToolMode = ChatToolMode.RequireAny;
options.AllowMultipleToolCalls = requiredFunctionChoiceBehavior.Options?.AllowParallelCalls;
}

options.Tools = [];
foreach (var function in functions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,7 @@ public async Task<KernelSearchResults<TextSearchResult>> GetTextSearchResultsAsy
var searchResult = await this.SearchInternalAsync(query, searchOptions, cancellationToken).ConfigureAwait(false);

var results = searchResult.Select(x => new TextSearchResult(x.Text ?? string.Empty) { Name = x.SourceName, Link = x.SourceLink });
return new(searchResult.Select(x =>
new TextSearchResult(x.Text ?? string.Empty)
{
Name = x.SourceName,
Link = x.SourceLink
}).ToAsyncEnumerable());
return new(results.ToAsyncEnumerable());
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,37 @@ public async Task SearchAsyncReturnsSearchResults()
Assert.Equal("Sample text", actualResultsList[0]);
}

[Fact]
public async Task GetTextSearchResultsAsyncReturnsTextSearchResults()
{
// Arrange
var mockResults = new List<VectorSearchResult<TextSearchStore<string>.TextRagStorageDocument<string>>>
{
new(new TextSearchStore<string>.TextRagStorageDocument<string>
{
Text = "Sample text",
SourceName = "src-name",
SourceLink = "src-link",
}, 0.9f)
};

this._recordCollectionMock
.Setup(r => r.SearchAsync("query", 3, It.IsAny<VectorSearchOptions<TextSearchStore<string>.TextRagStorageDocument<string>>>(), It.IsAny<CancellationToken>()))
.Returns(mockResults.ToAsyncEnumerable());

using var store = new TextSearchStore<string>(this._vectorStoreMock.Object, "testCollection", 128);

// Act
var actualResults = await store.GetTextSearchResultsAsync("query");

// Assert
var actualResultsList = await actualResults.Results.ToListAsync();
Assert.Single(actualResultsList);
Assert.Equal("Sample text", actualResultsList[0].Value);
Assert.Equal("src-name", actualResultsList[0].Name);
Assert.Equal("src-link", actualResultsList[0].Link);
}

[Fact]
public async Task SearchAsyncWithHybridReturnsSearchResults()
{
Expand Down
Loading