From 9c7f64c611f4b20c82f87bcce52303f2ea05bce9 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 8 Jul 2025 20:37:19 -0400 Subject: [PATCH 1/5] Update to latest M.E.AI{.Abstractions} --- dotnet/Directory.Packages.props | 6 +++--- .../AI/ChatCompletion/AIFunctionKernelFunction.cs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index c60c6aedd669..63d406fd81d3 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -88,7 +88,7 @@ - + @@ -97,8 +97,8 @@ - - + + diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/AIFunctionKernelFunction.cs b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/AIFunctionKernelFunction.cs index 374ddc1a5fe6..a319f6b1c85d 100644 --- a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/AIFunctionKernelFunction.cs +++ b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/AIFunctionKernelFunction.cs @@ -28,7 +28,7 @@ public AIFunctionKernelFunction(AIFunction aiFunction) : { Description = aiFunction.UnderlyingMethod?.ReturnParameter.GetCustomAttribute()?.Description, ParameterType = aiFunction.UnderlyingMethod?.ReturnParameter.ParameterType, - Schema = new KernelJsonSchema(AIJsonUtilities.CreateJsonSchema(aiFunction.UnderlyingMethod?.ReturnParameter.ParameterType)), + Schema = new KernelJsonSchema(aiFunction.ReturnJsonSchema ?? AIJsonUtilities.CreateJsonSchema(aiFunction.UnderlyingMethod?.ReturnParameter.ParameterType)), }) { // Kernel functions created from AI functions are always fully qualified From 34f0ac7c28d05db3e35fefbde3dbbb6195285631 Mon Sep 17 00:00:00 2001 From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com> Date: Thu, 17 Jul 2025 13:25:22 +0100 Subject: [PATCH 2/5] Bumping dependencies and other MEAI packages --- dotnet/Directory.Packages.props | 12 +-- .../OpenAIRealtime/OpenAIRealtime.csproj | 2 +- .../samples/Demos/OpenAIRealtime/Program.cs | 75 ++++++++++--------- .../Extensions/OpenAIResponseExtensions.cs | 13 ++-- .../Extensions/ResponseItemExtensionsTests.cs | 4 +- .../Connectors.AzureOpenAI.UnitTests.csproj | 2 +- .../Connectors.AzureOpenAI.csproj | 2 +- .../Connectors.OpenAI.UnitTests.csproj | 2 +- .../Connectors.OpenAI.csproj | 2 +- .../SemanticKernel.UnitTests.csproj | 2 +- 10 files changed, 61 insertions(+), 55 deletions(-) diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index 63d406fd81d3..22779c89ef7c 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -19,7 +19,7 @@ - + @@ -70,7 +70,7 @@ - + @@ -97,10 +97,10 @@ - - - - + + + + diff --git a/dotnet/samples/Demos/OpenAIRealtime/OpenAIRealtime.csproj b/dotnet/samples/Demos/OpenAIRealtime/OpenAIRealtime.csproj index 7aaa8d7e8c4c..79a0672716e0 100644 --- a/dotnet/samples/Demos/OpenAIRealtime/OpenAIRealtime.csproj +++ b/dotnet/samples/Demos/OpenAIRealtime/OpenAIRealtime.csproj @@ -5,7 +5,7 @@ net8.0 enable enable - $(NoWarn);VSTHRD111,CA2007,CS8618,CS1591,CA1052,SKEXP0001 + $(NoWarn);VSTHRD111,CA2007,CS8618,CS1591,CA1052,CA1810,SKEXP0001 5ee045b0-aea3-4f08-8d31-32d1a6f8fed0 diff --git a/dotnet/samples/Demos/OpenAIRealtime/Program.cs b/dotnet/samples/Demos/OpenAIRealtime/Program.cs index fb17b4bbfd3e..bab97990fe09 100644 --- a/dotnet/samples/Demos/OpenAIRealtime/Program.cs +++ b/dotnet/samples/Demos/OpenAIRealtime/Program.cs @@ -8,7 +8,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Connectors.OpenAI; -using OpenAI.RealtimeConversation; +using OpenAI.Realtime; namespace OpenAIRealtime; @@ -16,11 +16,25 @@ namespace OpenAIRealtime; /// /// Demonstrates the use of the OpenAI Realtime API with function calling and Semantic Kernel. -/// For conversational experiences, it is recommended to use from the Azure/OpenAI SDK. +/// For conversational experiences, it is recommended to use from the Azure/OpenAI SDK. /// Since the OpenAI Realtime API supports function calling, the example shows how to combine it with Semantic Kernel plugins and functions. /// internal sealed class Program { + private readonly static OpenAIOptions s_openAIOptions; + private readonly static AzureOpenAIOptions s_azureOpenAIOptions; + + static Program() + { + var config = new ConfigurationBuilder() + .AddUserSecrets() + .AddEnvironmentVariables() + .Build(); + + s_openAIOptions = config.GetSection(OpenAIOptions.SectionName).Get()!; + s_azureOpenAIOptions = config.GetSection(AzureOpenAIOptions.SectionName).Get()!; + } + public static async Task Main(string[] args) { // Retrieve the RealtimeConversationClient based on the available OpenAI or Azure OpenAI configuration. @@ -33,7 +47,7 @@ public static async Task Main(string[] args) kernel.ImportPluginFromType(); // Start a new conversation session. - using RealtimeConversationSession session = await realtimeConversationClient.StartConversationSessionAsync(); + using RealtimeSession session = await realtimeConversationClient.StartConversationSessionAsync(s_azureOpenAIOptions.DeploymentName); // Initialize session options. // Session options control connection-wide behavior shared across all conversations, @@ -41,8 +55,8 @@ public static async Task Main(string[] args) ConversationSessionOptions sessionOptions = new() { Voice = ConversationVoice.Alloy, - InputAudioFormat = ConversationAudioFormat.Pcm16, - OutputAudioFormat = ConversationAudioFormat.Pcm16, + InputAudioFormat = RealtimeAudioFormat.Pcm16, + OutputAudioFormat = RealtimeAudioFormat.Pcm16, InputTranscriptionOptions = new() { Model = "whisper-1", @@ -62,13 +76,12 @@ public static async Task Main(string[] args) } // Configure session with defined options. - await session.ConfigureSessionAsync(sessionOptions); + await session.ConfigureConversationSessionAsync(sessionOptions); // Items such as user, assistant, or system messages, as well as input audio, can be sent to the session. // An example of sending user message to the session. // ConversationItem can be constructed from Microsoft.SemanticKernel.ChatMessageContent if needed by mapping the relevant fields. - await session.AddItemAsync( - ConversationItem.CreateUserMessage(["I'm trying to decide what to wear on my trip."])); + await session.AddItemAsync(RealtimeItem.CreateUserMessage(["I'm trying to decide what to wear on my trip."])); // Use audio file that contains a recorded question: "What's the weather like in San Francisco, California?" string inputAudioPath = FindFile("Assets\\realtime_whats_the_weather_pcm16_24khz_mono.wav"); @@ -82,7 +95,7 @@ await session.AddItemAsync( Dictionary functionArgumentBuildersById = []; // Define a loop to receive conversation updates in the session. - await foreach (ConversationUpdate update in session.ReceiveUpdatesAsync()) + await foreach (RealtimeUpdate update in session.ReceiveUpdatesAsync()) { // Notification indicating the start of the conversation session. if (update is ConversationSessionStartedUpdate sessionStartedUpdate) @@ -92,21 +105,21 @@ await session.AddItemAsync( } // Notification indicating the start of detected voice activity. - if (update is ConversationInputSpeechStartedUpdate speechStartedUpdate) + if (update is InputAudioSpeechStartedUpdate speechStartedUpdate) { Console.WriteLine( $" -- Voice activity detection started at {speechStartedUpdate.AudioStartTime}"); } // Notification indicating the end of detected voice activity. - if (update is ConversationInputSpeechFinishedUpdate speechFinishedUpdate) + if (update is InputAudioSpeechFinishedUpdate speechFinishedUpdate) { Console.WriteLine( $" -- Voice activity detection ended at {speechFinishedUpdate.AudioEndTime}"); } // Notification indicating the start of item streaming, such as a function call or response message. - if (update is ConversationItemStreamingStartedUpdate itemStreamingStartedUpdate) + if (update is OutputStreamingStartedUpdate itemStreamingStartedUpdate) { Console.WriteLine(" -- Begin streaming of new item"); if (!string.IsNullOrEmpty(itemStreamingStartedUpdate.FunctionName)) @@ -116,7 +129,7 @@ await session.AddItemAsync( } // Notification about item streaming delta, which may include audio transcript, audio bytes, or function arguments. - if (update is ConversationItemStreamingPartDeltaUpdate deltaUpdate) + if (update is OutputDeltaUpdate deltaUpdate) { Console.Write(deltaUpdate.AudioTranscript); Console.Write(deltaUpdate.Text); @@ -148,7 +161,7 @@ await session.AddItemAsync( // Notification indicating the end of item streaming, such as a function call or response message. // At this point, audio transcript can be displayed on console, or a function can be called with aggregated arguments. - if (update is ConversationItemStreamingFinishedUpdate itemStreamingFinishedUpdate) + if (update is OutputStreamingFinishedUpdate itemStreamingFinishedUpdate) { Console.WriteLine(); Console.WriteLine($" -- Item streaming finished, item_id={itemStreamingFinishedUpdate.ItemId}"); @@ -176,7 +189,7 @@ await session.AddItemAsync( var resultContent = await functionCallContent.InvokeAsync(kernel); // Create a function call output conversation item with function call result. - ConversationItem functionOutputItem = ConversationItem.CreateFunctionCallOutput( + RealtimeItem functionOutputItem = RealtimeItem.CreateFunctionCallOutput( callId: itemStreamingFinishedUpdate.FunctionCallId, output: ProcessFunctionResult(resultContent.Result)); @@ -198,7 +211,7 @@ await session.AddItemAsync( } // Notification indicating the completion of transcription from input audio. - if (update is ConversationInputTranscriptionFinishedUpdate transcriptionCompletedUpdate) + if (update is InputAudioTranscriptionFinishedUpdate transcriptionCompletedUpdate) { Console.WriteLine(); Console.WriteLine($" -- User audio transcript: {transcriptionCompletedUpdate.Transcript}"); @@ -206,7 +219,7 @@ await session.AddItemAsync( } // Notification about completed model response turn. - if (update is ConversationResponseFinishedUpdate turnFinishedUpdate) + if (update is ResponseFinishedUpdate turnFinishedUpdate) { Console.WriteLine($" -- Model turn generation finished. Status: {turnFinishedUpdate.Status}"); @@ -226,7 +239,7 @@ await session.AddItemAsync( } // Notification about error in conversation session. - if (update is ConversationErrorUpdate errorUpdate) + if (update is RealtimeErrorUpdate errorUpdate) { Console.WriteLine(); Console.WriteLine($"ERROR: {errorUpdate.Message}"); @@ -375,32 +388,22 @@ private static string FindFile(string fileName) } /// - /// Helper method to get an instance of based on provided + /// Helper method to get an instance of based on provided /// OpenAI or Azure OpenAI configuration. /// - private static RealtimeConversationClient GetRealtimeConversationClient() + private static RealtimeClient GetRealtimeConversationClient() { - var config = new ConfigurationBuilder() - .AddUserSecrets() - .AddEnvironmentVariables() - .Build(); - - var openAIOptions = config.GetSection(OpenAIOptions.SectionName).Get(); - var azureOpenAIOptions = config.GetSection(AzureOpenAIOptions.SectionName).Get(); - - if (openAIOptions is not null && openAIOptions.IsValid) + if (s_openAIOptions is not null && s_openAIOptions.IsValid) { - return new RealtimeConversationClient( - model: "gpt-4o-realtime-preview", - credential: new ApiKeyCredential(openAIOptions.ApiKey)); + return new RealtimeClient(new ApiKeyCredential(s_openAIOptions.ApiKey)); } - else if (azureOpenAIOptions is not null && azureOpenAIOptions.IsValid) + else if (s_azureOpenAIOptions is not null && s_azureOpenAIOptions.IsValid) { var client = new AzureOpenAIClient( - endpoint: new Uri(azureOpenAIOptions.Endpoint), - credential: new ApiKeyCredential(azureOpenAIOptions.ApiKey)); + endpoint: new Uri(s_azureOpenAIOptions.Endpoint), + credential: new ApiKeyCredential(s_azureOpenAIOptions.ApiKey)); - return client.GetRealtimeConversationClient(azureOpenAIOptions.DeploymentName); + return client.GetRealtimeClient(); } else { diff --git a/dotnet/src/Agents/OpenAI/Extensions/OpenAIResponseExtensions.cs b/dotnet/src/Agents/OpenAI/Extensions/OpenAIResponseExtensions.cs index d65e0f940fff..dc1ec795cae0 100644 --- a/dotnet/src/Agents/OpenAI/Extensions/OpenAIResponseExtensions.cs +++ b/dotnet/src/Agents/OpenAI/Extensions/OpenAIResponseExtensions.cs @@ -52,7 +52,7 @@ public static ChatMessageContent ToChatMessageContent(this OpenAIResponse respon } else if (item is ReasoningResponseItem reasoningResponseItem) { - if (reasoningResponseItem.SummaryTextParts is not null && reasoningResponseItem.SummaryTextParts.Count > 0) + if (reasoningResponseItem.SummaryParts is not null && reasoningResponseItem.SummaryParts.Count > 0) { return new ChatMessageContent(AuthorRole.Assistant, item.ToChatMessageContentItemCollection(), innerContent: reasoningResponseItem); } @@ -77,7 +77,7 @@ public static ChatMessageContentItemCollection ToChatMessageContentItemCollectio } else if (item is ReasoningResponseItem reasoningResponseItem) { - return reasoningResponseItem.SummaryTextParts.ToChatMessageContentItemCollection(); + return reasoningResponseItem.SummaryParts.ToChatMessageContentItemCollection(); } else if (item is FunctionCallResponseItem functionCallResponseItem) { @@ -195,12 +195,15 @@ private static ChatMessageContentItemCollection ToChatMessageContentItemCollecti return collection; } - private static ChatMessageContentItemCollection ToChatMessageContentItemCollection(this IReadOnlyList texts) + private static ChatMessageContentItemCollection ToChatMessageContentItemCollection(this IReadOnlyList parts) { var collection = new ChatMessageContentItemCollection(); - foreach (var text in texts) + foreach (var part in parts) { - collection.Add(new TextContent(text, innerContent: null)); + if (part is ReasoningSummaryTextPart text) + { + collection.Add(new TextContent(text.Text, innerContent: text)); + } } return collection; } diff --git a/dotnet/src/Agents/UnitTests/Extensions/ResponseItemExtensionsTests.cs b/dotnet/src/Agents/UnitTests/Extensions/ResponseItemExtensionsTests.cs index 8683a19f379f..d547b27be0fa 100644 --- a/dotnet/src/Agents/UnitTests/Extensions/ResponseItemExtensionsTests.cs +++ b/dotnet/src/Agents/UnitTests/Extensions/ResponseItemExtensionsTests.cs @@ -66,7 +66,7 @@ public void VerifyToChatMessageContentFromInputFile() { // Arrange var fileBytes = new ReadOnlyMemory([1, 2, 3, 4, 5]); - IEnumerable contentParts = [ResponseContentPart.CreateInputFilePart("fileId", "fileName", new(fileBytes))]; + IEnumerable contentParts = [ResponseContentPart.CreateInputFilePart(BinaryData.FromBytes(fileBytes), "text/plain", "fileName")]; MessageResponseItem responseItem = ResponseItem.CreateUserMessageItem(contentParts); // Act @@ -102,7 +102,7 @@ public void VerifyToChatMessageContentFromRefusal() public void VerifyToChatMessageContentFromReasoning() { // Arrange - IEnumerable summaryParts = ["Foo"]; + IEnumerable summaryParts = [ReasoningSummaryPart.CreateTextPart("Foo")]; ReasoningResponseItem responseItem = ResponseItem.CreateReasoningItem(summaryParts); // Act diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Connectors.AzureOpenAI.UnitTests.csproj b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Connectors.AzureOpenAI.UnitTests.csproj index a0a695a6719c..efae2b241d3c 100644 --- a/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Connectors.AzureOpenAI.UnitTests.csproj +++ b/dotnet/src/Connectors/Connectors.AzureOpenAI.UnitTests/Connectors.AzureOpenAI.UnitTests.csproj @@ -8,7 +8,7 @@ true enable false - $(NoWarn);SKEXP0001;SKEXP0010;CA2007,CA1806,CA1869,CA1861,IDE0300,VSTHRD111,IDE1006 + $(NoWarn);SKEXP0001;SKEXP0010;CA2007,CA1806,CA1869,CA1861,IDE0300,VSTHRD111,IDE1006,OPENAI001 diff --git a/dotnet/src/Connectors/Connectors.AzureOpenAI/Connectors.AzureOpenAI.csproj b/dotnet/src/Connectors/Connectors.AzureOpenAI/Connectors.AzureOpenAI.csproj index d5e590afabbe..47d0ed0a85e5 100644 --- a/dotnet/src/Connectors/Connectors.AzureOpenAI/Connectors.AzureOpenAI.csproj +++ b/dotnet/src/Connectors/Connectors.AzureOpenAI/Connectors.AzureOpenAI.csproj @@ -5,7 +5,7 @@ Microsoft.SemanticKernel.Connectors.AzureOpenAI $(AssemblyName) net8.0;netstandard2.0 - $(NoWarn);NU5104;SKEXP0001,SKEXP0010 + $(NoWarn);NU5104;SKEXP0001,SKEXP0010,OPENAI001 true diff --git a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Connectors.OpenAI.UnitTests.csproj b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Connectors.OpenAI.UnitTests.csproj index 0a7171bbcd0d..0366175e98f8 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Connectors.OpenAI.UnitTests.csproj +++ b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Connectors.OpenAI.UnitTests.csproj @@ -7,7 +7,7 @@ true enable false - $(NoWarn);SKEXP0001;SKEXP0010;CS1591;IDE1006;RCS1261;CA1031;CA1308;CA1861;CA2007;CA2234;VSTHRD111;CA1812 + $(NoWarn);SKEXP0001;SKEXP0010;CS1591;IDE1006;RCS1261;CA1031;CA1308;CA1861;CA2007;CA2234;VSTHRD111;CA1812;OPENAI001 diff --git a/dotnet/src/Connectors/Connectors.OpenAI/Connectors.OpenAI.csproj b/dotnet/src/Connectors/Connectors.OpenAI/Connectors.OpenAI.csproj index 2f280b843e10..aab81a532403 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI/Connectors.OpenAI.csproj +++ b/dotnet/src/Connectors/Connectors.OpenAI/Connectors.OpenAI.csproj @@ -5,7 +5,7 @@ Microsoft.SemanticKernel.Connectors.OpenAI $(AssemblyName) net8.0;netstandard2.0 - $(NoWarn);NU5104;SKEXP0001,SKEXP0010 + $(NoWarn);NU5104;SKEXP0001,SKEXP0010,OPENAI001 true diff --git a/dotnet/src/SemanticKernel.UnitTests/SemanticKernel.UnitTests.csproj b/dotnet/src/SemanticKernel.UnitTests/SemanticKernel.UnitTests.csproj index 512cbf00ad91..6c4d9765fd02 100644 --- a/dotnet/src/SemanticKernel.UnitTests/SemanticKernel.UnitTests.csproj +++ b/dotnet/src/SemanticKernel.UnitTests/SemanticKernel.UnitTests.csproj @@ -6,7 +6,7 @@ net8.0 true false - $(NoWarn);CA2007,CA1861,IDE1006,VSTHRD111,SKEXP0001,SKEXP0010,SKEXP0050,SKEXP0110,SKEXP0120,SKEXP0130,MEVD9000 + $(NoWarn);CA2007,CA1861,IDE1006,VSTHRD111,SKEXP0001,SKEXP0010,SKEXP0050,SKEXP0110,SKEXP0120,SKEXP0130,MEVD9000,OPENAI001 From 76cbc6b13e54e2c67e7e45b63005c8769a616cb5 Mon Sep 17 00:00:00 2001 From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com> Date: Thu, 17 Jul 2025 13:36:12 +0100 Subject: [PATCH 3/5] Address config! --- .../samples/Demos/OpenAIRealtime/Program.cs | 34 ++++++++----------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/dotnet/samples/Demos/OpenAIRealtime/Program.cs b/dotnet/samples/Demos/OpenAIRealtime/Program.cs index bab97990fe09..2de269dd1609 100644 --- a/dotnet/samples/Demos/OpenAIRealtime/Program.cs +++ b/dotnet/samples/Demos/OpenAIRealtime/Program.cs @@ -21,20 +21,6 @@ namespace OpenAIRealtime; /// internal sealed class Program { - private readonly static OpenAIOptions s_openAIOptions; - private readonly static AzureOpenAIOptions s_azureOpenAIOptions; - - static Program() - { - var config = new ConfigurationBuilder() - .AddUserSecrets() - .AddEnvironmentVariables() - .Build(); - - s_openAIOptions = config.GetSection(OpenAIOptions.SectionName).Get()!; - s_azureOpenAIOptions = config.GetSection(AzureOpenAIOptions.SectionName).Get()!; - } - public static async Task Main(string[] args) { // Retrieve the RealtimeConversationClient based on the available OpenAI or Azure OpenAI configuration. @@ -47,7 +33,7 @@ public static async Task Main(string[] args) kernel.ImportPluginFromType(); // Start a new conversation session. - using RealtimeSession session = await realtimeConversationClient.StartConversationSessionAsync(s_azureOpenAIOptions.DeploymentName); + using RealtimeSession session = await realtimeConversationClient.StartConversationSessionAsync("gpt-4o-realtime-preview"); // Initialize session options. // Session options control connection-wide behavior shared across all conversations, @@ -393,15 +379,23 @@ private static string FindFile(string fileName) /// private static RealtimeClient GetRealtimeConversationClient() { - if (s_openAIOptions is not null && s_openAIOptions.IsValid) + var config = new ConfigurationBuilder() + .AddUserSecrets() + .AddEnvironmentVariables() + .Build(); + + var openAIOptions = config.GetSection(OpenAIOptions.SectionName).Get()!; + var azureOpenAIOptions = config.GetSection(AzureOpenAIOptions.SectionName).Get()!; + + if (openAIOptions is not null && openAIOptions.IsValid) { - return new RealtimeClient(new ApiKeyCredential(s_openAIOptions.ApiKey)); + return new RealtimeClient(new ApiKeyCredential(openAIOptions.ApiKey)); } - else if (s_azureOpenAIOptions is not null && s_azureOpenAIOptions.IsValid) + else if (azureOpenAIOptions is not null && azureOpenAIOptions.IsValid) { var client = new AzureOpenAIClient( - endpoint: new Uri(s_azureOpenAIOptions.Endpoint), - credential: new ApiKeyCredential(s_azureOpenAIOptions.ApiKey)); + endpoint: new Uri(azureOpenAIOptions.Endpoint), + credential: new ApiKeyCredential(azureOpenAIOptions.ApiKey)); return client.GetRealtimeClient(); } From b451c1e49eb86da7a17db813a80efec144dfcaa4 Mon Sep 17 00:00:00 2001 From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com> Date: Thu, 17 Jul 2025 17:42:02 +0100 Subject: [PATCH 4/5] Address UT Failures --- .../OpenAIResponseExtensionsTests.cs | 78 ++++++++++++------- .../KernelFunctionMetadataExtensionsTests.cs | 2 +- .../Core/OpenAIJsonSchemaTransformerTests.cs | 2 +- .../KernelFunctionMetadataExtensionsTests.cs | 2 +- .../OpenAIChatResponseFormatBuilderTests.cs | 29 +++++-- 5 files changed, 78 insertions(+), 35 deletions(-) diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIResponseExtensionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIResponseExtensionsTests.cs index 6fa230534427..a2f75f7a192b 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIResponseExtensionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIResponseExtensionsTests.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents.OpenAI; @@ -178,25 +179,39 @@ private OpenAIResponse CreateMockOpenAIResponse(string model, IEnumerable tools, float topP, IDictionary metadata, ResponseIncompleteStatusDetails incompleteStatusDetails, IEnumerable outputItems, bool parallelToolCallsEnabled, ResponseToolChoice toolChoice) { Type type = typeof(OpenAIResponse); + var assembly = type.Assembly; + var internalServiceTierType = assembly.GetType("OpenAI.Internal.InternalServiceTier"); + var nullableInternalServiceTierType = typeof(Nullable<>).MakeGenericType(internalServiceTierType!); ConstructorInfo? constructor = type.GetConstructor( BindingFlags.Instance | BindingFlags.NonPublic, null, [ - typeof(string), - typeof(DateTimeOffset), - typeof(ResponseError), - typeof(string), - typeof(string), - typeof(string), - typeof(float), - typeof(IEnumerable), - typeof(float), typeof(IDictionary), - typeof(ResponseIncompleteStatusDetails), - typeof(IEnumerable), - typeof(bool), - typeof(ResponseToolChoice) + typeof(float?), + typeof(float?), + nullableInternalServiceTierType, + typeof(string), + typeof(bool?), + typeof(string), + typeof(IList), + typeof(string), + typeof(ResponseStatus?), + typeof(DateTimeOffset), + typeof(ResponseError), + typeof(ResponseTokenUsage), + typeof(string), + typeof(ResponseReasoningOptions), + typeof(int?), + typeof(ResponseTextOptions), + typeof(ResponseTruncationMode?), + typeof(ResponseIncompleteStatusDetails), + typeof(IList), + typeof(bool), + typeof(ResponseToolChoice), + typeof(string), + typeof(string), + typeof(IDictionary) ], null); @@ -204,20 +219,31 @@ private OpenAIResponse CreateMockOpenAIResponse(string id, DateTimeOffset create { return (OpenAIResponse)constructor.Invoke( [ - id, - createdAt, - error, - instructions, - model, - previousResponseId, - temperature, - tools, - topP, metadata, - incompleteStatusDetails, - outputItems, - parallelToolCallsEnabled, - toolChoice + (float?)temperature, + (float?)topP, + null, // serviceTier + previousResponseId, + null, // background + instructions, + tools.ToList(), + id, + null, // status + createdAt, + error, + null, // usage + null, // endUserId + null, // reasoningOptions + null, // maxOutputTokenCount + null, // textOptions + null, // truncationMode + incompleteStatusDetails, + outputItems.ToList(), + parallelToolCallsEnabled, + toolChoice, + model, + "response", + null // additionalBinaryDataProperties ] ); } diff --git a/dotnet/src/Connectors/Connectors.Google.UnitTests/Extensions/KernelFunctionMetadataExtensionsTests.cs b/dotnet/src/Connectors/Connectors.Google.UnitTests/Extensions/KernelFunctionMetadataExtensionsTests.cs index a87816bfb949..360d5173cab8 100644 --- a/dotnet/src/Connectors/Connectors.Google.UnitTests/Extensions/KernelFunctionMetadataExtensionsTests.cs +++ b/dotnet/src/Connectors/Connectors.Google.UnitTests/Extensions/KernelFunctionMetadataExtensionsTests.cs @@ -200,7 +200,7 @@ public void ItCanCreateValidGeminiFunctionManualForPlugin() // Assert Assert.NotNull(result); Assert.Equal( - """{"type":"object","required":["parameter1","parameter2","parameter3"],"properties":{"parameter1":{"description":"String parameter","type":"string"},"parameter2":{"description":"Enum parameter","type":"string","enum":["Value1","Value2"]},"parameter3":{"description":"DateTime parameter","type":"string"}}}""", + """{"type":"object","required":["parameter1","parameter2","parameter3"],"properties":{"parameter1":{"description":"String parameter","type":"string"},"parameter2":{"description":"Enum parameter","type":"string","enum":["Value1","Value2"]},"parameter3":{"description":"DateTime parameter","type":"string","format":"date-time"}}}""", JsonSerializer.Serialize(result.Parameters) ); } diff --git a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Core/OpenAIJsonSchemaTransformerTests.cs b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Core/OpenAIJsonSchemaTransformerTests.cs index 2c15249a3ca6..e41408e2b36c 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Core/OpenAIJsonSchemaTransformerTests.cs +++ b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Core/OpenAIJsonSchemaTransformerTests.cs @@ -73,7 +73,7 @@ public void ItTransformsJsonSchemaCorrectly() "null" ], "items": { - "type": "object", + "type": ["object","null"], "properties": { "TextProperty": { "type": [ diff --git a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Extensions/KernelFunctionMetadataExtensionsTests.cs b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Extensions/KernelFunctionMetadataExtensionsTests.cs index ec64801c51b0..3029777f56a1 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Extensions/KernelFunctionMetadataExtensionsTests.cs +++ b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Extensions/KernelFunctionMetadataExtensionsTests.cs @@ -208,7 +208,7 @@ public void ItCanCreateValidAzureOpenAIFunctionManualForPlugin(bool strict) else { Assert.Equal( - """{"type":"object","required":["parameter1","parameter2","parameter3"],"properties":{"parameter1":{"description":"String parameter","type":"string"},"parameter2":{"description":"Enum parameter","type":"string","enum":["Value1","Value2"]},"parameter3":{"description":"DateTime parameter","type":"string"}}}""", + """{"type":"object","required":["parameter1","parameter2","parameter3"],"properties":{"parameter1":{"description":"String parameter","type":"string"},"parameter2":{"description":"Enum parameter","type":"string","enum":["Value1","Value2"]},"parameter3":{"description":"DateTime parameter","type":"string","format":"date-time"}}}""", parametersResult ); } diff --git a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Helpers/OpenAIChatResponseFormatBuilderTests.cs b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Helpers/OpenAIChatResponseFormatBuilderTests.cs index 13a5862b19b7..419a97c6b500 100644 --- a/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Helpers/OpenAIChatResponseFormatBuilderTests.cs +++ b/dotnet/src/Connectors/Connectors.OpenAI.UnitTests/Helpers/OpenAIChatResponseFormatBuilderTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.SemanticKernel.Connectors.OpenAI; @@ -34,11 +35,9 @@ public void GetJsonSchemaResponseFormatReturnsChatResponseFormatByDefault( // Act var chatResponseFormat = OpenAIChatResponseFormatBuilder.GetJsonSchemaResponseFormat(jsonElement); - var responseFormat = this.GetResponseFormat(chatResponseFormat); + var (jsonSchema, schema) = this.GetResponseFormatJsonSchema(chatResponseFormat); // Assert - Assert.True(responseFormat.TryGetProperty("JsonSchema", out var jsonSchema)); - Assert.True(jsonSchema.TryGetProperty("Schema", out var schema)); Assert.True(jsonSchema.TryGetProperty("Name", out var name)); Assert.True(jsonSchema.TryGetProperty("Strict", out var strict)); @@ -145,10 +144,28 @@ public void GetJsonSchemaResponseFormatThrowsExceptionWhenSchemaDoesNotExist() #region private - private JsonElement GetResponseFormat(ChatResponseFormat chatResponseFormat) + private (JsonElement JsonSchema, JsonElement JsonSchemaSchema) GetResponseFormatJsonSchema(ChatResponseFormat chatResponseFormat) { - var settings = new OpenAIPromptExecutionSettings { ResponseFormat = chatResponseFormat }; - return JsonDocument.Parse(JsonSerializer.Serialize(settings, this._options)).RootElement.GetProperty("response_format"); + var jsonSchemaProperty = chatResponseFormat.GetType().GetProperty("JsonSchema", BindingFlags.NonPublic | BindingFlags.Instance); + + // Assert + Assert.NotNull(jsonSchemaProperty); + var jsonSchemaPropertyValue = jsonSchemaProperty.GetValue(chatResponseFormat); + + Assert.NotNull(jsonSchemaPropertyValue); + var schemaProperty = jsonSchemaPropertyValue.GetType().GetProperty("Schema", BindingFlags.Public | BindingFlags.Instance); + + Assert.NotNull(schemaProperty); + var schemaPropertyValue = schemaProperty.GetValue(jsonSchemaPropertyValue); + + Assert.NotNull(schemaPropertyValue); + + var jsonSchema = JsonSerializer.Deserialize(JsonSerializer.Serialize(jsonSchemaProperty.GetValue(chatResponseFormat))); + + // Schema property gets serialized into a non-readable pattern in the jsonSchema JsonElement variable and needs to be returned separately. + var schema = JsonSerializer.Deserialize(schemaPropertyValue.ToString()!); + + return (jsonSchema, schema); } private sealed class BinaryDataJsonConverter : JsonConverter From 5d6c788f2e5f5699c187a7d759a1529add71651d Mon Sep 17 00:00:00 2001 From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com> Date: Thu, 17 Jul 2025 17:46:09 +0100 Subject: [PATCH 5/5] Fix inlining --- .../OpenAIResponseExtensionsTests.cs | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIResponseExtensionsTests.cs b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIResponseExtensionsTests.cs index a2f75f7a192b..8bd86c950e7b 100644 --- a/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIResponseExtensionsTests.cs +++ b/dotnet/src/Agents/UnitTests/OpenAI/Extensions/OpenAIResponseExtensionsTests.cs @@ -188,30 +188,30 @@ private OpenAIResponse CreateMockOpenAIResponse(string id, DateTimeOffset create null, [ typeof(IDictionary), - typeof(float?), - typeof(float?), - nullableInternalServiceTierType, - typeof(string), - typeof(bool?), - typeof(string), - typeof(IList), - typeof(string), - typeof(ResponseStatus?), - typeof(DateTimeOffset), - typeof(ResponseError), - typeof(ResponseTokenUsage), - typeof(string), - typeof(ResponseReasoningOptions), - typeof(int?), - typeof(ResponseTextOptions), - typeof(ResponseTruncationMode?), - typeof(ResponseIncompleteStatusDetails), - typeof(IList), - typeof(bool), - typeof(ResponseToolChoice), - typeof(string), - typeof(string), - typeof(IDictionary) + typeof(float?), + typeof(float?), + nullableInternalServiceTierType, + typeof(string), + typeof(bool?), + typeof(string), + typeof(IList), + typeof(string), + typeof(ResponseStatus?), + typeof(DateTimeOffset), + typeof(ResponseError), + typeof(ResponseTokenUsage), + typeof(string), + typeof(ResponseReasoningOptions), + typeof(int?), + typeof(ResponseTextOptions), + typeof(ResponseTruncationMode?), + typeof(ResponseIncompleteStatusDetails), + typeof(IList), + typeof(bool), + typeof(ResponseToolChoice), + typeof(string), + typeof(string), + typeof(IDictionary) ], null);