From 4487ca3d80df8f34624a3c24debea0d6995b9c4c Mon Sep 17 00:00:00 2001 From: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com> Date: Tue, 12 Dec 2023 15:30:14 +0000 Subject: [PATCH] .Net: Add new getting started samples (#4191) ### Motivation and Context Closes #4019 ### Description ### Contribution Checklist - [ ] The code builds clean without any errors or warnings - [ ] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [ ] All unit tests pass, and I have added new tests where possible - [ ] I didn't break anyone :smile: --- ...75_GPTVision.cs => Example74_GPTVision.cs} | 2 +- .../Getting_Started/Step1_Create_Kernel.cs | 37 ++++++++ .../Getting_Started/Step2_Add_Plugins.cs | 45 ++++++++++ .../Getting_Started/Step3_Yaml_Prompt.cs | 87 +++++++++++++++++++ .../Step4_Dependency_Injection.cs | 71 +++++++++++++++ .../Step5_Pipelining.cs} | 5 +- .../KernelSyntaxExamples.csproj | 2 +- .../samples/KernelSyntaxExamples/Program.cs | 8 +- 8 files changed, 247 insertions(+), 10 deletions(-) rename dotnet/samples/KernelSyntaxExamples/{Example75_GPTVision.cs => Example74_GPTVision.cs} (96%) create mode 100644 dotnet/samples/KernelSyntaxExamples/Getting_Started/Step1_Create_Kernel.cs create mode 100644 dotnet/samples/KernelSyntaxExamples/Getting_Started/Step2_Add_Plugins.cs create mode 100644 dotnet/samples/KernelSyntaxExamples/Getting_Started/Step3_Yaml_Prompt.cs create mode 100644 dotnet/samples/KernelSyntaxExamples/Getting_Started/Step4_Dependency_Injection.cs rename dotnet/samples/KernelSyntaxExamples/{Example74_Pipelining.cs => Getting_Started/Step5_Pipelining.cs} (98%) diff --git a/dotnet/samples/KernelSyntaxExamples/Example75_GPTVision.cs b/dotnet/samples/KernelSyntaxExamples/Example74_GPTVision.cs similarity index 96% rename from dotnet/samples/KernelSyntaxExamples/Example75_GPTVision.cs rename to dotnet/samples/KernelSyntaxExamples/Example74_GPTVision.cs index 9d4d3a4dd5cb..891b5d6a5833 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example75_GPTVision.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example74_GPTVision.cs @@ -9,7 +9,7 @@ * This example shows how to use GPT Vision model with different content types (text and image). */ // ReSharper disable once InconsistentNaming -public static class Example75_GPTVision +public static class Example74_GPTVision { public static async Task RunAsync() { diff --git a/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step1_Create_Kernel.cs b/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step1_Create_Kernel.cs new file mode 100644 index 000000000000..2a6f12e589a8 --- /dev/null +++ b/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step1_Create_Kernel.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Threading.Tasks; +using Microsoft.SemanticKernel; + +/** + * This example shows how to create and use a . + */ +public static class Step1_Create_Kernel +{ + /// + /// Show how to create a and use it to execute prompts. + /// + public static async Task RunAsync() + { + // Create a kernel with OpenAI chat completion + Kernel kernel = Kernel.CreateBuilder() + .AddOpenAIChatCompletion( + modelId: TestConfiguration.OpenAI.ChatModelId, + apiKey: TestConfiguration.OpenAI.ApiKey) + .Build(); + + // Example 1. Invoke the kernel with a prompt and display the result + Console.WriteLine(await kernel.InvokePromptAsync("What color is the sky?")); + + // Example 2. Invoke the kernel with a templated prompt and display the result + KernelArguments arguments = new() { { "topic", "sea" } }; + Console.WriteLine(await kernel.InvokePromptAsync("What color is the {{$topic}}?", arguments)); + + // Example 3. Invoke the kernel with a templated prompt and stream the results to the display + await foreach (var update in kernel.InvokePromptStreamingAsync("What color is the {{$topic}}? Provide a detailed explanation.", arguments)) + { + Console.Write(update); + } + } +} diff --git a/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step2_Add_Plugins.cs b/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step2_Add_Plugins.cs new file mode 100644 index 000000000000..c388a0f06674 --- /dev/null +++ b/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step2_Add_Plugins.cs @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft. All rights reserved. + +// ReSharper disable once InconsistentNaming +using System; +using System.Threading.Tasks; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.Connectors.OpenAI; +/** +* This example shows how to load a instances. +*/ +public static class Step2_Add_Plugins +{ + /// + /// Shows different ways to load a instances. + /// + public static async Task RunAsync() + { + // Create a kernel with OpenAI chat completion + IKernelBuilder kernelBuilder = Kernel.CreateBuilder(); + kernelBuilder.AddOpenAIChatCompletion( + modelId: TestConfiguration.OpenAI.ChatModelId, + apiKey: TestConfiguration.OpenAI.ApiKey); + kernelBuilder.Plugins.AddFromType(); + Kernel kernel = kernelBuilder.Build(); + + // Example 1. Invoke the kernel with a prompt that asks the AI for inromation it cannot provide and may hallucinate + Console.WriteLine(await kernel.InvokePromptAsync("How many days until Christmas?")); + + // Example 2. Invoke the kernel with a templated prompt that invokes a plugin and display the result + Console.WriteLine(await kernel.InvokePromptAsync("The current time is {{TimeInformation.GetCurrentUtcTime}}. How many days until Christmas?")); + + // Example 3. Invoke the kernel with a prompt and allow the AI to automatically invoke functions + OpenAIPromptExecutionSettings settings = new() { ToolCallBehavior = ToolCallBehavior.AutoInvokeKernelFunctions }; + Console.WriteLine(await kernel.InvokePromptAsync("How many days until Christmas? Explain your thinking.", new(settings))); + } + + /// + /// A plugin that returns the current time. + /// + public class TimeInformation + { + [KernelFunction] + public string GetCurrentUtcTime() => DateTime.UtcNow.ToString("R"); + } +} diff --git a/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step3_Yaml_Prompt.cs b/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step3_Yaml_Prompt.cs new file mode 100644 index 000000000000..d1188f9eecf5 --- /dev/null +++ b/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step3_Yaml_Prompt.cs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Threading.Tasks; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.PromptTemplate.Handlebars; + +/** + * This example shows how to create a prompt from a YAML resource. + */ +public static class Step3_Yaml_Prompt +{ + /// + /// Show how to create a prompt from a YAML resource. + /// + public static async Task RunAsync() + { + // Create a kernel with OpenAI chat completion + Kernel kernel = Kernel.CreateBuilder() + .AddOpenAIChatCompletion( + modelId: TestConfiguration.OpenAI.ChatModelId, + apiKey: TestConfiguration.OpenAI.ApiKey) + .Build(); + + // Load prompt from resource + var function = kernel.CreateFunctionFromPromptYaml(GenerateStoryYaml); + + // Invoke the prompt function and display the result + Console.WriteLine(await kernel.InvokeAsync(function, arguments: new() + { + { "topic", "Dog" }, + { "length", "3" }, + })); + + // Load prompt from resource + function = kernel.CreateFunctionFromPromptYaml(GenerateStoryHandlebarsYaml, new HandlebarsPromptTemplateFactory()); + + // Invoke the prompt function and display the result + Console.WriteLine(await kernel.InvokeAsync(function, arguments: new() + { + { "topic", "Cat" }, + { "length", "3" }, + })); + } + + private const string GenerateStoryYaml = @" +name: GenerateStory +template: | + Tell a story about {{$topic}} that is {{$length}} sentences long. +template_format: semantic-kernel +description: A function that generates a story about a topic. +input_variables: + - name: topic + description: The topic of the story. + is_required: true + - name: length + description: The number of sentences in the story. + is_required: true +output_variable: + description: The generated story. +execution_settings: + - temperature: 0.6 +"; + + private const string GenerateStoryHandlebarsYaml = @" +name: GenerateStory +template: | + Tell a story about {{topic}} that is {{length}} sentences long. +template_format: handlebars +description: A function that generates a story about a topic. +input_variables: + - name: topic + description: The topic of the story. + is_required: true + - name: length + description: The number of sentences in the story. + is_required: true +output_variable: + description: The generated story. +execution_settings: + - model_id: gpt-4 + temperature: 0.6 + - model_id: gpt-3.5-turbo + temperature: 0.4 + - temperature: 0.5 +"; +} diff --git a/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step4_Dependency_Injection.cs b/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step4_Dependency_Injection.cs new file mode 100644 index 000000000000..643b68996bdd --- /dev/null +++ b/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step4_Dependency_Injection.cs @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft. All rights reserved. + +// ReSharper disable once InconsistentNaming +// ReSharper disable once InconsistentNaming +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.SemanticKernel; +using RepoUtils; + +// ReSharper disable once InconsistentNaming +/** +* This example shows how to using Dependency Injection with the Semantic Kernel +*/ +public static class Step4_Dependency_Injection +{ + /// + /// Show how to create a that participates in Dependency Injection. + /// + public static async Task RunAsync() + { + // If an application follows DI guidelines, the following line is unnecessary because DI will inject an instance of the KernelClient class to a class that references it. + // DI container guidelines - https://learn.microsoft.com/en-us/dotnet/core/extensions/dependency-injection-guidelines#recommendations + var serviceProvider = BuildServiceProvider(); + var kernel = serviceProvider.GetRequiredService(); + + // Invoke the kernel with a templated prompt and stream the results to the display + KernelArguments arguments = new() { { "topic", "earth when viewed from space" } }; + await foreach (var update in kernel.InvokePromptStreamingAsync("What color is the {{$topic}}? Provide a detailed explanation.", arguments)) + { + Console.Write(update); + } + } + + /// + /// Build a ServiceProvdier that can be used to resolve services. + /// + private static ServiceProvider BuildServiceProvider() + { + var collection = new ServiceCollection(); + collection.AddSingleton(ConsoleLogger.LoggerFactory); + + var kernelBuilder = collection.AddKernel(); + kernelBuilder.Services.AddOpenAITextGeneration(TestConfiguration.OpenAI.ModelId, TestConfiguration.OpenAI.ApiKey); + kernelBuilder.Plugins.AddFromType(); + + return collection.BuildServiceProvider(); + } + + /// + /// A plugin that returns the current time. + /// + public class TimeInformation + { + private readonly ILogger _logger; + + public TimeInformation(ILoggerFactory loggerFactory) + { + this._logger = loggerFactory.CreateLogger(typeof(TimeInformation)); + } + + [KernelFunction] + public string GetCurrentUtcTime() + { + var utcNow = DateTime.UtcNow.ToString("R"); + this._logger.LogInformation("Returning current time {0}", utcNow); + return utcNow; + } + } +} diff --git a/dotnet/samples/KernelSyntaxExamples/Example74_Pipelining.cs b/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step5_Pipelining.cs similarity index 98% rename from dotnet/samples/KernelSyntaxExamples/Example74_Pipelining.cs rename to dotnet/samples/KernelSyntaxExamples/Getting_Started/Step5_Pipelining.cs index be7431c13b27..755076b72fc0 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example74_Pipelining.cs +++ b/dotnet/samples/KernelSyntaxExamples/Getting_Started/Step5_Pipelining.cs @@ -10,10 +10,7 @@ using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; -#pragma warning disable RCS1110 // Declare type inside namespace. -#pragma warning disable CA5394 - -public static class Example74_Pipelining +public static class Step5_Pipelining { /// /// Provides an example of combining multiple functions into a single function that invokes diff --git a/dotnet/samples/KernelSyntaxExamples/KernelSyntaxExamples.csproj b/dotnet/samples/KernelSyntaxExamples/KernelSyntaxExamples.csproj index a6d3b58afc16..ba66fb07332b 100644 --- a/dotnet/samples/KernelSyntaxExamples/KernelSyntaxExamples.csproj +++ b/dotnet/samples/KernelSyntaxExamples/KernelSyntaxExamples.csproj @@ -9,7 +9,7 @@ Exe false - CA1050,CA1707,CA2007,VSTHRD111,CS1591,SKEXP0001,SKEXP0002,SKEXP0003,SKEXP0004,SKEXP0010,SKEXP0011,,SKEXP0012,SKEXP0020,SKEXP0021,SKEXP0022,SKEXP0023,SKEXP0024,SKEXP0025,SKEXP0026,SKEXP0027,SKEXP0028,SKEXP0029,SKEXP0030,SKEXP0031,SKEXP0032,SKEXP0040,SKEXP0041,SKEXP0050,SKEXP0051,SKEXP0052,SKEXP0053,SKEXP0054,SKEXP0055,SKEXP0060,SKEXP0061,SKEXP0101,SKEXP0102 + CA1050,CA1707,CA2007,VSTHRD111,CS1591,RCS1110,CA5394,SKEXP0001,SKEXP0002,SKEXP0003,SKEXP0004,SKEXP0010,SKEXP0011,,SKEXP0012,SKEXP0020,SKEXP0021,SKEXP0022,SKEXP0023,SKEXP0024,SKEXP0025,SKEXP0026,SKEXP0027,SKEXP0028,SKEXP0029,SKEXP0030,SKEXP0031,SKEXP0032,SKEXP0040,SKEXP0041,SKEXP0050,SKEXP0051,SKEXP0052,SKEXP0053,SKEXP0054,SKEXP0055,SKEXP0060,SKEXP0061,SKEXP0101,SKEXP0102 diff --git a/dotnet/samples/KernelSyntaxExamples/Program.cs b/dotnet/samples/KernelSyntaxExamples/Program.cs index de2b07fdf982..bebfd6973b42 100644 --- a/dotnet/samples/KernelSyntaxExamples/Program.cs +++ b/dotnet/samples/KernelSyntaxExamples/Program.cs @@ -33,7 +33,6 @@ public static async Task Main(string[] args) private static async Task RunExamplesAsync(string? filter, CancellationToken cancellationToken) { var examples = (Assembly.GetExecutingAssembly().GetTypes()) - .Where(type => type.Name.StartsWith("Example", StringComparison.OrdinalIgnoreCase)) .Select(type => type.Name).ToList(); // Filter and run examples @@ -43,15 +42,15 @@ private static async Task RunExamplesAsync(string? filter, CancellationToken can { try { - Console.WriteLine($"Running {example}..."); - var method = Assembly.GetExecutingAssembly().GetType(example)?.GetMethod("RunAsync"); if (method == null) { - Console.WriteLine($"Example {example} not found"); + // Skip if the type does not have a RunAsync method continue; } + Console.WriteLine($"Running {example}..."); + bool hasCancellationToken = method.GetParameters().Any(param => param.ParameterType == typeof(CancellationToken)); var taskParameters = hasCancellationToken ? new object[] { cancellationToken } : null; @@ -99,6 +98,7 @@ private static async Task SafeWaitAsync(this Task task, try { await task.WaitAsync(cancellationToken); + Console.WriteLine(); Console.WriteLine("== DONE =="); } catch (ConfigurationNotFoundException ex)