From f426c7a8faefc8890f3931633c9a9906a957459d Mon Sep 17 00:00:00 2001 From: Jose Luis Latorre Millas Date: Sun, 16 Feb 2025 18:12:18 +0100 Subject: [PATCH 1/4] Reasoning Effort demo --- dotnet/SK-dotnet.sln | 23 ++-- .../Demos/ReasoningEffortModels/AgentBase.cs | 103 ++++++++++++++++++ .../Demos/ReasoningEffortModels/Agents.cs | 43 ++++++++ .../Demos/ReasoningEffortModels/Class1.cs | 8 ++ .../Options/AzureOpenAIOptions.cs | 31 ++++++ .../Demos/ReasoningEffortModels/Program.cs | 67 ++++++++++++ .../ReasoningEffortModels.csproj | 23 ++++ 7 files changed, 291 insertions(+), 7 deletions(-) create mode 100644 dotnet/samples/Demos/ReasoningEffortModels/AgentBase.cs create mode 100644 dotnet/samples/Demos/ReasoningEffortModels/Agents.cs create mode 100644 dotnet/samples/Demos/ReasoningEffortModels/Class1.cs create mode 100644 dotnet/samples/Demos/ReasoningEffortModels/Options/AzureOpenAIOptions.cs create mode 100644 dotnet/samples/Demos/ReasoningEffortModels/Program.cs create mode 100644 dotnet/samples/Demos/ReasoningEffortModels/ReasoningEffortModels.csproj diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln index 21f3cbc1da67..89cc3c16041b 100644 --- a/dotnet/SK-dotnet.sln +++ b/dotnet/SK-dotnet.sln @@ -429,21 +429,23 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Process.IntegrationTests.Re EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OllamaFunctionCalling", "samples\Demos\OllamaFunctionCalling\OllamaFunctionCalling.csproj", "{481A680F-476A-4627-83DE-2F56C484525E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenAIRealtime", "samples\Demos\OpenAIRealtime\OpenAIRealtime.csproj", "{6154129E-7A35-44A5-998E-B7001B5EDE14}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenAIRealtime", "samples\Demos\OpenAIRealtime\OpenAIRealtime.csproj", "{6154129E-7A35-44A5-998E-B7001B5EDE14}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CreateChatGpt", "CreateChatGpt", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "sk-chatgpt-azure-function", "samples\Demos\CreateChatGptPlugin\MathPlugin\azure-function\sk-chatgpt-azure-function.csproj", "{2EB6E4C2-606D-B638-2E08-49EA2061C428}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "sk-chatgpt-azure-function", "samples\Demos\CreateChatGptPlugin\MathPlugin\azure-function\sk-chatgpt-azure-function.csproj", "{2EB6E4C2-606D-B638-2E08-49EA2061C428}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "kernel-functions-generator", "samples\Demos\CreateChatGptPlugin\MathPlugin\kernel-functions-generator\kernel-functions-generator.csproj", "{78785CB1-66CF-4895-D7E5-A440DD84BE86}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "kernel-functions-generator", "samples\Demos\CreateChatGptPlugin\MathPlugin\kernel-functions-generator\kernel-functions-generator.csproj", "{78785CB1-66CF-4895-D7E5-A440DD84BE86}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Agents.AzureAI", "src\Agents\AzureAI\Agents.AzureAI.csproj", "{EA35F1B5-9148-4189-BE34-5E00AED56D65}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Agents.AzureAI", "src\Agents\AzureAI\Agents.AzureAI.csproj", "{EA35F1B5-9148-4189-BE34-5E00AED56D65}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugins.AI", "src\Plugins\Plugins.AI\Plugins.AI.csproj", "{0C64EC81-8116-4388-87AD-BA14D4B59974}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugins.AI", "src\Plugins\Plugins.AI\Plugins.AI.csproj", "{0C64EC81-8116-4388-87AD-BA14D4B59974}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugins.AI.UnitTests", "src\Plugins\Plugins.AI.UnitTests\Plugins.AI.UnitTests.csproj", "{03ACF9DD-00C9-4F2B-80F1-537E2151AF5F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugins.AI.UnitTests", "src\Plugins\Plugins.AI.UnitTests\Plugins.AI.UnitTests.csproj", "{03ACF9DD-00C9-4F2B-80F1-537E2151AF5F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Connectors.Postgres.UnitTests", "src\Connectors\Connectors.Postgres.UnitTests\Connectors.Postgres.UnitTests.csproj", "{2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connectors.Postgres.UnitTests", "src\Connectors\Connectors.Postgres.UnitTests\Connectors.Postgres.UnitTests.csproj", "{2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReasoningEffortModels", "samples\Demos\ReasoningEffortModels\ReasoningEffortModels.csproj", "{6D6FF6FA-9168-403B-9477-996F84E84C83}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -1196,6 +1198,12 @@ Global {2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC}.Publish|Any CPU.Build.0 = Debug|Any CPU {2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC}.Release|Any CPU.ActiveCfg = Release|Any CPU {2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC}.Release|Any CPU.Build.0 = Release|Any CPU + {6D6FF6FA-9168-403B-9477-996F84E84C83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6D6FF6FA-9168-403B-9477-996F84E84C83}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6D6FF6FA-9168-403B-9477-996F84E84C83}.Publish|Any CPU.ActiveCfg = Debug|Any CPU + {6D6FF6FA-9168-403B-9477-996F84E84C83}.Publish|Any CPU.Build.0 = Debug|Any CPU + {6D6FF6FA-9168-403B-9477-996F84E84C83}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6D6FF6FA-9168-403B-9477-996F84E84C83}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1360,6 +1368,7 @@ Global {0C64EC81-8116-4388-87AD-BA14D4B59974} = {D6D598DF-C17C-46F4-B2B9-CDE82E2DE132} {03ACF9DD-00C9-4F2B-80F1-537E2151AF5F} = {D6D598DF-C17C-46F4-B2B9-CDE82E2DE132} {2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC} = {5A7028A7-4DDF-4E4F-84A9-37CE8F8D7E89} + {6D6FF6FA-9168-403B-9477-996F84E84C83} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83} diff --git a/dotnet/samples/Demos/ReasoningEffortModels/AgentBase.cs b/dotnet/samples/Demos/ReasoningEffortModels/AgentBase.cs new file mode 100644 index 000000000000..85647a4684a3 --- /dev/null +++ b/dotnet/samples/Demos/ReasoningEffortModels/AgentBase.cs @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.Extensions.Configuration; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.Connectors.OpenAI; +using OpenAI.Chat; +using ReasoningEffortModels.Options; + +#pragma warning disable SKEXP0110, SKEXP0001, SKEXP0050, CS8600, CS8604 + +namespace ReasoningEffortModels; + +public abstract class AgentBase +{ + protected readonly Kernel _kernel; + // Store the reasoning effort level for use later + protected readonly ChatReasoningEffortLevel _reasoningEffort; + + public abstract string AgentName { get; } + public abstract string AgentPrompt { get; } + public abstract string DefaultDeploymentName { get; } + + /// + /// Initializes a new instance of the AgentBase class. + /// + /// An optional custom kernel. + /// An optional deployment name. If not provided, DefaultDeploymentName is used. + /// An optional reasoning effort level. Defaults to Medium if not provided. + public AgentBase( + Kernel? customKernel = null, + string? deploymentName = null, + ChatReasoningEffortLevel? reasoningEffort = null) + { + this._reasoningEffort = reasoningEffort ?? ChatReasoningEffortLevel.Low; + + var config = new ConfigurationBuilder() + .AddUserSecrets() + .AddEnvironmentVariables() + .Build(); + + var azureOpenAIOptions = config.GetSection(AzureOpenAIOptions.SectionName).Get(); + + + string chosenDeployment = string.IsNullOrWhiteSpace(deploymentName) + ? this.DefaultDeploymentName + : deploymentName; + + if (customKernel is null) + { + var builder = Kernel.CreateBuilder(); + this._kernel = builder.AddAzureOpenAIChatCompletion( + deploymentName: chosenDeployment, + endpoint: azureOpenAIOptions!.Endpoint, + apiKey: azureOpenAIOptions.ApiKey) + .Build(); + } + else + { + this._kernel = customKernel; + } + } + + /// + /// Determines if the provided model identifier corresponds to a reasoning model. + /// Adjust the logic as needed for your specific model identifiers. + /// + /// The model identifier to check. + /// True if it is a reasoning model; otherwise, false. + private bool IsReasoningModel(string model) + { + // Example logic: treat "o1" and "o3-mini" as reasoning models. + return model.Equals("o1", StringComparison.OrdinalIgnoreCase) || + model.Equals("o3-mini", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Creates and returns a ChatCompletionAgent with appropriate execution settings. + /// + /// A configured ChatCompletionAgent. + public ChatCompletionAgent CreateAgent() + { + // Initialize the prompt execution settings. + OpenAIPromptExecutionSettings executionSettings = new() + { + ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions, + }; + + // If the deployment model supports reasoning, add the reasoning effort level. + if (this.IsReasoningModel(this.DefaultDeploymentName)) + { + executionSettings.ReasoningEffort = this._reasoningEffort; + } + + return new ChatCompletionAgent + { + Name = this.AgentName, + Instructions = this.AgentPrompt, + Kernel = this._kernel, + Arguments = new KernelArguments(executionSettings) + }; + } +} diff --git a/dotnet/samples/Demos/ReasoningEffortModels/Agents.cs b/dotnet/samples/Demos/ReasoningEffortModels/Agents.cs new file mode 100644 index 000000000000..0d7315da7dfb --- /dev/null +++ b/dotnet/samples/Demos/ReasoningEffortModels/Agents.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. + +using OpenAI.Chat; + +namespace ReasoningEffortModels; +// A smart blog post agent that asks for an analytical blog post about Semantic Kernel. +public class SmartBlogPostAgent : AgentBase +{ + public override string AgentName => "SmartBlogPostAgent"; + public override string AgentPrompt => "Write a smart, detailed blog post on Semantic Kernel that explains its architecture, benefits, and real-world applications."; + public override string DefaultDeploymentName => "o3-mini"; + + public SmartBlogPostAgent(ChatReasoningEffortLevel reasoningEffort) + : base(reasoningEffort: reasoningEffort) + { + } +} + +// A poem agent that asks for a creative poem about Semantic Kernel. +public class PoemAgent : AgentBase +{ + public override string AgentName => "PoemAgent"; + public override string AgentPrompt => "Compose a creative and whimsical poem about Semantic Kernel and its innovative approach to artificial intelligence."; + public override string DefaultDeploymentName => "o3-mini"; + + public PoemAgent(ChatReasoningEffortLevel reasoningEffort) + : base(reasoningEffort: reasoningEffort) + { + } +} + +// A code example agent that asks for sample code demonstrating the use of Semantic Kernel. +public class CodeExampleAgent : AgentBase +{ + public override string AgentName => "CodeExampleAgent"; + public override string AgentPrompt => "Provide a sample C# code snippet that demonstrates how to integrate Semantic Kernel into a creative project, including explanations for each step."; + public override string DefaultDeploymentName => "o3-mini"; + + public CodeExampleAgent(ChatReasoningEffortLevel reasoningEffort) + : base(reasoningEffort: reasoningEffort) + { + } +} diff --git a/dotnet/samples/Demos/ReasoningEffortModels/Class1.cs b/dotnet/samples/Demos/ReasoningEffortModels/Class1.cs new file mode 100644 index 000000000000..7db67c42cac8 --- /dev/null +++ b/dotnet/samples/Demos/ReasoningEffortModels/Class1.cs @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace ReasoningEffortModels; + +public class Class1 +{ + +} diff --git a/dotnet/samples/Demos/ReasoningEffortModels/Options/AzureOpenAIOptions.cs b/dotnet/samples/Demos/ReasoningEffortModels/Options/AzureOpenAIOptions.cs new file mode 100644 index 000000000000..f24840aba611 --- /dev/null +++ b/dotnet/samples/Demos/ReasoningEffortModels/Options/AzureOpenAIOptions.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace ReasoningEffortModels.Options; + +/// +/// Configuration for Azure OpenAI service. +/// +public class AzureOpenAIOptions +{ + public const string SectionName = "AzureOpenAI"; + + /// + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// + public string DeploymentName { get; set; } + + /// + /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// + public string Endpoint { get; set; } + + /// + /// Azure OpenAI API key, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// + public string ApiKey { get; set; } + + public bool IsValid => + !string.IsNullOrWhiteSpace(this.DeploymentName) && + !string.IsNullOrWhiteSpace(this.Endpoint) && + !string.IsNullOrWhiteSpace(this.ApiKey); +} diff --git a/dotnet/samples/Demos/ReasoningEffortModels/Program.cs b/dotnet/samples/Demos/ReasoningEffortModels/Program.cs new file mode 100644 index 000000000000..9eaf1b0f19b1 --- /dev/null +++ b/dotnet/samples/Demos/ReasoningEffortModels/Program.cs @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Agents; +using Microsoft.SemanticKernel.Connectors.AzureOpenAI; +using Microsoft.SemanticKernel.ChatCompletion; +using OpenAI.Chat; + +#pragma warning disable SKEXP0110, SKEXP0001, SKEXP0050, CS8600, CS8604, SKEXP0010 + +namespace ReasoningEffortModels; + +internal class Program +{ + // Entry point: creates and runs three agents with different reasoning efforts. + private static async Task Main(string[] args) + { + // Instantiate each agent with its designated reasoning effort + var smartBlogPostAgent = new SmartBlogPostAgent(ChatReasoningEffortLevel.Low); + var poemAgent = new PoemAgent(ChatReasoningEffortLevel.Medium); + var codeExampleAgent = new CodeExampleAgent(ChatReasoningEffortLevel.High); + + // Demonstrate Smart Blog Post Agent (Low reasoning) + Console.WriteLine("=== Smart Blog Post Demo (Low Reasoning) ==="); + var blogAgentInstance = smartBlogPostAgent.CreateAgent(); + await InvokeAgentAsync(blogAgentInstance, "Please generate a detailed blog post about Semantic Kernel that covers its architecture, benefits, and real-world applications."); + + // Demonstrate Poem Agent (Medium reasoning) + Console.WriteLine("\n=== Poem Demo (Medium Reasoning) ==="); + var poemAgentInstance = poemAgent.CreateAgent(); + await InvokeAgentAsync(poemAgentInstance, "Write a creative, whimsical poem about Semantic Kernel and its innovative approach to artificial intelligence."); + + // Demonstrate Code Example Agent (High reasoning) + Console.WriteLine("\n=== Code Example Demo (High Reasoning) ==="); + var codeAgentInstance = codeExampleAgent.CreateAgent(); + await InvokeAgentAsync(codeAgentInstance, "Show me a sample C# code snippet that demonstrates how to integrate Semantic Kernel into a creative project, including explanations for each step."); + + Console.WriteLine("Demo complete. Press any key to exit."); + Console.ReadKey(); + } + + /// + /// Helper method to invoke an agent with a given user prompt and print out the conversation. + /// + /// The ChatCompletionAgent to invoke. + /// The user prompt to send to the agent. + /// A Task representing the asynchronous operation. + private static async Task InvokeAgentAsync(ChatCompletionAgent agent, string ask) + { + // Create a new chat history instance. + ChatHistory chat = new(); + + // Add the user message (the ask). + Microsoft.SemanticKernel.ChatMessageContent userMessage = new(AuthorRole.User, ask); + + chat.Add(userMessage); + Console.WriteLine($"> User: {ask}"); + + // Invoke the agent asynchronously. The agent's InvokeAsync returns an async stream of responses. + await foreach (var response in agent.InvokeAsync(chat)) + { + // Add each agent response to the chat history and print it. + chat.Add(response); + Console.WriteLine($"> {response.Role}: {response.Content}"); + } + } +} diff --git a/dotnet/samples/Demos/ReasoningEffortModels/ReasoningEffortModels.csproj b/dotnet/samples/Demos/ReasoningEffortModels/ReasoningEffortModels.csproj new file mode 100644 index 000000000000..2bdc8b71185f --- /dev/null +++ b/dotnet/samples/Demos/ReasoningEffortModels/ReasoningEffortModels.csproj @@ -0,0 +1,23 @@ + + + + Exe + net8.0 + enable + enable + $(NoWarn);VSTHRD111,CA2007,CS8618,CS1591,CA1052,SKEXP0001,SKEXP0010 + 5ee045b0-aea3-4f08-8d31-32d1a6f8fed0 + + + + + + + + + + + + + + From a07d921a0704f757672519e051071b01c814f4b9 Mon Sep 17 00:00:00 2001 From: Jose Luis Latorre Millas Date: Sun, 16 Feb 2025 18:17:34 +0100 Subject: [PATCH 2/4] improvements and fixes --- dotnet/samples/Demos/ReasoningEffortModels/AgentBase.cs | 9 +++------ dotnet/samples/Demos/ReasoningEffortModels/Class1.cs | 8 -------- dotnet/samples/Demos/ReasoningEffortModels/Program.cs | 4 +--- .../ReasoningEffortModels/ReasoningEffortModels.csproj | 2 +- 4 files changed, 5 insertions(+), 18 deletions(-) delete mode 100644 dotnet/samples/Demos/ReasoningEffortModels/Class1.cs diff --git a/dotnet/samples/Demos/ReasoningEffortModels/AgentBase.cs b/dotnet/samples/Demos/ReasoningEffortModels/AgentBase.cs index 85647a4684a3..77ce20775e2b 100644 --- a/dotnet/samples/Demos/ReasoningEffortModels/AgentBase.cs +++ b/dotnet/samples/Demos/ReasoningEffortModels/AgentBase.cs @@ -7,15 +7,13 @@ using OpenAI.Chat; using ReasoningEffortModels.Options; -#pragma warning disable SKEXP0110, SKEXP0001, SKEXP0050, CS8600, CS8604 - namespace ReasoningEffortModels; public abstract class AgentBase { - protected readonly Kernel _kernel; + private protected readonly Kernel _kernel; // Store the reasoning effort level for use later - protected readonly ChatReasoningEffortLevel _reasoningEffort; + private protected readonly ChatReasoningEffortLevel _reasoningEffort; public abstract string AgentName { get; } public abstract string AgentPrompt { get; } @@ -27,7 +25,7 @@ public abstract class AgentBase /// An optional custom kernel. /// An optional deployment name. If not provided, DefaultDeploymentName is used. /// An optional reasoning effort level. Defaults to Medium if not provided. - public AgentBase( + protected AgentBase( Kernel? customKernel = null, string? deploymentName = null, ChatReasoningEffortLevel? reasoningEffort = null) @@ -41,7 +39,6 @@ public AgentBase( var azureOpenAIOptions = config.GetSection(AzureOpenAIOptions.SectionName).Get(); - string chosenDeployment = string.IsNullOrWhiteSpace(deploymentName) ? this.DefaultDeploymentName : deploymentName; diff --git a/dotnet/samples/Demos/ReasoningEffortModels/Class1.cs b/dotnet/samples/Demos/ReasoningEffortModels/Class1.cs deleted file mode 100644 index 7db67c42cac8..000000000000 --- a/dotnet/samples/Demos/ReasoningEffortModels/Class1.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace ReasoningEffortModels; - -public class Class1 -{ - -} diff --git a/dotnet/samples/Demos/ReasoningEffortModels/Program.cs b/dotnet/samples/Demos/ReasoningEffortModels/Program.cs index 9eaf1b0f19b1..571d04f4fefc 100644 --- a/dotnet/samples/Demos/ReasoningEffortModels/Program.cs +++ b/dotnet/samples/Demos/ReasoningEffortModels/Program.cs @@ -1,8 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. -using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Agents; -using Microsoft.SemanticKernel.Connectors.AzureOpenAI; using Microsoft.SemanticKernel.ChatCompletion; using OpenAI.Chat; @@ -10,7 +8,7 @@ namespace ReasoningEffortModels; -internal class Program +internal sealed class Program { // Entry point: creates and runs three agents with different reasoning efforts. private static async Task Main(string[] args) diff --git a/dotnet/samples/Demos/ReasoningEffortModels/ReasoningEffortModels.csproj b/dotnet/samples/Demos/ReasoningEffortModels/ReasoningEffortModels.csproj index 2bdc8b71185f..8032edc14899 100644 --- a/dotnet/samples/Demos/ReasoningEffortModels/ReasoningEffortModels.csproj +++ b/dotnet/samples/Demos/ReasoningEffortModels/ReasoningEffortModels.csproj @@ -5,7 +5,7 @@ net8.0 enable enable - $(NoWarn);VSTHRD111,CA2007,CS8618,CS1591,CA1052,SKEXP0001,SKEXP0010 + $(NoWarn);VSTHRD111,CA2007,CS8618,CS1591,CA1052,SKEXP0001,SKEXP0010,SKEXP0110,SKEXP0050,CS8600,CS8604 5ee045b0-aea3-4f08-8d31-32d1a6f8fed0 From e5d09c56b39cf0e1fafc4339287f3e449088c12f Mon Sep 17 00:00:00 2001 From: Jose Luis Latorre Millas Date: Sun, 16 Feb 2025 20:43:05 +0100 Subject: [PATCH 3/4] README and tests. --- .../Demos/ReasoningEffortModels/README.md | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 dotnet/samples/Demos/ReasoningEffortModels/README.md diff --git a/dotnet/samples/Demos/ReasoningEffortModels/README.md b/dotnet/samples/Demos/ReasoningEffortModels/README.md new file mode 100644 index 000000000000..04d9698ce919 --- /dev/null +++ b/dotnet/samples/Demos/ReasoningEffortModels/README.md @@ -0,0 +1,68 @@ +# Reasoning Effort Models Demo + +This demo application illustrates how to leverage different reasoning effort levels when using agentic AI with Semantic Kernel. The sample project showcases three distinct agents: + +- **Smart Blog Post Agent (Low Reasoning):** Generates an analytical blog post on Semantic Kernel. +- **Poem Agent (Medium Reasoning):** Composes a creative poem about Semantic Kernel. +- **Code Example Agent (High Reasoning):** Provides a sample C# code snippet demonstrating integration with Semantic Kernel. + +Each agent uses the `ChatCompletionAgent` with reasoning parameters configured via an extended `AgentBase` class. + +## Prerequisites + +- [.NET 8 SDK](https://dotnet.microsoft.com/download) +- Valid Azure OpenAI credentials (Deployment Name, Endpoint, and API Key) + +## Configuration + +The application loads its configuration from either **User Secrets** or **Environment Variables**. + +### Using User Secrets (Secret Manager) + +**Initialize User Secrets:** + Open a terminal in the project directory and run: + ```bash + dotnet user-secrets init + ``` + +Set Your Azure OpenAI Credentials: +Replace the placeholders with your actual credentials: + +``` +dotnet user-secrets set "AzureOpenAI:DeploymentName" "your-deployment-name" +dotnet user-secrets set "AzureOpenAI:Endpoint" "https://your-resource-name.openai.azure.com/" +dotnet user-secrets set "AzureOpenAI:ApiKey" "your-api-key" +``` +Note: We are assigning the deployment in the Agent creation, so DeploymentName is not needed. If you add it, it will override the Agent Creation one (used in the AgentBase class constructor) + +### Using environment variables + +Use these names: + +``` +# OpenAI +OpenAI__ApiKey + +# Azure OpenAI +AzureOpenAI__DeploymentName +AzureOpenAI__Endpoint +AzureOpenAI__ApiKey +``` + +## Running the Application +1. Set as Default Startup Project: + - In Visual Studio, right-click the ReasoningEffortModels project in the Solution Explorer and select "Set as Startup Project." +2. Build and Run: + - Via Visual Studio: Press F5 or click the "Start" button. + - Via Command Line: Navigate to the project folder and run: +``` +dotnet build +dotnet run +``` +The console will display the output for each of the three agents, demonstrating how different reasoning effort levels affect the responses. + +## Project Structure +- AgentBase.cs: Contains the abstract base class that sets up the Semantic Kernel and configures reasoning effort. +- SmartBlogPostAgent.cs, PoemAgent.cs, CodeExampleAgent.cs: Implementations of agents with different reasoning settings. +- Program.cs: The demo entry point that instantiates each agent and invokes them with sample prompts. +- Options/AzureOpenAIOptions.cs: Holds the configuration options for connecting to Azure OpenAI. \ No newline at end of file From 6c136f05028aedd4e8c29383d9ac7795ba00b1d4 Mon Sep 17 00:00:00 2001 From: Jose Luis Latorre Millas Date: Thu, 20 Feb 2025 19:47:13 +0100 Subject: [PATCH 4/4] simplified. --- dotnet/SK-dotnet.sln | 9 -- ...zureOpenAI_ChatCompletion_WithReasoning.cs | 47 ++++++++ .../Demos/ReasoningEffortModels/AgentBase.cs | 100 ------------------ .../Demos/ReasoningEffortModels/Agents.cs | 43 -------- .../Options/AzureOpenAIOptions.cs | 31 ------ .../Demos/ReasoningEffortModels/Program.cs | 65 ------------ .../Demos/ReasoningEffortModels/README.md | 68 ------------ .../ReasoningEffortModels.csproj | 23 ---- 8 files changed, 47 insertions(+), 339 deletions(-) create mode 100644 dotnet/samples/Concepts/ChatCompletion/AzureOpenAI_ChatCompletion_WithReasoning.cs delete mode 100644 dotnet/samples/Demos/ReasoningEffortModels/AgentBase.cs delete mode 100644 dotnet/samples/Demos/ReasoningEffortModels/Agents.cs delete mode 100644 dotnet/samples/Demos/ReasoningEffortModels/Options/AzureOpenAIOptions.cs delete mode 100644 dotnet/samples/Demos/ReasoningEffortModels/Program.cs delete mode 100644 dotnet/samples/Demos/ReasoningEffortModels/README.md delete mode 100644 dotnet/samples/Demos/ReasoningEffortModels/ReasoningEffortModels.csproj diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln index 89cc3c16041b..ad6e0b577e88 100644 --- a/dotnet/SK-dotnet.sln +++ b/dotnet/SK-dotnet.sln @@ -445,8 +445,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugins.AI.UnitTests", "src EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connectors.Postgres.UnitTests", "src\Connectors\Connectors.Postgres.UnitTests\Connectors.Postgres.UnitTests.csproj", "{2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReasoningEffortModels", "samples\Demos\ReasoningEffortModels\ReasoningEffortModels.csproj", "{6D6FF6FA-9168-403B-9477-996F84E84C83}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1198,12 +1196,6 @@ Global {2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC}.Publish|Any CPU.Build.0 = Debug|Any CPU {2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC}.Release|Any CPU.ActiveCfg = Release|Any CPU {2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC}.Release|Any CPU.Build.0 = Release|Any CPU - {6D6FF6FA-9168-403B-9477-996F84E84C83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6D6FF6FA-9168-403B-9477-996F84E84C83}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6D6FF6FA-9168-403B-9477-996F84E84C83}.Publish|Any CPU.ActiveCfg = Debug|Any CPU - {6D6FF6FA-9168-403B-9477-996F84E84C83}.Publish|Any CPU.Build.0 = Debug|Any CPU - {6D6FF6FA-9168-403B-9477-996F84E84C83}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6D6FF6FA-9168-403B-9477-996F84E84C83}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1368,7 +1360,6 @@ Global {0C64EC81-8116-4388-87AD-BA14D4B59974} = {D6D598DF-C17C-46F4-B2B9-CDE82E2DE132} {03ACF9DD-00C9-4F2B-80F1-537E2151AF5F} = {D6D598DF-C17C-46F4-B2B9-CDE82E2DE132} {2A1EC0DA-AD01-4421-AADC-1DFF65C71CCC} = {5A7028A7-4DDF-4E4F-84A9-37CE8F8D7E89} - {6D6FF6FA-9168-403B-9477-996F84E84C83} = {5D4C0700-BBB5-418F-A7B2-F392B9A18263} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {FBDC56A3-86AD-4323-AA0F-201E59123B83} diff --git a/dotnet/samples/Concepts/ChatCompletion/AzureOpenAI_ChatCompletion_WithReasoning.cs b/dotnet/samples/Concepts/ChatCompletion/AzureOpenAI_ChatCompletion_WithReasoning.cs new file mode 100644 index 000000000000..04e5cd0d1905 --- /dev/null +++ b/dotnet/samples/Concepts/ChatCompletion/AzureOpenAI_ChatCompletion_WithReasoning.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Text; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Connectors.OpenAI; +using OpenAI.Chat; + +namespace ChatCompletion; + +// The following example shows how to use Semantic Kernel with Azure OpenAI API +public class AzureOpenAI_ChatCompletion_WithReasoning(ITestOutputHelper output) : BaseTest(output) +{ + [Fact] + public async Task ChatPromptWithReasoningAsync() + { + Assert.NotNull(TestConfiguration.AzureOpenAI.ModelId); + + StringBuilder chatPrompt = new(""" + You are an expert software engineer, specialized in the Semantic Kernel SDK and NET framework + Hi, Please craft me an example code in .NET using Semantic Kernel that implements a chat loop . + """); + + var kernel = Kernel.CreateBuilder() + .AddAzureOpenAIChatCompletion( + deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName, + endpoint: TestConfiguration.AzureOpenAI.Endpoint, + apiKey: TestConfiguration.AzureOpenAI.ApiKey, + modelId: TestConfiguration.AzureOpenAI.ChatModelId) + .Build(); + + // Create execution settings with high reasoning effort. + var executionSettings = new OpenAIPromptExecutionSettings + { + FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(), + // Note: reasoning effort is only available for reasoning models (at this moment o3-mini & o1 models) + ReasoningEffort = ChatReasoningEffortLevel.High + }; + + // Create KernelArguments using the execution settings. + var kernelArgs = new KernelArguments(executionSettings); + + // Invoke the prompt with high reasoning effort. + var reply = await kernel.InvokePromptAsync(chatPrompt.ToString(), kernelArgs); + + Console.WriteLine(reply); + } +} diff --git a/dotnet/samples/Demos/ReasoningEffortModels/AgentBase.cs b/dotnet/samples/Demos/ReasoningEffortModels/AgentBase.cs deleted file mode 100644 index 77ce20775e2b..000000000000 --- a/dotnet/samples/Demos/ReasoningEffortModels/AgentBase.cs +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.Extensions.Configuration; -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Agents; -using Microsoft.SemanticKernel.Connectors.OpenAI; -using OpenAI.Chat; -using ReasoningEffortModels.Options; - -namespace ReasoningEffortModels; - -public abstract class AgentBase -{ - private protected readonly Kernel _kernel; - // Store the reasoning effort level for use later - private protected readonly ChatReasoningEffortLevel _reasoningEffort; - - public abstract string AgentName { get; } - public abstract string AgentPrompt { get; } - public abstract string DefaultDeploymentName { get; } - - /// - /// Initializes a new instance of the AgentBase class. - /// - /// An optional custom kernel. - /// An optional deployment name. If not provided, DefaultDeploymentName is used. - /// An optional reasoning effort level. Defaults to Medium if not provided. - protected AgentBase( - Kernel? customKernel = null, - string? deploymentName = null, - ChatReasoningEffortLevel? reasoningEffort = null) - { - this._reasoningEffort = reasoningEffort ?? ChatReasoningEffortLevel.Low; - - var config = new ConfigurationBuilder() - .AddUserSecrets() - .AddEnvironmentVariables() - .Build(); - - var azureOpenAIOptions = config.GetSection(AzureOpenAIOptions.SectionName).Get(); - - string chosenDeployment = string.IsNullOrWhiteSpace(deploymentName) - ? this.DefaultDeploymentName - : deploymentName; - - if (customKernel is null) - { - var builder = Kernel.CreateBuilder(); - this._kernel = builder.AddAzureOpenAIChatCompletion( - deploymentName: chosenDeployment, - endpoint: azureOpenAIOptions!.Endpoint, - apiKey: azureOpenAIOptions.ApiKey) - .Build(); - } - else - { - this._kernel = customKernel; - } - } - - /// - /// Determines if the provided model identifier corresponds to a reasoning model. - /// Adjust the logic as needed for your specific model identifiers. - /// - /// The model identifier to check. - /// True if it is a reasoning model; otherwise, false. - private bool IsReasoningModel(string model) - { - // Example logic: treat "o1" and "o3-mini" as reasoning models. - return model.Equals("o1", StringComparison.OrdinalIgnoreCase) || - model.Equals("o3-mini", StringComparison.OrdinalIgnoreCase); - } - - /// - /// Creates and returns a ChatCompletionAgent with appropriate execution settings. - /// - /// A configured ChatCompletionAgent. - public ChatCompletionAgent CreateAgent() - { - // Initialize the prompt execution settings. - OpenAIPromptExecutionSettings executionSettings = new() - { - ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions, - }; - - // If the deployment model supports reasoning, add the reasoning effort level. - if (this.IsReasoningModel(this.DefaultDeploymentName)) - { - executionSettings.ReasoningEffort = this._reasoningEffort; - } - - return new ChatCompletionAgent - { - Name = this.AgentName, - Instructions = this.AgentPrompt, - Kernel = this._kernel, - Arguments = new KernelArguments(executionSettings) - }; - } -} diff --git a/dotnet/samples/Demos/ReasoningEffortModels/Agents.cs b/dotnet/samples/Demos/ReasoningEffortModels/Agents.cs deleted file mode 100644 index 0d7315da7dfb..000000000000 --- a/dotnet/samples/Demos/ReasoningEffortModels/Agents.cs +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using OpenAI.Chat; - -namespace ReasoningEffortModels; -// A smart blog post agent that asks for an analytical blog post about Semantic Kernel. -public class SmartBlogPostAgent : AgentBase -{ - public override string AgentName => "SmartBlogPostAgent"; - public override string AgentPrompt => "Write a smart, detailed blog post on Semantic Kernel that explains its architecture, benefits, and real-world applications."; - public override string DefaultDeploymentName => "o3-mini"; - - public SmartBlogPostAgent(ChatReasoningEffortLevel reasoningEffort) - : base(reasoningEffort: reasoningEffort) - { - } -} - -// A poem agent that asks for a creative poem about Semantic Kernel. -public class PoemAgent : AgentBase -{ - public override string AgentName => "PoemAgent"; - public override string AgentPrompt => "Compose a creative and whimsical poem about Semantic Kernel and its innovative approach to artificial intelligence."; - public override string DefaultDeploymentName => "o3-mini"; - - public PoemAgent(ChatReasoningEffortLevel reasoningEffort) - : base(reasoningEffort: reasoningEffort) - { - } -} - -// A code example agent that asks for sample code demonstrating the use of Semantic Kernel. -public class CodeExampleAgent : AgentBase -{ - public override string AgentName => "CodeExampleAgent"; - public override string AgentPrompt => "Provide a sample C# code snippet that demonstrates how to integrate Semantic Kernel into a creative project, including explanations for each step."; - public override string DefaultDeploymentName => "o3-mini"; - - public CodeExampleAgent(ChatReasoningEffortLevel reasoningEffort) - : base(reasoningEffort: reasoningEffort) - { - } -} diff --git a/dotnet/samples/Demos/ReasoningEffortModels/Options/AzureOpenAIOptions.cs b/dotnet/samples/Demos/ReasoningEffortModels/Options/AzureOpenAIOptions.cs deleted file mode 100644 index f24840aba611..000000000000 --- a/dotnet/samples/Demos/ReasoningEffortModels/Options/AzureOpenAIOptions.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace ReasoningEffortModels.Options; - -/// -/// Configuration for Azure OpenAI service. -/// -public class AzureOpenAIOptions -{ - public const string SectionName = "AzureOpenAI"; - - /// - /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource - /// - public string DeploymentName { get; set; } - - /// - /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart - /// - public string Endpoint { get; set; } - - /// - /// Azure OpenAI API key, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart - /// - public string ApiKey { get; set; } - - public bool IsValid => - !string.IsNullOrWhiteSpace(this.DeploymentName) && - !string.IsNullOrWhiteSpace(this.Endpoint) && - !string.IsNullOrWhiteSpace(this.ApiKey); -} diff --git a/dotnet/samples/Demos/ReasoningEffortModels/Program.cs b/dotnet/samples/Demos/ReasoningEffortModels/Program.cs deleted file mode 100644 index 571d04f4fefc..000000000000 --- a/dotnet/samples/Demos/ReasoningEffortModels/Program.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.SemanticKernel.Agents; -using Microsoft.SemanticKernel.ChatCompletion; -using OpenAI.Chat; - -#pragma warning disable SKEXP0110, SKEXP0001, SKEXP0050, CS8600, CS8604, SKEXP0010 - -namespace ReasoningEffortModels; - -internal sealed class Program -{ - // Entry point: creates and runs three agents with different reasoning efforts. - private static async Task Main(string[] args) - { - // Instantiate each agent with its designated reasoning effort - var smartBlogPostAgent = new SmartBlogPostAgent(ChatReasoningEffortLevel.Low); - var poemAgent = new PoemAgent(ChatReasoningEffortLevel.Medium); - var codeExampleAgent = new CodeExampleAgent(ChatReasoningEffortLevel.High); - - // Demonstrate Smart Blog Post Agent (Low reasoning) - Console.WriteLine("=== Smart Blog Post Demo (Low Reasoning) ==="); - var blogAgentInstance = smartBlogPostAgent.CreateAgent(); - await InvokeAgentAsync(blogAgentInstance, "Please generate a detailed blog post about Semantic Kernel that covers its architecture, benefits, and real-world applications."); - - // Demonstrate Poem Agent (Medium reasoning) - Console.WriteLine("\n=== Poem Demo (Medium Reasoning) ==="); - var poemAgentInstance = poemAgent.CreateAgent(); - await InvokeAgentAsync(poemAgentInstance, "Write a creative, whimsical poem about Semantic Kernel and its innovative approach to artificial intelligence."); - - // Demonstrate Code Example Agent (High reasoning) - Console.WriteLine("\n=== Code Example Demo (High Reasoning) ==="); - var codeAgentInstance = codeExampleAgent.CreateAgent(); - await InvokeAgentAsync(codeAgentInstance, "Show me a sample C# code snippet that demonstrates how to integrate Semantic Kernel into a creative project, including explanations for each step."); - - Console.WriteLine("Demo complete. Press any key to exit."); - Console.ReadKey(); - } - - /// - /// Helper method to invoke an agent with a given user prompt and print out the conversation. - /// - /// The ChatCompletionAgent to invoke. - /// The user prompt to send to the agent. - /// A Task representing the asynchronous operation. - private static async Task InvokeAgentAsync(ChatCompletionAgent agent, string ask) - { - // Create a new chat history instance. - ChatHistory chat = new(); - - // Add the user message (the ask). - Microsoft.SemanticKernel.ChatMessageContent userMessage = new(AuthorRole.User, ask); - - chat.Add(userMessage); - Console.WriteLine($"> User: {ask}"); - - // Invoke the agent asynchronously. The agent's InvokeAsync returns an async stream of responses. - await foreach (var response in agent.InvokeAsync(chat)) - { - // Add each agent response to the chat history and print it. - chat.Add(response); - Console.WriteLine($"> {response.Role}: {response.Content}"); - } - } -} diff --git a/dotnet/samples/Demos/ReasoningEffortModels/README.md b/dotnet/samples/Demos/ReasoningEffortModels/README.md deleted file mode 100644 index 04d9698ce919..000000000000 --- a/dotnet/samples/Demos/ReasoningEffortModels/README.md +++ /dev/null @@ -1,68 +0,0 @@ -# Reasoning Effort Models Demo - -This demo application illustrates how to leverage different reasoning effort levels when using agentic AI with Semantic Kernel. The sample project showcases three distinct agents: - -- **Smart Blog Post Agent (Low Reasoning):** Generates an analytical blog post on Semantic Kernel. -- **Poem Agent (Medium Reasoning):** Composes a creative poem about Semantic Kernel. -- **Code Example Agent (High Reasoning):** Provides a sample C# code snippet demonstrating integration with Semantic Kernel. - -Each agent uses the `ChatCompletionAgent` with reasoning parameters configured via an extended `AgentBase` class. - -## Prerequisites - -- [.NET 8 SDK](https://dotnet.microsoft.com/download) -- Valid Azure OpenAI credentials (Deployment Name, Endpoint, and API Key) - -## Configuration - -The application loads its configuration from either **User Secrets** or **Environment Variables**. - -### Using User Secrets (Secret Manager) - -**Initialize User Secrets:** - Open a terminal in the project directory and run: - ```bash - dotnet user-secrets init - ``` - -Set Your Azure OpenAI Credentials: -Replace the placeholders with your actual credentials: - -``` -dotnet user-secrets set "AzureOpenAI:DeploymentName" "your-deployment-name" -dotnet user-secrets set "AzureOpenAI:Endpoint" "https://your-resource-name.openai.azure.com/" -dotnet user-secrets set "AzureOpenAI:ApiKey" "your-api-key" -``` -Note: We are assigning the deployment in the Agent creation, so DeploymentName is not needed. If you add it, it will override the Agent Creation one (used in the AgentBase class constructor) - -### Using environment variables - -Use these names: - -``` -# OpenAI -OpenAI__ApiKey - -# Azure OpenAI -AzureOpenAI__DeploymentName -AzureOpenAI__Endpoint -AzureOpenAI__ApiKey -``` - -## Running the Application -1. Set as Default Startup Project: - - In Visual Studio, right-click the ReasoningEffortModels project in the Solution Explorer and select "Set as Startup Project." -2. Build and Run: - - Via Visual Studio: Press F5 or click the "Start" button. - - Via Command Line: Navigate to the project folder and run: -``` -dotnet build -dotnet run -``` -The console will display the output for each of the three agents, demonstrating how different reasoning effort levels affect the responses. - -## Project Structure -- AgentBase.cs: Contains the abstract base class that sets up the Semantic Kernel and configures reasoning effort. -- SmartBlogPostAgent.cs, PoemAgent.cs, CodeExampleAgent.cs: Implementations of agents with different reasoning settings. -- Program.cs: The demo entry point that instantiates each agent and invokes them with sample prompts. -- Options/AzureOpenAIOptions.cs: Holds the configuration options for connecting to Azure OpenAI. \ No newline at end of file diff --git a/dotnet/samples/Demos/ReasoningEffortModels/ReasoningEffortModels.csproj b/dotnet/samples/Demos/ReasoningEffortModels/ReasoningEffortModels.csproj deleted file mode 100644 index 8032edc14899..000000000000 --- a/dotnet/samples/Demos/ReasoningEffortModels/ReasoningEffortModels.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - Exe - net8.0 - enable - enable - $(NoWarn);VSTHRD111,CA2007,CS8618,CS1591,CA1052,SKEXP0001,SKEXP0010,SKEXP0110,SKEXP0050,CS8600,CS8604 - 5ee045b0-aea3-4f08-8d31-32d1a6f8fed0 - - - - - - - - - - - - - -