From a2a7b8aeac9450479ba69bd819107d1e25a6b6e0 Mon Sep 17 00:00:00 2001 From: Shawn Callegari <36091529+shawncal@users.noreply.github.com> Date: Fri, 26 May 2023 12:02:06 -0700 Subject: [PATCH] Merge "KernelBuilder" sample into KernelSyntaxExamples (#1232) ### Motivation and Context - The KernelBuilder project contains only a single file, and has the same intent as KernelSyntaxExamples. - The project name, "KernelBuilder", doesn't make it clear that these are samples, and seemingly indicates that this is the implementation of KernelBuilder itself. - Having this be a standalone project increases the time required to run "dotnet format", "dotnet build", VS loading/rebuilding and other solution-wide operations. ### Description Merges the KernelBuilder program file into KernelSyntaxExamples, matching the format of the other examples in that project. --- dotnet/SK-dotnet.sln | 10 +- samples/dotnet/KernelBuilder/GlobalUsings.cs | 12 -- .../dotnet/KernelBuilder/KernelBuilder.csproj | 23 --- samples/dotnet/KernelBuilder/Program.cs | 163 ---------------- .../Example42_KernelBuilder.cs | 183 ++++++++++++++++++ .../dotnet/kernel-syntax-examples/Program.cs | 3 + 6 files changed, 187 insertions(+), 207 deletions(-) delete mode 100644 samples/dotnet/KernelBuilder/GlobalUsings.cs delete mode 100644 samples/dotnet/KernelBuilder/KernelBuilder.csproj delete mode 100644 samples/dotnet/KernelBuilder/Program.cs create mode 100644 samples/dotnet/kernel-syntax-examples/Example42_KernelBuilder.cs diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln index 2e063fc42411..cc6605d08282 100644 --- a/dotnet/SK-dotnet.sln +++ b/dotnet/SK-dotnet.sln @@ -31,8 +31,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Directory.Build.props = Directory.Build.props Directory.Build.targets = Directory.Build.targets Directory.Packages.props = Directory.Packages.props - ..\README.md = ..\README.md ..\.github\workflows\dotnet-format.yml = ..\.github\workflows\dotnet-format.yml + ..\README.md = ..\README.md EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SemanticKernel.UnitTests", "src\SemanticKernel.UnitTests\SemanticKernel.UnitTests.csproj", "{37E39C68-5A40-4E63-9D3C-0C66AD98DFCB}" @@ -48,8 +48,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "nuget", "nuget", "{F4243136 nuget\NUGET.md = nuget\NUGET.md EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KernelBuilder", "..\samples\dotnet\KernelBuilder\KernelBuilder.csproj", "{A52818AC-57FB-495F-818F-9E1E7BC5618C}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GitHubSkills", "..\samples\dotnet\github-skills\GitHubSkills.csproj", "{39E5F0F6-8B36-4ECA-A5F6-FC7522DC2ECF}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Skills.OpenAPI", "src\Skills\Skills.OpenAPI\Skills.OpenAPI.csproj", "{F2A1F81E-700E-4C0E-B021-B9EF29AA20BD}" @@ -170,11 +168,6 @@ Global {107156B4-5A8B-45C7-97A2-4544D7FA19DE}.Publish|Any CPU.Build.0 = Release|Any CPU {107156B4-5A8B-45C7-97A2-4544D7FA19DE}.Release|Any CPU.ActiveCfg = Release|Any CPU {107156B4-5A8B-45C7-97A2-4544D7FA19DE}.Release|Any CPU.Build.0 = Release|Any CPU - {A52818AC-57FB-495F-818F-9E1E7BC5618C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A52818AC-57FB-495F-818F-9E1E7BC5618C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A52818AC-57FB-495F-818F-9E1E7BC5618C}.Publish|Any CPU.ActiveCfg = Release|Any CPU - {A52818AC-57FB-495F-818F-9E1E7BC5618C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A52818AC-57FB-495F-818F-9E1E7BC5618C}.Release|Any CPU.Build.0 = Release|Any CPU {39E5F0F6-8B36-4ECA-A5F6-FC7522DC2ECF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {39E5F0F6-8B36-4ECA-A5F6-FC7522DC2ECF}.Debug|Any CPU.Build.0 = Debug|Any CPU {39E5F0F6-8B36-4ECA-A5F6-FC7522DC2ECF}.Publish|Any CPU.ActiveCfg = Release|Any CPU @@ -315,7 +308,6 @@ Global {9ECD1AA0-75B3-4E25-B0B5-9F0945B64974} = {831DDCA2-7D2C-4C31-80DB-6BDB3E1F7AE0} {107156B4-5A8B-45C7-97A2-4544D7FA19DE} = {9ECD1AA0-75B3-4E25-B0B5-9F0945B64974} {F4243136-252A-4459-A7C4-EE8C056D6B0B} = {158A4E5E-AEE0-4D60-83C7-8E089B2D881D} - {A52818AC-57FB-495F-818F-9E1E7BC5618C} = {FA3720F1-C99A-49B2-9577-A940257098BF} {39E5F0F6-8B36-4ECA-A5F6-FC7522DC2ECF} = {FA3720F1-C99A-49B2-9577-A940257098BF} {F2A1F81E-700E-4C0E-B021-B9EF29AA20BD} = {9ECD1AA0-75B3-4E25-B0B5-9F0945B64974} {BC70A5D8-2125-4C37-8C0E-C903EAFA9772} = {FA3720F1-C99A-49B2-9577-A940257098BF} diff --git a/samples/dotnet/KernelBuilder/GlobalUsings.cs b/samples/dotnet/KernelBuilder/GlobalUsings.cs deleted file mode 100644 index 41add0ac078a..000000000000 --- a/samples/dotnet/KernelBuilder/GlobalUsings.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -global using Microsoft.Extensions.Logging; -global using Microsoft.Extensions.Logging.Abstractions; -global using Microsoft.SemanticKernel; -global using Microsoft.SemanticKernel.AI; -global using Microsoft.SemanticKernel.Memory; -global using Microsoft.SemanticKernel.Reliability; -global using Microsoft.SemanticKernel.SkillDefinition; -global using Microsoft.SemanticKernel.TemplateEngine; -global using Polly; -global using Polly.Retry; diff --git a/samples/dotnet/KernelBuilder/KernelBuilder.csproj b/samples/dotnet/KernelBuilder/KernelBuilder.csproj deleted file mode 100644 index 0c94b9489f4f..000000000000 --- a/samples/dotnet/KernelBuilder/KernelBuilder.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - KernelSetup - net6.0 - LatestMajor - Exe - enable - enable - CA2000 - false - - - - - - - - - - - - diff --git a/samples/dotnet/KernelBuilder/Program.cs b/samples/dotnet/KernelBuilder/Program.cs deleted file mode 100644 index c1c055812ee6..000000000000 --- a/samples/dotnet/KernelBuilder/Program.cs +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -// ========================================================================================================== -// The easier way to instantiate the Semantic Kernel is to use KernelBuilder. -// You can access the builder using either Kernel.Builder or KernelBuilder. - -#pragma warning disable CA1852 -#pragma warning disable CA1050 - -using Microsoft.SemanticKernel.AI.TextCompletion; -using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion; -using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; -using Microsoft.SemanticKernel.Services; - -#pragma warning disable CA1852 // Seal internal types -IKernel kernel1 = Kernel.Builder.Build(); -#pragma warning restore CA1852 // Seal internal types - -IKernel kernel2 = Kernel.Builder.Build(); - -// ========================================================================================================== -// Kernel.Builder returns a new builder instance, in case you want to configure the builder differently. -// The following are 3 distinct builder instances. - -var builder1 = new KernelBuilder(); - -var builder2 = Kernel.Builder; - -var builder3 = Kernel.Builder; - -// ========================================================================================================== -// A builder instance can create multiple kernel instances, e.g. in case you need -// multiple kernels that share the same dependencies. - -var builderX = new KernelBuilder(); - -var kernelX1 = builderX.Build(); -var kernelX2 = builderX.Build(); -var kernelX3 = builderX.Build(); - -// ========================================================================================================== -// Kernel instances can be created the usual way with "new", though the process requires particular -// attention to how dependencies are wired together. Although the building blocks are available -// to enable custom configurations, we highly recommend using KernelBuilder instead, to ensure -// a correct dependency injection. - -// Manually setup all the dependencies used internally by the kernel -var logger = NullLogger.Instance; -var memoryStorage = new VolatileMemoryStore(); -var textEmbeddingGenerator = new AzureTextEmbeddingGeneration("modelId", "https://...", "apiKey", logger: logger); -var memory = new SemanticTextMemory(memoryStorage, textEmbeddingGenerator); -var skills = new SkillCollection(); -var templateEngine = new PromptTemplateEngine(logger); -var config = new KernelConfig(); - -var httpHandler = new DefaultHttpRetryHandler(new HttpRetryConfig(), logger); -var httpClient = new HttpClient(httpHandler); -var aiServices = new AIServiceCollection(); -ITextCompletion Factory() => new AzureTextCompletion("deploymentName", "https://...", "apiKey", httpClient, logger); -aiServices.SetService("foo", Factory); -IAIServiceProvider aiServiceProvider = aiServices.Build(); - -// Create kernel manually injecting all the dependencies -var kernel3 = new Kernel(skills, aiServiceProvider, templateEngine, memory, config, logger); - -// ========================================================================================================== -// The kernel builder purpose is to simplify this process, automating how dependencies -// are connected, still allowing to customize parts of the composition. - -// Example: how to use a custom memory and configure Azure OpenAI -var kernel4 = Kernel.Builder - .WithLogger(NullLogger.Instance) - .WithMemory(memory) - .WithAzureTextCompletionService("deploymentName", "https://...", "apiKey") - .Build(); - -// Example: how to use a custom memory storage and custom embedding generator -var kernel5 = Kernel.Builder - .WithLogger(NullLogger.Instance) - .WithMemoryStorageAndTextEmbeddingGeneration(memoryStorage, textEmbeddingGenerator) - .Build(); - -// Example: how to use a custom memory storage -var kernel6 = Kernel.Builder - .WithLogger(NullLogger.Instance) - .WithMemoryStorage(memoryStorage) // Custom memory storage - .WithAzureTextCompletionService("myName1", "completionDeploymentName", "https://...", "apiKey") // This will be used when using AI completions - .WithAzureTextEmbeddingGenerationService("myName2", "embeddingsDeploymentName", "https://...", "apiKey") // This will be used when indexing memory records - .Build(); - -// ========================================================================================================== -// The AI services are defined with the builder - -var kernel7 = Kernel.Builder - .WithAzureTextCompletionService("myName1", "completionDeploymentName", "https://...", "apiKey", true) - .Build(); - -// ========================================================================================================== -// When invoking AI, by default the kernel will retry on transient errors, such as throttling and timeouts. -// The default behavior can be configured or a custom retry handler can be injected that will apply to all -// AI requests (when using the kernel). - -var kernel8 = Kernel.Builder - .Configure(c => c.SetDefaultHttpRetryConfig(new HttpRetryConfig - { - MaxRetryCount = 3, - UseExponentialBackoff = true, - // MinRetryDelay = TimeSpan.FromSeconds(2), - // MaxRetryDelay = TimeSpan.FromSeconds(8), - // MaxTotalRetryTime = TimeSpan.FromSeconds(30), - // RetryableStatusCodes = new[] { HttpStatusCode.TooManyRequests, HttpStatusCode.RequestTimeout }, - // RetryableExceptions = new[] { typeof(HttpRequestException) } - })) - .Build(); - -var kernel9 = Kernel.Builder - .Configure(c => c.SetHttpRetryHandlerFactory(new NullHttpRetryHandlerFactory())) - .Build(); - -var kernel10 = Kernel.Builder.WithRetryHandlerFactory(new RetryThreeTimesFactory()).Build(); - -// Example of a basic custom retry handler -public class RetryThreeTimesFactory : IDelegatingHandlerFactory -{ - public DelegatingHandler Create(ILogger? log) - { - return new RetryThreeTimes(log); - } -} - -public class RetryThreeTimes : DelegatingHandler -{ - private readonly AsyncRetryPolicy _policy; - - public RetryThreeTimes(ILogger? log = null) - { - this._policy = GetPolicy(log ?? NullLogger.Instance); - } - - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - return await this._policy.ExecuteAsync(async () => - { - var response = await base.SendAsync(request, cancellationToken); - return response; - }); - } - - private static AsyncRetryPolicy GetPolicy(ILogger log) - { - return Policy - .Handle(ex => ex.ErrorCode == AIException.ErrorCodes.Throttling) - .WaitAndRetryAsync(new[] - { - TimeSpan.FromSeconds(2), - TimeSpan.FromSeconds(4), - TimeSpan.FromSeconds(8) - }, - (ex, timespan, retryCount, _) => log.LogWarning(ex, - "Error executing action [attempt {0} of 3], pausing {1}ms", - retryCount, timespan.TotalMilliseconds)); - } -} diff --git a/samples/dotnet/kernel-syntax-examples/Example42_KernelBuilder.cs b/samples/dotnet/kernel-syntax-examples/Example42_KernelBuilder.cs new file mode 100644 index 000000000000..b18e65d1f358 --- /dev/null +++ b/samples/dotnet/kernel-syntax-examples/Example42_KernelBuilder.cs @@ -0,0 +1,183 @@ +// Copyright (c) Microsoft. All rights reserved. + +// ========================================================================================================== +// The easier way to instantiate the Semantic Kernel is to use KernelBuilder. +// You can access the builder using either Kernel.Builder or KernelBuilder. + +#pragma warning disable CA1852 + +using System; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.AI; +using Microsoft.SemanticKernel.AI.TextCompletion; +using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion; +using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; +using Microsoft.SemanticKernel.Memory; +using Microsoft.SemanticKernel.Reliability; +using Microsoft.SemanticKernel.Services; +using Microsoft.SemanticKernel.SkillDefinition; +using Microsoft.SemanticKernel.TemplateEngine; +using Polly; +using Polly.Retry; + +// ReSharper disable once InconsistentNaming +public static class Example42_KernelBuilder +{ + public static void Run() + { +#pragma warning disable CA1852 // Seal internal types + IKernel kernel1 = Kernel.Builder.Build(); +#pragma warning restore CA1852 // Seal internal types + + IKernel kernel2 = Kernel.Builder.Build(); + + // ========================================================================================================== + // Kernel.Builder returns a new builder instance, in case you want to configure the builder differently. + // The following are 3 distinct builder instances. + + var builder1 = new KernelBuilder(); + + var builder2 = Kernel.Builder; + + var builder3 = Kernel.Builder; + + // ========================================================================================================== + // A builder instance can create multiple kernel instances, e.g. in case you need + // multiple kernels that share the same dependencies. + + var builderX = new KernelBuilder(); + + var kernelX1 = builderX.Build(); + var kernelX2 = builderX.Build(); + var kernelX3 = builderX.Build(); + + // ========================================================================================================== + // Kernel instances can be created the usual way with "new", though the process requires particular + // attention to how dependencies are wired together. Although the building blocks are available + // to enable custom configurations, we highly recommend using KernelBuilder instead, to ensure + // a correct dependency injection. + + // Manually setup all the dependencies used internally by the kernel + var logger = NullLogger.Instance; + var memoryStorage = new VolatileMemoryStore(); + var textEmbeddingGenerator = new AzureTextEmbeddingGeneration("modelId", "https://...", "apiKey", logger: logger); + using var memory = new SemanticTextMemory(memoryStorage, textEmbeddingGenerator); + var skills = new SkillCollection(); + var templateEngine = new PromptTemplateEngine(logger); + var config = new KernelConfig(); + + using var httpHandler = new DefaultHttpRetryHandler(new HttpRetryConfig(), logger); + using var httpClient = new HttpClient(httpHandler); + var aiServices = new AIServiceCollection(); + ITextCompletion Factory() => new AzureTextCompletion("deploymentName", "https://...", "apiKey", httpClient, logger); + aiServices.SetService("foo", Factory); + IAIServiceProvider aiServiceProvider = aiServices.Build(); + + // Create kernel manually injecting all the dependencies + using var kernel3 = new Kernel(skills, aiServiceProvider, templateEngine, memory, config, logger); + + // ========================================================================================================== + // The kernel builder purpose is to simplify this process, automating how dependencies + // are connected, still allowing to customize parts of the composition. + + // Example: how to use a custom memory and configure Azure OpenAI + var kernel4 = Kernel.Builder + .WithLogger(NullLogger.Instance) + .WithMemory(memory) + .WithAzureTextCompletionService("deploymentName", "https://...", "apiKey") + .Build(); + + // Example: how to use a custom memory storage and custom embedding generator + var kernel5 = Kernel.Builder + .WithLogger(NullLogger.Instance) + .WithMemoryStorageAndTextEmbeddingGeneration(memoryStorage, textEmbeddingGenerator) + .Build(); + + // Example: how to use a custom memory storage + var kernel6 = Kernel.Builder + .WithLogger(NullLogger.Instance) + .WithMemoryStorage(memoryStorage) // Custom memory storage + .WithAzureTextCompletionService("myName1", "completionDeploymentName", "https://...", "apiKey") // This will be used when using AI completions + .WithAzureTextEmbeddingGenerationService("myName2", "embeddingsDeploymentName", "https://...", "apiKey") // This will be used when indexing memory records + .Build(); + + // ========================================================================================================== + // The AI services are defined with the builder + + var kernel7 = Kernel.Builder + .WithAzureTextCompletionService("myName1", "completionDeploymentName", "https://...", "apiKey", true) + .Build(); + + // ========================================================================================================== + // When invoking AI, by default the kernel will retry on transient errors, such as throttling and timeouts. + // The default behavior can be configured or a custom retry handler can be injected that will apply to all + // AI requests (when using the kernel). + + var kernel8 = Kernel.Builder + .Configure(c => c.SetDefaultHttpRetryConfig(new HttpRetryConfig + { + MaxRetryCount = 3, + UseExponentialBackoff = true, + // MinRetryDelay = TimeSpan.FromSeconds(2), + // MaxRetryDelay = TimeSpan.FromSeconds(8), + // MaxTotalRetryTime = TimeSpan.FromSeconds(30), + // RetryableStatusCodes = new[] { HttpStatusCode.TooManyRequests, HttpStatusCode.RequestTimeout }, + // RetryableExceptions = new[] { typeof(HttpRequestException) } + })) + .Build(); + + var kernel9 = Kernel.Builder + .Configure(c => c.SetHttpRetryHandlerFactory(new NullHttpRetryHandlerFactory())) + .Build(); + + var kernel10 = Kernel.Builder.WithRetryHandlerFactory(new RetryThreeTimesFactory()).Build(); + } + + // Example of a basic custom retry handler + public class RetryThreeTimesFactory : IDelegatingHandlerFactory + { + public DelegatingHandler Create(ILogger? log) + { + return new RetryThreeTimes(log); + } + } + + public class RetryThreeTimes : DelegatingHandler + { + private readonly AsyncRetryPolicy _policy; + + public RetryThreeTimes(ILogger? log = null) + { + this._policy = GetPolicy(log ?? NullLogger.Instance); + } + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + return await this._policy.ExecuteAsync(async () => + { + var response = await base.SendAsync(request, cancellationToken); + return response; + }); + } + + private static AsyncRetryPolicy GetPolicy(ILogger log) + { + return Policy + .Handle(ex => ex.ErrorCode == AIException.ErrorCodes.Throttling) + .WaitAndRetryAsync(new[] + { + TimeSpan.FromSeconds(2), + TimeSpan.FromSeconds(4), + TimeSpan.FromSeconds(8) + }, + (ex, timespan, retryCount, _) => log.LogWarning(ex, + "Error executing action [attempt {0} of 3], pausing {1}ms", + retryCount, timespan.TotalMilliseconds)); + } + } +} diff --git a/samples/dotnet/kernel-syntax-examples/Program.cs b/samples/dotnet/kernel-syntax-examples/Program.cs index 194662a82dfc..d54442f6b02a 100644 --- a/samples/dotnet/kernel-syntax-examples/Program.cs +++ b/samples/dotnet/kernel-syntax-examples/Program.cs @@ -129,5 +129,8 @@ public static async Task Main() Example41_HttpClientUsage.Run(); Console.WriteLine("== DONE =="); + + Example42_KernelBuilder.Run(); + Console.WriteLine("== DONE =="); } }