mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
### Motivation and Context Reduce the responsibility surface of Semantic Kernel regarding Resiliency implementations, keeping only the abstractions that can be delegated and handling by well know resiliency libraries and custom implementations. Extract existing logic from Kernel Core to an extension project that can handle retry logic similar to the prior using Polly Policies. Resolves #2271 Closes #2271 ⚠️ Small breaking changes. ### Description This change removes and deprecates the usage of Retry implementations in our Resiliency namespace with `NullHttpHandlerFactory` implementations. ### Contribution Checklist - [x] The code builds clean without any errors or warnings - [x] 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 - [x] All unit tests pass, and I have added new tests where possible - [ ] I didn't break anyone 😄
223 lines
10 KiB
C#
223 lines
10 KiB
C#
// 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.TextCompletion;
|
|
using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion;
|
|
using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding;
|
|
using Microsoft.SemanticKernel.Diagnostics;
|
|
using Microsoft.SemanticKernel.Http;
|
|
using Microsoft.SemanticKernel.Memory;
|
|
using Microsoft.SemanticKernel.Reliability.Basic;
|
|
using Microsoft.SemanticKernel.Reliability.Polly;
|
|
using Microsoft.SemanticKernel.Services;
|
|
using Microsoft.SemanticKernel.SkillDefinition;
|
|
using Microsoft.SemanticKernel.TemplateEngine.Prompt;
|
|
using Polly;
|
|
using Polly.Retry;
|
|
|
|
// ReSharper disable once InconsistentNaming
|
|
public static class Example42_KernelBuilder
|
|
{
|
|
public static Task RunAsync()
|
|
{
|
|
string azureOpenAIKey = TestConfiguration.AzureOpenAI.ApiKey;
|
|
string azureOpenAIEndpoint = TestConfiguration.AzureOpenAI.Endpoint;
|
|
string azureOpenAIChatCompletionDeployment = TestConfiguration.AzureOpenAI.ChatDeploymentName;
|
|
string azureOpenAIEmbeddingDeployment = TestConfiguration.AzureOpenAIEmbeddings.DeploymentName;
|
|
|
|
#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 loggerFactory = NullLoggerFactory.Instance;
|
|
var memoryStorage = new VolatileMemoryStore();
|
|
var textEmbeddingGenerator = new AzureTextEmbeddingGeneration(
|
|
modelId: azureOpenAIEmbeddingDeployment,
|
|
endpoint: azureOpenAIEndpoint,
|
|
apiKey: azureOpenAIKey,
|
|
loggerFactory: loggerFactory);
|
|
using var memory = new SemanticTextMemory(memoryStorage, textEmbeddingGenerator);
|
|
var skills = new SkillCollection();
|
|
var templateEngine = new PromptTemplateEngine(loggerFactory);
|
|
var kernelConfig = new KernelConfig();
|
|
using var httpHandler = kernelConfig.HttpHandlerFactory.Create(loggerFactory);
|
|
using var httpClient = new HttpClient(httpHandler);
|
|
var aiServices = new AIServiceCollection();
|
|
ITextCompletion Factory() => new AzureTextCompletion(
|
|
modelId: azureOpenAIChatCompletionDeployment,
|
|
endpoint: azureOpenAIEndpoint,
|
|
apiKey: azureOpenAIKey,
|
|
httpClient,
|
|
loggerFactory);
|
|
aiServices.SetService("foo", Factory);
|
|
IAIServiceProvider aiServiceProvider = aiServices.Build();
|
|
|
|
// Create kernel manually injecting all the dependencies
|
|
using var kernel3 = new Kernel(skills, aiServiceProvider, templateEngine, memory, kernelConfig, loggerFactory);
|
|
|
|
// ==========================================================================================================
|
|
// 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
|
|
.WithLoggerFactory(NullLoggerFactory.Instance)
|
|
.WithMemory(memory)
|
|
.WithAzureChatCompletionService(
|
|
deploymentName: azureOpenAIChatCompletionDeployment,
|
|
endpoint: azureOpenAIEndpoint,
|
|
apiKey: azureOpenAIKey)
|
|
.Build();
|
|
|
|
// Example: how to use a custom memory storage
|
|
var kernel6 = Kernel.Builder
|
|
.WithLoggerFactory(NullLoggerFactory.Instance)
|
|
.WithMemoryStorage(memoryStorage) // Custom memory storage
|
|
.WithAzureChatCompletionService(
|
|
deploymentName: azureOpenAIChatCompletionDeployment,
|
|
endpoint: azureOpenAIEndpoint,
|
|
apiKey: azureOpenAIKey) // This will be used when using AI completions
|
|
.WithAzureTextEmbeddingGenerationService(
|
|
deploymentName: azureOpenAIEmbeddingDeployment,
|
|
endpoint: azureOpenAIEndpoint,
|
|
apiKey: azureOpenAIKey) // This will be used when indexing memory records
|
|
.Build();
|
|
|
|
// ==========================================================================================================
|
|
// The AI services are defined with the builder
|
|
|
|
var kernel7 = Kernel.Builder
|
|
.WithAzureChatCompletionService(
|
|
deploymentName: azureOpenAIChatCompletionDeployment,
|
|
endpoint: azureOpenAIEndpoint,
|
|
apiKey: azureOpenAIKey,
|
|
setAsDefault: 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.WithRetryBasic(
|
|
new BasicRetryConfig
|
|
{
|
|
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 logger = loggerFactory.CreateLogger<PollyHttpRetryHandlerFactory>();
|
|
var retryThreeTimesPolicy = Policy
|
|
.Handle<HttpOperationException>(ex
|
|
=> ex.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
|
|
.WaitAndRetryAsync(new[]
|
|
{
|
|
TimeSpan.FromSeconds(2),
|
|
TimeSpan.FromSeconds(4),
|
|
TimeSpan.FromSeconds(8)
|
|
},
|
|
(ex, timespan, retryCount, _)
|
|
=> logger?.LogWarning(ex, "Error executing action [attempt {RetryCount} of 3], pausing {PausingMilliseconds}ms", retryCount, timespan.TotalMilliseconds));
|
|
|
|
var kernel9 = Kernel.Builder.WithHttpHandlerFactory(new PollyHttpRetryHandlerFactory(retryThreeTimesPolicy)).Build();
|
|
|
|
var kernel10 = Kernel.Builder.WithHttpHandlerFactory(new PollyRetryThreeTimesFactory()).Build();
|
|
|
|
var kernel11 = Kernel.Builder.WithHttpHandlerFactory(new MyCustomHandlerFactory()).Build();
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
// Example using the PollyHttpRetryHandler from Reliability.Polly extension
|
|
public class PollyRetryThreeTimesFactory : HttpHandlerFactory<PollyHttpRetryHandler>
|
|
{
|
|
public override DelegatingHandler Create(ILoggerFactory? loggerFactory = null)
|
|
{
|
|
var logger = loggerFactory?.CreateLogger<PollyRetryThreeTimesFactory>();
|
|
|
|
Activator.CreateInstance(typeof(PollyHttpRetryHandler), GetPolicy(logger), logger);
|
|
return base.Create(loggerFactory);
|
|
}
|
|
|
|
private static AsyncRetryPolicy GetPolicy(ILogger? logger)
|
|
{
|
|
return Policy
|
|
.Handle<HttpOperationException>(ex
|
|
=> ex.StatusCode == System.Net.HttpStatusCode.TooManyRequests)
|
|
.WaitAndRetryAsync(new[]
|
|
{
|
|
TimeSpan.FromSeconds(2),
|
|
TimeSpan.FromSeconds(4),
|
|
TimeSpan.FromSeconds(8)
|
|
},
|
|
(ex, timespan, retryCount, _)
|
|
=> logger?.LogWarning(ex, "Error executing action [attempt {RetryCount} of 3], pausing {PausingMilliseconds}ms",
|
|
retryCount,
|
|
timespan.TotalMilliseconds));
|
|
}
|
|
}
|
|
|
|
// Basic custom retry handler factory
|
|
public class MyCustomHandlerFactory : HttpHandlerFactory<MyCustomHandler>
|
|
{
|
|
}
|
|
|
|
// Basic custom empty retry handler
|
|
public class MyCustomHandler : DelegatingHandler
|
|
{
|
|
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
|
{
|
|
// Your custom handler implementation
|
|
|
|
throw new NotImplementedException();
|
|
}
|
|
}
|
|
}
|