From 73afd473d7ad462ae51e86ba987cc33fc651e871 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 28 Nov 2023 15:59:04 -0500 Subject: [PATCH] .Net: Replace SK's IAIServiceProvider with IServiceProvider (#3714) Closes https://github.com/microsoft/semantic-kernel/issues/3694 Closes https://github.com/microsoft/semantic-kernel/issues/3693 Closes https://github.com/microsoft/semantic-kernel/issues/3659 Closes https://github.com/microsoft/semantic-kernel/issues/3571 Closes https://github.com/microsoft/semantic-kernel/issues/3191 Closes https://github.com/microsoft/semantic-kernel/issues/2463 (This replaces https://github.com/microsoft/semantic-kernel/pull/3676, including that as a first commit. That resiliency functionality is no longer necessary after this change, as developers can simply use libraries like Microsoft.Extensions.Http.Resilience to achieve the desired support.) SK has had a custom IAIServiceProvider interface that's represented a provider of AI related services. Kernel has then wrapped one of these, but also wrapped other individual non-AI services, like ILoggerFactory and HttpClient. This leads to a variety of problems, such as it being more complicated to construct a Kernel instance, it not integrating as nicely with a broader dependency injection system, and not being as future proof because there isn't a good way to flow additional non-AI-related services through the Kernel to anywhere that might want access. On the DI front, it's also more difficult to integrate because all of the KernelBuilder extension methods are specific to KernelBuilder, and even though they're creating services, the helpers aren't usable with IServiceCollection and thus with the rest of the DI system; a developer needs to go directly to the underlying types and register those directly. There are also a handful of features that are of little value and in some cases exposed but not actually implemented, such as setAsDefault and alsoAsTextCompletion. And other cases where there's something valuable but missing, such as the ability to create certain kinds of Azure-related service instances with an existing OpenAIClient. This PR attempts to address all of these issues and set up SK to evolve more easily: - Kernel now wraps an IServiceProvider, and all services (AI and non-AI) are retrieved from it. This has a variety of benefits, including future-proofing for wanting to flow more state around, better integration with other systems (e.g. Microsoft.Extensions.DependencyInjection), simpler construction, etc. - KernelBuilder still has dedicated WithXx methods for first-class features, but they're all layered on top of a new ConfigureServices method, which lets you add anything to an IServiceCollection. Because everything is based on IServiceCollection/IServiceProvider, dependencies are automatically satisfied from the container. For example, for services that accept an optional HttpClient, if one isn't specified to the WithXx method, the system will pull one out of the DI container if it can, which means all existing support for adding configured HttpClient instances to an IServiceCollection immediately apply to Kernel{Builder} as well. - KernelBuilder also exposes a ConfigurePlugins method, largely for parity with ConfigureServices, as fundmantally a Kernel is just the combination of a collection of services and a collection of plugins (plus some additional transient data, event handlers, etc.) This also integrates with IServiceProvider, with one passed into the callback, so that if the plugin itself needs access to something like logging, it can simply get it from the container. These ConfigureServices and ConfigurePlugins methods can be called any number of times. - KernelBuilder is now just a simplified, opinionated way to achieve something you can achieve in two other ways: construct the Kernel directly, or construct the Kernel via DI. For the latter, you can simply add Kernel as a DI resource, and it'll automatically be composed out of what's available. For example, if in ASP.NET you AddAzureOpenChatCompletion, AddLogging, AddHttp, etc., and then ask for a Kernel to be injected, that Kernel will be constructed from the IServiceProvider that contains all of those other resources, and thus anyone using the Kernel will get that logger, will get that HttpClient, etc.; the HttpClient will also be used by the Azure OpenAI chat completion. @matthewbolanos, @markwallace-microsoft, @SergeyMenshykh, please review from an SK perspective to let me know if you agree with the direction, whether there are specific things you'd like to see changed, etc. There are some straggling things we should still do after this (like add a few more extension methods for adding plugins to a plugin collection, adding more tests, revamping XML comments, cleaning up more code, etc.), but this is a sweeping change and I wanted to get it in. @eerhardt, @halter73, I'd appreciate it if you could review this from a DI perspective, e.g. are there variations in the patterns I should be following, any best-practices I'm violating, whether I'm using singleton when I should be using transient, etc. etc. Note that I ran into one issue as part of this: the Microsoft.Extensions.DependencyInjection support for keyed services doesn't let you enumerate all services for a particular T, regardless of key. That's something SK depends on. To workaround that, KernelBuilder tracks all of the type/keys and injects a dictionary as a service with that information; then places the Kernel needs it, it can query for that dictionary and use it. This means that a Kernel constructed with KernelBuilder will fully support keys, whereas a Kernel constructed with DI or directly won't work as nicely with named services. My hope is that M.E.DI can fix this asap (offline conversation) in a way that will allow SK to upgrade to a patched version, delete its hack, and have those broader scenarios "just work". In the meantime, though, KernelBuilder behaves as desired. I also stopped short of adding Add{Azure}OpenAIXx methods that resolve the OpenAIClient from the service provider. That'll be an important thing to do once there's an Aspire component for Azure.AI.OpenAI, but in the meantime, I didn't want there to be an attractive Add{Azure}OpenAIXx method that looked like you didn't need to supply the necessary information even though you do. --------- Co-authored-by: SergeyMenshykh --- dotnet/Directory.Packages.props | 5 +- dotnet/SK-dotnet.sln | 18 - .../Example05_InlineFunctionDefinition.cs | 2 +- .../Example06_TemplateLanguage.cs | 2 +- .../Example07_BingAndGooglePlugins.cs | 2 +- .../Example08_RetryHandler.cs | 175 +-- .../Example09_FunctionTypes.cs | 2 +- ...xample10_DescribeAllPluginsAndFunctions.cs | 2 +- .../Example12_SequentialPlanner.cs | 8 +- .../Example13_ConversationSummaryPlugin.cs | 2 +- .../Example14_SemanticMemory.cs | 4 +- .../Example15_TextMemoryPlugin.cs | 4 +- .../Example16_CustomLLM.cs | 12 +- .../KernelSyntaxExamples/Example18_DallE.cs | 8 +- .../Example20_HuggingFace.cs | 4 +- .../Example22_OpenApiPlugin_AzureKeyVault.cs | 5 - .../KernelSyntaxExamples/Example26_AADAuth.cs | 2 +- ...Example27_SemanticFunctionsUsingChatGPT.cs | 2 +- .../Example28_ActionPlanner.cs | 2 +- .../Example30_ChatWithPrompts.cs | 2 +- .../Example31_CustomPlanner.cs | 6 +- .../Example40_DIContainer.cs | 74 +- .../Example41_HttpClientUsage.cs | 8 +- .../Example42_KernelBuilder.cs | 206 +-- .../Example43_GetModelResult.cs | 6 +- .../Example48_GroundednessChecks.cs | 4 +- .../Example51_StepwisePlanner.cs | 14 +- .../Example52_ApimAuth.cs | 23 +- .../Example54_AzureChatCompletionWithData.cs | 2 +- ...ateNativeFunctionsWithMultipleArguments.cs | 2 +- .../Example57_KernelHooks.cs | 12 +- .../Example58_ConfigureRequestSettings.cs | 2 +- .../Example59_OpenAIFunctionCalling.cs | 4 +- .../Example61_MultipleLLMs.cs | 4 +- .../Example62_CustomAIServiceSelector.cs | 12 +- .../Example63_ChatCompletionPrompts.cs | 2 +- .../Example63_FlowOrchestrator.cs | 12 +- .../Example64_MultiplePromptTemplates.cs | 2 +- .../Example65_HandlebarsPlanner.cs | 2 +- ...xample66_FunctionCallingStepwisePlanner.cs | 2 +- .../Example72_KernelStreaming.cs | 4 +- .../KernelSyntaxExamples.csproj | 10 +- .../Reliability/RetryThreeTimesWithBackoff.cs | 69 - .../RetryThreeTimesWithRetryAfterBackoff.cs | 75 -- dotnet/samples/TelemetryExample/Program.cs | 2 +- .../HuggingFaceKernelBuilderExtensions.cs | 110 +- .../HuggingFaceTextCompletion.cs | 4 +- .../HuggingFaceTextEmbeddingGeneration.cs | 4 +- .../AzureSdk/AzureOpenAIClientBase.cs | 30 +- .../AzureSdk/ClientBase.cs | 26 +- .../AzureSdk/OpenAIClientBase.cs | 28 +- .../AzureOpenAIChatCompletionWithData.cs | 2 +- .../CustomClient/OpenAIClientBase.cs | 2 +- .../OpenAIKernelBuilderExtensions.cs | 1162 +++++++++++++---- .../OpenAIMemoryBuilderExtensions.cs | 36 +- .../TextCompletion/OpenAITextCompletion.cs | 16 + .../AzureOpenAITextEmbeddingGeneration.cs | 17 + .../OpenAITextEmbeddingGeneration.cs | 16 + .../Connectors.Memory.Chroma/ChromaClient.cs | 2 +- .../ChromaMemoryBuilderExtensions.cs | 8 +- .../PineconeClient.cs | 2 +- .../PineconeMemoryBuilderExtensions.cs | 4 +- .../QdrantMemoryBuilderExtensions.cs | 8 +- .../QdrantVectorDbClient.cs | 2 +- .../WeaviateMemoryBuilderExtensions.cs | 8 +- .../WeaviateMemoryStore.cs | 2 +- .../Connectors.UnitTests.csproj | 1 + .../OpenAI/AIServicesOpenAIExtensionsTests.cs | 72 +- .../KernelFunctionMetadataExtensionsTests.cs | 10 +- .../Extensions/KernelExtensionTests.cs | 4 +- .../Integration/RunHarness.cs | 4 +- .../Integration/ThreadHarness.cs | 2 +- .../Assistants/AssistantBuilder.Static.cs | 4 +- .../Assistants/AssistantBuilder.cs | 2 +- .../Assistants/Internal/Assistant.cs | 16 +- .../CollectEmailPlugin.cs | 1 - ...Orchestration.Flow.IntegrationTests.csproj | 2 - .../FlowOrchestratorTests.cs | 11 +- .../Execution/FlowExecutor.cs | 6 +- .../Execution/ReActEngine.cs | 2 +- .../Extensions.UnitTests.csproj | 2 - .../Basic/BasicHttpRetryHandlerTests.cs | 678 ---------- .../Basic/BasicRetryConfigTests.cs | 67 - .../Polly/PollyHttpRetryHandlerTests.cs | 186 --- .../HandlebarsPromptTemplateTests.cs | 2 +- .../BasicHttpRetryHandler.cs | 232 ---- .../BasicHttpRetryHandlerFactory.cs | 55 - .../Reliability.Basic/BasicRetryConfig.cs | 74 -- .../Reliability.Basic.csproj | 32 - ...ReliabilityBasicKernelBuilderExtensions.cs | 25 - .../PollyHttpRetryHandler.cs | 60 - .../PollyHttpRetryHandlerFactory.cs | 54 - .../Reliability.Polly.csproj | 36 - ...ReliabilityPollyKernelBuilderExtensions.cs | 40 - .../Extensions/KernelGrpcExtensions.cs | 51 +- .../KernelFunctionsMarkdownExtensions.cs | 4 +- .../KernelOpenApiPluginExtensions.cs | 19 +- .../OpenAI/KernelOpenAIPluginExtensions.cs | 8 +- .../Functions/KernelFunctionMarkdownTests.cs | 2 +- .../KernelOpenApiPluginExtensionsTests.cs | 4 +- .../KernelOpenAIPluginExtensionsTests.cs | 2 +- .../KernelFunctionsPromptYamlExtensions.cs | 5 +- .../OpenAI/AzureOpenAICompletionTests.cs | 112 -- .../OpenAI/OpenAICompletionTests.cs | 71 +- ...ts.cs => KernelFunctionExtensionsTests.cs} | 23 +- .../IntegrationTests/IntegrationTests.csproj | 10 +- .../Handlebars/HandlebarsPlannerTests.cs | 68 +- .../IntegrationTests/Planners/PlanTests.cs | 69 +- .../SequentialPlanParserTests.cs | 6 +- .../SequentialPlannerTests.cs | 76 +- .../FunctionCallingStepwisePlannerTests.cs | 49 +- .../StepwisePlanner/StepwisePlannerTests.cs | 76 +- .../IntegrationTests/Plugins/PluginTests.cs | 12 +- .../Plugins/SamplePluginsTests.cs | 4 +- .../src/Http/HttpClientProvider.cs | 60 +- .../Http/NonDisposableHttpClientHandler.cs | 35 - .../InternalUtilities/test/FunctionHelpers.cs | 2 +- .../Action/ActionPlannerTests.cs | 7 +- ...adOnlyFunctionCollectionExtensionsTests.cs | 17 +- .../Planning/PlanSerializationTests.cs | 3 +- .../Planning/PlanTests.cs | 11 +- .../Sequential/SequentialPlanParserTests.cs | 7 +- .../Sequential/SequentialPlannerTests.cs | 7 +- .../Stepwise/ParseResultTests.cs | 5 +- .../Stepwise/StepwisePlannerTests.cs | 3 +- .../Planners.Core/Action/ActionPlanner.cs | 2 +- .../Sequential/SequentialPlanner.cs | 2 +- .../Planners.Core/Stepwise/StepwisePlanner.cs | 9 +- ...HandlebarsTemplateEngineExtensionsTests.cs | 6 +- .../Handlebars/HandlebarsPlanner.cs | 2 +- .../FunctionCallingStepwisePlanner.cs | 6 +- dotnet/src/Plugins/Plugins.Core/HttpPlugin.cs | 2 +- .../Plugins/Plugins.Memory/MemoryBuilder.cs | 24 +- .../Core/FileIOPluginTests.cs | 10 +- .../Plugins.UnitTests/Core/HttpPluginTests.cs | 6 +- .../Plugins.UnitTests/Core/MathPluginTests.cs | 5 +- .../Plugins.UnitTests/Core/TextPluginTests.cs | 5 +- .../Plugins.UnitTests/Core/TimePluginTests.cs | 5 +- .../Plugins.UnitTests/Core/WaitPluginTests.cs | 5 +- .../Memory/MemoryBuilderTests.cs | 24 +- .../Web/SearchUrlSkillTests.cs | 5 +- .../Plugins/Plugins.Web/Bing/BingConnector.cs | 2 +- .../Plugins.Web/WebFileDownloadPlugin.cs | 2 +- .../ChatCompletionServiceExtensions.cs | 39 - .../TextEmbeddingServiceExtensions.cs | 40 - .../ImageGenerationServiceExtensions.cs | 39 - .../TextCompletionServiceExtensions.cs | 39 - .../Functions/KernelFunction.cs | 4 +- .../Http/HttpHandlerFactory{THandler}.cs | 24 - .../Http/IDelegatingHandlerFactory.cs | 19 - .../Http/NullHttpHandler.cs | 12 - .../Http/NullHttpHandlerFactory.cs | 27 - .../src/SemanticKernel.Abstractions/Kernel.cs | 315 +++-- .../SemanticKernel.Abstractions.csproj | 1 + .../Services/EmptyServiceProvider.cs | 27 + .../Services/IAIService.cs | 2 +- .../Services/IAIServiceProvider.cs | 10 - .../Services/IAIServiceSelector.cs | 4 +- .../Services/INamedServiceProvider.cs | 27 - .../Services/OrderedIAIServiceSelector.cs | 39 +- .../Services/ServiceExtensions.cs | 37 - .../Functions/KernelFunctionFromMethod.cs | 4 +- .../Functions/KernelFunctionFromPrompt.cs | 2 +- .../src/SemanticKernel.Core/KernelBuilder.cs | 231 ++-- .../SemanticKernel.Core/KernelExtensions.cs | 54 +- .../Reliability/NullHttpRetryHandler.cs | 33 - .../SemanticKernel.Core.csproj | 4 + .../Services/AIServiceCollection.cs | 115 -- .../Services/AIServiceProvider.cs | 21 - .../Services/NamedServiceProvider.cs | 107 -- .../TemplateEngine/Blocks/CodeBlock.cs | 4 +- .../SemanticKernel.MetaPackage.csproj | 1 - .../ChatCompletionServiceExtensionTests.cs | 147 --- .../TextEmbeddingServiceExtensionTests.cs | 147 --- .../ImageCompletionServiceExtensionTests.cs | 146 --- .../TextCompletionServiceExtensionTests.cs | 147 --- .../Functions/FunctionFromMethodTests.cs | 10 +- .../Functions/FunctionFromPromptTests.cs | 85 +- .../Functions/KernelFunctionMetadataTests.cs | 3 +- .../Functions/KernelFunctionTests2.cs | 2 +- .../Functions/KernelFunctionTests3.cs | 5 +- .../Functions/MultipleModelTests.cs | 51 +- ...redIAIServiceConfigurationProviderTests.cs | 79 +- .../SemanticKernel.UnitTests/KernelTests.cs | 82 +- .../KernelPromptTemplateTests.cs | 2 +- .../Reliability/NullHttpRetryHandlerTests.cs | 108 -- .../Services/ServiceRegistryTests.cs | 223 ---- .../TemplateEngine/Blocks/CodeBlockTests.cs | 48 +- .../Blocks/FunctionIdBlockTests.cs | 5 +- .../Blocks/NamedArgBlockTests.cs | 5 +- 190 files changed, 2214 insertions(+), 5303 deletions(-) delete mode 100644 dotnet/samples/KernelSyntaxExamples/Reliability/RetryThreeTimesWithBackoff.cs delete mode 100644 dotnet/samples/KernelSyntaxExamples/Reliability/RetryThreeTimesWithRetryAfterBackoff.cs delete mode 100644 dotnet/src/Extensions/Extensions.UnitTests/Reliability/Basic/BasicHttpRetryHandlerTests.cs delete mode 100644 dotnet/src/Extensions/Extensions.UnitTests/Reliability/Basic/BasicRetryConfigTests.cs delete mode 100644 dotnet/src/Extensions/Extensions.UnitTests/Reliability/Polly/PollyHttpRetryHandlerTests.cs delete mode 100644 dotnet/src/Extensions/Reliability.Basic/BasicHttpRetryHandler.cs delete mode 100644 dotnet/src/Extensions/Reliability.Basic/BasicHttpRetryHandlerFactory.cs delete mode 100644 dotnet/src/Extensions/Reliability.Basic/BasicRetryConfig.cs delete mode 100644 dotnet/src/Extensions/Reliability.Basic/Reliability.Basic.csproj delete mode 100644 dotnet/src/Extensions/Reliability.Basic/ReliabilityBasicKernelBuilderExtensions.cs delete mode 100644 dotnet/src/Extensions/Reliability.Polly/PollyHttpRetryHandler.cs delete mode 100644 dotnet/src/Extensions/Reliability.Polly/PollyHttpRetryHandlerFactory.cs delete mode 100644 dotnet/src/Extensions/Reliability.Polly/Reliability.Polly.csproj delete mode 100644 dotnet/src/Extensions/Reliability.Polly/ReliabilityPollyKernelBuilderExtensions.cs delete mode 100644 dotnet/src/IntegrationTests/Connectors/OpenAI/AzureOpenAICompletionTests.cs rename dotnet/src/IntegrationTests/Extensions/{KernelSemanticFunctionExtensionsTests.cs => KernelFunctionExtensionsTests.cs} (81%) delete mode 100644 dotnet/src/InternalUtilities/src/Http/NonDisposableHttpClientHandler.cs delete mode 100644 dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatCompletionServiceExtensions.cs delete mode 100644 dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/TextEmbeddingServiceExtensions.cs delete mode 100644 dotnet/src/SemanticKernel.Abstractions/AI/ImageGeneration/ImageGenerationServiceExtensions.cs delete mode 100644 dotnet/src/SemanticKernel.Abstractions/AI/TextCompletion/TextCompletionServiceExtensions.cs delete mode 100644 dotnet/src/SemanticKernel.Abstractions/Http/HttpHandlerFactory{THandler}.cs delete mode 100644 dotnet/src/SemanticKernel.Abstractions/Http/IDelegatingHandlerFactory.cs delete mode 100644 dotnet/src/SemanticKernel.Abstractions/Http/NullHttpHandler.cs delete mode 100644 dotnet/src/SemanticKernel.Abstractions/Http/NullHttpHandlerFactory.cs create mode 100644 dotnet/src/SemanticKernel.Abstractions/Services/EmptyServiceProvider.cs delete mode 100644 dotnet/src/SemanticKernel.Abstractions/Services/IAIServiceProvider.cs delete mode 100644 dotnet/src/SemanticKernel.Abstractions/Services/INamedServiceProvider.cs delete mode 100644 dotnet/src/SemanticKernel.Abstractions/Services/ServiceExtensions.cs delete mode 100644 dotnet/src/SemanticKernel.Core/Reliability/NullHttpRetryHandler.cs delete mode 100644 dotnet/src/SemanticKernel.Core/Services/AIServiceCollection.cs delete mode 100644 dotnet/src/SemanticKernel.Core/Services/AIServiceProvider.cs delete mode 100644 dotnet/src/SemanticKernel.Core/Services/NamedServiceProvider.cs delete mode 100644 dotnet/src/SemanticKernel.UnitTests/AI/ChatCompletion/ChatCompletionServiceExtensionTests.cs delete mode 100644 dotnet/src/SemanticKernel.UnitTests/AI/Embeddings/TextEmbeddingServiceExtensionTests.cs delete mode 100644 dotnet/src/SemanticKernel.UnitTests/AI/ImageGeneration/ImageCompletionServiceExtensionTests.cs delete mode 100644 dotnet/src/SemanticKernel.UnitTests/AI/TextCompletion/TextCompletionServiceExtensionTests.cs delete mode 100644 dotnet/src/SemanticKernel.UnitTests/Reliability/NullHttpRetryHandlerTests.cs delete mode 100644 dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props index 5a6518ddcaa0..c89538bc422a 100644 --- a/dotnet/Directory.Packages.props +++ b/dotnet/Directory.Packages.props @@ -35,6 +35,8 @@ + + @@ -72,7 +74,8 @@ - + + all diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln index 792a51fd09e1..c0b9361af540 100644 --- a/dotnet/SK-dotnet.sln +++ b/dotnet/SK-dotnet.sln @@ -139,10 +139,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TelemetryExample", "samples EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Connectors.Memory.Kusto", "src\Connectors\Connectors.Memory.Kusto\Connectors.Memory.Kusto.csproj", "{E07608CC-D710-4655-BB9E-D22CF3CDD193}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reliability.Polly", "src\Extensions\Reliability.Polly\Reliability.Polly.csproj", "{D4540A0F-98E3-4E70-9093-1948AE5B2AAD}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Reliability.Basic", "src\Extensions\Reliability.Basic\Reliability.Basic.csproj", "{3DC4DBD8-20A5-4937-B4F5-BB5E24E7A567}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "plugins", "plugins", "{D6D598DF-C17C-46F4-B2B9-CDE82E2DE132}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plugins.UnitTests", "src\Plugins\Plugins.UnitTests\Plugins.UnitTests.csproj", "{5CB78CE4-895B-4A14-98AA-716A37DEEBB1}" @@ -374,18 +370,6 @@ Global {E07608CC-D710-4655-BB9E-D22CF3CDD193}.Publish|Any CPU.Build.0 = Publish|Any CPU {E07608CC-D710-4655-BB9E-D22CF3CDD193}.Release|Any CPU.ActiveCfg = Release|Any CPU {E07608CC-D710-4655-BB9E-D22CF3CDD193}.Release|Any CPU.Build.0 = Release|Any CPU - {D4540A0F-98E3-4E70-9093-1948AE5B2AAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D4540A0F-98E3-4E70-9093-1948AE5B2AAD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D4540A0F-98E3-4E70-9093-1948AE5B2AAD}.Publish|Any CPU.ActiveCfg = Publish|Any CPU - {D4540A0F-98E3-4E70-9093-1948AE5B2AAD}.Publish|Any CPU.Build.0 = Publish|Any CPU - {D4540A0F-98E3-4E70-9093-1948AE5B2AAD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D4540A0F-98E3-4E70-9093-1948AE5B2AAD}.Release|Any CPU.Build.0 = Release|Any CPU - {3DC4DBD8-20A5-4937-B4F5-BB5E24E7A567}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3DC4DBD8-20A5-4937-B4F5-BB5E24E7A567}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3DC4DBD8-20A5-4937-B4F5-BB5E24E7A567}.Publish|Any CPU.ActiveCfg = Publish|Any CPU - {3DC4DBD8-20A5-4937-B4F5-BB5E24E7A567}.Publish|Any CPU.Build.0 = Publish|Any CPU - {3DC4DBD8-20A5-4937-B4F5-BB5E24E7A567}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3DC4DBD8-20A5-4937-B4F5-BB5E24E7A567}.Release|Any CPU.Build.0 = Release|Any CPU {5CB78CE4-895B-4A14-98AA-716A37DEEBB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5CB78CE4-895B-4A14-98AA-716A37DEEBB1}.Debug|Any CPU.Build.0 = Debug|Any CPU {5CB78CE4-895B-4A14-98AA-716A37DEEBB1}.Publish|Any CPU.ActiveCfg = Debug|Any CPU @@ -504,8 +488,6 @@ Global {E6EDAB8F-3406-4DBF-9AAB-DF40DC2CA0FA} = {FA3720F1-C99A-49B2-9577-A940257098BF} {C754950A-E16C-4F96-9CC7-9328E361B5AF} = {FA3720F1-C99A-49B2-9577-A940257098BF} {E07608CC-D710-4655-BB9E-D22CF3CDD193} = {0247C2C9-86C3-45BA-8873-28B0948EDC0C} - {D4540A0F-98E3-4E70-9093-1948AE5B2AAD} = {078F96B4-09E1-4E0E-B214-F71A4F4BF633} - {3DC4DBD8-20A5-4937-B4F5-BB5E24E7A567} = {078F96B4-09E1-4E0E-B214-F71A4F4BF633} {D6D598DF-C17C-46F4-B2B9-CDE82E2DE132} = {831DDCA2-7D2C-4C31-80DB-6BDB3E1F7AE0} {5CB78CE4-895B-4A14-98AA-716A37DEEBB1} = {D6D598DF-C17C-46F4-B2B9-CDE82E2DE132} {A21FAC7C-0C09-4EAD-843B-926ACEF73C80} = {831DDCA2-7D2C-4C31-80DB-6BDB3E1F7AE0} diff --git a/dotnet/samples/KernelSyntaxExamples/Example05_InlineFunctionDefinition.cs b/dotnet/samples/KernelSyntaxExamples/Example05_InlineFunctionDefinition.cs index 0f89dad2c11c..eec101443f0c 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example05_InlineFunctionDefinition.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example05_InlineFunctionDefinition.cs @@ -30,7 +30,7 @@ public static async Task RunAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithOpenAIChatCompletionService( + .WithOpenAIChatCompletion( modelId: openAIModelId, apiKey: openAIApiKey) .Build(); diff --git a/dotnet/samples/KernelSyntaxExamples/Example06_TemplateLanguage.cs b/dotnet/samples/KernelSyntaxExamples/Example06_TemplateLanguage.cs index 9d4ca48ec60d..1f6c6bcc8b4a 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example06_TemplateLanguage.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example06_TemplateLanguage.cs @@ -30,7 +30,7 @@ public static async Task RunAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithOpenAIChatCompletionService( + .WithOpenAIChatCompletion( modelId: openAIModelId, apiKey: openAIApiKey) .Build(); diff --git a/dotnet/samples/KernelSyntaxExamples/Example07_BingAndGooglePlugins.cs b/dotnet/samples/KernelSyntaxExamples/Example07_BingAndGooglePlugins.cs index fe04cc6b128e..1d196a1a91ca 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example07_BingAndGooglePlugins.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example07_BingAndGooglePlugins.cs @@ -32,7 +32,7 @@ public static async Task RunAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithOpenAIChatCompletionService( + .WithOpenAIChatCompletion( modelId: openAIModelId, apiKey: openAIApiKey) .Build(); diff --git a/dotnet/samples/KernelSyntaxExamples/Example08_RetryHandler.cs b/dotnet/samples/KernelSyntaxExamples/Example08_RetryHandler.cs index 34da9b5cdc08..a9f6d0455ab6 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example08_RetryHandler.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example08_RetryHandler.cs @@ -1,177 +1,46 @@ // Copyright (c) Microsoft. All rights reserved. using System; -using System.IO; using System.Net; -using System.Net.Http; -using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Http.Resilience; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Http; -using Microsoft.SemanticKernel.Plugins.Core; -using Microsoft.SemanticKernel.Reliability.Basic; -using Polly; -using RepoUtils; -// ReSharper disable once InconsistentNaming +#pragma warning disable CA1031 // Do not catch general exception types +#pragma warning disable CA2000 // Dispose objects before losing scope + public static class Example08_RetryHandler { public static async Task RunAsync() { - await DefaultNoRetryAsync(); - - await ReliabilityBasicExtensionAsync(); - - await ReliabilityPollyExtensionAsync(); - - await CustomHandlerAsync(); - } - - private static async Task DefaultNoRetryAsync() - { - InfoLogger.Logger.LogInformation("============================== Kernel default behavior: No Retry =============================="); - var kernel = InitializeKernelBuilder() - .Build(); - - await ImportAndExecutePluginAsync(kernel); - } - - private static async Task ReliabilityBasicExtensionAsync() - { - InfoLogger.Logger.LogInformation("============================== Using Reliability.Basic extension =============================="); - var retryConfig = new BasicRetryConfig + // Create a Kernel with the HttpClient + var kernel = new KernelBuilder().ConfigureServices(c => { - MaxRetryCount = 3, - UseExponentialBackoff = true, - }; - retryConfig.RetryableStatusCodes.Add(HttpStatusCode.Unauthorized); - - var kernel = InitializeKernelBuilder() - .WithRetryBasic(retryConfig) - .Build(); - - await ImportAndExecutePluginAsync(kernel); - } - - private static async Task ReliabilityPollyExtensionAsync() - { - InfoLogger.Logger.LogInformation("============================== Using Reliability.Polly extension =============================="); - var kernel = InitializeKernelBuilder() - .WithRetryPolly(GetPollyPolicy(InfoLogger.LoggerFactory)) - .Build(); - - await ImportAndExecutePluginAsync(kernel); - } - - private static async Task CustomHandlerAsync() - { - InfoLogger.Logger.LogInformation("============================== Using a Custom Http Handler =============================="); - var kernel = InitializeKernelBuilder() - .WithHttpHandlerFactory(new MyCustomHandlerFactory()) - .Build(); - - await ImportAndExecutePluginAsync(kernel); - } - - private static KernelBuilder InitializeKernelBuilder() - { - return new KernelBuilder() - .WithLoggerFactory(InfoLogger.LoggerFactory) - // OpenAI settings - you can set the OpenAI.ApiKey to an invalid value to see the retry policy in play - .WithOpenAIChatCompletionService(TestConfiguration.OpenAI.ChatModelId, "BAD_KEY"); - } - - private static AsyncPolicy GetPollyPolicy(ILoggerFactory? logger) - { - // Handle 429 and 401 errors - // Typically 401 would not be something we retry but for demonstration - // purposes we are doing so as it's easy to trigger when using an invalid key. - const int TooManyRequests = 429; - const int Unauthorized = 401; - - return Policy - .HandleResult(response => - (int)response.StatusCode is TooManyRequests or Unauthorized) - .WaitAndRetryAsync(new[] + c.AddLogging(c => c.AddConsole().SetMinimumLevel(LogLevel.Information)); + c.ConfigureHttpClientDefaults(c => + { + // Use a standard resiliency policy, augmented to retry on 401 Unauthorized for this example + c.AddStandardResilienceHandler().Configure(o => { - TimeSpan.FromSeconds(2), - TimeSpan.FromSeconds(4), - TimeSpan.FromSeconds(8) - }, - (outcome, timespan, retryCount, _) - => InfoLogger.Logger.LogWarning("Error executing action [attempt {RetryCount} of 3], pausing {PausingMilliseconds}ms. Outcome: {StatusCode}", - retryCount, - timespan.TotalMilliseconds, - outcome.Result.StatusCode)); - } - - private static async Task ImportAndExecutePluginAsync(Kernel kernel) - { - // Load semantic plugin defined with prompt templates - string folder = RepoFiles.SamplePluginsPath(); - - kernel.ImportPluginFromObject(); - - var qaPlugin = kernel.ImportPluginFromPromptDirectory(Path.Combine(folder, "QAPlugin")); + o.Retry.ShouldHandle = args => ValueTask.FromResult(args.Outcome.Result?.StatusCode is HttpStatusCode.Unauthorized); + }); + }); + c.AddOpenAIChatCompletion("gpt-4", "BAD_KEY"); // OpenAI settings - you can set the OpenAI.ApiKey to an invalid value to see the retry policy in play + }).Build(); - var question = "How popular is Polly library?"; + var logger = kernel.GetService().CreateLogger(typeof(Example08_RetryHandler)); - InfoLogger.Logger.LogInformation("Question: {0}", question); - // To see the retry policy in play, you can set the OpenAI.ApiKey to an invalid value -#pragma warning disable CA1031 // Do not catch general exception types + const string Question = "How popular is Polly library?"; + logger.LogInformation("Question: {Question}", Question); try { - var answer = await kernel.InvokeAsync(qaPlugin["Question"], question); - InfoLogger.Logger.LogInformation("Answer: {0}", answer.GetValue()); + logger.LogInformation("Answer: {Result}", await kernel.InvokePromptAsync(Question)); } catch (Exception ex) { - InfoLogger.Logger.LogInformation("Error: {0}", ex.Message); - } -#pragma warning restore CA1031 // Do not catch general exception types - } - - // Basic custom retry handler factory - public sealed class MyCustomHandlerFactory : HttpHandlerFactory - { - } - - // Basic custom empty retry handler - public sealed class MyCustomHandler : DelegatingHandler - { - public MyCustomHandler(ILoggerFactory loggerFactory) - { - } - - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - // Your custom http handling implementation - return Task.FromResult(new HttpResponseMessage(HttpStatusCode.BadRequest) - { - Content = new StringContent("My custom bad request override") - }); - } - } - - private static class InfoLogger - { - internal static ILogger Logger => LoggerFactory.CreateLogger("Example08_RetryHandler"); - internal static ILoggerFactory LoggerFactory => s_loggerFactory.Value; - private static readonly Lazy s_loggerFactory = new(LogBuilder); - - private static ILoggerFactory LogBuilder() - { - return Microsoft.Extensions.Logging.LoggerFactory.Create(builder => - { - builder.SetMinimumLevel(LogLevel.Information); - builder.AddFilter("Microsoft", LogLevel.Information); - builder.AddFilter("Microsoft.SemanticKernel", LogLevel.Critical); - builder.AddFilter("Microsoft.SemanticKernel.Reliability", LogLevel.Information); - builder.AddFilter("System", LogLevel.Information); - - builder.AddConsole(); - }); + logger.LogInformation("Error: {Message}", ex.Message); } } } diff --git a/dotnet/samples/KernelSyntaxExamples/Example09_FunctionTypes.cs b/dotnet/samples/KernelSyntaxExamples/Example09_FunctionTypes.cs index 12dce8813461..49ebb0f39219 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example09_FunctionTypes.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example09_FunctionTypes.cs @@ -18,7 +18,7 @@ public static async Task RunAsync() var kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithOpenAIChatCompletionService(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) + .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) .Build(); var variables = new ContextVariables(); diff --git a/dotnet/samples/KernelSyntaxExamples/Example10_DescribeAllPluginsAndFunctions.cs b/dotnet/samples/KernelSyntaxExamples/Example10_DescribeAllPluginsAndFunctions.cs index b3537a576e11..2a6728a8e959 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example10_DescribeAllPluginsAndFunctions.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example10_DescribeAllPluginsAndFunctions.cs @@ -22,7 +22,7 @@ public static Task RunAsync() Console.WriteLine("======== Describe all plugins and functions ========"); var kernel = new KernelBuilder() - .WithOpenAIChatCompletionService( + .WithOpenAIChatCompletion( modelId: TestConfiguration.OpenAI.ChatModelId, apiKey: TestConfiguration.OpenAI.ApiKey) .Build(); diff --git a/dotnet/samples/KernelSyntaxExamples/Example12_SequentialPlanner.cs b/dotnet/samples/KernelSyntaxExamples/Example12_SequentialPlanner.cs index 0b6e51200649..c4fd2ac99d6b 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example12_SequentialPlanner.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example12_SequentialPlanner.cs @@ -73,7 +73,7 @@ private static async Task PoetrySamplesAsync() Console.WriteLine("======== Sequential Planner - Create and Execute Poetry Plan ========"); var kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) @@ -257,7 +257,7 @@ private static Kernel InitializeKernelAndPlanner(out SequentialPlanner planner, { var kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) @@ -274,11 +274,11 @@ private static Kernel InitializeKernel() // use these to generate and store embeddings for the function descriptions. var kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) - .WithAzureOpenAITextEmbeddingGenerationService( + .WithAzureOpenAITextEmbeddingGeneration( TestConfiguration.AzureOpenAIEmbeddings.DeploymentName, TestConfiguration.AzureOpenAIEmbeddings.Endpoint, TestConfiguration.AzureOpenAIEmbeddings.ApiKey) diff --git a/dotnet/samples/KernelSyntaxExamples/Example13_ConversationSummaryPlugin.cs b/dotnet/samples/KernelSyntaxExamples/Example13_ConversationSummaryPlugin.cs index d4a7487794a7..681c638f4c0e 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example13_ConversationSummaryPlugin.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example13_ConversationSummaryPlugin.cs @@ -172,7 +172,7 @@ private static Kernel InitializeKernel() { Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) diff --git a/dotnet/samples/KernelSyntaxExamples/Example14_SemanticMemory.cs b/dotnet/samples/KernelSyntaxExamples/Example14_SemanticMemory.cs index 57fa1838eeff..b30f9368bddf 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example14_SemanticMemory.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example14_SemanticMemory.cs @@ -38,7 +38,7 @@ public static async Task RunAsync() var memoryWithACS = new MemoryBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithOpenAITextEmbeddingGenerationService("text-embedding-ada-002", TestConfiguration.OpenAI.ApiKey) + .WithOpenAITextEmbeddingGeneration("text-embedding-ada-002", TestConfiguration.OpenAI.ApiKey) .WithMemoryStore(new AzureCognitiveSearchMemoryStore(TestConfiguration.ACS.Endpoint, TestConfiguration.ACS.ApiKey)) .Build(); @@ -59,7 +59,7 @@ public static async Task RunAsync() var memoryWithCustomDb = new MemoryBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithOpenAITextEmbeddingGenerationService("text-embedding-ada-002", TestConfiguration.OpenAI.ApiKey) + .WithOpenAITextEmbeddingGeneration("text-embedding-ada-002", TestConfiguration.OpenAI.ApiKey) .WithMemoryStore(new VolatileMemoryStore()) .Build(); diff --git a/dotnet/samples/KernelSyntaxExamples/Example15_TextMemoryPlugin.cs b/dotnet/samples/KernelSyntaxExamples/Example15_TextMemoryPlugin.cs index fb1c75f2b967..c39967495142 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example15_TextMemoryPlugin.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example15_TextMemoryPlugin.cs @@ -154,8 +154,8 @@ private static async Task RunWithStoreAsync(IMemoryStore memoryStore, Cancellati { var kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithOpenAIChatCompletionService(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) - .WithOpenAITextEmbeddingGenerationService(TestConfiguration.OpenAI.EmbeddingModelId, TestConfiguration.OpenAI.ApiKey) + .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) + .WithOpenAITextEmbeddingGeneration(TestConfiguration.OpenAI.EmbeddingModelId, TestConfiguration.OpenAI.ApiKey) .Build(); // Create an embedding generator to use for semantic memory. diff --git a/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs b/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs index 4f84896247da..3c6bb8d8bf1d 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs @@ -7,6 +7,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.AI.TextCompletion; @@ -52,13 +53,14 @@ private static async Task CustomTextCompletionWithSKFunctionAsync() { Console.WriteLine("======== Custom LLM - Text Completion - SKFunction ========"); - Kernel kernel = new KernelBuilder() - .WithLoggerFactory(ConsoleLogger.LoggerFactory) + Kernel kernel = new KernelBuilder().ConfigureServices(c => + { + c.AddSingleton(ConsoleLogger.LoggerFactory) // Add your text completion service as a singleton instance - .WithAIService("myService1", new MyTextCompletionService()) + .AddKeyedSingleton("myService1", new MyTextCompletionService()) // Add your text completion service as a factory method - .WithAIService("myService2", (log) => new MyTextCompletionService()) - .Build(); + .AddKeyedSingleton("myService2", (_, _) => new MyTextCompletionService()); + }).Build(); const string FunctionDefinition = "Does the text contain grammar errors (Y/N)? Text: {{$input}}"; diff --git a/dotnet/samples/KernelSyntaxExamples/Example18_DallE.cs b/dotnet/samples/KernelSyntaxExamples/Example18_DallE.cs index d4d14ac9d417..300f799fb7b3 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example18_DallE.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example18_DallE.cs @@ -27,9 +27,9 @@ private static async Task OpenAIDallEAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) // Add your image generation service - .WithOpenAIImageGenerationService(TestConfiguration.OpenAI.ApiKey) + .WithOpenAIImageGeneration(TestConfiguration.OpenAI.ApiKey) // Add your chat completion service - .WithOpenAIChatCompletionService(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) + .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey) .Build(); IImageGeneration dallE = kernel.GetService(); @@ -96,9 +96,9 @@ public static async Task AzureOpenAIDallEAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) // Add your image generation service - .WithAzureOpenAIImageGenerationService(TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) + .WithAzureOpenAIImageGeneration(TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) // Add your chat completion service - .WithAzureOpenAIChatCompletionService(TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) + .WithAzureOpenAIChatCompletion(TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) .Build(); IImageGeneration dallE = kernel.GetService(); diff --git a/dotnet/samples/KernelSyntaxExamples/Example20_HuggingFace.cs b/dotnet/samples/KernelSyntaxExamples/Example20_HuggingFace.cs index f6bf820bd565..8535de96c4a6 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example20_HuggingFace.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example20_HuggingFace.cs @@ -27,7 +27,7 @@ private static async Task RunInferenceApiExampleAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithHuggingFaceTextCompletionService( + .WithHuggingFaceTextCompletion( model: TestConfiguration.HuggingFace.ModelId, apiKey: TestConfiguration.HuggingFace.ApiKey) .Build(); @@ -62,7 +62,7 @@ private static async Task RunLlamaExampleAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithHuggingFaceTextCompletionService( + .WithHuggingFaceTextCompletion( model: Model, endpoint: Endpoint, apiKey: TestConfiguration.HuggingFace.ApiKey) diff --git a/dotnet/samples/KernelSyntaxExamples/Example22_OpenApiPlugin_AzureKeyVault.cs b/dotnet/samples/KernelSyntaxExamples/Example22_OpenApiPlugin_AzureKeyVault.cs index a47104dc72b8..d599687cc7c1 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example22_OpenApiPlugin_AzureKeyVault.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example22_OpenApiPlugin_AzureKeyVault.cs @@ -33,11 +33,6 @@ public static async Task GetSecretFromAzureKeyVaultWithRetryAsync(InteractiveMsa { var kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithRetryBasic(new() - { - MaxRetryCount = 3, - UseExponentialBackoff = true - }) .Build(); var type = typeof(PluginResourceNames); diff --git a/dotnet/samples/KernelSyntaxExamples/Example26_AADAuth.cs b/dotnet/samples/KernelSyntaxExamples/Example26_AADAuth.cs index ff7cfea154a7..051cd9826f76 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example26_AADAuth.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example26_AADAuth.cs @@ -44,7 +44,7 @@ public static async Task RunAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) // Add Azure OpenAI chat completion service using DefaultAzureCredential AAD auth - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.Endpoint, new DefaultAzureCredential(authOptions)) diff --git a/dotnet/samples/KernelSyntaxExamples/Example27_SemanticFunctionsUsingChatGPT.cs b/dotnet/samples/KernelSyntaxExamples/Example27_SemanticFunctionsUsingChatGPT.cs index dd283d6aed87..f1eb9db0781b 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example27_SemanticFunctionsUsingChatGPT.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example27_SemanticFunctionsUsingChatGPT.cs @@ -17,7 +17,7 @@ public static async Task RunAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithAzureOpenAIChatCompletionService(TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) + .WithAzureOpenAIChatCompletion(TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) .Build(); var func = kernel.CreateFunctionFromPrompt( diff --git a/dotnet/samples/KernelSyntaxExamples/Example28_ActionPlanner.cs b/dotnet/samples/KernelSyntaxExamples/Example28_ActionPlanner.cs index 2b4b3237b1d9..da6990220168 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example28_ActionPlanner.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example28_ActionPlanner.cs @@ -15,7 +15,7 @@ public static async Task RunAsync() Console.WriteLine("======== Action Planner ========"); var kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) diff --git a/dotnet/samples/KernelSyntaxExamples/Example30_ChatWithPrompts.cs b/dotnet/samples/KernelSyntaxExamples/Example30_ChatWithPrompts.cs index 3da22c70dc6d..56fa09fd8ee8 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example30_ChatWithPrompts.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example30_ChatWithPrompts.cs @@ -63,7 +63,7 @@ public static async Task RunAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithOpenAIChatCompletionService(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey, serviceId: "chat") + .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey, serviceId: "chat") .Build(); // As an example, we import the time plugin, which is used in system prompt to read the current date. diff --git a/dotnet/samples/KernelSyntaxExamples/Example31_CustomPlanner.cs b/dotnet/samples/KernelSyntaxExamples/Example31_CustomPlanner.cs index d9460a45e1a0..2b0e2d63854a 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example31_CustomPlanner.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example31_CustomPlanner.cs @@ -132,11 +132,11 @@ private static Kernel InitializeKernel() { return new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) - .WithAzureOpenAITextEmbeddingGenerationService( + .WithAzureOpenAITextEmbeddingGeneration( TestConfiguration.AzureOpenAIEmbeddings.DeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) @@ -147,7 +147,7 @@ private static ISemanticTextMemory InitializeMemory() { return new MemoryBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithAzureOpenAITextEmbeddingGenerationService( + .WithAzureOpenAITextEmbeddingGeneration( TestConfiguration.AzureOpenAIEmbeddings.DeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) diff --git a/dotnet/samples/KernelSyntaxExamples/Example40_DIContainer.cs b/dotnet/samples/KernelSyntaxExamples/Example40_DIContainer.cs index 656b58fe6d98..daa7e0bfa412 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example40_DIContainer.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example40_DIContainer.cs @@ -5,12 +5,6 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.AI.TextCompletion; -using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion; -using Microsoft.SemanticKernel.Http; -using Microsoft.SemanticKernel.Memory; -using Microsoft.SemanticKernel.Reliability.Basic; -using Microsoft.SemanticKernel.Services; using RepoUtils; /** @@ -20,72 +14,12 @@ public static class Example40_DIContainer { public static async Task RunAsync() { - await UseKernelInDIPowerAppAsync(); - - await UseKernelInDIPowerApp_AdvancedScenarioAsync(); - } - - /// - /// This example shows how to register a Kernel in a DI container using KernelBuilder instead of - /// registering its dependencies. - /// - private static async Task UseKernelInDIPowerAppAsync() - { - //Bootstrapping code that initializes the modules, components, and classes that applications use. - //For regular .NET applications, the bootstrapping code usually resides either in the Main method or very close to it. - //In ASP.NET Core applications, the bootstrapping code is typically located in the ConfigureServices method of the Startup class. - - //Registering Kernel dependencies - var collection = new ServiceCollection(); - collection.AddTransient((_) => ConsoleLogger.LoggerFactory); - - //Registering Kernel - collection.AddTransient((serviceProvider) => - { - return new KernelBuilder() - .WithLoggerFactory(serviceProvider.GetRequiredService()) - .WithOpenAITextCompletionService(TestConfiguration.OpenAI.ModelId, TestConfiguration.OpenAI.ApiKey) - .Build(); - }); - - //Registering class that uses Kernel to execute a plugin - collection.AddTransient(); - - //Creating a service provider for resolving registered services - var serviceProvider = collection.BuildServiceProvider(); - - //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 kernelClient = serviceProvider.GetRequiredService(); - - //Execute the function - await kernelClient.SummarizeAsync("What's the tallest building in South America?"); - } - - /// - /// This example shows how to registered Kernel and all its dependencies in DI container. - /// - private static async Task UseKernelInDIPowerApp_AdvancedScenarioAsync() - { - //Bootstrapping code that initializes the modules, components, and classes that applications use. - //For regular .NET applications, the bootstrapping code usually resides either in the Main method or very close to it. - //In ASP.NET Core applications, the bootstrapping code is typically located in the ConfigureServices method of the Startup class. - - //Registering AI services Kernel is going to use - var aiServicesCollection = new AIServiceCollection(); - aiServicesCollection.SetService(() => new OpenAITextCompletion(TestConfiguration.OpenAI.ModelId, TestConfiguration.OpenAI.ApiKey)); - - //Registering Kernel dependencies var collection = new ServiceCollection(); - collection.AddTransient((_) => ConsoleLogger.LoggerFactory); - collection.AddTransient((_) => BasicHttpRetryHandlerFactory.Instance); - collection.AddTransient((_) => NullMemory.Instance); - collection.AddTransient((_) => aiServicesCollection.Build()); //Registering AI service provider that is used by Kernel to resolve AI services runtime - - //Registering Kernel - collection.AddTransient(); + collection.AddSingleton(ConsoleLogger.LoggerFactory); + collection.AddOpenAITextCompletion(TestConfiguration.OpenAI.ModelId, TestConfiguration.OpenAI.ApiKey); + collection.AddSingleton(); - //Registering class that uses Kernel to execute a plugin + // Registering class that uses Kernel to execute a plugin collection.AddTransient(); //Creating a service provider for resolving registered services diff --git a/dotnet/samples/KernelSyntaxExamples/Example41_HttpClientUsage.cs b/dotnet/samples/KernelSyntaxExamples/Example41_HttpClientUsage.cs index e52a8bdf39d4..a7d7c084e7da 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example41_HttpClientUsage.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example41_HttpClientUsage.cs @@ -32,7 +32,7 @@ public static Task RunAsync() private static void UseDefaultHttpClient() { var kernel = new KernelBuilder() - .WithOpenAIChatCompletionService( + .WithOpenAIChatCompletion( modelId: TestConfiguration.OpenAI.ChatModelId, apiKey: TestConfiguration.OpenAI.ApiKey) // If you need to use the default HttpClient from the SK SDK, simply omit the argument for the httpMessageInvoker parameter. .Build(); @@ -47,7 +47,7 @@ private static void UseCustomHttpClient() // If you need to use a custom HttpClient, simply pass it as an argument for the httpClient parameter. var kernel = new KernelBuilder() - .WithOpenAIChatCompletionService( + .WithOpenAIChatCompletion( modelId: TestConfiguration.OpenAI.ModelId, apiKey: TestConfiguration.OpenAI.ApiKey, httpClient: httpClient) @@ -68,7 +68,7 @@ private static void UseBasicRegistrationWithHttpClientFactory() var factory = sp.GetRequiredService(); var kernel = new KernelBuilder() - .WithOpenAIChatCompletionService( + .WithOpenAIChatCompletion( modelId: TestConfiguration.OpenAI.ChatModelId, apiKey: TestConfiguration.OpenAI.ApiKey, httpClient: factory.CreateClient()) @@ -99,7 +99,7 @@ private static void UseNamedRegistrationWitHttpClientFactory() var factory = sp.GetRequiredService(); var kernel = new KernelBuilder() - .WithOpenAIChatCompletionService( + .WithOpenAIChatCompletion( modelId: TestConfiguration.OpenAI.ChatModelId, apiKey: TestConfiguration.OpenAI.ApiKey, httpClient: factory.CreateClient("test-client")) diff --git a/dotnet/samples/KernelSyntaxExamples/Example42_KernelBuilder.cs b/dotnet/samples/KernelSyntaxExamples/Example42_KernelBuilder.cs index 536e9cda475e..b5217b634d1c 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example42_KernelBuilder.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example42_KernelBuilder.cs @@ -6,25 +6,13 @@ #pragma warning disable CA1852 -using System; -using System.Net.Http; -using System.Threading; +using System.Diagnostics; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.AI.TextCompletion; -using Microsoft.SemanticKernel.Connectors.AI.OpenAI.ChatCompletion; -using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; -using Microsoft.SemanticKernel.Http; -using Microsoft.SemanticKernel.Memory; -using Microsoft.SemanticKernel.Plugins.Memory; -using Microsoft.SemanticKernel.Reliability.Basic; -using Microsoft.SemanticKernel.Reliability.Polly; -using Microsoft.SemanticKernel.Services; - -using Polly; -using Polly.Retry; +using RepoUtils; // ReSharper disable once InconsistentNaming public static class Example42_KernelBuilder @@ -36,165 +24,57 @@ public static Task RunAsync() string azureOpenAIChatCompletionDeployment = TestConfiguration.AzureOpenAI.ChatDeploymentName; string azureOpenAIEmbeddingDeployment = TestConfiguration.AzureOpenAIEmbeddings.DeploymentName; -#pragma warning disable CA1852 // Seal internal types - Kernel kernel1 = new KernelBuilder().Build(); -#pragma warning restore CA1852 // Seal internal types - - Kernel kernel2 = new KernelBuilder().Build(); - - // ========================================================================================================== - // new KernelBuilder() 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 = new KernelBuilder(); - - var builder3 = new KernelBuilder(); - - // ========================================================================================================== - // 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 to be used by the kernel - var loggerFactory = NullLoggerFactory.Instance; - var memoryStorage = new VolatileMemoryStore(); - var textEmbeddingGenerator = new AzureOpenAITextEmbeddingGeneration( - deploymentName: azureOpenAIEmbeddingDeployment, - endpoint: azureOpenAIEndpoint, - apiKey: azureOpenAIKey, - loggerFactory: loggerFactory); - - var memory = new SemanticTextMemory(memoryStorage, textEmbeddingGenerator); - var plugins = new KernelPluginCollection(); - - var httpHandlerFactory = BasicHttpRetryHandlerFactory.Instance; - //var httpHandlerFactory = new PollyHttpRetryHandlerFactory( your policy ); - - using var httpHandler = httpHandlerFactory.Create(loggerFactory); - using var httpClient = new HttpClient(httpHandler); - var aiServices = new AIServiceCollection(); - ITextCompletion Factory() => new AzureOpenAIChatCompletion( - deploymentName: azureOpenAIChatCompletionDeployment, - endpoint: azureOpenAIEndpoint, - apiKey: azureOpenAIKey, - httpClient: httpClient, - loggerFactory: loggerFactory); - aiServices.SetService("foo", Factory); - IAIServiceProvider aiServiceProvider = aiServices.Build(); - - // Create kernel manually injecting all the dependencies - var kernel3 = new Kernel(aiServiceProvider, plugins, httpHandlerFactory: httpHandlerFactory, loggerFactory: loggerFactory); - - // ========================================================================================================== - // The kernel builder purpose is to simplify this process, automating how dependencies - // are connected, still allowing to customize parts of the composition. - - // ========================================================================================================== - // The AI services are defined with the builder - - var kernel7 = new KernelBuilder() - .WithAzureOpenAIChatCompletionService( - deploymentName: azureOpenAIChatCompletionDeployment, - endpoint: azureOpenAIEndpoint, - apiKey: azureOpenAIKey, - setAsDefault: true) + // KernelBuilder provides a simple way to configure a Kernel. This constructs a kernel + // with logging and an Azure OpenAI chat completion service configured. + Kernel kernel1 = new KernelBuilder() + .WithLoggerFactory(ConsoleLogger.LoggerFactory) + .WithAzureOpenAIChatCompletion(azureOpenAIChatCompletionDeployment, azureOpenAIEndpoint, azureOpenAIKey) .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 = new KernelBuilder().WithRetryBasic( - new BasicRetryConfig + // For greater flexibility and to incorporate arbitrary services, KernelBuilder.ConfigureServices + // provides direct access to an underlying IServiceCollection. Multiple calls to ConfigureServices + // may be made, each of which may register any number of services. + Kernel kernel2 = new KernelBuilder() + .ConfigureServices(c => c.AddLogging(c => c.AddConsole().SetMinimumLevel(LogLevel.Information))) + .ConfigureServices(c => { - 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) } + c.AddHttpClient(); + c.AddAzureOpenAIChatCompletion(azureOpenAIChatCompletionDeployment, azureOpenAIEndpoint, azureOpenAIKey); }) .Build(); - var logger = loggerFactory.CreateLogger(); - var retryThreeTimesPolicy = Policy - .Handle(ex - => ex.StatusCode == System.Net.HttpStatusCode.TooManyRequests) - .WaitAndRetryAsync(new[] + // Plugins may also be configured via the corresponding ConfigurePlugins method. There are multiple + // overloads of ConfigurePlugins, one of which provides access to the services registered with the + // builder, such that a plugin can resolve and use those services. + Kernel kernel3 = new KernelBuilder() + .ConfigurePlugins((serviceProvider, plugins) => + { + ILogger logger = serviceProvider.GetService>() ?? (ILogger)NullLogger.Instance; + plugins.Add(new KernelPlugin("Example", 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)); + KernelFunctionFactory.CreateFromMethod(() => logger.LogInformation("Function invoked"), functionName: "LogInvocation") + })); + }) + .Build(); - var kernel9 = new KernelBuilder().WithHttpHandlerFactory(new PollyHttpRetryHandlerFactory(retryThreeTimesPolicy)).Build(); + // Every call to KernelBuilder.Build creates a new Kernel instance, with a new service provider + // and a new plugin collection. + var builder = new KernelBuilder(); + Debug.Assert(!ReferenceEquals(builder.Build(), builder.Build())); - var kernel10 = new KernelBuilder().WithHttpHandlerFactory(new PollyRetryThreeTimesFactory()).Build(); + // KernelBuilder provides a convenient API for creating Kernel instances. However, it is just a + // wrapper around a service collection and plugin collection, ultimately constructing a Kernel + // using the public constructor that's available for anyone to use directly if desired. + var services = new ServiceCollection(); + services.AddLogging(c => c.AddConsole().SetMinimumLevel(LogLevel.Information)); + services.AddHttpClient(); + services.AddAzureOpenAIChatCompletion(azureOpenAIChatCompletionDeployment, azureOpenAIEndpoint, azureOpenAIKey); + Kernel kernel4 = new(services.BuildServiceProvider()); - var kernel11 = new KernelBuilder().WithHttpHandlerFactory(new MyCustomHandlerFactory()).Build(); + // Kernels can also be constructed and resolved via such a dependency injection container. + services.AddTransient(); + Kernel kernel5 = services.BuildServiceProvider().GetRequiredService(); return Task.CompletedTask; } - - // Example using the PollyHttpRetryHandler from Reliability.Polly extension - public class PollyRetryThreeTimesFactory : HttpHandlerFactory - { - public override DelegatingHandler Create(ILoggerFactory? loggerFactory = null) - { - var logger = loggerFactory?.CreateLogger(); - - Activator.CreateInstance(typeof(PollyHttpRetryHandler), GetPolicy(logger), logger); - return base.Create(loggerFactory); - } - - private static AsyncRetryPolicy GetPolicy(ILogger? logger) - { - return Policy - .Handle(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 - { - } - - // Basic custom empty retry handler - public class MyCustomHandler : DelegatingHandler - { - protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - // Your custom handler implementation - - throw new NotImplementedException(); - } - } } diff --git a/dotnet/samples/KernelSyntaxExamples/Example43_GetModelResult.cs b/dotnet/samples/KernelSyntaxExamples/Example43_GetModelResult.cs index c2b646d90342..35c3c950b4f1 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example43_GetModelResult.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example43_GetModelResult.cs @@ -23,7 +23,7 @@ public static async Task RunAsync() Console.WriteLine("======== Inline Function Definition + Result ========"); Kernel kernel = new KernelBuilder() - .WithOpenAIChatCompletionService( + .WithOpenAIChatCompletion( modelId: TestConfiguration.OpenAI.ChatModelId, apiKey: TestConfiguration.OpenAI.ApiKey) .Build(); @@ -64,7 +64,7 @@ public static async Task RunAsync() // Getting the error details kernel = new KernelBuilder() - .WithOpenAIChatCompletionService(TestConfiguration.OpenAI.ChatModelId, "Invalid Key") + .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, "Invalid Key") .Build(); var errorFunction = kernel.CreateFunctionFromPrompt(FunctionDefinition); @@ -79,7 +79,7 @@ public static async Task RunAsync() } #pragma warning restore CA1031 // Do not catch general exception types - string OutputExceptionDetail(Exception? exception) + static string OutputExceptionDetail(Exception? exception) { return exception switch { diff --git a/dotnet/samples/KernelSyntaxExamples/Example48_GroundednessChecks.cs b/dotnet/samples/KernelSyntaxExamples/Example48_GroundednessChecks.cs index 938f27535d30..a367cfb1db30 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example48_GroundednessChecks.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example48_GroundednessChecks.cs @@ -63,7 +63,7 @@ public static async Task GroundednessCheckingAsync() Console.WriteLine("======== Groundedness Checks ========"); var kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) @@ -127,7 +127,7 @@ which are not grounded in the original. var kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) diff --git a/dotnet/samples/KernelSyntaxExamples/Example51_StepwisePlanner.cs b/dotnet/samples/KernelSyntaxExamples/Example51_StepwisePlanner.cs index 73f24c38d1ba..6984ecad3240 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example51_StepwisePlanner.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example51_StepwisePlanner.cs @@ -191,19 +191,17 @@ private static Kernel GetKernel(ref ExecutionResult result, bool useChat = false var maxTokens = 0; if (useChat) { - builder.WithAzureOpenAIChatCompletionService( + builder.WithAzureOpenAIChatCompletion( model ?? ChatModelOverride ?? TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.Endpoint, - TestConfiguration.AzureOpenAI.ApiKey, - alsoAsTextCompletion: true, - setAsDefault: true); + TestConfiguration.AzureOpenAI.ApiKey); maxTokens = ChatMaxTokens ?? new StepwisePlannerConfig().MaxTokens; result.model = model ?? ChatModelOverride ?? TestConfiguration.AzureOpenAI.ChatDeploymentName; } else { - builder.WithAzureTextCompletionService( + builder.WithAzureOpenAITextCompletion( model ?? TextModelOverride ?? TestConfiguration.AzureOpenAI.DeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey); @@ -216,12 +214,6 @@ private static Kernel GetKernel(ref ExecutionResult result, bool useChat = false var kernel = builder .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithRetryBasic(new() - { - MaxRetryCount = 3, - UseExponentialBackoff = true, - MinRetryDelay = TimeSpan.FromSeconds(3), - }) .Build(); return kernel; diff --git a/dotnet/samples/KernelSyntaxExamples/Example52_ApimAuth.cs b/dotnet/samples/KernelSyntaxExamples/Example52_ApimAuth.cs index bcd8e636cac2..6a33a3830681 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example52_ApimAuth.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example52_ApimAuth.cs @@ -9,10 +9,9 @@ using Azure.Core; using Azure.Core.Pipeline; using Azure.Identity; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.AI.ChatCompletion; -using Microsoft.SemanticKernel.Connectors.AI.OpenAI.ChatCompletion; using RepoUtils; // ReSharper disable once InconsistentNaming @@ -49,28 +48,20 @@ public static async Task RunAsync() }; var openAIClient = new OpenAIClient(apimUri, new BearerTokenCredential(accessToken), clientOptions); - // Create logger factory with default level as warning - using ILoggerFactory loggerFactory = LoggerFactory.Create(builder => + var kernel = new KernelBuilder().ConfigureServices(c => { - builder - .SetMinimumLevel(LogLevel.Warning) - .AddConsole(); - }); - - var kernel = new KernelBuilder() - .WithLoggerFactory(loggerFactory) - .WithAIService(TestConfiguration.AzureOpenAI.ChatDeploymentName, (loggerFactory) => - new AzureOpenAIChatCompletion(deploymentName: TestConfiguration.AzureOpenAI.ChatDeploymentName, openAIClient: openAIClient, loggerFactory: loggerFactory)) - .Build(); + c.AddLogging(c => c.SetMinimumLevel(LogLevel.Warning).AddConsole()); + c.AddAzureOpenAIChatCompletion(TestConfiguration.AzureOpenAI.ChatDeploymentName, openAIClient); + }).Build(); // Load semantic plugin defined with prompt templates string folder = RepoFiles.SamplePluginsPath(); - var plugin = kernel.ImportPluginFromPromptDirectory(Path.Combine(folder, "FunPlugin")); + kernel.ImportPluginFromPromptDirectory(Path.Combine(folder, "FunPlugin")); // Run var result = await kernel.InvokeAsync( - plugin["Excuses"], + kernel.Plugins["FunPlugin"]["Excuses"], "I have no homework" ); Console.WriteLine(result.GetValue()); diff --git a/dotnet/samples/KernelSyntaxExamples/Example54_AzureChatCompletionWithData.cs b/dotnet/samples/KernelSyntaxExamples/Example54_AzureChatCompletionWithData.cs index d2699475033c..3dfa2349da96 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example54_AzureChatCompletionWithData.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example54_AzureChatCompletionWithData.cs @@ -84,7 +84,7 @@ private static async Task ExampleWithKernelAsync() var completionWithDataConfig = GetCompletionWithDataConfig(); Kernel kernel = new KernelBuilder() - .WithAzureOpenAIChatCompletionService(config: completionWithDataConfig) + .WithAzureOpenAIChatCompletion(config: completionWithDataConfig) .Build(); var function = kernel.CreateFunctionFromPrompt("Question: {{$input}}"); diff --git a/dotnet/samples/KernelSyntaxExamples/Example56_TemplateNativeFunctionsWithMultipleArguments.cs b/dotnet/samples/KernelSyntaxExamples/Example56_TemplateNativeFunctionsWithMultipleArguments.cs index 7168374382e7..d4c05c8a5656 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example56_TemplateNativeFunctionsWithMultipleArguments.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example56_TemplateNativeFunctionsWithMultipleArguments.cs @@ -32,7 +32,7 @@ public static async Task RunAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( deploymentName: deploymentName, endpoint: endpoint, serviceId: serviceId, diff --git a/dotnet/samples/KernelSyntaxExamples/Example57_KernelHooks.cs b/dotnet/samples/KernelSyntaxExamples/Example57_KernelHooks.cs index 5e0d9af2b30b..17fe730ca855 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example57_KernelHooks.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example57_KernelHooks.cs @@ -47,7 +47,7 @@ private static async Task GetUsageAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithOpenAIChatCompletionService( + .WithOpenAIChatCompletion( modelId: s_openAIModelId!, apiKey: s_openAIApiKey!) .Build(); @@ -94,7 +94,7 @@ private static async Task GetRenderedPromptAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithOpenAIChatCompletionService( + .WithOpenAIChatCompletion( modelId: s_openAIModelId!, apiKey: s_openAIApiKey!) .Build(); @@ -134,7 +134,7 @@ private static async Task ChangingResultAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithOpenAIChatCompletionService( + .WithOpenAIChatCompletion( modelId: s_openAIModelId!, apiKey: s_openAIApiKey!) .Build(); @@ -146,7 +146,7 @@ private static async Task ChangingResultAsync() functionName: "Writer", executionSettings: new OpenAIPromptExecutionSettings() { MaxTokens = 100, Temperature = 0.4, TopP = 1 }); - void MyChangeDataHandler(object? sender, FunctionInvokedEventArgs e) + static void MyChangeDataHandler(object? sender, FunctionInvokedEventArgs e) { var originalOutput = e.Variables.Input; @@ -169,7 +169,7 @@ private static async Task BeforeInvokeCancellationAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithOpenAIChatCompletionService( + .WithOpenAIChatCompletion( modelId: s_openAIModelId!, apiKey: s_openAIApiKey!) .Build(); @@ -205,7 +205,7 @@ private static async Task AfterInvokeCancellationAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithOpenAIChatCompletionService( + .WithOpenAIChatCompletion( modelId: s_openAIModelId!, apiKey: s_openAIApiKey!) .Build(); diff --git a/dotnet/samples/KernelSyntaxExamples/Example58_ConfigureRequestSettings.cs b/dotnet/samples/KernelSyntaxExamples/Example58_ConfigureRequestSettings.cs index 7d4017fa0d77..34b2d2b86df8 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example58_ConfigureRequestSettings.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example58_ConfigureRequestSettings.cs @@ -30,7 +30,7 @@ public static async Task RunAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( deploymentName: chatDeploymentName, endpoint: endpoint, serviceId: serviceId, diff --git a/dotnet/samples/KernelSyntaxExamples/Example59_OpenAIFunctionCalling.cs b/dotnet/samples/KernelSyntaxExamples/Example59_OpenAIFunctionCalling.cs index a6a54c8cde3f..334944a4da72 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example59_OpenAIFunctionCalling.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example59_OpenAIFunctionCalling.cs @@ -62,8 +62,8 @@ private static async Task InitializeKernelAsync() // Create kernel with chat completions service Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithOpenAIChatCompletionService(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey, serviceId: "chat") - //.WithAzureOpenAIChatCompletionService(TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey, serviceId: "chat") + .WithOpenAIChatCompletion(TestConfiguration.OpenAI.ChatModelId, TestConfiguration.OpenAI.ApiKey, serviceId: "chat") + //.WithAzureOpenAIChatCompletion(TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey, serviceId: "chat") .Build(); // Load functions to kernel diff --git a/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs b/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs index aa0e3030d4be..e7dc0aeaf29d 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs @@ -39,13 +39,13 @@ public static async Task RunAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( deploymentName: azureDeploymentName, endpoint: azureEndpoint, serviceId: "AzureOpenAIChat", modelId: azureModelId, apiKey: azureApiKey) - .WithOpenAIChatCompletionService( + .WithOpenAIChatCompletion( modelId: openAIModelId, serviceId: "OpenAIChat", apiKey: openAIApiKey) diff --git a/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs b/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs index d718c9ea0a96..f2126e54e4cc 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs @@ -41,14 +41,13 @@ public static async Task RunAsync() var kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( deploymentName: azureDeploymentName, endpoint: azureEndpoint, serviceId: "AzureOpenAIChat", modelId: azureModelId, - apiKey: azureApiKey, - setAsDefault: true) - .WithOpenAIChatCompletionService( + apiKey: azureApiKey) + .WithOpenAIChatCompletion( modelId: openAIModelId, serviceId: "OpenAIChat", apiKey: openAIApiKey) @@ -66,10 +65,9 @@ public static async Task RunAsync() /// private sealed class Gpt3xAIServiceSelector : IAIServiceSelector { - public (T?, PromptExecutionSettings?) SelectAIService(Kernel kernel, ContextVariables variables, KernelFunction function) where T : IAIService + public (T?, PromptExecutionSettings?) SelectAIService(Kernel kernel, ContextVariables variables, KernelFunction function) where T : class, IAIService { - var services = kernel.ServiceProvider.GetServices(); - foreach (var service in services) + foreach (var service in kernel.GetAllServices()) { // Find the first service that has a model id that starts with "gpt-3" var serviceModelId = service.GetModelId(); diff --git a/dotnet/samples/KernelSyntaxExamples/Example63_ChatCompletionPrompts.cs b/dotnet/samples/KernelSyntaxExamples/Example63_ChatCompletionPrompts.cs index d584dd4723fe..0c46abb34b27 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example63_ChatCompletionPrompts.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example63_ChatCompletionPrompts.cs @@ -19,7 +19,7 @@ public static async Task RunAsync() "; var kernel = new KernelBuilder() - .WithOpenAIChatCompletionService( + .WithOpenAIChatCompletion( modelId: TestConfiguration.OpenAI.ChatModelId, apiKey: TestConfiguration.OpenAI.ApiKey) .Build(); diff --git a/dotnet/samples/KernelSyntaxExamples/Example63_FlowOrchestrator.cs b/dotnet/samples/KernelSyntaxExamples/Example63_FlowOrchestrator.cs index 3209e41d3487..dc4a5a568b03 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example63_FlowOrchestrator.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example63_FlowOrchestrator.cs @@ -206,18 +206,10 @@ private static KernelBuilder GetKernelBuilder(ILoggerFactory loggerFactory) var builder = new KernelBuilder(); return builder - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.Endpoint, - TestConfiguration.AzureOpenAI.ApiKey, - true, - setAsDefault: true) - .WithRetryBasic(new() - { - MaxRetryCount = 3, - UseExponentialBackoff = true, - MinRetryDelay = TimeSpan.FromSeconds(3), - }) + TestConfiguration.AzureOpenAI.ApiKey) .WithLoggerFactory(loggerFactory); } diff --git a/dotnet/samples/KernelSyntaxExamples/Example64_MultiplePromptTemplates.cs b/dotnet/samples/KernelSyntaxExamples/Example64_MultiplePromptTemplates.cs index d7864e65676a..4594219a8c64 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example64_MultiplePromptTemplates.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example64_MultiplePromptTemplates.cs @@ -32,7 +32,7 @@ public static async Task RunAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( deploymentName: chatDeploymentName, endpoint: endpoint, serviceId: "AzureOpenAIChat", diff --git a/dotnet/samples/KernelSyntaxExamples/Example65_HandlebarsPlanner.cs b/dotnet/samples/KernelSyntaxExamples/Example65_HandlebarsPlanner.cs index 46de996dd518..f67b104fb536 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example65_HandlebarsPlanner.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example65_HandlebarsPlanner.cs @@ -58,7 +58,7 @@ private static async Task RunSampleAsync(string goal, bool shouldPrintPrompt = f var kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( deploymentName: chatDeploymentName, endpoint: endpoint, serviceId: "AzureOpenAIChat", diff --git a/dotnet/samples/KernelSyntaxExamples/Example66_FunctionCallingStepwisePlanner.cs b/dotnet/samples/KernelSyntaxExamples/Example66_FunctionCallingStepwisePlanner.cs index d5971036251a..e01a0ef56e06 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example66_FunctionCallingStepwisePlanner.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example66_FunctionCallingStepwisePlanner.cs @@ -47,7 +47,7 @@ private static Kernel InitializeKernel() { Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( TestConfiguration.AzureOpenAI.ChatDeploymentName, TestConfiguration.AzureOpenAI.Endpoint, TestConfiguration.AzureOpenAI.ApiKey) diff --git a/dotnet/samples/KernelSyntaxExamples/Example72_KernelStreaming.cs b/dotnet/samples/KernelSyntaxExamples/Example72_KernelStreaming.cs index 2989a8018d02..7544fd050b04 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example72_KernelStreaming.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example72_KernelStreaming.cs @@ -33,7 +33,7 @@ public static async Task RunAsync() Kernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( deploymentName: chatDeploymentName, endpoint: endpoint, serviceId: "AzureOpenAIChat", @@ -60,6 +60,6 @@ public static async Task RunAsync() { Console.Write(update.ContentUpdate); } - }; + } } } diff --git a/dotnet/samples/KernelSyntaxExamples/KernelSyntaxExamples.csproj b/dotnet/samples/KernelSyntaxExamples/KernelSyntaxExamples.csproj index d30d7910b245..f69cf39be4ae 100644 --- a/dotnet/samples/KernelSyntaxExamples/KernelSyntaxExamples.csproj +++ b/dotnet/samples/KernelSyntaxExamples/KernelSyntaxExamples.csproj @@ -28,6 +28,9 @@ + + + @@ -51,8 +54,6 @@ - - @@ -67,6 +68,11 @@ + + + + + diff --git a/dotnet/samples/KernelSyntaxExamples/Reliability/RetryThreeTimesWithBackoff.cs b/dotnet/samples/KernelSyntaxExamples/Reliability/RetryThreeTimesWithBackoff.cs deleted file mode 100644 index 3ed0929422a6..000000000000 --- a/dotnet/samples/KernelSyntaxExamples/Reliability/RetryThreeTimesWithBackoff.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.SemanticKernel.Http; -using Polly; -using Polly.Retry; - -namespace Reliability; - -/// -/// A factory for creating a retry handler. -/// -public class RetryThreeTimesWithBackoffFactory : IDelegatingHandlerFactory -{ - public DelegatingHandler Create(ILoggerFactory? loggerFactory) - { - return new RetryThreeTimesWithBackoff(loggerFactory); - } -} - -/// -/// A basic example of a retry mechanism that retries three times with backoff. -/// -public class RetryThreeTimesWithBackoff : DelegatingHandler -{ - private readonly AsyncRetryPolicy _policy; - - public RetryThreeTimesWithBackoff(ILoggerFactory? loggerFactory) - { - this._policy = GetPolicy(loggerFactory); - } - - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - return await this._policy.ExecuteAsync(async () => - { - var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); - return response; - }).ConfigureAwait(false); - } - - private static AsyncRetryPolicy GetPolicy(ILoggerFactory? logger) - { - // Handle 429 and 401 errors - // Typically 401 would not be something we retry but for demonstration - // purposes we are doing so as it's easy to trigger when using an invalid key. - const int TooManyRequests = 429; - const int Unauthorized = 401; - - return Policy - .HandleResult(response => - (int)response.StatusCode is TooManyRequests or Unauthorized) - .WaitAndRetryAsync(new[] - { - TimeSpan.FromSeconds(2), - TimeSpan.FromSeconds(4), - TimeSpan.FromSeconds(8) - }, - (outcome, timespan, retryCount, _) => logger?.CreateLogger(typeof(RetryThreeTimesWithBackoff)).LogWarning( - "Error executing action [attempt {0} of 3], pausing {1}ms. Outcome: {2}", - retryCount, - timespan.TotalMilliseconds, - outcome.Result.StatusCode)); - } -} diff --git a/dotnet/samples/KernelSyntaxExamples/Reliability/RetryThreeTimesWithRetryAfterBackoff.cs b/dotnet/samples/KernelSyntaxExamples/Reliability/RetryThreeTimesWithRetryAfterBackoff.cs deleted file mode 100644 index 1586b9af9c90..000000000000 --- a/dotnet/samples/KernelSyntaxExamples/Reliability/RetryThreeTimesWithRetryAfterBackoff.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.SemanticKernel.Http; -using Polly; -using Polly.Retry; - -namespace Reliability; - -/// -/// A factory for creating a retry handler. -/// -public class RetryThreeTimesWithRetryAfterBackoffFactory : IDelegatingHandlerFactory -{ - public DelegatingHandler Create(ILoggerFactory? loggerFactory) - { - return new RetryThreeTimesWithRetryAfterBackoff(loggerFactory); - } -} - -/// -/// An example of a retry mechanism that retries three times with backoff using the RetryAfter value. -/// -public class RetryThreeTimesWithRetryAfterBackoff : DelegatingHandler -{ - private readonly AsyncRetryPolicy _policy; - - public RetryThreeTimesWithRetryAfterBackoff(ILoggerFactory? loggerFactory) - { - this._policy = GetPolicy(loggerFactory); - } - - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - return await this._policy.ExecuteAsync(async () => - { - var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); - return response; - }).ConfigureAwait(false); - } - - private static AsyncRetryPolicy GetPolicy(ILoggerFactory? loggerFactory) - { - // Handle 429 and 401 errors - // Typically 401 would not be something we retry but for demonstration - // purposes we are doing so as it's easy to trigger when using an invalid key. - const int TooManyRequests = 429; - const int Unauthorized = 401; - - return Policy - .HandleResult(response => - (int)response.StatusCode is Unauthorized or TooManyRequests) - .WaitAndRetryAsync( - retryCount: 3, - sleepDurationProvider: (_, r, _) => - { - var response = r.Result; - var retryAfter = response.Headers.RetryAfter?.Delta ?? response.Headers.RetryAfter?.Date - DateTimeOffset.Now; - return retryAfter ?? TimeSpan.FromSeconds(2); - }, - (outcome, timespan, retryCount, _) => - { - loggerFactory?.CreateLogger(typeof(RetryThreeTimesWithRetryAfterBackoff)).LogWarning( - "Error executing action [attempt {0} of 3], pausing {1}ms. Outcome: {2}", - retryCount, - timespan.TotalMilliseconds, - outcome.Result.StatusCode); - return Task.CompletedTask; - }); - } -} diff --git a/dotnet/samples/TelemetryExample/Program.cs b/dotnet/samples/TelemetryExample/Program.cs index 44badae42c21..c487c2d8cc41 100644 --- a/dotnet/samples/TelemetryExample/Program.cs +++ b/dotnet/samples/TelemetryExample/Program.cs @@ -96,7 +96,7 @@ private static Kernel GetKernel(ILoggerFactory loggerFactory) var kernel = new KernelBuilder() .WithLoggerFactory(loggerFactory) - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( Env.Var("AzureOpenAI__ChatDeploymentName"), Env.Var("AzureOpenAI__Endpoint"), Env.Var("AzureOpenAI__ApiKey")) diff --git a/dotnet/src/Connectors/Connectors.AI.HuggingFace/HuggingFaceKernelBuilderExtensions.cs b/dotnet/src/Connectors/Connectors.AI.HuggingFace/HuggingFaceKernelBuilderExtensions.cs index 82699068c568..98e4775ff2f1 100644 --- a/dotnet/src/Connectors/Connectors.AI.HuggingFace/HuggingFaceKernelBuilderExtensions.cs +++ b/dotnet/src/Connectors/Connectors.AI.HuggingFace/HuggingFaceKernelBuilderExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System.Net.Http; +using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel.AI.Embeddings; using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Connectors.AI.HuggingFace.TextCompletion; @@ -17,85 +18,100 @@ namespace Microsoft.SemanticKernel; public static class HuggingFaceKernelBuilderExtensions { /// - /// Registers an Hugging Face text completion service with the specified configuration. + /// Adds an Hugging Face text completion service with the specified configuration. /// - /// The instance. + /// The instance to augment. /// The name of the Hugging Face model. /// The API key required for accessing the Hugging Face service. /// The endpoint URL for the text completion service. /// A local identifier for the given AI service. - /// Indicates whether the service should be the default for its type. - /// The optional to be used for making HTTP requests. - /// If not provided, a default instance will be used. - /// The modified instance. - public static KernelBuilder WithHuggingFaceTextCompletionService(this KernelBuilder builder, + /// The HttpClient to use with this service. + /// The same instance as . + public static KernelBuilder WithHuggingFaceTextCompletion( + this KernelBuilder builder, string model, string? apiKey = null, string? endpoint = null, string? serviceId = null, - bool setAsDefault = false, HttpClient? httpClient = null) { - builder.WithAIService(serviceId, (loggerFactory, httpHandlerFactory) => - new HuggingFaceTextCompletion( - model, - apiKey, - HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), - endpoint), - setAsDefault); + Verify.NotNull(builder); + Verify.NotNull(model); - return builder; + return builder.ConfigureServices(c => + { + c.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new HuggingFaceTextCompletion(model, apiKey, HttpClientProvider.GetHttpClient(httpClient, serviceProvider), endpoint)); + }); } /// - /// Registers an Hugging Face text embedding generation service with the specified configuration. + /// Adds an Hugging Face text completion service with the specified configuration. /// - /// The instance. + /// The instance to augment. + /// The name of the Hugging Face model. + /// The API key required for accessing the Hugging Face service. + /// The endpoint URL for the text completion service. + /// A local identifier for the given AI service. + /// The same instance as . + public static IServiceCollection AddHuggingFaceTextCompletion( + this IServiceCollection services, + string model, + string? apiKey = null, + string? endpoint = null, + string? serviceId = null) + { + Verify.NotNull(services); + Verify.NotNull(model); + + return services.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new HuggingFaceTextCompletion(model, apiKey, HttpClientProvider.GetHttpClient(serviceProvider), endpoint)); + } + + /// + /// Adds an Hugging Face text embedding generation service with the specified configuration. + /// + /// The instance to augment. /// The name of the Hugging Face model. /// The endpoint for the text embedding generation service. /// A local identifier for the given AI service. - /// Indicates whether the service should be the default for its type. - /// The instance. - public static KernelBuilder WithHuggingFaceTextEmbeddingGenerationService(this KernelBuilder builder, + /// The HttpClient to use with this service. + /// The same instance as . + public static KernelBuilder WithHuggingFaceTextEmbeddingGeneration( + this KernelBuilder builder, string model, - string endpoint, + string? endpoint = null, string? serviceId = null, - bool setAsDefault = false) + HttpClient? httpClient = null) { - builder.WithAIService(serviceId, (loggerFactory, httpHandlerFactory) => - new HuggingFaceTextEmbeddingGeneration( - model, - HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient: null, loggerFactory), - endpoint), - setAsDefault); + Verify.NotNull(builder); + Verify.NotNull(model); - return builder; + return builder.ConfigureServices(c => + { + c.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new HuggingFaceTextEmbeddingGeneration(model, HttpClientProvider.GetHttpClient(httpClient, serviceProvider), endpoint)); + }); } /// - /// Registers an Hugging Face text embedding generation service with the specified configuration. + /// Adds an Hugging Face text embedding generation service with the specified configuration. /// - /// The instance. + /// The instance to augment. /// The name of the Hugging Face model. - /// The optional instance used for making HTTP requests. /// The endpoint for the text embedding generation service. - /// A local identifier for the given AI serviceю - /// Indicates whether the service should be the default for its type. - /// The instance. - public static KernelBuilder WithHuggingFaceTextEmbeddingGenerationService(this KernelBuilder builder, + /// A local identifier for the given AI service. + /// The same instance as . + public static IServiceCollection AddHuggingFaceTextEmbeddingGeneration( + this IServiceCollection services, string model, - HttpClient? httpClient = null, string? endpoint = null, - string? serviceId = null, - bool setAsDefault = false) + string? serviceId = null) { - builder.WithAIService(serviceId, (loggerFactory, httpHandlerFactory) => - new HuggingFaceTextEmbeddingGeneration( - model, - HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), - endpoint), - setAsDefault); + Verify.NotNull(services); + Verify.NotNull(model); - return builder; + return services.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new HuggingFaceTextEmbeddingGeneration(model, HttpClientProvider.GetHttpClient(serviceProvider), endpoint)); } } diff --git a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs index bf2a9aa94da1..c450206b0d0b 100644 --- a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs @@ -45,7 +45,7 @@ public HuggingFaceTextCompletion(Uri endpoint, string model) this._attributes.Add(IAIServiceExtensions.ModelIdKey, this._model); this._attributes.Add(IAIServiceExtensions.EndpointKey, this._endpoint); - this._httpClient = new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); + this._httpClient = HttpClientProvider.GetHttpClient(); } /// @@ -63,7 +63,7 @@ public HuggingFaceTextCompletion(string model, string? apiKey = null, HttpClient this._model = model; this._apiKey = apiKey; - this._httpClient = httpClient ?? new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); + this._httpClient = HttpClientProvider.GetHttpClient(httpClient); this._endpoint = endpoint; this._attributes.Add(IAIServiceExtensions.ModelIdKey, this._model); this._attributes.Add(IAIServiceExtensions.EndpointKey, this._endpoint ?? HuggingFaceApiEndpoint); diff --git a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs index fd71c2d99992..8f82f2fc88b7 100644 --- a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs @@ -40,7 +40,7 @@ public HuggingFaceTextEmbeddingGeneration(Uri endpoint, string model) this._endpoint = endpoint.AbsoluteUri; this._attributes.Add(IAIServiceExtensions.ModelIdKey, this._model); this._attributes.Add(IAIServiceExtensions.EndpointKey, this._endpoint); - this._httpClient = new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); + this._httpClient = HttpClientProvider.GetHttpClient(); } /// @@ -57,7 +57,7 @@ public HuggingFaceTextEmbeddingGeneration(string model, string endpoint) this._endpoint = endpoint; this._attributes.Add(IAIServiceExtensions.ModelIdKey, this._model); this._attributes.Add(IAIServiceExtensions.EndpointKey, this._endpoint); - this._httpClient = new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); + this._httpClient = HttpClientProvider.GetHttpClient(); } /// diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs index a2bcd7bdae71..df1c12ffb89c 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs @@ -6,9 +6,7 @@ using Azure; using Azure.AI.OpenAI; using Azure.Core; -using Azure.Core.Pipeline; using Microsoft.Extensions.Logging; -using Microsoft.SemanticKernel.Http; using Microsoft.SemanticKernel.Services; namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.AzureSdk; @@ -47,7 +45,7 @@ private protected AzureOpenAIClientBase( Verify.StartsWith(endpoint, "https://", "The Azure OpenAI endpoint must start with 'https://'"); Verify.NotNullOrWhiteSpace(apiKey); - var options = GetClientOptions(httpClient); + var options = GetOpenAIClientOptions(httpClient); this.DeploymentOrModelName = deploymentName; this.Client = new OpenAIClient(new Uri(endpoint), new AzureKeyCredential(apiKey), options); @@ -72,7 +70,7 @@ private protected AzureOpenAIClientBase( Verify.NotNullOrWhiteSpace(endpoint); Verify.StartsWith(endpoint, "https://", "The Azure OpenAI endpoint must start with 'https://'"); - var options = GetClientOptions(httpClient); + var options = GetOpenAIClientOptions(httpClient); this.DeploymentOrModelName = deploymentName; this.Client = new OpenAIClient(new Uri(endpoint), credential, options); @@ -100,30 +98,6 @@ private protected AzureOpenAIClientBase( this.AddAttribute(DeploymentNameKey, deploymentName); } - /// - /// Options used by the Azure OpenAI client, e.g. User Agent. - /// - /// Custom for HTTP requests. - /// An instance of . - private static OpenAIClientOptions GetClientOptions(HttpClient? httpClient) - { - var options = new OpenAIClientOptions - { - Diagnostics = - { - ApplicationId = HttpHeaderValues.UserAgent, - } - }; - - if (httpClient != null) - { - options.Transport = new HttpClientTransport(httpClient); - options.RetryPolicy = new RetryPolicy(maxRetries: 0); //Disabling Azure SDK retry policy to use the one provided by the custom HTTP client. - } - - return options; - } - /// /// Logs Azure OpenAI action details. /// diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/ClientBase.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/ClientBase.cs index 16a7f50292c0..c7d0da2e6b58 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/ClientBase.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/ClientBase.cs @@ -4,17 +4,20 @@ using System.Collections.Generic; using System.Diagnostics.Metrics; using System.Linq; +using System.Net.Http; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Azure; using Azure.AI.OpenAI; +using Azure.Core.Pipeline; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.AI.ChatCompletion; using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.ChatCompletion; +using Microsoft.SemanticKernel.Http; using Microsoft.SemanticKernel.Prompt; namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.AzureSdk; @@ -127,8 +130,8 @@ private protected async Task> InternalGetTextResultsA private protected async IAsyncEnumerable InternalGetTextStreamingUpdatesAsync( string prompt, - PromptExecutionSettings? executionSettings, - [EnumeratorCancellation] CancellationToken cancellationToken = default) + PromptExecutionSettings? executionSettings, + [EnumeratorCancellation] CancellationToken cancellationToken = default) { OpenAIPromptExecutionSettings textRequestSettings = OpenAIPromptExecutionSettings.FromRequestSettings(executionSettings, OpenAIPromptExecutionSettings.DefaultTextMaxTokens); @@ -345,6 +348,25 @@ private protected void AddAttribute(string key, string? value) } } + /// Gets options to use for an OpenAIClient + /// Custom for HTTP requests. + /// An instance of . + internal static OpenAIClientOptions GetOpenAIClientOptions(HttpClient? httpClient) + { + OpenAIClientOptions options = new() + { + Diagnostics = { ApplicationId = HttpHeaderValues.UserAgent } + }; + + if (httpClient is not null) + { + options.Transport = new HttpClientTransport(httpClient); + options.RetryPolicy = new RetryPolicy(maxRetries: 0); // Disable Azure SDK retry policy if and only if a custom HttpClient is provided. + } + + return options; + } + private static OpenAIChatHistory PrepareChatHistory(string text, PromptExecutionSettings? executionSettings, out OpenAIPromptExecutionSettings settings) { settings = OpenAIPromptExecutionSettings.FromRequestSettings(executionSettings); diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/OpenAIClientBase.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/OpenAIClientBase.cs index 944c1fd90b46..804af25cf71e 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/OpenAIClientBase.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/OpenAIClientBase.cs @@ -4,9 +4,7 @@ using System.Runtime.CompilerServices; using Azure.AI.OpenAI; using Azure.Core; -using Azure.Core.Pipeline; using Microsoft.Extensions.Logging; -using Microsoft.SemanticKernel.Http; using Microsoft.SemanticKernel.Services; namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.AzureSdk; @@ -46,7 +44,7 @@ private protected OpenAIClientBase( this.DeploymentOrModelName = modelId; - var options = GetClientOptions(httpClient); + var options = GetOpenAIClientOptions(httpClient); if (!string.IsNullOrWhiteSpace(organization)) { @@ -84,28 +82,4 @@ private protected void LogActionDetails([CallerMemberName] string? callerMemberN { this.Logger.LogInformation("Action: {Action}. OpenAI Model ID: {ModelId}.", callerMemberName, this.DeploymentOrModelName); } - - /// - /// Options used by the OpenAI client, e.g. User Agent. - /// - /// Custom for HTTP requests. - /// An instance of . - private static OpenAIClientOptions GetClientOptions(HttpClient? httpClient) - { - var options = new OpenAIClientOptions - { - Diagnostics = - { - ApplicationId = HttpHeaderValues.UserAgent, - } - }; - - if (httpClient != null) - { - options.Transport = new HttpClientTransport(httpClient); - options.RetryPolicy = new RetryPolicy(maxRetries: 0); //Disabling Azure SDK retry policy to use the one provided by the custom HTTP client. - } - - return options; - } } diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureOpenAIChatCompletionWithData.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureOpenAIChatCompletionWithData.cs index 5e043b8a40ec..3c380a6c2f5c 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureOpenAIChatCompletionWithData.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureOpenAIChatCompletionWithData.cs @@ -44,7 +44,7 @@ public AzureOpenAIChatCompletionWithData( this._config = config; - this._httpClient = httpClient ?? new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); + this._httpClient = HttpClientProvider.GetHttpClient(httpClient); this._logger = loggerFactory is not null ? loggerFactory.CreateLogger(this.GetType()) : NullLogger.Instance; this._attributes.Add(IAIServiceExtensions.ModelIdKey, config.CompletionModelId); } diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/CustomClient/OpenAIClientBase.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/CustomClient/OpenAIClientBase.cs index e4f04d4e5ff4..0ce1c665bbd0 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/CustomClient/OpenAIClientBase.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/CustomClient/OpenAIClientBase.cs @@ -33,7 +33,7 @@ public abstract class OpenAIClientBase /// The ILoggerFactory used to create a logger for logging. If null, no logging will be performed. private protected OpenAIClientBase(HttpClient? httpClient, ILoggerFactory? loggerFactory = null) { - this._httpClient = httpClient ?? new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); + this._httpClient = HttpClientProvider.GetHttpClient(httpClient); this._logger = loggerFactory is not null ? loggerFactory.CreateLogger(this.GetType()) : NullLogger.Instance; } diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs index e29aaa8cb04f..e2a0e8ad7ab4 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs @@ -5,151 +5,335 @@ using Azure; using Azure.AI.OpenAI; using Azure.Core; -using Azure.Core.Pipeline; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.AI.ChatCompletion; using Microsoft.SemanticKernel.AI.Embeddings; using Microsoft.SemanticKernel.AI.ImageGeneration; using Microsoft.SemanticKernel.AI.TextCompletion; +using Microsoft.SemanticKernel.Connectors.AI.OpenAI.AzureSdk; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.ChatCompletion; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.ChatCompletionWithData; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.ImageGeneration; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; -using Microsoft.SemanticKernel.Http; +#pragma warning disable IDE0039 // Use local function +#pragma warning disable CA2000 // Dispose objects before losing scope #pragma warning disable IDE0130 + // ReSharper disable once CheckNamespace - Using NS of KernelConfig namespace Microsoft.SemanticKernel; -#pragma warning restore IDE0130 /// -/// Provides extension methods for the class to configure OpenAI and AzureOpenAI connectors. +/// Provides extension methods for the class to configure OpenAI and Azure OpenAI connectors. /// -public static class OpenAIKernelBuilderExtensions +public static class OpenAIServiceCollectionExtensions { #region Text Completion /// - /// Adds an Azure OpenAI text completion service to the list. - /// See https://learn.microsoft.com/azure/cognitive-services/openai for service details. + /// Adds an Azure OpenAI text completion service with the specified configuration. /// - /// The instance + /// The instance to augment. /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Azure OpenAI API key, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// A local identifier for the given AI service /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart - /// Whether the service should be the default for its type. - /// Custom for HTTP requests. - /// Self instance - public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder builder, + /// The HttpClient to use with this service. + /// The same instance as . + public static KernelBuilder WithAzureOpenAITextCompletion( + this KernelBuilder builder, string deploymentName, string endpoint, string apiKey, string? serviceId = null, string? modelId = null, - bool setAsDefault = false, HttpClient? httpClient = null) { - builder.WithAIService(serviceId, (loggerFactory, httpHandlerFactory) => + Verify.NotNull(builder); + Verify.NotNull(deploymentName); + Verify.NotNull(endpoint); + Verify.NotNull(apiKey); + + return builder.ConfigureServices(c => { - var client = CreateAzureOpenAIClient(loggerFactory, httpHandlerFactory, deploymentName, endpoint, new AzureKeyCredential(apiKey), httpClient); - return new AzureTextCompletion(deploymentName, client, modelId, loggerFactory); - }, setAsDefault); + c.AddKeyedSingleton(serviceId, (serviceProvider, _) => + { + var client = CreateAzureOpenAIClient(deploymentName, endpoint, new AzureKeyCredential(apiKey), httpClient ?? serviceProvider.GetService()); + return new AzureTextCompletion(deploymentName, client, modelId, serviceProvider.GetService()); + }); + }); + } - return builder; + /// + /// Adds an Azure OpenAI text completion service with the specified configuration. + /// + /// The instance to augment. + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// Azure OpenAI API key, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// A local identifier for the given AI service + /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// The same instance as . + public static IServiceCollection AddAzureOpenAITextCompletion( + this IServiceCollection services, + string deploymentName, + string endpoint, + string apiKey, + string? serviceId = null, + string? modelId = null) + { + Verify.NotNull(services); + Verify.NotNull(deploymentName); + Verify.NotNull(endpoint); + Verify.NotNull(apiKey); + + return services.AddKeyedSingleton(serviceId, (serviceProvider, _) => + { + var client = CreateAzureOpenAIClient(deploymentName, endpoint, new AzureKeyCredential(apiKey), serviceProvider.GetService()); + return new AzureTextCompletion(deploymentName, client, modelId, serviceProvider.GetService()); + }); } /// - /// Adds an Azure OpenAI text completion service to the list. - /// See https://learn.microsoft.com/azure/cognitive-services/openai for service details. + /// Adds an Azure OpenAI text completion service with the specified configuration. /// - /// The instance + /// The instance to augment. /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Token credentials, e.g. DefaultAzureCredential, ManagedIdentityCredential, EnvironmentCredential, etc. /// A local identifier for the given AI service /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart - /// Whether the service should be the default for its type. - /// Custom for HTTP requests. - /// Self instance - public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder builder, + /// The HttpClient to use with this service. + /// The same instance as . + public static KernelBuilder WithAzureOpenAITextCompletion( + this KernelBuilder builder, string deploymentName, string endpoint, TokenCredential credentials, string? serviceId = null, string? modelId = null, - bool setAsDefault = false, HttpClient? httpClient = null) { - builder.WithAIService(serviceId, (loggerFactory, httpHandlerFactory) => + Verify.NotNull(builder); + Verify.NotNull(deploymentName); + Verify.NotNull(endpoint); + Verify.NotNull(credentials); + + return builder.ConfigureServices(c => { - var client = CreateAzureOpenAIClient(loggerFactory, httpHandlerFactory, deploymentName, endpoint, credentials, httpClient); - return new AzureTextCompletion(deploymentName, client, modelId, loggerFactory); - }, setAsDefault); + c.AddKeyedSingleton(serviceId, (serviceProvider, _) => + { + var client = CreateAzureOpenAIClient(deploymentName, endpoint, credentials, httpClient ?? serviceProvider.GetService()); + return new AzureTextCompletion(deploymentName, client, modelId, serviceProvider.GetService()); + }); + }); + } + + /// + /// Adds an Azure OpenAI text completion service with the specified configuration. + /// + /// The instance to augment. + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// Token credentials, e.g. DefaultAzureCredential, ManagedIdentityCredential, EnvironmentCredential, etc. + /// A local identifier for the given AI service + /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// The same instance as . + public static IServiceCollection AddAzureOpenAITextCompletion( + this IServiceCollection services, + string deploymentName, + string endpoint, + TokenCredential credentials, + string? serviceId = null, + string? modelId = null) + { + Verify.NotNull(services); + Verify.NotNull(deploymentName); + Verify.NotNull(endpoint); + Verify.NotNull(credentials); - return builder; + return services.AddKeyedSingleton(serviceId, (serviceProvider, _) => + { + var client = CreateAzureOpenAIClient(deploymentName, endpoint, credentials, serviceProvider.GetService()); + return new AzureTextCompletion(deploymentName, client, modelId, serviceProvider.GetService()); + }); } /// - /// Adds an Azure OpenAI text completion service to the list. - /// See https://learn.microsoft.com/azure/cognitive-services/openai for service details. + /// Adds an Azure OpenAI text completion service with the specified configuration. /// - /// The instance + /// The instance to augment. /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Custom . /// A local identifier for the given AI service /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart - /// Whether the service should be the default for its type. - /// Self instance - public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder builder, + /// The same instance as . + public static KernelBuilder WithAzureOpenAITextCompletion( + this KernelBuilder builder, string deploymentName, OpenAIClient openAIClient, string? serviceId = null, - string? modelId = null, - bool setAsDefault = false) + string? modelId = null) + { + Verify.NotNull(builder); + Verify.NotNull(deploymentName); + Verify.NotNull(openAIClient); + + return builder.ConfigureServices(c => + { + c.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new AzureTextCompletion( + deploymentName, + openAIClient, + modelId, + serviceProvider.GetService())); + }); + } + + /// + /// Adds an Azure OpenAI text completion service with the specified configuration. + /// + /// The instance to augment. + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Custom . + /// A local identifier for the given AI service + /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// The same instance as . + public static IServiceCollection AddAzureOpenAITextCompletion( + this IServiceCollection services, + string deploymentName, + OpenAIClient openAIClient, + string? serviceId = null, + string? modelId = null) { - builder.WithAIService(serviceId, (loggerFactory) => + Verify.NotNull(services); + Verify.NotNull(deploymentName); + Verify.NotNull(openAIClient); + + return services.AddKeyedSingleton(serviceId, (serviceProvider, _) => new AzureTextCompletion( deploymentName, openAIClient, modelId, - loggerFactory), - setAsDefault); - - return builder; + serviceProvider.GetService())); } /// - /// Adds the OpenAI text completion service to the list. - /// See https://platform.openai.com/docs for service details. + /// Adds an OpenAI text completion service with the specified configuration. /// - /// The instance + /// The instance to augment. /// OpenAI model name, see https://platform.openai.com/docs/models /// OpenAI API key, see https://platform.openai.com/account/api-keys /// OpenAI organization id. This is usually optional unless your account belongs to multiple organizations. /// A local identifier for the given AI service - /// Whether the service should be the default for its type. - /// Custom for HTTP requests. - /// Self instance - public static KernelBuilder WithOpenAITextCompletionService(this KernelBuilder builder, + /// The HttpClient to use with this service. + /// The same instance as . + public static KernelBuilder WithOpenAITextCompletion( + this KernelBuilder builder, string modelId, string apiKey, string? orgId = null, string? serviceId = null, - bool setAsDefault = false, HttpClient? httpClient = null) { - builder.WithAIService(serviceId, (loggerFactory, httpHandlerFactory) => + Verify.NotNull(builder); + Verify.NotNull(modelId); + Verify.NotNull(apiKey); + + return builder.ConfigureServices(c => + { + c.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new OpenAITextCompletion( + modelId, + apiKey, + orgId, + HttpClientProvider.GetHttpClient(httpClient, serviceProvider), + serviceProvider.GetService())); + }); + } + + /// + /// Adds an OpenAI text completion service with the specified configuration. + /// + /// The instance to augment. + /// OpenAI model name, see https://platform.openai.com/docs/models + /// OpenAI API key, see https://platform.openai.com/account/api-keys + /// OpenAI organization id. This is usually optional unless your account belongs to multiple organizations. + /// A local identifier for the given AI service + /// The same instance as . + public static IServiceCollection AddOpenAITextCompletion( + this IServiceCollection services, + string modelId, + string apiKey, + string? orgId = null, + string? serviceId = null) + { + Verify.NotNull(services); + Verify.NotNull(modelId); + Verify.NotNull(apiKey); + + return services.AddKeyedSingleton(serviceId, (serviceProvider, _) => new OpenAITextCompletion( modelId, apiKey, orgId, - HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), - loggerFactory), - setAsDefault); - return builder; + HttpClientProvider.GetHttpClient(serviceProvider), + serviceProvider.GetService())); + } + + /// + /// Adds an OpenAI text completion service with the specified configuration. + /// + /// The instance to augment. + /// OpenAI model name, see https://platform.openai.com/docs/models + /// Custom . + /// A local identifier for the given AI service + /// The same instance as . + public static KernelBuilder WithOpenAITextCompletion( + this KernelBuilder builder, + string modelId, + OpenAIClient openAIClient, + string? serviceId = null) + { + Verify.NotNull(builder); + Verify.NotNull(modelId); + Verify.NotNull(openAIClient); + + return builder.ConfigureServices(c => + { + c.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new OpenAITextCompletion( + modelId, + openAIClient, + serviceProvider.GetService())); + }); + } + + /// + /// Adds an OpenAI text completion service with the specified configuration. + /// + /// The instance to augment. + /// OpenAI model name, see https://platform.openai.com/docs/models + /// Custom . + /// A local identifier for the given AI service + /// The same instance as . + public static IServiceCollection AddOpenAITextCompletion(this IServiceCollection services, + string modelId, + OpenAIClient openAIClient, + string? serviceId = null) + { + Verify.NotNull(services); + Verify.NotNull(modelId); + Verify.NotNull(openAIClient); + + return services.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new OpenAITextCompletion( + modelId, + openAIClient, + serviceProvider.GetService())); } #endregion @@ -158,101 +342,318 @@ public static KernelBuilder WithOpenAITextCompletionService(this KernelBuilder b /// /// Adds an Azure OpenAI text embeddings service to the list. - /// See https://learn.microsoft.com/azure/cognitive-services/openai for service details. /// - /// The instance + /// The instance to augment. /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Azure OpenAI API key, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// A local identifier for the given AI service /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart - /// Whether the service should be the default for its type. - /// Custom for HTTP requests. - /// Self instance - public static KernelBuilder WithAzureOpenAITextEmbeddingGenerationService(this KernelBuilder builder, + /// The HttpClient to use with this service. + /// The same instance as . + public static KernelBuilder WithAzureOpenAITextEmbeddingGeneration( + this KernelBuilder builder, string deploymentName, string endpoint, string apiKey, string? serviceId = null, string? modelId = null, - bool setAsDefault = false, HttpClient? httpClient = null) { - builder.WithAIService(serviceId, (loggerFactory, httpHandlerFactory) => + Verify.NotNull(builder); + Verify.NotNull(deploymentName); + Verify.NotNull(endpoint); + Verify.NotNull(apiKey); + + return builder.ConfigureServices(c => + { + c.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new AzureOpenAITextEmbeddingGeneration( + deploymentName, + endpoint, + apiKey, + modelId, + HttpClientProvider.GetHttpClient(httpClient, serviceProvider), + serviceProvider.GetService())); + }); + } + + /// + /// Adds an Azure OpenAI text embeddings service to the list. + /// + /// The instance to augment. + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// Azure OpenAI API key, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// A local identifier for the given AI service + /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// The same instance as . + public static IServiceCollection AddAzureOpenAITextEmbeddingGeneration( + this IServiceCollection services, + string deploymentName, + string endpoint, + string apiKey, + string? serviceId = null, + string? modelId = null) + { + Verify.NotNull(services); + Verify.NotNull(deploymentName); + Verify.NotNull(endpoint); + Verify.NotNull(apiKey); + + return services.AddKeyedSingleton(serviceId, (serviceProvider, _) => new AzureOpenAITextEmbeddingGeneration( deploymentName, endpoint, apiKey, modelId, - HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), - loggerFactory), - setAsDefault); - return builder; + HttpClientProvider.GetHttpClient(serviceProvider), + serviceProvider.GetService())); } /// /// Adds an Azure OpenAI text embeddings service to the list. - /// See https://learn.microsoft.com/azure/cognitive-services/openai for service details. /// - /// The instance + /// The instance to augment. /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Token credentials, e.g. DefaultAzureCredential, ManagedIdentityCredential, EnvironmentCredential, etc. /// A local identifier for the given AI service /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart - /// Whether the service should be the default for its type. - /// Custom for HTTP requests. - /// Self instance - public static KernelBuilder WithAzureOpenAITextEmbeddingGenerationService(this KernelBuilder builder, + /// The HttpClient to use with this service. + /// The same instance as . + public static KernelBuilder WithAzureOpenAITextEmbeddingGeneration( + this KernelBuilder builder, string deploymentName, string endpoint, TokenCredential credential, string? serviceId = null, string? modelId = null, - bool setAsDefault = false, HttpClient? httpClient = null) { - builder.WithAIService(serviceId, (loggerFactory, httpHandlerFactory) => + Verify.NotNull(builder); + Verify.NotNull(deploymentName); + Verify.NotNull(endpoint); + Verify.NotNull(credential); + + return builder.ConfigureServices(c => + { + c.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new AzureOpenAITextEmbeddingGeneration( + deploymentName, + endpoint, + credential, + modelId, + HttpClientProvider.GetHttpClient(httpClient, serviceProvider), + serviceProvider.GetService())); + }); + } + + /// + /// Adds an Azure OpenAI text embeddings service to the list. + /// + /// The instance to augment. + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// Token credentials, e.g. DefaultAzureCredential, ManagedIdentityCredential, EnvironmentCredential, etc. + /// A local identifier for the given AI service + /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// The same instance as . + public static IServiceCollection AddAzureOpenAITextEmbeddingGeneration( + this IServiceCollection services, + string deploymentName, + string endpoint, + TokenCredential credential, + string? serviceId = null, + string? modelId = null) + { + Verify.NotNull(services); + Verify.NotNull(deploymentName); + Verify.NotNull(endpoint); + Verify.NotNull(credential); + + return services.AddKeyedSingleton(serviceId, (serviceProvider, _) => new AzureOpenAITextEmbeddingGeneration( deploymentName, endpoint, credential, modelId, - HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), - loggerFactory), - setAsDefault); - return builder; + HttpClientProvider.GetHttpClient(serviceProvider), + serviceProvider.GetService())); + } + + /// + /// Adds an Azure OpenAI text embeddings service to the list. + /// + /// The instance to augment. + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Custom . + /// A local identifier for the given AI service + /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// The same instance as . + public static KernelBuilder WithAzureOpenAITextEmbeddingGeneration( + this KernelBuilder builder, + string deploymentName, + OpenAIClient openAIClient, + string? serviceId = null, + string? modelId = null) + { + Verify.NotNull(builder); + Verify.NotNull(deploymentName); + Verify.NotNull(openAIClient); + + return builder.ConfigureServices(c => + { + c.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new AzureOpenAITextEmbeddingGeneration( + deploymentName, + openAIClient, + modelId, + serviceProvider.GetService())); + }); + } + + /// + /// Adds an Azure OpenAI text embeddings service to the list. + /// + /// The instance to augment. + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Custom . + /// A local identifier for the given AI service + /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// The same instance as . + public static IServiceCollection AddAzureOpenAITextEmbeddingGeneration( + this IServiceCollection services, + string deploymentName, + OpenAIClient openAIClient, + string? serviceId = null, + string? modelId = null) + { + Verify.NotNull(services); + Verify.NotNull(deploymentName); + Verify.NotNull(openAIClient); + + return services.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new AzureOpenAITextEmbeddingGeneration( + deploymentName, + openAIClient, + modelId, + serviceProvider.GetService())); } /// /// Adds the OpenAI text embeddings service to the list. - /// See https://platform.openai.com/docs for service details. /// - /// The instance + /// The instance to augment. /// OpenAI model name, see https://platform.openai.com/docs/models /// OpenAI API key, see https://platform.openai.com/account/api-keys /// OpenAI organization id. This is usually optional unless your account belongs to multiple organizations. /// A local identifier for the given AI service - /// Whether the service should be the default for its type. - /// Custom for HTTP requests. - /// Self instance - public static KernelBuilder WithOpenAITextEmbeddingGenerationService(this KernelBuilder builder, + /// The HttpClient to use with this service. + /// The same instance as . + public static KernelBuilder WithOpenAITextEmbeddingGeneration( + this KernelBuilder builder, string modelId, string apiKey, string? orgId = null, string? serviceId = null, - bool setAsDefault = false, HttpClient? httpClient = null) { - builder.WithAIService(serviceId, (loggerFactory, httpHandlerFactory) => + Verify.NotNull(builder); + Verify.NotNull(modelId); + Verify.NotNull(apiKey); + + return builder.ConfigureServices(c => + { + c.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new OpenAITextEmbeddingGeneration( + modelId, + apiKey, + orgId, + HttpClientProvider.GetHttpClient(httpClient, serviceProvider), + serviceProvider.GetService())); + }); + } + + /// + /// Adds the OpenAI text embeddings service to the list. + /// + /// The instance to augment. + /// OpenAI model name, see https://platform.openai.com/docs/models + /// OpenAI API key, see https://platform.openai.com/account/api-keys + /// OpenAI organization id. This is usually optional unless your account belongs to multiple organizations. + /// A local identifier for the given AI service + /// The same instance as . + public static IServiceCollection AddOpenAITextEmbeddingGeneration( + this IServiceCollection services, + string modelId, + string apiKey, + string? orgId = null, + string? serviceId = null) + { + Verify.NotNull(services); + Verify.NotNull(modelId); + Verify.NotNull(apiKey); + + return services.AddKeyedSingleton(serviceId, (serviceProvider, _) => new OpenAITextEmbeddingGeneration( modelId, apiKey, orgId, - HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), - loggerFactory), - setAsDefault); - return builder; + HttpClientProvider.GetHttpClient(serviceProvider), + serviceProvider.GetService())); + } + + /// + /// Adds the OpenAI text embeddings service to the list. + /// + /// The instance to augment. + /// OpenAI model name, see https://platform.openai.com/docs/models + /// Custom . + /// A local identifier for the given AI service + /// The same instance as . + public static KernelBuilder WithOpenAITextEmbeddingGeneration( + this KernelBuilder builder, + string modelId, + OpenAIClient openAIClient, + string? serviceId = null) + { + Verify.NotNull(builder); + Verify.NotNull(modelId); + Verify.NotNull(openAIClient); + + return builder.ConfigureServices(c => + { + c.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new OpenAITextEmbeddingGeneration( + modelId, + openAIClient, + serviceProvider.GetService())); + }); + } + + /// + /// Adds the OpenAI text embeddings service to the list. + /// + /// The instance to augment. + /// OpenAI model name, see https://platform.openai.com/docs/models + /// Custom . + /// A local identifier for the given AI service + /// The same instance as . + public static IServiceCollection AddOpenAITextEmbeddingGeneration(this IServiceCollection services, + string modelId, + OpenAIClient openAIClient, + string? serviceId = null) + { + Verify.NotNull(services); + Verify.NotNull(modelId); + Verify.NotNull(openAIClient); + + return services.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new OpenAITextEmbeddingGeneration( + modelId, + openAIClient, + serviceProvider.GetService())); } #endregion @@ -260,230 +661,410 @@ public static KernelBuilder WithOpenAITextEmbeddingGenerationService(this Kernel #region Chat Completion /// - /// Adds the Azure OpenAI ChatGPT completion service to the list. - /// See https://platform.openai.com/docs for service details. + /// Adds the Azure OpenAI chat completion service to the list. /// - /// The instance + /// The instance to augment. /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Azure OpenAI API key, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart - /// Whether to use the service also for text completion, if supported /// A local identifier for the given AI service /// Model identifier - /// Whether the service should be the default for its type. - /// Custom for HTTP requests. - /// Self instance - public static KernelBuilder WithAzureOpenAIChatCompletionService(this KernelBuilder builder, + /// The HttpClient to use with this service. + /// The same instance as . + public static KernelBuilder WithAzureOpenAIChatCompletion( + this KernelBuilder builder, string deploymentName, string endpoint, string apiKey, - bool alsoAsTextCompletion = true, string? serviceId = null, string? modelId = null, - bool setAsDefault = false, HttpClient? httpClient = null) { - AzureOpenAIChatCompletion Factory(ILoggerFactory loggerFactory, IDelegatingHandlerFactory httpHandlerFactory) + Verify.NotNull(builder); + Verify.NotNull(deploymentName); + Verify.NotNull(endpoint); + Verify.NotNull(apiKey); + + return builder.ConfigureServices(c => { - OpenAIClient client = CreateAzureOpenAIClient(loggerFactory, httpHandlerFactory, deploymentName, endpoint, new AzureKeyCredential(apiKey), httpClient); + Func factory = (serviceProvider, _) => + { + OpenAIClient client = CreateAzureOpenAIClient( + deploymentName, + endpoint, + new AzureKeyCredential(apiKey), + HttpClientProvider.GetHttpClient(httpClient, serviceProvider)); - return new(deploymentName, client, modelId, loggerFactory); - }; + return new(deploymentName, client, modelId, serviceProvider.GetService()); + }; - builder.WithAIService(serviceId, Factory, setAsDefault); + c.AddKeyedSingleton(serviceId, factory); + c.AddKeyedSingleton(serviceId, factory); + }); + } - // If the class implements the text completion interface, allow to use it also for semantic functions - if (alsoAsTextCompletion && typeof(ITextCompletion).IsAssignableFrom(typeof(AzureOpenAIChatCompletion))) + /// + /// Adds the Azure OpenAI chat completion service to the list. + /// + /// The instance to augment. + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// Azure OpenAI API key, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// A local identifier for the given AI service + /// Model identifier + /// The same instance as . + public static IServiceCollection AddAzureOpenAIChatCompletion( + this IServiceCollection services, + string deploymentName, + string endpoint, + string apiKey, + string? serviceId = null, + string? modelId = null) + { + Verify.NotNull(services); + Verify.NotNull(deploymentName); + Verify.NotNull(endpoint); + Verify.NotNull(apiKey); + + Func factory = (serviceProvider, _) => { - builder.WithAIService(serviceId, Factory, setAsDefault); - } + OpenAIClient client = CreateAzureOpenAIClient( + deploymentName, + endpoint, + new AzureKeyCredential(apiKey), + HttpClientProvider.GetHttpClient(serviceProvider)); + + return new(deploymentName, client, modelId, serviceProvider.GetService()); + }; - return builder; + services.AddKeyedSingleton(serviceId, factory); + services.AddKeyedSingleton(serviceId, factory); + + return services; } /// - /// Adds the Azure OpenAI ChatGPT completion service to the list. - /// See https://platform.openai.com/docs for service details. + /// Adds the Azure OpenAI chat completion service to the list. /// - /// The instance + /// The instance to augment. /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Token credentials, e.g. DefaultAzureCredential, ManagedIdentityCredential, EnvironmentCredential, etc. - /// Whether to use the service also for text completion, if supported /// A local identifier for the given AI service /// Model identifier - /// Whether the service should be the default for its type. - /// Custom for HTTP requests. - /// Self instance - public static KernelBuilder WithAzureOpenAIChatCompletionService(this KernelBuilder builder, + /// The HttpClient to use with this service. + /// The same instance as . + public static KernelBuilder WithAzureOpenAIChatCompletion( + this KernelBuilder builder, string deploymentName, string endpoint, TokenCredential credentials, - bool alsoAsTextCompletion = true, string? serviceId = null, string? modelId = null, - bool setAsDefault = false, HttpClient? httpClient = null) { - AzureOpenAIChatCompletion Factory(ILoggerFactory loggerFactory, IDelegatingHandlerFactory httpHandlerFactory) + Verify.NotNull(builder); + Verify.NotNull(deploymentName); + Verify.NotNull(endpoint); + Verify.NotNull(credentials); + + return builder.ConfigureServices(c => + { + Func factory = (serviceProvider, _) => + { + OpenAIClient client = CreateAzureOpenAIClient( + deploymentName, + endpoint, + credentials, + HttpClientProvider.GetHttpClient(httpClient, serviceProvider)); + + return new(deploymentName, client, modelId, serviceProvider.GetService()); + }; + + c.AddKeyedSingleton(serviceId, factory); + c.AddKeyedSingleton(serviceId, factory); + }); + } + + /// + /// Adds the Azure OpenAI chat completion service to the list. + /// + /// The instance to augment. + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// Token credentials, e.g. DefaultAzureCredential, ManagedIdentityCredential, EnvironmentCredential, etc. + /// A local identifier for the given AI service + /// Model identifier + /// The same instance as . + public static IServiceCollection AddAzureOpenAIChatCompletion( + this IServiceCollection services, + string deploymentName, + string endpoint, + TokenCredential credentials, + string? serviceId = null, + string? modelId = null) + { + Verify.NotNull(services); + Verify.NotNull(deploymentName); + Verify.NotNull(endpoint); + Verify.NotNull(credentials); + + Func factory = (serviceProvider, _) => { - OpenAIClient client = CreateAzureOpenAIClient(loggerFactory, httpHandlerFactory, deploymentName, endpoint, credentials, httpClient); + OpenAIClient client = CreateAzureOpenAIClient( + deploymentName, + endpoint, + credentials, + HttpClientProvider.GetHttpClient(serviceProvider)); - return new(deploymentName, client, modelId, loggerFactory); + return new(deploymentName, client, modelId, serviceProvider.GetService()); }; - builder.WithAIService(serviceId, Factory, setAsDefault); + services.AddKeyedSingleton(serviceId, factory); + services.AddKeyedSingleton(serviceId, factory); + + return services; + } + + /// + /// Adds the Azure OpenAI chat completion service to the list. + /// + /// The instance to augment. + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Custom . + /// A local identifier for the given AI service + /// Model identifier + /// The same instance as . + public static KernelBuilder WithAzureOpenAIChatCompletion( + this KernelBuilder builder, + string deploymentName, + OpenAIClient openAIClient, + string? serviceId = null, + string? modelId = null) + { + Verify.NotNull(builder); + Verify.NotNull(deploymentName); + Verify.NotNull(openAIClient); - // If the class implements the text completion interface, allow to use it also for semantic functions - if (alsoAsTextCompletion && typeof(ITextCompletion).IsAssignableFrom(typeof(AzureOpenAIChatCompletion))) + return builder.ConfigureServices(c => { - builder.WithAIService(serviceId, Factory, setAsDefault); - } + Func factory = (serviceProvider, _) => + new(deploymentName, openAIClient, modelId, serviceProvider.GetService()); - return builder; + c.AddKeyedSingleton(serviceId, factory); + c.AddKeyedSingleton(serviceId, factory); + }); + } + + /// + /// Adds the Azure OpenAI chat completion service to the list. + /// + /// The instance to augment. + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Custom . + /// A local identifier for the given AI service + /// Model identifier + /// The same instance as . + public static IServiceCollection AddAzureOpenAIChatCompletion( + this IServiceCollection services, + string deploymentName, + OpenAIClient openAIClient, + string? serviceId = null, + string? modelId = null) + { + Verify.NotNull(services); + Verify.NotNull(deploymentName); + Verify.NotNull(openAIClient); + + Func factory = (serviceProvider, _) => + new(deploymentName, openAIClient, modelId, serviceProvider.GetService()); + + services.AddKeyedSingleton(serviceId, factory); + services.AddKeyedSingleton(serviceId, factory); + + return services; } /// /// Adds the Azure OpenAI chat completion with data service to the list. - /// More information: /// /// The instance. /// Required configuration for Azure OpenAI chat completion with data. - /// Whether to use the service also for text completion, if supported. /// A local identifier for the given AI service. - /// Whether the service should be the default for its type. - /// Custom for HTTP requests. - /// Self instance - public static KernelBuilder WithAzureOpenAIChatCompletionService(this KernelBuilder builder, + /// The same instance as . + /// + /// More information: + /// + public static KernelBuilder WithAzureOpenAIChatCompletion( + this KernelBuilder builder, AzureOpenAIChatCompletionWithDataConfig config, - bool alsoAsTextCompletion = true, - string? serviceId = null, - bool setAsDefault = false, - HttpClient? httpClient = null) + string? serviceId = null) { - AzureOpenAIChatCompletionWithData Factory(ILoggerFactory loggerFactory, IDelegatingHandlerFactory httpHandlerFactory) => new( - config, - HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), - loggerFactory); + Verify.NotNull(builder); + Verify.NotNull(config); - builder.WithAIService(serviceId, Factory, setAsDefault); - - if (alsoAsTextCompletion && typeof(ITextCompletion).IsAssignableFrom(typeof(AzureOpenAIChatCompletionWithData))) + return builder.ConfigureServices(c => { - builder.WithAIService(serviceId, Factory, setAsDefault); - } + Func factory = (serviceProvider, _) => + new(config, + HttpClientProvider.GetHttpClient(serviceProvider), + serviceProvider.GetService()); - return builder; + c.AddKeyedSingleton(serviceId, factory); + c.AddKeyedSingleton(serviceId, factory); + }); } /// - /// Adds the OpenAI ChatGPT completion service to the list. - /// See https://platform.openai.com/docs for service details. + /// Adds the Azure OpenAI chat completion with data service to the list. /// - /// The instance + /// The instance. + /// Required configuration for Azure OpenAI chat completion with data. + /// A local identifier for the given AI service. + /// The same instance as . + /// + /// More information: + /// + public static IServiceCollection AddAzureOpenAIChatCompletion( + this IServiceCollection services, + AzureOpenAIChatCompletionWithDataConfig config, + string? serviceId = null) + { + Verify.NotNull(services); + Verify.NotNull(config); + + Func factory = (serviceProvider, _) => + new(config, + HttpClientProvider.GetHttpClient(serviceProvider), + serviceProvider.GetService()); + + services.AddKeyedSingleton(serviceId, factory); + services.AddKeyedSingleton(serviceId, factory); + + return services; + } + + /// + /// Adds the OpenAI chat completion service to the list. + /// + /// The instance to augment. /// OpenAI model name, see https://platform.openai.com/docs/models /// OpenAI API key, see https://platform.openai.com/account/api-keys /// OpenAI organization id. This is usually optional unless your account belongs to multiple organizations. /// A local identifier for the given AI service - /// Whether to use the service also for text completion, if supported - /// Whether the service should be the default for its type. - /// Custom for HTTP requests. - /// Self instance - public static KernelBuilder WithOpenAIChatCompletionService(this KernelBuilder builder, + /// The HttpClient to use with this service. + /// The same instance as . + public static KernelBuilder WithOpenAIChatCompletion( + this KernelBuilder builder, string modelId, string apiKey, string? orgId = null, string? serviceId = null, - bool alsoAsTextCompletion = true, - bool setAsDefault = false, HttpClient? httpClient = null) { - OpenAIChatCompletion Factory(ILoggerFactory loggerFactory, IDelegatingHandlerFactory httpHandlerFactory) => new( - modelId, - apiKey, - orgId, - HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), - loggerFactory); - - builder.WithAIService(serviceId, Factory, setAsDefault); + Verify.NotNull(builder); + Verify.NotNull(modelId); + Verify.NotNull(apiKey); - // If the class implements the text completion interface, allow to use it also for semantic functions - if (alsoAsTextCompletion && typeof(ITextCompletion).IsAssignableFrom(typeof(OpenAIChatCompletion))) + return builder.ConfigureServices(c => { - builder.WithAIService(serviceId, Factory, setAsDefault); - } + Func factory = (serviceProvider, _) => + new(modelId, + apiKey, + orgId, + HttpClientProvider.GetHttpClient(httpClient, serviceProvider), + serviceProvider.GetService()); - return builder; + c.AddKeyedSingleton(serviceId, factory); + c.AddKeyedSingleton(serviceId, factory); + }); } /// - /// Adds the Azure OpenAI ChatGPT completion service to the list. - /// See https://platform.openai.com/docs for service details. + /// Adds the OpenAI chat completion service to the list. /// - /// The instance - /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// The instance to augment. + /// OpenAI model name, see https://platform.openai.com/docs/models + /// OpenAI API key, see https://platform.openai.com/account/api-keys + /// OpenAI organization id. This is usually optional unless your account belongs to multiple organizations. + /// A local identifier for the given AI service + /// The same instance as . + public static IServiceCollection AddOpenAIChatCompletion( + this IServiceCollection services, + string modelId, + string apiKey, + string? orgId = null, + string? serviceId = null) + { + Verify.NotNull(services); + Verify.NotNull(modelId); + Verify.NotNull(apiKey); + + Func factory = (serviceProvider, _) => + new(modelId, + apiKey, + orgId, + HttpClientProvider.GetHttpClient(serviceProvider), + serviceProvider.GetService()); + + services.AddKeyedSingleton(serviceId, factory); + services.AddKeyedSingleton(serviceId, factory); + + return services; + } + + /// + /// Adds the OpenAI chat completion service to the list. + /// + /// The instance to augment. + /// OpenAI model id /// Custom for HTTP requests. - /// Whether to use the service also for text completion, if supported /// A local identifier for the given AI service - /// Model identifier - /// Whether the service should be the default for its type. - /// Self instance - public static KernelBuilder WithAzureOpenAIChatCompletionService(this KernelBuilder builder, - string deploymentName, + /// The same instance as . + public static KernelBuilder WithOpenAIChatCompletion( + this KernelBuilder builder, + string modelId, OpenAIClient openAIClient, - bool alsoAsTextCompletion = true, - string? serviceId = null, - string? modelId = null, - bool setAsDefault = false) + string? serviceId = null) { - AzureOpenAIChatCompletion Factory(ILoggerFactory loggerFactory) - { - return new(deploymentName, openAIClient, modelId, loggerFactory); - }; + Verify.NotNull(builder); + Verify.NotNull(modelId); + Verify.NotNull(openAIClient); - builder.WithAIService(serviceId, Factory, setAsDefault); - - // If the class implements the text completion interface, allow to use it also for semantic functions - if (alsoAsTextCompletion && typeof(ITextCompletion).IsAssignableFrom(typeof(AzureOpenAIChatCompletion))) + return builder.ConfigureServices(c => { - builder.WithAIService(serviceId, Factory, setAsDefault); - } + Func factory = (serviceProvider, _) => + new(modelId, openAIClient, serviceProvider.GetService()); - return builder; + c.AddKeyedSingleton(serviceId, factory); + c.AddKeyedSingleton(serviceId, factory); + }); } /// - /// Adds the OpenAI ChatGPT completion service to the list. - /// See https://platform.openai.com/docs for service details. + /// Adds the OpenAI chat completion service to the list. /// - /// The instance + /// The instance to augment. /// OpenAI model id /// Custom for HTTP requests. - /// Whether to use the service also for text completion, if supported /// A local identifier for the given AI service - /// Whether the service should be the default for its type. - /// Self instance - public static KernelBuilder WithOpenAIChatCompletionService(this KernelBuilder builder, + /// The same instance as . + public static IServiceCollection AddOpenAIChatCompletion(this IServiceCollection services, string modelId, OpenAIClient openAIClient, - bool alsoAsTextCompletion = true, - string? serviceId = null, - bool setAsDefault = false) + string? serviceId = null) { - OpenAIChatCompletion Factory(ILoggerFactory loggerFactory) - { - return new(modelId, openAIClient, loggerFactory); - } + Verify.NotNull(services); + Verify.NotNull(modelId); + Verify.NotNull(openAIClient); - builder.WithAIService(serviceId, Factory, setAsDefault); + Func factory = (serviceProvider, _) => + new(modelId, openAIClient, serviceProvider.GetService()); - // If the class implements the text completion interface, allow to use it also for semantic functions - if (alsoAsTextCompletion && typeof(ITextCompletion).IsAssignableFrom(typeof(AzureOpenAIChatCompletion))) - { - builder.WithAIService(serviceId, Factory, setAsDefault); - } + services.AddKeyedSingleton(serviceId, factory); + services.AddKeyedSingleton(serviceId, factory); - return builder; + return services; } #endregion @@ -491,90 +1072,127 @@ OpenAIChatCompletion Factory(ILoggerFactory loggerFactory) #region Images /// - /// Add the OpenAI DallE image generation service to the list + /// Add the Azure OpenAI DallE image generation service to the list /// - /// The instance - /// OpenAI API key, see https://platform.openai.com/account/api-keys - /// OpenAI organization id. This is usually optional unless your account belongs to multiple organizations. + /// The instance to augment. + /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart + /// Azure OpenAI API key, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// A local identifier for the given AI service - /// Whether the service should be the default for its type. - /// Custom for HTTP requests. - /// Self instance - public static KernelBuilder WithOpenAIImageGenerationService(this KernelBuilder builder, + /// Maximum number of attempts to retrieve the image generation operation result. + /// The HttpClient to use with this service. + /// The same instance as . + public static KernelBuilder WithAzureOpenAIImageGeneration( + this KernelBuilder builder, + string endpoint, string apiKey, - string? orgId = null, string? serviceId = null, - bool setAsDefault = false, + int maxRetryCount = 5, HttpClient? httpClient = null) { - builder.WithAIService(serviceId, (loggerFactory, httpHandlerFactory) => - new OpenAIImageGeneration( - apiKey, - orgId, - HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), - loggerFactory), - setAsDefault); + Verify.NotNull(builder); + Verify.NotNull(endpoint); + Verify.NotNull(apiKey); - return builder; + return builder.ConfigureServices(c => + { + c.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new AzureOpenAIImageGeneration( + endpoint, + apiKey, + HttpClientProvider.GetHttpClient(httpClient, serviceProvider), + serviceProvider.GetService(), + maxRetryCount)); + }); } /// /// Add the Azure OpenAI DallE image generation service to the list /// - /// The instance + /// The instance to augment. /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Azure OpenAI API key, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// A local identifier for the given AI service - /// Whether the service should be the default for its type. - /// Custom for HTTP requests. /// Maximum number of attempts to retrieve the image generation operation result. - /// Self instance - public static KernelBuilder WithAzureOpenAIImageGenerationService(this KernelBuilder builder, + /// The same instance as . + public static IServiceCollection AddAzureOpenAIImageGeneration( + this IServiceCollection services, string endpoint, string apiKey, string? serviceId = null, - bool setAsDefault = false, - HttpClient? httpClient = null, int maxRetryCount = 5) { - builder.WithAIService(serviceId, (loggerFactory, httpHandlerFactory) => + Verify.NotNull(services); + Verify.NotNull(endpoint); + Verify.NotNull(apiKey); + + return services.AddKeyedSingleton(serviceId, (serviceProvider, _) => new AzureOpenAIImageGeneration( endpoint, apiKey, - HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), - loggerFactory, - maxRetryCount), - setAsDefault); - - return builder; + HttpClientProvider.GetHttpClient(serviceProvider), + serviceProvider.GetService(), + maxRetryCount)); } - #endregion - - private static OpenAIClient CreateAzureOpenAIClient(ILoggerFactory loggerFactory, IDelegatingHandlerFactory httpHandlerFactory, string deploymentName, string endpoint, AzureKeyCredential credentials, HttpClient? httpClient) + /// + /// Add the OpenAI Dall-E image generation service to the list + /// + /// The instance to augment. + /// OpenAI API key, see https://platform.openai.com/account/api-keys + /// OpenAI organization id. This is usually optional unless your account belongs to multiple organizations. + /// A local identifier for the given AI service + /// The HttpClient to use with this service. + /// The same instance as . + public static KernelBuilder WithOpenAIImageGeneration( + this KernelBuilder builder, + string apiKey, + string? orgId = null, + string? serviceId = null, + HttpClient? httpClient = null) { - OpenAIClientOptions options = CreateOpenAIClientOptions(loggerFactory, httpHandlerFactory, httpClient); + Verify.NotNull(builder); + Verify.NotNull(apiKey); - return new(new Uri(endpoint), credentials, options); + return builder.ConfigureServices(c => + { + c.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new OpenAIImageGeneration( + apiKey, + orgId, + HttpClientProvider.GetHttpClient(httpClient, serviceProvider), + serviceProvider.GetService())); + }); } - private static OpenAIClient CreateAzureOpenAIClient(ILoggerFactory loggerFactory, IDelegatingHandlerFactory httpHandlerFactory, string deploymentName, string endpoint, TokenCredential credentials, HttpClient? httpClient) + /// + /// Add the OpenAI Dall-E image generation service to the list + /// + /// The instance to augment. + /// OpenAI API key, see https://platform.openai.com/account/api-keys + /// OpenAI organization id. This is usually optional unless your account belongs to multiple organizations. + /// A local identifier for the given AI service + /// The same instance as . + public static IServiceCollection AddOpenAIImageGeneration(this IServiceCollection services, + string apiKey, + string? orgId = null, + string? serviceId = null) { - OpenAIClientOptions options = CreateOpenAIClientOptions(loggerFactory, httpHandlerFactory, httpClient); + Verify.NotNull(services); + Verify.NotNull(apiKey); - return new(new Uri(endpoint), credentials, options); + return services.AddKeyedSingleton(serviceId, (serviceProvider, _) => + new OpenAIImageGeneration( + apiKey, + orgId, + HttpClientProvider.GetHttpClient(serviceProvider), + serviceProvider.GetService())); } - private static OpenAIClientOptions CreateOpenAIClientOptions(ILoggerFactory loggerFactory, IDelegatingHandlerFactory httpHandlerFactory, HttpClient? httpClient) - { - OpenAIClientOptions options = new() - { -#pragma warning disable CA2000 // Dispose objects before losing scope - Transport = new HttpClientTransport(HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory)), - RetryPolicy = new RetryPolicy(maxRetries: 0) //Disabling Azure SDK retry policy to use the one provided by the delegating handler factory or the HTTP client. - }; -#pragma warning restore CA2000 // Dispose objects before losing scope + #endregion - return options; - } + private static OpenAIClient CreateAzureOpenAIClient(string deploymentName, string endpoint, AzureKeyCredential credentials, HttpClient? httpClient) => + new(new Uri(endpoint), credentials, ClientBase.GetOpenAIClientOptions(httpClient)); + + private static OpenAIClient CreateAzureOpenAIClient(string deploymentName, string endpoint, TokenCredential credentials, HttpClient? httpClient) => + new(new Uri(endpoint), credentials, ClientBase.GetOpenAIClientOptions(httpClient)); } diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIMemoryBuilderExtensions.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIMemoryBuilderExtensions.cs index 6fe4e3ed8ee8..a9f07c1d23ec 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIMemoryBuilderExtensions.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIMemoryBuilderExtensions.cs @@ -20,31 +20,25 @@ public static class OpenAIMemoryBuilderExtensions /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Azure OpenAI API key, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart - /// A local identifier for the given AI service /// Model identifier - /// Whether the service should be the default for its type. /// Custom for HTTP requests. /// Self instance - public static MemoryBuilder WithAzureOpenAITextEmbeddingGenerationService( + public static MemoryBuilder WithAzureOpenAITextEmbeddingGeneration( this MemoryBuilder builder, string deploymentName, string endpoint, string apiKey, - string? serviceId = null, string? modelId = null, - bool setAsDefault = false, HttpClient? httpClient = null) { - builder.WithTextEmbeddingGeneration((loggerFactory, httpHandlerFactory) => + return builder.WithTextEmbeddingGeneration((loggerFactory, httpClient) => new AzureOpenAITextEmbeddingGeneration( deploymentName, endpoint, apiKey, modelId, - HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), + HttpClientProvider.GetHttpClient(httpClient), loggerFactory)); - - return builder; } /// @@ -55,31 +49,25 @@ public static MemoryBuilder WithAzureOpenAITextEmbeddingGenerationService( /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Token credentials, e.g. DefaultAzureCredential, ManagedIdentityCredential, EnvironmentCredential, etc. - /// A local identifier for the given AI service /// Model identifier - /// Whether the service should be the default for its type. /// Custom for HTTP requests. /// Self instance - public static MemoryBuilder WithAzureOpenAITextEmbeddingGenerationService( + public static MemoryBuilder WithAzureOpenAITextEmbeddingGeneration( this MemoryBuilder builder, string deploymentName, string endpoint, TokenCredential credential, - string? serviceId = null, string? modelId = null, - bool setAsDefault = false, HttpClient? httpClient = null) { - builder.WithTextEmbeddingGeneration((loggerFactory, httpHandlerFactory) => + return builder.WithTextEmbeddingGeneration((loggerFactory, httpClient) => new AzureOpenAITextEmbeddingGeneration( deploymentName, endpoint, credential, modelId, - HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), + HttpClientProvider.GetHttpClient(httpClient), loggerFactory)); - - return builder; } /// @@ -90,27 +78,21 @@ public static MemoryBuilder WithAzureOpenAITextEmbeddingGenerationService( /// OpenAI model name, see https://platform.openai.com/docs/models /// OpenAI API key, see https://platform.openai.com/account/api-keys /// OpenAI organization id. This is usually optional unless your account belongs to multiple organizations. - /// A local identifier for the given AI service - /// Whether the service should be the default for its type. /// Custom for HTTP requests. /// Self instance - public static MemoryBuilder WithOpenAITextEmbeddingGenerationService( + public static MemoryBuilder WithOpenAITextEmbeddingGeneration( this MemoryBuilder builder, string modelId, string apiKey, string? orgId = null, - string? serviceId = null, - bool setAsDefault = false, HttpClient? httpClient = null) { - builder.WithTextEmbeddingGeneration((loggerFactory, httpHandlerFactory) => + return builder.WithTextEmbeddingGeneration((loggerFactory, httpClient) => new OpenAITextEmbeddingGeneration( modelId, apiKey, orgId, - HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), + HttpClientProvider.GetHttpClient(httpClient), loggerFactory)); - - return builder; } } diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs index b61060a3c813..fd9d0ef88749 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs @@ -4,6 +4,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Azure.AI.OpenAI; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.AI.TextCompletion; @@ -38,6 +39,21 @@ public OpenAITextCompletion( this.AddAttribute(OrganizationKey, organization); } + /// + /// Create an instance of the OpenAI text completion connector + /// + /// Model name + /// Custom for HTTP requests. + /// The to use for logging. If null, no logging will be performed. + public OpenAITextCompletion( + string modelId, + OpenAIClient openAIClient, + ILoggerFactory? loggerFactory = null + ) : base(modelId, openAIClient, loggerFactory) + { + this.AddAttribute(IAIServiceExtensions.ModelIdKey, modelId); + } + /// public IReadOnlyDictionary Attributes => this.InternalAttributes; diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs index cb759e88c589..6824416f3ea6 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs @@ -5,6 +5,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Azure.AI.OpenAI; using Azure.Core; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.AI.Embeddings; @@ -58,6 +59,22 @@ public AzureOpenAITextEmbeddingGeneration( this.AddAttribute(IAIServiceExtensions.ModelIdKey, modelId); } + /// + /// Creates a new client. + /// + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Custom for HTTP requests. + /// Azure OpenAI model id, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// The to use for logging. If null, no logging will be performed. + public AzureOpenAITextEmbeddingGeneration( + string deploymentName, + OpenAIClient openAIClient, + string? modelId = null, + ILoggerFactory? loggerFactory = null) : base(deploymentName, openAIClient, loggerFactory) + { + this.AddAttribute(IAIServiceExtensions.ModelIdKey, modelId); + } + /// public IReadOnlyDictionary Attributes => this.InternalAttributes; diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs index af409f0bd4f5..01d1da8b3b72 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs @@ -5,6 +5,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Azure.AI.OpenAI; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.AI.Embeddings; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.AzureSdk; @@ -36,6 +37,21 @@ public OpenAITextEmbeddingGeneration( this.AddAttribute(IAIServiceExtensions.ModelIdKey, modelId); } + /// + /// Create an instance of the OpenAI text embedding connector + /// + /// Model name + /// Custom for HTTP requests. + /// The to use for logging. If null, no logging will be performed. + public OpenAITextEmbeddingGeneration( + string modelId, + OpenAIClient openAIClient, + ILoggerFactory? loggerFactory = null + ) : base(modelId, openAIClient, loggerFactory) + { + this.AddAttribute(IAIServiceExtensions.ModelIdKey, modelId); + } + /// public IReadOnlyDictionary Attributes => this.InternalAttributes; diff --git a/dotnet/src/Connectors/Connectors.Memory.Chroma/ChromaClient.cs b/dotnet/src/Connectors/Connectors.Memory.Chroma/ChromaClient.cs index 192f03f985b5..6fea1697a84f 100644 --- a/dotnet/src/Connectors/Connectors.Memory.Chroma/ChromaClient.cs +++ b/dotnet/src/Connectors/Connectors.Memory.Chroma/ChromaClient.cs @@ -33,7 +33,7 @@ public ChromaClient(string endpoint, ILoggerFactory? loggerFactory = null) { Verify.NotNull(endpoint); - this._httpClient = new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); + this._httpClient = HttpClientProvider.GetHttpClient(); this._endpoint = endpoint; this._logger = loggerFactory is not null ? loggerFactory.CreateLogger(typeof(ChromaClient)) : NullLogger.Instance; } diff --git a/dotnet/src/Connectors/Connectors.Memory.Chroma/ChromaMemoryBuilderExtensions.cs b/dotnet/src/Connectors/Connectors.Memory.Chroma/ChromaMemoryBuilderExtensions.cs index cbe98e0a6748..894d28460997 100644 --- a/dotnet/src/Connectors/Connectors.Memory.Chroma/ChromaMemoryBuilderExtensions.cs +++ b/dotnet/src/Connectors/Connectors.Memory.Chroma/ChromaMemoryBuilderExtensions.cs @@ -18,10 +18,10 @@ public static class ChromaMemoryBuilderExtensions /// Updated Memory builder including Chroma memory connector. public static MemoryBuilder WithChromaMemoryStore(this MemoryBuilder builder, string endpoint) { - builder.WithMemoryStore((loggerFactory, httpHandlerFactory) => + builder.WithMemoryStore((loggerFactory, injectedClient) => { return new ChromaMemoryStore( - HttpClientProvider.GetHttpClient(httpHandlerFactory, null, loggerFactory), + HttpClientProvider.GetHttpClient(injectedClient), endpoint, loggerFactory); }); @@ -41,10 +41,10 @@ public static MemoryBuilder WithChromaMemoryStore( HttpClient httpClient, string? endpoint = null) { - builder.WithMemoryStore((loggerFactory, httpHandlerFactory) => + builder.WithMemoryStore((loggerFactory, injectedClient) => { return new ChromaMemoryStore( - HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), + HttpClientProvider.GetHttpClient(httpClient ?? injectedClient), endpoint, loggerFactory); }); diff --git a/dotnet/src/Connectors/Connectors.Memory.Pinecone/PineconeClient.cs b/dotnet/src/Connectors/Connectors.Memory.Pinecone/PineconeClient.cs index 442d6121d6ca..a76159a4a441 100644 --- a/dotnet/src/Connectors/Connectors.Memory.Pinecone/PineconeClient.cs +++ b/dotnet/src/Connectors/Connectors.Memory.Pinecone/PineconeClient.cs @@ -36,7 +36,7 @@ public PineconeClient(string pineconeEnvironment, string apiKey, ILoggerFactory? this._authHeader = new KeyValuePair("Api-Key", apiKey); this._jsonSerializerOptions = PineconeUtils.DefaultSerializerOptions; this._logger = loggerFactory is not null ? loggerFactory.CreateLogger(typeof(PineconeClient)) : NullLogger.Instance; - this._httpClient = httpClient ?? new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); + this._httpClient = HttpClientProvider.GetHttpClient(httpClient); this._indexHostMapping = new ConcurrentDictionary(); } diff --git a/dotnet/src/Connectors/Connectors.Memory.Pinecone/PineconeMemoryBuilderExtensions.cs b/dotnet/src/Connectors/Connectors.Memory.Pinecone/PineconeMemoryBuilderExtensions.cs index 984fd97e42db..d63dd3488f62 100644 --- a/dotnet/src/Connectors/Connectors.Memory.Pinecone/PineconeMemoryBuilderExtensions.cs +++ b/dotnet/src/Connectors/Connectors.Memory.Pinecone/PineconeMemoryBuilderExtensions.cs @@ -24,13 +24,13 @@ public static MemoryBuilder WithPineconeMemoryStore( string apiKey, HttpClient? httpClient = null) { - builder.WithMemoryStore((loggerFactory, httpHandlerFactory) => + builder.WithMemoryStore((loggerFactory, injectedClient) => { var client = new PineconeClient( environment, apiKey, loggerFactory, - HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory)); + HttpClientProvider.GetHttpClient(httpClient ?? injectedClient)); return new PineconeMemoryStore(client, loggerFactory); }); diff --git a/dotnet/src/Connectors/Connectors.Memory.Qdrant/QdrantMemoryBuilderExtensions.cs b/dotnet/src/Connectors/Connectors.Memory.Qdrant/QdrantMemoryBuilderExtensions.cs index 8f18fbe4f850..44217164a57e 100644 --- a/dotnet/src/Connectors/Connectors.Memory.Qdrant/QdrantMemoryBuilderExtensions.cs +++ b/dotnet/src/Connectors/Connectors.Memory.Qdrant/QdrantMemoryBuilderExtensions.cs @@ -22,10 +22,10 @@ public static MemoryBuilder WithQdrantMemoryStore( string endpoint, int vectorSize) { - builder.WithMemoryStore((loggerFactory, httpHandlerFactory) => + builder.WithMemoryStore((loggerFactory, injectedClient) => { var client = new QdrantVectorDbClient( - HttpClientProvider.GetHttpClient(httpHandlerFactory, null, loggerFactory), + HttpClientProvider.GetHttpClient(injectedClient), vectorSize, endpoint, loggerFactory); @@ -50,10 +50,10 @@ public static MemoryBuilder WithQdrantMemoryStore( int vectorSize, string? endpoint = null) { - builder.WithMemoryStore((loggerFactory, httpHandlerFactory) => + builder.WithMemoryStore((loggerFactory, injectedClient) => { var client = new QdrantVectorDbClient( - HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), + HttpClientProvider.GetHttpClient(httpClient ?? injectedClient), vectorSize, endpoint, loggerFactory); diff --git a/dotnet/src/Connectors/Connectors.Memory.Qdrant/QdrantVectorDbClient.cs b/dotnet/src/Connectors/Connectors.Memory.Qdrant/QdrantVectorDbClient.cs index 2d494bc27fb0..854f66e664a5 100644 --- a/dotnet/src/Connectors/Connectors.Memory.Qdrant/QdrantVectorDbClient.cs +++ b/dotnet/src/Connectors/Connectors.Memory.Qdrant/QdrantVectorDbClient.cs @@ -36,7 +36,7 @@ public QdrantVectorDbClient( ILoggerFactory? loggerFactory = null) { this._vectorSize = vectorSize; - this._httpClient = new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); + this._httpClient = HttpClientProvider.GetHttpClient(); this._httpClient.BaseAddress = SanitizeEndpoint(endpoint); this._logger = loggerFactory is not null ? loggerFactory.CreateLogger(typeof(QdrantVectorDbClient)) : NullLogger.Instance; } diff --git a/dotnet/src/Connectors/Connectors.Memory.Weaviate/WeaviateMemoryBuilderExtensions.cs b/dotnet/src/Connectors/Connectors.Memory.Weaviate/WeaviateMemoryBuilderExtensions.cs index b1dbd2686707..7ac38b7f8dca 100644 --- a/dotnet/src/Connectors/Connectors.Memory.Weaviate/WeaviateMemoryBuilderExtensions.cs +++ b/dotnet/src/Connectors/Connectors.Memory.Weaviate/WeaviateMemoryBuilderExtensions.cs @@ -24,10 +24,10 @@ public static MemoryBuilder WithWeaviateMemoryStore( string? apiKey, string? apiVersion = null) { - builder.WithMemoryStore((loggerFactory, httpHandlerFactory) => + builder.WithMemoryStore((loggerFactory, injectedClient) => { return new WeaviateMemoryStore( - HttpClientProvider.GetHttpClient(httpHandlerFactory, null, loggerFactory), + HttpClientProvider.GetHttpClient(injectedClient), apiKey, endpoint, apiVersion, @@ -53,10 +53,10 @@ public static MemoryBuilder WithWeaviateMemoryStore( string? apiKey = null, string? apiVersion = null) { - builder.WithMemoryStore((loggerFactory, httpHandlerFactory) => + builder.WithMemoryStore((loggerFactory, injectedClient) => { return new WeaviateMemoryStore( - HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), + HttpClientProvider.GetHttpClient(httpClient ?? injectedClient), apiKey, endpoint, apiVersion, diff --git a/dotnet/src/Connectors/Connectors.Memory.Weaviate/WeaviateMemoryStore.cs b/dotnet/src/Connectors/Connectors.Memory.Weaviate/WeaviateMemoryStore.cs index 2b4097388621..5fbc3970f1af 100644 --- a/dotnet/src/Connectors/Connectors.Memory.Weaviate/WeaviateMemoryStore.cs +++ b/dotnet/src/Connectors/Connectors.Memory.Weaviate/WeaviateMemoryStore.cs @@ -77,7 +77,7 @@ public WeaviateMemoryStore( this._apiKey = apiKey; this._apiVersion = apiVersion; this._logger = loggerFactory is not null ? loggerFactory.CreateLogger(typeof(WeaviateMemoryStore)) : NullLogger.Instance; - this._httpClient = new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); + this._httpClient = HttpClientProvider.GetHttpClient(); } /// diff --git a/dotnet/src/Connectors/Connectors.UnitTests/Connectors.UnitTests.csproj b/dotnet/src/Connectors/Connectors.UnitTests/Connectors.UnitTests.csproj index 9b0c1a872bf6..bfa654f256fa 100644 --- a/dotnet/src/Connectors/Connectors.UnitTests/Connectors.UnitTests.csproj +++ b/dotnet/src/Connectors/Connectors.UnitTests/Connectors.UnitTests.csproj @@ -22,6 +22,7 @@ + diff --git a/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs index f0fe84e98112..dcfcc4289179 100644 --- a/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs +++ b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI.Embeddings; using Microsoft.SemanticKernel.AI.TextCompletion; @@ -9,18 +10,18 @@ namespace SemanticKernel.Connectors.UnitTests.OpenAI; /// -/// Unit tests of . +/// Unit tests of . /// public class AIServicesOpenAIExtensionsTests { [Fact] public void ItSucceedsWhenAddingDifferentServiceTypeWithSameId() { - KernelBuilder targetBuilder = new(); - targetBuilder.WithAzureTextCompletionService("depl", "https://url", "key", "azure"); - targetBuilder.WithAzureOpenAITextEmbeddingGenerationService("depl2", "https://url", "key", "azure"); + Kernel targetKernel = new KernelBuilder() + .WithAzureOpenAITextCompletion("depl", "https://url", "key", "azure") + .WithAzureOpenAITextEmbeddingGeneration("depl2", "https://url", "key", "azure") + .Build(); - Kernel targetKernel = targetBuilder.Build(); Assert.NotNull(targetKernel.GetService("azure")); Assert.NotNull(targetKernel.GetService("azure")); } @@ -28,14 +29,14 @@ public void ItSucceedsWhenAddingDifferentServiceTypeWithSameId() [Fact] public void ItTellsIfAServiceIsAvailable() { - KernelBuilder targetBuilder = new(); - targetBuilder.WithAzureTextCompletionService("depl", "https://url", "key", serviceId: "azure"); - targetBuilder.WithOpenAITextCompletionService("model", "apikey", serviceId: "oai"); - targetBuilder.WithAzureOpenAITextEmbeddingGenerationService("depl2", "https://url2", "key", serviceId: "azure"); - targetBuilder.WithOpenAITextEmbeddingGenerationService("model2", "apikey2", serviceId: "oai2"); + Kernel targetKernel = new KernelBuilder() + .WithAzureOpenAITextCompletion("depl", "https://url", "key", serviceId: "azure") + .WithOpenAITextCompletion("model", "apikey", serviceId: "oai") + .WithAzureOpenAITextEmbeddingGeneration("depl2", "https://url2", "key", serviceId: "azure") + .WithOpenAITextEmbeddingGeneration("model2", "apikey2", serviceId: "oai2") + .Build(); // Assert - Kernel targetKernel = targetBuilder.Build(); Assert.NotNull(targetKernel.GetService("azure")); Assert.NotNull(targetKernel.GetService("oai")); Assert.NotNull(targetKernel.GetService("azure")); @@ -46,40 +47,41 @@ public void ItTellsIfAServiceIsAvailable() public void ItCanOverwriteServices() { // Arrange - KernelBuilder targetBuilder = new(); - // Act - Assert no exception occurs - targetBuilder.WithAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); - targetBuilder.WithAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); + new KernelBuilder().ConfigureServices(c => + { + c.AddAzureOpenAITextCompletion("dep", "https://localhost", "key", serviceId: "one"); + c.AddAzureOpenAITextCompletion("dep", "https://localhost", "key", serviceId: "one"); - targetBuilder.WithOpenAITextCompletionService("model", "key", serviceId: "one"); - targetBuilder.WithOpenAITextCompletionService("model", "key", serviceId: "one"); + c.AddOpenAITextCompletion("model", "key", serviceId: "one"); + c.AddOpenAITextCompletion("model", "key", serviceId: "one"); - targetBuilder.WithAzureOpenAITextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "one"); - targetBuilder.WithAzureOpenAITextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "one"); + c.AddAzureOpenAITextEmbeddingGeneration("dep", "https://localhost", "key", serviceId: "one"); + c.AddAzureOpenAITextEmbeddingGeneration("dep", "https://localhost", "key", serviceId: "one"); - targetBuilder.WithOpenAITextEmbeddingGenerationService("model", "key", serviceId: "one"); - targetBuilder.WithOpenAITextEmbeddingGenerationService("model", "key", serviceId: "one"); + c.AddOpenAITextEmbeddingGeneration("model", "key", serviceId: "one"); + c.AddOpenAITextEmbeddingGeneration("model", "key", serviceId: "one"); - targetBuilder.WithAzureOpenAIChatCompletionService("dep", "https://localhost", "key", serviceId: "one"); - targetBuilder.WithAzureOpenAIChatCompletionService("dep", "https://localhost", "key", serviceId: "one"); + c.AddAzureOpenAIChatCompletion("dep", "https://localhost", "key", serviceId: "one"); + c.AddAzureOpenAIChatCompletion("dep", "https://localhost", "key", serviceId: "one"); - targetBuilder.WithOpenAIChatCompletionService("model", "key", serviceId: "one"); - targetBuilder.WithOpenAIChatCompletionService("model", "key", serviceId: "one"); + c.AddOpenAIChatCompletion("model", "key", serviceId: "one"); + c.AddOpenAIChatCompletion("model", "key", serviceId: "one"); - targetBuilder.WithOpenAIImageGenerationService("model", "key", serviceId: "one"); - targetBuilder.WithOpenAIImageGenerationService("model", "key", serviceId: "one"); + c.AddOpenAIImageGeneration("model", "key", serviceId: "one"); + c.AddOpenAIImageGeneration("model", "key", serviceId: "one"); - targetBuilder.WithDefaultAIService(new OpenAITextCompletion("model", "key")); - targetBuilder.WithDefaultAIService(new OpenAITextCompletion("model", "key")); + c.AddSingleton(new OpenAITextCompletion("model", "key")); + c.AddSingleton(new OpenAITextCompletion("model", "key")); - targetBuilder.WithDefaultAIService((_) => new OpenAITextCompletion("model", "key")); - targetBuilder.WithDefaultAIService((_) => new OpenAITextCompletion("model", "key")); + c.AddSingleton((_) => new OpenAITextCompletion("model", "key")); + c.AddSingleton((_) => new OpenAITextCompletion("model", "key")); - targetBuilder.WithAIService("one", new OpenAITextCompletion("model", "key")); - targetBuilder.WithAIService("one", new OpenAITextCompletion("model", "key")); + c.AddKeyedSingleton("one", new OpenAITextCompletion("model", "key")); + c.AddKeyedSingleton("one", new OpenAITextCompletion("model", "key")); - targetBuilder.WithAIService("one", (loggerFactory) => new OpenAITextCompletion("model", "key")); - targetBuilder.WithAIService("one", (loggerFactory) => new OpenAITextCompletion("model", "key")); + c.AddKeyedSingleton("one", (_, _) => new OpenAITextCompletion("model", "key")); + c.AddKeyedSingleton("one", (_, _) => new OpenAITextCompletion("model", "key")); + }).Build(); } } diff --git a/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/FunctionCalling/KernelFunctionMetadataExtensionsTests.cs b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/FunctionCalling/KernelFunctionMetadataExtensionsTests.cs index dc80a01ab1f9..a3ed67ddc4e7 100644 --- a/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/FunctionCalling/KernelFunctionMetadataExtensionsTests.cs +++ b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/FunctionCalling/KernelFunctionMetadataExtensionsTests.cs @@ -152,13 +152,11 @@ public void ItCanConvertToOpenAIFunctionWithNoReturnParameterType() public void ItCanCreateValidOpenAIFunctionManual() { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new KernelBuilder() + .ConfigurePlugins(plugins => plugins.AddPluginFromObject("MyPlugin")) + .Build(); - var plugin = kernel.ImportPluginFromObject(new MyPlugin(), "MyPlugin"); - - var function = plugin.First(); - - var functionView = function.Metadata; + var functionView = kernel.Plugins["MyPlugin"].First().Metadata; var sut = functionView.ToOpenAIFunction(); diff --git a/dotnet/src/Experimental/Assistants.UnitTests/Extensions/KernelExtensionTests.cs b/dotnet/src/Experimental/Assistants.UnitTests/Extensions/KernelExtensionTests.cs index a28b7b2757f9..3ed811ff8dd3 100644 --- a/dotnet/src/Experimental/Assistants.UnitTests/Extensions/KernelExtensionTests.cs +++ b/dotnet/src/Experimental/Assistants.UnitTests/Extensions/KernelExtensionTests.cs @@ -18,7 +18,7 @@ public static void InvokeTwoPartTool() //Arrange var function = KernelFunctionFactory.CreateFromMethod(() => { }, functionName: "Bogus"); - var kernel = KernelBuilder.Create(); + var kernel = new Kernel(); kernel.Plugins.Add(new KernelPlugin("Fake", new[] { function })); //Act @@ -35,7 +35,7 @@ public static void InvokeTwoPartTool() public static void InvokeInvalidSinglePartTool(string toolName) { //Arrange - var kernel = KernelBuilder.Create(); + var kernel = new Kernel(); //Act & Assert Assert.Throws(() => kernel.GetAssistantTool(toolName)); diff --git a/dotnet/src/Experimental/Assistants.UnitTests/Integration/RunHarness.cs b/dotnet/src/Experimental/Assistants.UnitTests/Integration/RunHarness.cs index d57d6457187d..ad19452dff5c 100644 --- a/dotnet/src/Experimental/Assistants.UnitTests/Integration/RunHarness.cs +++ b/dotnet/src/Experimental/Assistants.UnitTests/Integration/RunHarness.cs @@ -88,9 +88,7 @@ await this.ChatAsync( [Fact(Skip = SkipReason)] public async Task VerifyFunctionLifecycleAsync() { - var kernel = new KernelBuilder().Build(); - - var gamePlugin = kernel.ImportPluginFromObject(new GuessingGame(), nameof(GuessingGame)); + var gamePlugin = KernelPluginFactory.CreateFromObject(); var assistant = await AssistantBuilder.FromTemplateAsync( diff --git a/dotnet/src/Experimental/Assistants.UnitTests/Integration/ThreadHarness.cs b/dotnet/src/Experimental/Assistants.UnitTests/Integration/ThreadHarness.cs index e98aa3d153f2..fd09e7b33726 100644 --- a/dotnet/src/Experimental/Assistants.UnitTests/Integration/ThreadHarness.cs +++ b/dotnet/src/Experimental/Assistants.UnitTests/Integration/ThreadHarness.cs @@ -46,7 +46,7 @@ public async Task VerifyThreadLifecycleAsync() { var assistant = await new AssistantBuilder() - .WithOpenAIChatCompletionService(TestConfig.SupportedGpt35TurboModel, TestConfig.OpenAIApiKey) + .AddOpenAIChatCompletion(TestConfig.SupportedGpt35TurboModel, TestConfig.OpenAIApiKey) .WithName("DeleteMe") .BuildAsync() .ConfigureAwait(true); diff --git a/dotnet/src/Experimental/Assistants/AssistantBuilder.Static.cs b/dotnet/src/Experimental/Assistants/AssistantBuilder.Static.cs index 872da8915b79..643a76b4f26d 100644 --- a/dotnet/src/Experimental/Assistants/AssistantBuilder.Static.cs +++ b/dotnet/src/Experimental/Assistants/AssistantBuilder.Static.cs @@ -40,7 +40,7 @@ public static async Task FromDefinitionAsync( return await new AssistantBuilder() - .WithOpenAIChatCompletionService(model, apiKey) + .AddOpenAIChatCompletion(model, apiKey) .WithInstructions(assistantKernelModel.Instructions.Trim()) .WithName(assistantKernelModel.Name.Trim()) .WithDescription(assistantKernelModel.Description.Trim()) @@ -88,7 +88,7 @@ public static async Task NewAsync( { return await new AssistantBuilder() - .WithOpenAIChatCompletionService(model, apiKey) + .AddOpenAIChatCompletion(model, apiKey) .WithInstructions(instructions) .WithName(name) .WithDescription(description) diff --git a/dotnet/src/Experimental/Assistants/AssistantBuilder.cs b/dotnet/src/Experimental/Assistants/AssistantBuilder.cs index 1fd61dbde521..43218e169413 100644 --- a/dotnet/src/Experimental/Assistants/AssistantBuilder.cs +++ b/dotnet/src/Experimental/Assistants/AssistantBuilder.cs @@ -61,7 +61,7 @@ await Assistant.CreateAsync( /// Define the OpenAI chat completion service (required). /// /// instance for fluid expression. - public AssistantBuilder WithOpenAIChatCompletionService(string model, string apiKey) + public AssistantBuilder AddOpenAIChatCompletion(string model, string apiKey) { this._apiKey = apiKey; this._model.Model = model; diff --git a/dotnet/src/Experimental/Assistants/Internal/Assistant.cs b/dotnet/src/Experimental/Assistants/Internal/Assistant.cs index 5be3d94c5541..f91c42a3c0ca 100644 --- a/dotnet/src/Experimental/Assistants/Internal/Assistant.cs +++ b/dotnet/src/Experimental/Assistants/Internal/Assistant.cs @@ -6,13 +6,12 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel.AI.ChatCompletion; using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.ChatCompletion; using Microsoft.SemanticKernel.Experimental.Assistants.Extensions; using Microsoft.SemanticKernel.Experimental.Assistants.Models; -using Microsoft.SemanticKernel.Http; -using Microsoft.SemanticKernel.Services; namespace Microsoft.SemanticKernel.Experimental.Assistants.Internal; @@ -90,15 +89,10 @@ internal Assistant( this._model = model; this._restContext = restContext; - var services = new AIServiceCollection(); - services.SetService(chatService); - services.SetService(chatService); - this.Kernel = - new Kernel( - services.Build(), - plugins, - httpHandlerFactory: NullHttpHandlerFactory.Instance, - loggerFactory: null); + var services = new ServiceCollection(); + services.AddSingleton(chatService); + services.AddSingleton(chatService); + this.Kernel = new Kernel(services.BuildServiceProvider(), plugins is not null ? new KernelPluginCollection(plugins) : null); } /// diff --git a/dotnet/src/Experimental/Orchestration.Flow.IntegrationTests/CollectEmailPlugin.cs b/dotnet/src/Experimental/Orchestration.Flow.IntegrationTests/CollectEmailPlugin.cs index 16e760700298..d8764e312281 100644 --- a/dotnet/src/Experimental/Orchestration.Flow.IntegrationTests/CollectEmailPlugin.cs +++ b/dotnet/src/Experimental/Orchestration.Flow.IntegrationTests/CollectEmailPlugin.cs @@ -9,7 +9,6 @@ using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.AI.ChatCompletion; using Microsoft.SemanticKernel.Connectors.AI.OpenAI; -using Microsoft.SemanticKernel.Experimental.Orchestration; using Microsoft.SemanticKernel.Orchestration; #pragma warning disable SKEXP0001 diff --git a/dotnet/src/Experimental/Orchestration.Flow.IntegrationTests/Experimental.Orchestration.Flow.IntegrationTests.csproj b/dotnet/src/Experimental/Orchestration.Flow.IntegrationTests/Experimental.Orchestration.Flow.IntegrationTests.csproj index fe7cf8bd868d..f0876060a9ce 100644 --- a/dotnet/src/Experimental/Orchestration.Flow.IntegrationTests/Experimental.Orchestration.Flow.IntegrationTests.csproj +++ b/dotnet/src/Experimental/Orchestration.Flow.IntegrationTests/Experimental.Orchestration.Flow.IntegrationTests.csproj @@ -30,10 +30,8 @@ - - diff --git a/dotnet/src/Experimental/Orchestration.Flow.IntegrationTests/FlowOrchestratorTests.cs b/dotnet/src/Experimental/Orchestration.Flow.IntegrationTests/FlowOrchestratorTests.cs index 6c9093319187..377fdaa0147d 100644 --- a/dotnet/src/Experimental/Orchestration.Flow.IntegrationTests/FlowOrchestratorTests.cs +++ b/dotnet/src/Experimental/Orchestration.Flow.IntegrationTests/FlowOrchestratorTests.cs @@ -6,7 +6,6 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Experimental.Orchestration; using Microsoft.SemanticKernel.Plugins.Memory; using Microsoft.SemanticKernel.Plugins.Web; using Microsoft.SemanticKernel.Plugins.Web.Bing; @@ -106,16 +105,12 @@ private KernelBuilder InitializeKernelBuilder() AzureOpenAIConfiguration? azureOpenAIConfiguration = this._configuration.GetSection("AzureOpenAI").Get(); Assert.NotNull(azureOpenAIConfiguration); - var builder = new KernelBuilder() + return new KernelBuilder() .WithLoggerFactory(this._logger) - .WithRetryBasic() - .WithAzureOpenAIChatCompletionService( + .WithAzureOpenAIChatCompletion( deploymentName: azureOpenAIConfiguration.ChatDeploymentName!, endpoint: azureOpenAIConfiguration.Endpoint, - apiKey: azureOpenAIConfiguration.ApiKey, - alsoAsTextCompletion: true); - - return builder; + apiKey: azureOpenAIConfiguration.ApiKey); } private readonly ILoggerFactory _logger; diff --git a/dotnet/src/Experimental/Orchestration.Flow/Execution/FlowExecutor.cs b/dotnet/src/Experimental/Orchestration.Flow/Execution/FlowExecutor.cs index 28b9b94879b8..fce6ad0b39c2 100644 --- a/dotnet/src/Experimental/Orchestration.Flow/Execution/FlowExecutor.cs +++ b/dotnet/src/Experimental/Orchestration.Flow/Execution/FlowExecutor.cs @@ -35,7 +35,7 @@ internal class FlowExecutor : IFlowExecutor /// /// The logger /// - private readonly ILogger _logger; + private readonly ILogger _logger; /// /// The global plugin collection @@ -100,7 +100,7 @@ internal FlowExecutor(KernelBuilder kernelBuilder, IFlowStatusProvider statusPro this._kernelBuilder = kernelBuilder; this._systemKernel = kernelBuilder.Build(); - this._logger = this._systemKernel.LoggerFactory.CreateLogger(); + this._logger = this._systemKernel.GetService().CreateLogger(typeof(FlowExecutor)); this._config = config ?? new FlowOrchestratorConfig(); this._flowStatusProvider = statusProvider; @@ -687,7 +687,7 @@ private async Task ExecuteStepAsync(FlowStep step, string sess private static KernelFunction CreateSemanticFunction(Kernel kernel, string functionName, string promptTemplate, PromptTemplateConfig config) { - var factory = new KernelPromptTemplateFactory(kernel.LoggerFactory); + var factory = new KernelPromptTemplateFactory(kernel.GetService()); var template = factory.Create(promptTemplate, config); return kernel.CreateFunctionFromPrompt(template, config, functionName); } diff --git a/dotnet/src/Experimental/Orchestration.Flow/Execution/ReActEngine.cs b/dotnet/src/Experimental/Orchestration.Flow/Execution/ReActEngine.cs index b6b1a377f0b3..c833276f2e1e 100644 --- a/dotnet/src/Experimental/Orchestration.Flow/Execution/ReActEngine.cs +++ b/dotnet/src/Experimental/Orchestration.Flow/Execution/ReActEngine.cs @@ -235,7 +235,7 @@ private ContextVariables CreateActionContextVariables(Dictionary private KernelFunction ImportSemanticFunction(Kernel kernel, string functionName, string promptTemplate, PromptTemplateConfig config) { - var factory = new KernelPromptTemplateFactory(kernel.LoggerFactory); + var factory = new KernelPromptTemplateFactory(kernel.GetService()); var template = factory.Create(promptTemplate, config); var plugin = new KernelPlugin(RestrictedPluginName); diff --git a/dotnet/src/Extensions/Extensions.UnitTests/Extensions.UnitTests.csproj b/dotnet/src/Extensions/Extensions.UnitTests/Extensions.UnitTests.csproj index 356fa7ff5bea..661a9d4c8cb7 100644 --- a/dotnet/src/Extensions/Extensions.UnitTests/Extensions.UnitTests.csproj +++ b/dotnet/src/Extensions/Extensions.UnitTests/Extensions.UnitTests.csproj @@ -25,8 +25,6 @@ - - \ No newline at end of file diff --git a/dotnet/src/Extensions/Extensions.UnitTests/Reliability/Basic/BasicHttpRetryHandlerTests.cs b/dotnet/src/Extensions/Extensions.UnitTests/Reliability/Basic/BasicHttpRetryHandlerTests.cs deleted file mode 100644 index 94751de1b100..000000000000 --- a/dotnet/src/Extensions/Extensions.UnitTests/Reliability/Basic/BasicHttpRetryHandlerTests.cs +++ /dev/null @@ -1,678 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.SemanticKernel.Reliability.Basic; -using Moq; -using Moq.Protected; -using Xunit; - -namespace SemanticKernel.Extensions.UnitTests.Reliability.Basic; - -public class BasicHttpRetryHandlerTests -{ - [Theory] - [InlineData(HttpStatusCode.RequestTimeout)] - [InlineData(HttpStatusCode.ServiceUnavailable)] - [InlineData(HttpStatusCode.GatewayTimeout)] - [InlineData(HttpStatusCode.TooManyRequests)] - public async Task NoMaxRetryCountCallsOnceForStatusAsync(HttpStatusCode statusCode) - { - // Arrange - using var retry = new BasicHttpRetryHandler(new BasicRetryConfig() { MaxRetryCount = 0 }, NullLoggerFactory.Instance); - using var mockResponse = new HttpResponseMessage(statusCode); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal(statusCode, response.StatusCode); - } - - [Theory] - [InlineData(HttpStatusCode.RequestTimeout)] - [InlineData(HttpStatusCode.ServiceUnavailable)] - [InlineData(HttpStatusCode.GatewayTimeout)] - [InlineData(HttpStatusCode.TooManyRequests)] - public async Task ItRetriesOnceOnRetryableStatusAsync(HttpStatusCode statusCode) - { - // Arrange - using var retry = ConfigureRetryHandler(); - using var mockResponse = new HttpResponseMessage(statusCode); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Exactly(2), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal(statusCode, response.StatusCode); - } - - [Theory] - [InlineData(typeof(HttpRequestException))] - public async Task ItRetriesOnceOnRetryableExceptionAsync(Type exceptionType) - { - // Arrange - using var retry = ConfigureRetryHandler(); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(exceptionType); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await Assert.ThrowsAsync(exceptionType, - async () => await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None)); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Exactly(2), ItExpr.IsAny(), ItExpr.IsAny()); - } - - [Theory] - [InlineData(typeof(HttpRequestException))] - public async Task NoMaxRetryCountCallsOnceForExceptionAsync(Type exceptionType) - { - // Arrange - using var retry = ConfigureRetryHandler(new BasicRetryConfig() { MaxRetryCount = 0 }); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(exceptionType); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await Assert.ThrowsAsync(exceptionType, - async () => await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None)); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); - } - - [Theory] - [InlineData(HttpStatusCode.RequestTimeout)] - [InlineData(HttpStatusCode.ServiceUnavailable)] - [InlineData(HttpStatusCode.GatewayTimeout)] - [InlineData(HttpStatusCode.TooManyRequests)] - public async Task ItRetriesOnceOnTransientStatusWithExponentialBackoffAsync(HttpStatusCode statusCode) - { - // Arrange - using var retry = ConfigureRetryHandler(new BasicRetryConfig() { UseExponentialBackoff = true }); - using var mockResponse = new HttpResponseMessage(statusCode); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Exactly(2), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal(statusCode, response.StatusCode); - } - - [Theory] - [InlineData(typeof(HttpRequestException))] - public async Task ItRetriesOnceOnRetryableExceptionWithExponentialBackoffAsync(Type exceptionType) - { - // Arrange - using var retry = ConfigureRetryHandler(new BasicRetryConfig() { UseExponentialBackoff = true }); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(exceptionType); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await Assert.ThrowsAsync(exceptionType, - async () => await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None)); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Exactly(2), ItExpr.IsAny(), ItExpr.IsAny()); - } - - [Theory] - [InlineData(HttpStatusCode.RequestTimeout)] - [InlineData(HttpStatusCode.ServiceUnavailable)] - [InlineData(HttpStatusCode.GatewayTimeout)] - public async Task ItRetriesExponentiallyWithExponentialBackoffAsync(HttpStatusCode statusCode) - { - // Arrange - var currentTime = DateTimeOffset.UtcNow; - var mockTimeProvider = new Mock(); - var mockDelayProvider = new Mock(); - mockTimeProvider.SetupSequence(x => x.GetCurrentTime()) - .Returns(() => currentTime) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(5)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(510)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(1015)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(1520)); - using var retry = ConfigureRetryHandler(new BasicRetryConfig() - { - UseExponentialBackoff = true, MaxRetryCount = 3, - MinRetryDelay = TimeSpan.FromMilliseconds(500) - }, mockTimeProvider, mockDelayProvider); - using var mockResponse = new HttpResponseMessage(statusCode); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Exactly(4), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal(statusCode, response.StatusCode); - mockTimeProvider.Verify(x => x.GetCurrentTime(), Times.Exactly(4)); - mockDelayProvider.Verify(x => x.DelayAsync(TimeSpan.FromMilliseconds(500), It.IsAny()), Times.Once); - mockDelayProvider.Verify(x => x.DelayAsync(TimeSpan.FromMilliseconds(1000), It.IsAny()), Times.Once); - mockDelayProvider.Verify(x => x.DelayAsync(TimeSpan.FromMilliseconds(2000), It.IsAny()), Times.Once); - } - - [Theory] - [InlineData(HttpStatusCode.RequestTimeout)] - [InlineData(HttpStatusCode.ServiceUnavailable)] - [InlineData(HttpStatusCode.GatewayTimeout)] - public async Task ItRetriesOnceOnTransientStatusCodeWithRetryValueAsync(HttpStatusCode statusCode) - { - // Arrange - using var retry = ConfigureRetryHandler(new BasicRetryConfig(), null); - using var mockResponse = new HttpResponseMessage() - { - StatusCode = statusCode, - Headers = { RetryAfter = new RetryConditionHeaderValue(new TimeSpan(0, 0, 0, 1)) }, - }; - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - using var testContent = new StringContent("test"); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Exactly(2), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal(statusCode, response.StatusCode); - Assert.Equal(new TimeSpan(0, 0, 0, 1), response.Headers.RetryAfter?.Delta); - } - - [Theory] - [InlineData(HttpStatusCode.RequestTimeout)] - [InlineData(HttpStatusCode.ServiceUnavailable)] - [InlineData(HttpStatusCode.GatewayTimeout)] - public async Task ItRetriesStatusCustomCountAsync(HttpStatusCode expectedStatus) - { - // Arrange - using var retry = ConfigureRetryHandler(new BasicRetryConfig() { MaxRetryCount = 3 }, null); - using var mockResponse = new HttpResponseMessage(expectedStatus); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Exactly(4), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal(expectedStatus, response.StatusCode); - } - - [Theory] - [InlineData(typeof(HttpRequestException))] - public async Task ItRetriesExceptionsCustomCountAsync(Type expectedException) - { - // Arrange - using var retry = ConfigureRetryHandler(new BasicRetryConfig() { MaxRetryCount = 3 }, null); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(expectedException); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await Assert.ThrowsAsync(expectedException, - async () => await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None)); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Exactly(4), ItExpr.IsAny(), ItExpr.IsAny()); - } - - [Fact] - public async Task NoExceptionNoRetryAsync() - { - // Arrange - using var retry = ConfigureRetryHandler(new BasicRetryConfig() { MaxRetryCount = 3 }, null); - using var mockResponse = new HttpResponseMessage(HttpStatusCode.OK); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Exactly(1), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - [Fact] - public async Task ItDoesNotExecuteOnCancellationTokenAsync() - { - // Arrange - using var retry = ConfigureRetryHandler(new BasicRetryConfig() { MaxRetryCount = 3 }, null); - using var mockResponse = new HttpResponseMessage(HttpStatusCode.OK); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - var cancellationToken = new CancellationToken(true); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await Assert.ThrowsAsync(async () => - await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, cancellationToken)); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Never(), ItExpr.IsAny(), ItExpr.IsAny()); - } - - [Fact] - public async Task ItDoestExecuteOnFalseCancellationTokenAsync() - { - // Arrange - using var retry = ConfigureRetryHandler(new BasicRetryConfig() { MaxRetryCount = 3 }, null); - using var mockResponse = new HttpResponseMessage(HttpStatusCode.OK); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - var cancellationToken = new CancellationToken(false); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, cancellationToken); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Exactly(1), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - [Fact] - public async Task ItRetriesWithMinRetryDelayAsync() - { - var BasicRetryConfig = new BasicRetryConfig - { - MinRetryDelay = TimeSpan.FromMilliseconds(500) - }; - - var mockDelayProvider = new Mock(); - var mockTimeProvider = new Mock(); - - var currentTime = DateTimeOffset.UtcNow; - - mockTimeProvider.SetupSequence(x => x.GetCurrentTime()) - .Returns(() => currentTime) - .Returns(() => currentTime.AddMilliseconds(5)) - .Returns(() => currentTime.AddMilliseconds(510)); - - mockDelayProvider.Setup(x => x.DelayAsync(It.IsAny(), It.IsAny())) - .Returns(() => Task.CompletedTask); - - using var retry = ConfigureRetryHandler(BasicRetryConfig, mockTimeProvider, mockDelayProvider); - using var mockResponse = new HttpResponseMessage(HttpStatusCode.TooManyRequests); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None); - - // Assert - mockTimeProvider.Verify(x => x.GetCurrentTime(), Times.Exactly(2)); - mockDelayProvider.Verify(x => x.DelayAsync(TimeSpan.FromMilliseconds(500), It.IsAny()), Times.Once); - mockHandler.Protected() - .Verify>("SendAsync", Times.Exactly(2), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal(HttpStatusCode.TooManyRequests, response.StatusCode); - } - - [Fact] - public async Task ItRetriesWithMaxRetryDelayAsync() - { - var BasicRetryConfig = new BasicRetryConfig - { - MinRetryDelay = TimeSpan.FromMilliseconds(1), - MaxRetryDelay = TimeSpan.FromMilliseconds(500) - }; - - var mockDelayProvider = new Mock(); - var mockTimeProvider = new Mock(); - - var currentTime = DateTimeOffset.UtcNow; - - mockTimeProvider.SetupSequence(x => x.GetCurrentTime()) - .Returns(() => currentTime) - .Returns(() => currentTime.AddMilliseconds(5)) - .Returns(() => currentTime.AddMilliseconds(505)); - - mockDelayProvider.Setup(x => x.DelayAsync(It.IsAny(), It.IsAny())) - .Returns(() => Task.CompletedTask); - - using var retry = ConfigureRetryHandler(BasicRetryConfig, mockTimeProvider, mockDelayProvider); - using var mockResponse = new HttpResponseMessage(HttpStatusCode.TooManyRequests) - { - Headers = { RetryAfter = new RetryConditionHeaderValue(TimeSpan.FromMilliseconds(2000)) } - }; - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None); - - // Assert - mockTimeProvider.Verify(x => x.GetCurrentTime(), Times.Exactly(2)); - mockDelayProvider.Verify(x => x.DelayAsync(TimeSpan.FromMilliseconds(500), It.IsAny()), Times.Once); - mockHandler.Protected() - .Verify>("SendAsync", Times.Exactly(2), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal(HttpStatusCode.TooManyRequests, response.StatusCode); - Assert.Equal(TimeSpan.FromMilliseconds(2000), response.Headers.RetryAfter?.Delta); - } - - [Theory] - [InlineData(HttpStatusCode.TooManyRequests)] - [InlineData(HttpStatusCode.ServiceUnavailable)] - [InlineData(HttpStatusCode.GatewayTimeout)] - [InlineData(HttpStatusCode.RequestTimeout)] - public async Task ItRetriesWithMaxTotalDelayAsync(HttpStatusCode statusCode) - { - // Arrange - var BasicRetryConfig = new BasicRetryConfig - { - MaxRetryCount = 5, - MinRetryDelay = TimeSpan.FromMilliseconds(50), - MaxRetryDelay = TimeSpan.FromMilliseconds(50), - MaxTotalRetryTime = TimeSpan.FromMilliseconds(350) - }; - - var mockDelayProvider = new Mock(); - var mockTimeProvider = new Mock(); - - var currentTime = DateTimeOffset.UtcNow; - mockTimeProvider.SetupSequence(x => x.GetCurrentTime()) - .Returns(() => currentTime) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(5)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(55)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(110)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(165)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(220)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(275)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(330)); - - using var retry = ConfigureRetryHandler(BasicRetryConfig, mockTimeProvider, mockDelayProvider); - - using var mockResponse = new HttpResponseMessage(statusCode); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None); - - // Assert - mockTimeProvider.Verify(x => x.GetCurrentTime(), Times.Exactly(6)); - mockDelayProvider.Verify(x => x.DelayAsync(TimeSpan.FromMilliseconds(50), It.IsAny()), Times.Exactly(5)); - mockHandler.Protected() - .Verify>("SendAsync", Times.Exactly(6), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal(statusCode, response.StatusCode); - } - - [Fact] - public async Task ItRetriesFewerWithMaxTotalDelayAsync() - { - // Arrange - var BasicRetryConfig = new BasicRetryConfig - { - MaxRetryCount = 5, - MinRetryDelay = TimeSpan.FromMilliseconds(50), - MaxRetryDelay = TimeSpan.FromMilliseconds(50), - MaxTotalRetryTime = TimeSpan.FromMilliseconds(100) - }; - - var mockDelayProvider = new Mock(); - var mockTimeProvider = new Mock(); - - var currentTime = DateTimeOffset.UtcNow; - mockTimeProvider.SetupSequence(x => x.GetCurrentTime()) - .Returns(() => currentTime) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(5)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(55)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(110)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(165)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(220)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(275)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(330)); - - using var retry = ConfigureRetryHandler(BasicRetryConfig, mockTimeProvider, mockDelayProvider); - - using var mockResponse = new HttpResponseMessage(HttpStatusCode.TooManyRequests); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None); - - // Assert - mockTimeProvider.Verify(x => x.GetCurrentTime(), Times.Exactly(4)); // 1 initial, 2 retries, 1 for logging time taken. - mockDelayProvider.Verify(x => x.DelayAsync(TimeSpan.FromMilliseconds(50), It.IsAny()), Times.Exactly(1)); - mockHandler.Protected() - .Verify>("SendAsync", Times.Exactly(2), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal(HttpStatusCode.TooManyRequests, response.StatusCode); - } - - [Fact] - public async Task ItRetriesFewerWithMaxTotalDelayOnExceptionAsync() - { - // Arrange - var BasicRetryConfig = new BasicRetryConfig - { - MaxRetryCount = 5, - MinRetryDelay = TimeSpan.FromMilliseconds(50), - MaxRetryDelay = TimeSpan.FromMilliseconds(50), - MaxTotalRetryTime = TimeSpan.FromMilliseconds(100) - }; - - var mockDelayProvider = new Mock(); - var mockTimeProvider = new Mock(); - - var currentTime = DateTimeOffset.UtcNow; - mockTimeProvider.SetupSequence(x => x.GetCurrentTime()) - .Returns(() => currentTime) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(5)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(55)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(110)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(165)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(220)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(275)) - .Returns(() => currentTime + TimeSpan.FromMilliseconds(330)); - - using var retry = ConfigureRetryHandler(BasicRetryConfig, mockTimeProvider, mockDelayProvider); - var mockHandler = GetHttpMessageHandlerMock(typeof(HttpRequestException)); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - await Assert.ThrowsAsync(() => httpClient.GetAsync(new Uri("https://www.microsoft.com"), CancellationToken.None)); - - // Assert - mockTimeProvider.Verify(x => x.GetCurrentTime(), Times.Exactly(4)); // 1 initial, 2 retries, 1 for logging time taken. - mockDelayProvider.Verify(x => x.DelayAsync(TimeSpan.FromMilliseconds(50), It.IsAny()), Times.Exactly(1)); - mockHandler.Protected() - .Verify>("SendAsync", Times.Exactly(2), ItExpr.IsAny(), ItExpr.IsAny()); - } - - [Fact] - public async Task ItRetriesOnRetryableStatusCodesAsync() - { - // Arrange - var config = new BasicRetryConfig() { RetryableStatusCodes = new List { HttpStatusCode.Unauthorized } }; - using var retry = ConfigureRetryHandler(config); - using var mockResponse = new HttpResponseMessage(HttpStatusCode.Unauthorized); - - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Exactly(2), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); - } - - [Fact] - public async Task ItDoesNotRetryOnNonRetryableStatusCodesAsync() - { - // Arrange - var config = new BasicRetryConfig() { RetryableStatusCodes = new List { HttpStatusCode.Unauthorized } }; - using var retry = ConfigureRetryHandler(config); - using var mockResponse = new HttpResponseMessage(HttpStatusCode.TooManyRequests); - - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal(HttpStatusCode.TooManyRequests, response.StatusCode); - } - - [Fact] - public async Task ItRetriesOnRetryableExceptionsAsync() - { - // Arrange - var config = new BasicRetryConfig() { RetryableExceptionTypes = new List { typeof(InvalidOperationException) } }; - using var retry = ConfigureRetryHandler(config); - - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(typeof(InvalidOperationException)); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - await Assert.ThrowsAsync(async () => - await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None)); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Exactly(2), ItExpr.IsAny(), ItExpr.IsAny()); - } - - [Fact] - public async Task ItDoesNotRetryOnNonRetryableExceptionsAsync() - { - // Arrange - var config = new BasicRetryConfig() { RetryableExceptionTypes = new List { typeof(InvalidOperationException) } }; - using var retry = ConfigureRetryHandler(config); - - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(typeof(ArgumentException)); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - await Assert.ThrowsAsync(async () => - await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None)); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); - } - - private static BasicHttpRetryHandler ConfigureRetryHandler(BasicRetryConfig? config = null, - Mock? timeProvider = null, Mock? delayProvider = null) - { - delayProvider ??= new Mock(); - timeProvider ??= new Mock(); - - var retry = new BasicHttpRetryHandler(config ?? new BasicRetryConfig(), null, delayProvider.Object, timeProvider.Object); - return retry; - } - - private static Mock GetHttpMessageHandlerMock(HttpResponseMessage mockResponse) - { - var mockHandler = new Mock(); - mockHandler.Protected() - .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) - .ReturnsAsync(mockResponse); - return mockHandler; - } - - private static Mock GetHttpMessageHandlerMock(Type exceptionType) - { - var mockHandler = new Mock(); - mockHandler.Protected() - .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) - .ThrowsAsync(Activator.CreateInstance(exceptionType) as Exception); - return mockHandler; - } -} diff --git a/dotnet/src/Extensions/Extensions.UnitTests/Reliability/Basic/BasicRetryConfigTests.cs b/dotnet/src/Extensions/Extensions.UnitTests/Reliability/Basic/BasicRetryConfigTests.cs deleted file mode 100644 index f210722cdf34..000000000000 --- a/dotnet/src/Extensions/Extensions.UnitTests/Reliability/Basic/BasicRetryConfigTests.cs +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Threading.Tasks; -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Reliability.Basic; -using Xunit; - -namespace SemanticKernel.Extensions.UnitTests.Reliability.Basic; - -/// -/// Unit tests of . -/// -public class BasicRetryConfigTests -{ - [Fact] - public async Task NegativeMaxRetryCountThrowsAsync() - { - // Act - await Assert.ThrowsAsync(() => - { - var BasicRetryConfig = new BasicRetryConfig() { MaxRetryCount = -1 }; - return Task.CompletedTask; - }); - } - - [Fact] - public void SetDefaultBasicRetryConfig() - { - // Arrange - var builder = new KernelBuilder(); - var basicRetryConfig = new BasicRetryConfig() { MaxRetryCount = 3 }; - builder.WithRetryBasic(basicRetryConfig); - - // Act - var kernel = builder.Build(); - - // Assert - Assert.IsType(kernel.HttpHandlerFactory); - var httpHandlerFactory = kernel.HttpHandlerFactory as BasicHttpRetryHandlerFactory; - Assert.NotNull(httpHandlerFactory); - Assert.Equal(basicRetryConfig, httpHandlerFactory.Config); - } - - [Fact] - public void SetDefaultBasicRetryConfigToDefaultIfNotSet() - { - // Arrange - var retryConfig = new BasicRetryConfig(); - var builder = new KernelBuilder(); - builder.WithRetryBasic(retryConfig); - - // Act - var kernel = builder.Build(); - - // Assert - Assert.IsType(kernel.HttpHandlerFactory); - var httpHandlerFactory = kernel.HttpHandlerFactory as BasicHttpRetryHandlerFactory; - Assert.NotNull(httpHandlerFactory); - Assert.Equal(retryConfig.MaxRetryCount, httpHandlerFactory.Config.MaxRetryCount); - Assert.Equal(retryConfig.MaxRetryDelay, httpHandlerFactory.Config.MaxRetryDelay); - Assert.Equal(retryConfig.MinRetryDelay, httpHandlerFactory.Config.MinRetryDelay); - Assert.Equal(retryConfig.MaxTotalRetryTime, httpHandlerFactory.Config.MaxTotalRetryTime); - Assert.Equal(retryConfig.UseExponentialBackoff, httpHandlerFactory.Config.UseExponentialBackoff); - Assert.Equal(retryConfig.RetryableStatusCodes, httpHandlerFactory.Config.RetryableStatusCodes); - Assert.Equal(retryConfig.RetryableExceptionTypes, httpHandlerFactory.Config.RetryableExceptionTypes); - } -} diff --git a/dotnet/src/Extensions/Extensions.UnitTests/Reliability/Polly/PollyHttpRetryHandlerTests.cs b/dotnet/src/Extensions/Extensions.UnitTests/Reliability/Polly/PollyHttpRetryHandlerTests.cs deleted file mode 100644 index a42f5f052959..000000000000 --- a/dotnet/src/Extensions/Extensions.UnitTests/Reliability/Polly/PollyHttpRetryHandlerTests.cs +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.SemanticKernel.Reliability.Polly; -using Moq; -using Moq.Protected; -using Polly; -using Polly.Utilities; -using Xunit; - -namespace SemanticKernel.Extensions.UnitTests.Reliability.Polly; - -public sealed class PollyHttpRetryHandlerTests : IDisposable -{ - public PollyHttpRetryHandlerTests() - { - SystemClock.SleepAsync = (_, _) => Task.CompletedTask; - SystemClock.Sleep = (_, _) => { }; - } - - public void Dispose() - { - SystemClock.Reset(); - } - - [Theory] - [InlineData(HttpStatusCode.RequestTimeout)] - [InlineData(HttpStatusCode.ServiceUnavailable)] - [InlineData(HttpStatusCode.GatewayTimeout)] - [InlineData(HttpStatusCode.TooManyRequests)] - public async Task CustomPolicyNoOpShouldNotAvoidSendRequestsAsync(HttpStatusCode statusCode) - { - // Arrange - var asyncPolicy = Policy.NoOpAsync(); - var (mockLoggerFactory, mockLogger) = GetLoggerMocks(); - using var retry = new PollyHttpRetryHandler(asyncPolicy); - using var mockResponse = new HttpResponseMessage(statusCode); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal(statusCode, response.StatusCode); - } - - [Theory] - [InlineData(HttpStatusCode.RequestTimeout)] - [InlineData(HttpStatusCode.ServiceUnavailable)] - [InlineData(HttpStatusCode.GatewayTimeout)] - [InlineData(HttpStatusCode.TooManyRequests)] - public async Task CustomPolicyStatusDontMatchNeverTriggersAsync(HttpStatusCode statusCode) - { - // Arrange - var asyncPolicy = Policy - .HandleResult(result => result.StatusCode != statusCode) - .WaitAndRetryAsync( - retryCount: 1, - sleepDurationProvider: (retryTimes) => TimeSpan.FromMilliseconds(10)); - - var (mockLoggerFactory, mockLogger) = GetLoggerMocks(); - using var retry = new PollyHttpRetryHandler(asyncPolicy); - using var mockResponse = new HttpResponseMessage(statusCode); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal(statusCode, response.StatusCode); - } - - [Theory] - [InlineData(HttpStatusCode.RequestTimeout, HttpStatusCode.TooManyRequests)] - [InlineData(HttpStatusCode.ServiceUnavailable, HttpStatusCode.TooManyRequests)] - [InlineData(HttpStatusCode.GatewayTimeout, HttpStatusCode.TooManyRequests)] - [InlineData(HttpStatusCode.TooManyRequests, HttpStatusCode.TooManyRequests)] - public async Task CustomPolicyRetryStatusShouldTriggerRetrialsAsync(HttpStatusCode statusCode, HttpStatusCode retryStatusCode) - { - // Arrange - var retryCount = 3; - var asyncPolicy = Policy - .HandleResult(result => result.StatusCode == retryStatusCode) - .WaitAndRetryAsync( - retryCount, - (retryNumber) => TimeSpan.FromMilliseconds(10)); - - var (mockLoggerFactory, mockLogger) = GetLoggerMocks(); - using var retry = new PollyHttpRetryHandler(asyncPolicy); - using var mockResponse = new HttpResponseMessage(statusCode); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None); - - // Assert - var expectedSendAsyncTimes = (statusCode == retryStatusCode) - ? retryCount + 1 - : 1; - - mockHandler.Protected() - .Verify>("SendAsync", Times.Exactly(expectedSendAsyncTimes), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal(statusCode, response.StatusCode); - } - - [Theory] - [InlineData(typeof(ApplicationException), typeof(HttpRequestException))] - [InlineData(typeof(HttpRequestException), typeof(HttpRequestException))] - public async Task CustomPolicyRetryExceptionsShouldTriggerRetrialsAsync(Type exceptionType, Type retryExceptionType) - { - // Arrange - var retryCount = 1; - var asyncPolicy = Policy.Handle(exception => exception.GetType() == retryExceptionType) - .WaitAndRetryAsync( - retryCount, - (retryNumber) => TimeSpan.FromMilliseconds(10)); - - var (mockLoggerFactory, mockLogger) = GetLoggerMocks(); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(exceptionType); - using var retry = new PollyHttpRetryHandler(asyncPolicy); - - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await Assert.ThrowsAsync(exceptionType, - async () => await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None)); - - // Assert - var expectedSendAsyncTimes = (exceptionType == retryExceptionType) - ? retryCount + 1 - : 1; - - mockHandler.Protected() - .Verify>("SendAsync", Times.Exactly(expectedSendAsyncTimes), ItExpr.IsAny(), ItExpr.IsAny()); - } - - private static (Mock, Mock) GetLoggerMocks() - { - var mockLoggerFactory = new Mock(); - var mockLogger = new Mock(); - mockLoggerFactory.Setup(x => x.CreateLogger(It.IsAny())).Returns(mockLogger.Object); - - return (mockLoggerFactory, mockLogger); - } - - private static Mock GetHttpMessageHandlerMock(HttpResponseMessage mockResponse) - { - var mockHandler = new Mock(); - mockHandler.Protected() - .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) - .ReturnsAsync(mockResponse); - return mockHandler; - } - - private static Mock GetHttpMessageHandlerMock(Type exceptionType) - { - var mockHandler = new Mock(); - mockHandler.Protected() - .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) - .ThrowsAsync(Activator.CreateInstance(exceptionType) as Exception); - return mockHandler; - } -} diff --git a/dotnet/src/Extensions/Extensions.UnitTests/TemplateEngine/Handlebars/HandlebarsPromptTemplateTests.cs b/dotnet/src/Extensions/Extensions.UnitTests/TemplateEngine/Handlebars/HandlebarsPromptTemplateTests.cs index 28824089de8b..565537828fa0 100644 --- a/dotnet/src/Extensions/Extensions.UnitTests/TemplateEngine/Handlebars/HandlebarsPromptTemplateTests.cs +++ b/dotnet/src/Extensions/Extensions.UnitTests/TemplateEngine/Handlebars/HandlebarsPromptTemplateTests.cs @@ -21,7 +21,7 @@ public sealed class HandlebarsPromptTemplateTests public HandlebarsPromptTemplateTests() { this._factory = new HandlebarsPromptTemplateFactory(TestConsoleLogger.LoggerFactory); - this._kernel = new KernelBuilder().Build(); + this._kernel = new Kernel(); this._variables = new ContextVariables(Guid.NewGuid().ToString("X")); } diff --git a/dotnet/src/Extensions/Reliability.Basic/BasicHttpRetryHandler.cs b/dotnet/src/Extensions/Reliability.Basic/BasicHttpRetryHandler.cs deleted file mode 100644 index 9adb71c3d90d..000000000000 --- a/dotnet/src/Extensions/Reliability.Basic/BasicHttpRetryHandler.cs +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; - -namespace Microsoft.SemanticKernel.Reliability.Basic; - -/// -/// Handler that retries HTTP requests based on a . -/// -public sealed class BasicHttpRetryHandler : DelegatingHandler -{ - /// - /// Initializes a new instance of the class. - /// - /// The retry configuration. - /// The to use for logging. If null, no logging will be performed. - internal BasicHttpRetryHandler(BasicRetryConfig? config = null, ILoggerFactory? loggerFactory = null) - : this(config ?? new(), loggerFactory, null, null) - { - } - - internal BasicHttpRetryHandler( - BasicRetryConfig config, - ILoggerFactory? loggerFactory = null, - IDelayProvider? delayProvider = null, - ITimeProvider? timeProvider = null) - { - this._config = config; - this._logger = loggerFactory is not null ? loggerFactory.CreateLogger() : NullLogger.Instance; - this._delayProvider = delayProvider ?? new TaskDelayProvider(); - this._timeProvider = timeProvider ?? new DefaultTimeProvider(); - } - - /// - /// Executes the action with retry logic - /// - /// - /// The request is retried if it throws an exception that is a retryable exception. - /// If the request throws an exception that is not a retryable exception, it is not retried. - /// If the request returns a response with a retryable error code, it is retried. - /// If the request returns a response with a non-retryable error code, it is not retried. - /// If the exception contains a RetryAfter header, the request is retried after the specified delay. - /// If configured to use exponential backoff, the delay is doubled for each retry. - /// - /// The request. - /// The cancellation token. - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - int retryCount = 0; - - var start = this._timeProvider.GetCurrentTime(); - while (true) - { - cancellationToken.ThrowIfCancellationRequested(); - - TimeSpan waitFor; - string reason; - HttpResponseMessage? response = null; - try - { - response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false); - - // If the request does not require a retry then we're done - if (!this.ShouldRetry(response.StatusCode)) - { - return response; - } - - reason = response.StatusCode.ToString(); - - // If the retry count is greater than the max retry count then we'll - // just return - if (retryCount >= this._config.MaxRetryCount) - { - this._logger.LogError( - "Error executing request, max retry count reached. Reason: {0}", reason); - return response; - } - - // If the retry delay is longer than the total timeout, then we'll - // just return - if (!this.HasTimeForRetry(start, retryCount, response, out waitFor)) - { - var timeTaken = this._timeProvider.GetCurrentTime() - start; - this._logger.LogError( - "Error executing request, max total retry time reached. Reason: {0}. Time spent: {1}ms", reason, - timeTaken.TotalMilliseconds); - return response; - } - } - catch (Exception e) when (this.ShouldRetry(e) || this.ShouldRetry(e.InnerException)) - { - reason = e.GetType().ToString(); - if (retryCount >= this._config.MaxRetryCount) - { - this._logger.LogError(e, - "Error executing request, max retry count reached. Reason: {0}", reason); - throw; - } - else if (!this.HasTimeForRetry(start, retryCount, response, out waitFor)) - { - var timeTaken = this._timeProvider.GetCurrentTime() - start; - this._logger.LogError( - "Error executing request, max total retry time reached. Reason: {0}. Time spent: {1}ms", reason, - timeTaken.TotalMilliseconds); - throw; - } - } - - // If the request requires a retry then we'll retry - this._logger.LogWarning( - "Error executing action [attempt {0} of {1}]. Reason: {2}. Will retry after {3}ms", - retryCount + 1, - this._config.MaxRetryCount, - reason, - waitFor.TotalMilliseconds); - - // Increase retryCount - retryCount++; - - response?.Dispose(); - - // Delay - await this._delayProvider.DelayAsync(waitFor, cancellationToken).ConfigureAwait(false); - } - } - - /// - /// Interface for a delay provider, primarily to enable unit testing. - /// - internal interface IDelayProvider - { - Task DelayAsync(TimeSpan delay, CancellationToken cancellationToken); - } - - internal sealed class TaskDelayProvider : IDelayProvider - { - public Task DelayAsync(TimeSpan delay, CancellationToken cancellationToken) - { - return Task.Delay(delay, cancellationToken); - } - } - - /// - /// Interface for a time provider, primarily to enable unit testing. - /// - internal interface ITimeProvider - { - DateTimeOffset GetCurrentTime(); - } - - internal sealed class DefaultTimeProvider : ITimeProvider - { - public DateTimeOffset GetCurrentTime() - { - return DateTimeOffset.UtcNow; - } - } - - private readonly BasicRetryConfig _config; - private readonly ILogger _logger; - private readonly IDelayProvider _delayProvider; - private readonly ITimeProvider _timeProvider; - - /// - /// Get the wait time for the next retry. - /// - /// Current retry count - /// The response message that potentially contains RetryAfter header. - private TimeSpan GetWaitTime(int retryCount, HttpResponseMessage? response) - { - // If the response contains a RetryAfter header, use that value - // Otherwise, use the configured min retry delay - var retryAfter = response?.Headers.RetryAfter?.Date.HasValue == true - ? response?.Headers.RetryAfter?.Date - this._timeProvider.GetCurrentTime() - : (response?.Headers.RetryAfter?.Delta) ?? this._config.MinRetryDelay; - retryAfter ??= this._config.MinRetryDelay; - - // If the retry delay is longer than the max retry delay, use the max retry delay - var timeToWait = retryAfter > this._config.MaxRetryDelay - ? this._config.MaxRetryDelay - : retryAfter < this._config.MinRetryDelay - ? this._config.MinRetryDelay - : retryAfter ?? default; - - // If exponential backoff is enabled, and the server didn't provide a RetryAfter header, double the delay for each retry - if (this._config.UseExponentialBackoff - && response?.Headers.RetryAfter?.Date is null - && response?.Headers.RetryAfter?.Delta is null) - { - for (var backoffRetryCount = 1; backoffRetryCount < retryCount + 1; backoffRetryCount++) - { - timeToWait = timeToWait.Add(timeToWait); - } - } - - return timeToWait; - } - - /// - /// Determines if there is time left for a retry. - /// - /// The start time of the original request. - /// The current retry count. - /// The response message that potentially contains RetryAfter header. - /// The wait time for the next retry. - /// True if there is time left for a retry, false otherwise. - private bool HasTimeForRetry(DateTimeOffset start, int retryCount, HttpResponseMessage? response, out TimeSpan waitFor) - { - waitFor = this.GetWaitTime(retryCount, response); - var currentTIme = this._timeProvider.GetCurrentTime(); - var result = currentTIme - start + waitFor; - - return result < this._config.MaxTotalRetryTime; - } - - private bool ShouldRetry(HttpStatusCode statusCode) - { - return this._config.RetryableStatusCodes.Contains(statusCode); - } - - private bool ShouldRetry(Exception? exception) - { - return exception != null && this._config.RetryableExceptionTypes.Contains(exception.GetType()); - } -} diff --git a/dotnet/src/Extensions/Reliability.Basic/BasicHttpRetryHandlerFactory.cs b/dotnet/src/Extensions/Reliability.Basic/BasicHttpRetryHandlerFactory.cs deleted file mode 100644 index 3e2d097aed71..000000000000 --- a/dotnet/src/Extensions/Reliability.Basic/BasicHttpRetryHandlerFactory.cs +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Net.Http; -using Microsoft.Extensions.Logging; -using Microsoft.SemanticKernel.Http; - -namespace Microsoft.SemanticKernel.Reliability.Basic; - -/// -/// Internal factory for creating instances. -/// -public sealed class BasicHttpRetryHandlerFactory : HttpHandlerFactory -{ - /// - /// Gets the singleton instance of . - /// - public static BasicHttpRetryHandlerFactory Instance { get; } = new BasicHttpRetryHandlerFactory(); - - /// - /// Creates a new instance of with the provided configuration. - /// - /// Http retry configuration - internal BasicHttpRetryHandlerFactory(BasicRetryConfig? config = null) - { - this.Config = config ?? new(); - } - - /// - /// Creates a new instance of with the default configuration. - /// - /// Logger factory - /// Returns the created handler - public override DelegatingHandler Create(ILoggerFactory? loggerFactory = null) - { - return new BasicHttpRetryHandler(this.Config, loggerFactory); - } - - /// - /// Creates a new instance of with a specified configuration. - /// - /// Specific configuration - /// Logger factory - /// Returns the created handler - public DelegatingHandler Create(BasicRetryConfig config, ILoggerFactory? loggerFactory = null) - { - Verify.NotNull(config, nameof(config)); - - return new BasicHttpRetryHandler(config, loggerFactory); - } - - /// - /// Default retry configuration used when creating a new instance of . - /// - internal BasicRetryConfig Config { get; } -} diff --git a/dotnet/src/Extensions/Reliability.Basic/BasicRetryConfig.cs b/dotnet/src/Extensions/Reliability.Basic/BasicRetryConfig.cs deleted file mode 100644 index 60722884361f..000000000000 --- a/dotnet/src/Extensions/Reliability.Basic/BasicRetryConfig.cs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; - -namespace Microsoft.SemanticKernel.Reliability.Basic; - -/// -/// Retry configuration for DefaultKernelRetryHandler that uses RetryAfter header when present. -/// -public sealed record BasicRetryConfig -{ - /// - /// Maximum number of retries. - /// - /// Thrown when value is negative. - public int MaxRetryCount - { - get => this._maxRetryCount; - set - { - if (value < 0) - { - throw new ArgumentOutOfRangeException(nameof(this.MaxRetryCount), "Max retry count cannot be negative."); - } - - this._maxRetryCount = value; - } - } - - /// - /// Minimum delay between retries. - /// - public TimeSpan MinRetryDelay { get; set; } = TimeSpan.FromSeconds(2); - - /// - /// Maximum delay between retries. - /// - public TimeSpan MaxRetryDelay { get; set; } = TimeSpan.FromSeconds(60); - - /// - /// Maximum total time spent retrying. - /// - public TimeSpan MaxTotalRetryTime { get; set; } = TimeSpan.FromMinutes(2); - - /// - /// Whether to use exponential backoff or not. - /// - public bool UseExponentialBackoff { get; set; } - - /// - /// List of status codes that should be retried. - /// - public List RetryableStatusCodes { get; set; } = new() - { - HttpStatusCode.RequestTimeout, - HttpStatusCode.ServiceUnavailable, - HttpStatusCode.GatewayTimeout, - (HttpStatusCode)429 /* TooManyRequests */, - HttpStatusCode.BadGateway, - }; - - /// - /// List of exception types that should be retried. - /// - public List RetryableExceptionTypes { get; set; } = new() - { - typeof(HttpRequestException) - }; - - private int _maxRetryCount = 1; -} diff --git a/dotnet/src/Extensions/Reliability.Basic/Reliability.Basic.csproj b/dotnet/src/Extensions/Reliability.Basic/Reliability.Basic.csproj deleted file mode 100644 index ccc1d232283c..000000000000 --- a/dotnet/src/Extensions/Reliability.Basic/Reliability.Basic.csproj +++ /dev/null @@ -1,32 +0,0 @@ - - - - - Microsoft.SemanticKernel.Reliability.Basic - Microsoft.SemanticKernel.Reliability.Basic - netstandard2.0 - - - - - - - - - - Semantic Kernel - Basic Reliability Extension - Semantic Kernel Basic Reliability Extension - - - - - - - - - - - - - - \ No newline at end of file diff --git a/dotnet/src/Extensions/Reliability.Basic/ReliabilityBasicKernelBuilderExtensions.cs b/dotnet/src/Extensions/Reliability.Basic/ReliabilityBasicKernelBuilderExtensions.cs deleted file mode 100644 index e1c7dfabe436..000000000000 --- a/dotnet/src/Extensions/Reliability.Basic/ReliabilityBasicKernelBuilderExtensions.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.SemanticKernel.Reliability.Basic; - -#pragma warning disable IDE0130 -// ReSharper disable once CheckNamespace - Using NS of KernelConfig -namespace Microsoft.SemanticKernel; -#pragma warning restore IDE0130 - -/// -/// Provides extension methods for the . -/// -public static class ReliabilityBasicKernelBuilderExtensions -{ - /// - /// Sets the default retry configuration for any kernel http request. - /// - /// Target instance - /// Retry configuration - /// Self instance - public static KernelBuilder WithRetryBasic(this KernelBuilder builder, BasicRetryConfig? retryConfig = null) - { - return builder.WithHttpHandlerFactory(new BasicHttpRetryHandlerFactory(retryConfig)); - } -} diff --git a/dotnet/src/Extensions/Reliability.Polly/PollyHttpRetryHandler.cs b/dotnet/src/Extensions/Reliability.Polly/PollyHttpRetryHandler.cs deleted file mode 100644 index 2374fb7004d8..000000000000 --- a/dotnet/src/Extensions/Reliability.Polly/PollyHttpRetryHandler.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Polly; - -namespace Microsoft.SemanticKernel.Reliability.Polly; - -/// -/// Customizable PollyHttpHandler that will follow the provided policy. -/// -public class PollyHttpRetryHandler : DelegatingHandler -{ - private readonly AsyncPolicy? _typedAsyncPolicy; - private readonly AsyncPolicy? _asyncPolicy; - - /// - /// Creates a new instance of . - /// - /// HttpResponseMessage typed AsyncPolicy dedicated for typed policies. - public PollyHttpRetryHandler(AsyncPolicy typedAsyncPolicy) - { - Verify.NotNull(typedAsyncPolicy); - - this._typedAsyncPolicy = typedAsyncPolicy; - } - - /// - /// Creates a new instance of dedicated for non-typed policies. - /// - /// A non-typed AsyncPolicy - public PollyHttpRetryHandler(AsyncPolicy asyncPolicy) - { - Verify.NotNull(asyncPolicy); - - this._asyncPolicy = asyncPolicy; - } - - /// - protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); - - if (this._typedAsyncPolicy is not null) - { - return await this._typedAsyncPolicy.ExecuteAsync(async (cancelToken) => - { - var response = await base.SendAsync(request, cancelToken).ConfigureAwait(false); - return response; - }, cancellationToken).ConfigureAwait(false); - } - - return await this._asyncPolicy!.ExecuteAsync(async (cancelToken) => - { - var response = await base.SendAsync(request, cancelToken).ConfigureAwait(false); - return response; - }, cancellationToken).ConfigureAwait(false); - } -} diff --git a/dotnet/src/Extensions/Reliability.Polly/PollyHttpRetryHandlerFactory.cs b/dotnet/src/Extensions/Reliability.Polly/PollyHttpRetryHandlerFactory.cs deleted file mode 100644 index 3d72f53f663c..000000000000 --- a/dotnet/src/Extensions/Reliability.Polly/PollyHttpRetryHandlerFactory.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Net.Http; -using Microsoft.Extensions.Logging; -using Microsoft.SemanticKernel.Http; -using Polly; - -namespace Microsoft.SemanticKernel.Reliability.Polly; - -/// -/// Customizable PollyHttpHandlerFactory that will create handlers with the provided policy. -/// -public class PollyHttpRetryHandlerFactory : HttpHandlerFactory -{ - private readonly AsyncPolicy? _typedAsyncPolicy; - private readonly AsyncPolicy? _asyncPolicy; - - /// - /// Creates a new instance of . - /// - /// HttpResponseMessage typed AsyncPolicy dedicated for typed policies. - public PollyHttpRetryHandlerFactory(AsyncPolicy typedAsyncPolicy) - { - Verify.NotNull(typedAsyncPolicy); - - this._typedAsyncPolicy = typedAsyncPolicy; - } - - /// - /// Creates a new instance of dedicated for non-typed policies. - /// - /// A non-typed AsyncPolicy - public PollyHttpRetryHandlerFactory(AsyncPolicy asyncPolicy) - { - Verify.NotNull(asyncPolicy); - - this._asyncPolicy = asyncPolicy; - } - - /// - /// Creates a new instance of with the default configuration. - /// - /// Logger factory - /// Returns the created handler - public override DelegatingHandler Create(ILoggerFactory? loggerFactory = null) - { - if (this._typedAsyncPolicy is not null) - { - return new PollyHttpRetryHandler(this._typedAsyncPolicy); - } - - return new PollyHttpRetryHandler(this._asyncPolicy!); - } -} diff --git a/dotnet/src/Extensions/Reliability.Polly/Reliability.Polly.csproj b/dotnet/src/Extensions/Reliability.Polly/Reliability.Polly.csproj deleted file mode 100644 index aac4803037bc..000000000000 --- a/dotnet/src/Extensions/Reliability.Polly/Reliability.Polly.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - - - Microsoft.SemanticKernel.Reliability.Polly - Microsoft.SemanticKernel.Reliability.Polly - netstandard2.0 - - - - - - - - - - Semantic Kernel - Polly Reliability Extension - Semantic Kernel Polly Reliability Extension - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/dotnet/src/Extensions/Reliability.Polly/ReliabilityPollyKernelBuilderExtensions.cs b/dotnet/src/Extensions/Reliability.Polly/ReliabilityPollyKernelBuilderExtensions.cs deleted file mode 100644 index 0236e2e81d8e..000000000000 --- a/dotnet/src/Extensions/Reliability.Polly/ReliabilityPollyKernelBuilderExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Net.Http; -using Microsoft.SemanticKernel.Reliability.Polly; -using Polly; - -#pragma warning disable IDE0130 -// ReSharper disable once CheckNamespace - Using NS of KernelConfig -namespace Microsoft.SemanticKernel; -#pragma warning restore IDE0130 - -/// -/// Provides extension methods for the . -/// -public static class ReliabilityPollyKernelBuilderExtensions -{ - /// - /// Sets the default retry configuration for any kernel http request. - /// - /// Target instance - /// Provided AsyncPolicy - /// Returns target instance for fluent compatibility - public static KernelBuilder WithRetryPolly(this KernelBuilder kernelConfig, AsyncPolicy retryPolicy) - { - var pollyHandler = new PollyHttpRetryHandlerFactory(retryPolicy); - return kernelConfig.WithHttpHandlerFactory(pollyHandler); - } - - /// - /// Sets the default retry configuration for any kernel http request. - /// - /// Target instance - /// Provided HttpResponseMessage AsyncPolicy - /// Returns target instance for fluent compatibility - public static KernelBuilder WithRetryPolly(this KernelBuilder kernelConfig, AsyncPolicy retryPolicy) - { - var pollyHandler = new PollyHttpRetryHandlerFactory(retryPolicy); - return kernelConfig.WithHttpHandlerFactory(pollyHandler); - } -} diff --git a/dotnet/src/Functions/Functions.Grpc/Extensions/KernelGrpcExtensions.cs b/dotnet/src/Functions/Functions.Grpc/Extensions/KernelGrpcExtensions.cs index ba2afa3ba8bf..361ee188df6e 100644 --- a/dotnet/src/Functions/Functions.Grpc/Extensions/KernelGrpcExtensions.cs +++ b/dotnet/src/Functions/Functions.Grpc/Extensions/KernelGrpcExtensions.cs @@ -7,6 +7,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.Functions.Grpc.Model; using Microsoft.SemanticKernel.Functions.Grpc.Protobuf; @@ -27,15 +28,13 @@ public static class KernelGrpcExtensions /// Semantic Kernel instance. /// Directory containing the plugin directory. /// Name of the directory containing the selected plugin. - /// HttpClient to use for sending requests. /// A list of all the semantic functions representing the plugin. public static IKernelPlugin ImportPluginFromGrpcDirectory( this Kernel kernel, string parentDirectory, - string pluginDirectoryName, - HttpClient? httpClient = null) + string pluginDirectoryName) { - IKernelPlugin plugin = CreatePluginFromGrpcDirectory(kernel, parentDirectory, pluginDirectoryName, httpClient); + IKernelPlugin plugin = CreatePluginFromGrpcDirectory(kernel, parentDirectory, pluginDirectoryName); kernel.Plugins.Add(plugin); return plugin; } @@ -46,15 +45,13 @@ public static IKernelPlugin ImportPluginFromGrpcDirectory( /// Semantic Kernel instance. /// File path to .proto document. /// Name of the plugin to register. - /// HttpClient to use for sending requests. /// A list of all the semantic functions representing the plugin. public static IKernelPlugin ImportPluginFromGrpcFile( this Kernel kernel, string filePath, - string pluginName, - HttpClient? httpClient = null) + string pluginName) { - IKernelPlugin plugin = CreatePluginFromGrpcFile(kernel, filePath, pluginName, httpClient); + IKernelPlugin plugin = CreatePluginFromGrpcFile(kernel, filePath, pluginName); kernel.Plugins.Add(plugin); return plugin; } @@ -65,15 +62,13 @@ public static IKernelPlugin ImportPluginFromGrpcFile( /// Semantic Kernel instance. /// .proto document stream. /// Plugin name. - /// HttpClient to use for sending requests. /// A list of all the semantic functions representing the plugin. public static IKernelPlugin ImportPluginFromGrpc( this Kernel kernel, Stream documentStream, - string pluginName, - HttpClient? httpClient = null) + string pluginName) { - IKernelPlugin plugin = CreatePluginFromGrpc(kernel, documentStream, pluginName, httpClient); + IKernelPlugin plugin = CreatePluginFromGrpc(kernel, documentStream, pluginName); kernel.Plugins.Add(plugin); return plugin; } @@ -84,13 +79,11 @@ public static IKernelPlugin ImportPluginFromGrpc( /// Semantic Kernel instance. /// Directory containing the plugin directory. /// Name of the directory containing the selected plugin. - /// HttpClient to use for sending requests. /// A list of all the semantic functions representing the plugin. public static IKernelPlugin CreatePluginFromGrpcDirectory( this Kernel kernel, string parentDirectory, - string pluginDirectoryName, - HttpClient? httpClient = null) + string pluginDirectoryName) { const string ProtoFile = "grpc.proto"; @@ -105,13 +98,11 @@ public static IKernelPlugin CreatePluginFromGrpcDirectory( throw new FileNotFoundException($"No .proto document for the specified path - {filePath} is found."); } - kernel.LoggerFactory - .CreateLogger(typeof(KernelGrpcExtensions)) - .LogTrace("Registering gRPC functions from {0} .proto document", filePath); + kernel.GetService().CreateLogger(typeof(KernelGrpcExtensions)).LogTrace("Registering gRPC functions from {0} .proto document", filePath); using var stream = File.OpenRead(filePath); - return kernel.CreatePluginFromGrpc(stream, pluginDirectoryName, httpClient); + return kernel.CreatePluginFromGrpc(stream, pluginDirectoryName); } /// @@ -120,26 +111,22 @@ public static IKernelPlugin CreatePluginFromGrpcDirectory( /// Semantic Kernel instance. /// File path to .proto document. /// Name of the plugin to register. - /// HttpClient to use for sending requests. /// A list of all the semantic functions representing the plugin. public static IKernelPlugin CreatePluginFromGrpcFile( this Kernel kernel, string filePath, - string pluginName, - HttpClient? httpClient = null) + string pluginName) { if (!File.Exists(filePath)) { throw new FileNotFoundException($"No .proto document for the specified path - {filePath} is found."); } - kernel.LoggerFactory - .CreateLogger(typeof(KernelGrpcExtensions)) - .LogTrace("Registering gRPC functions from {0} .proto document", filePath); + kernel.GetService().CreateLogger(typeof(KernelGrpcExtensions)).LogTrace("Registering gRPC functions from {0} .proto document", filePath); using var stream = File.OpenRead(filePath); - return kernel.CreatePluginFromGrpc(stream, pluginName, httpClient); + return kernel.CreatePluginFromGrpc(stream, pluginName); } /// @@ -148,13 +135,11 @@ public static IKernelPlugin CreatePluginFromGrpcFile( /// Semantic Kernel instance. /// .proto document stream. /// Plugin name. - /// HttpClient to use for sending requests. /// A list of all the semantic functions representing the plugin. public static IKernelPlugin CreatePluginFromGrpc( this Kernel kernel, Stream documentStream, - string pluginName, - HttpClient? httpClient = null) + string pluginName) { Verify.NotNull(kernel); Verify.ValidPluginName(pluginName, kernel.Plugins); @@ -166,17 +151,19 @@ public static IKernelPlugin CreatePluginFromGrpc( var plugin = new KernelPlugin(pluginName); - var client = HttpClientProvider.GetHttpClient(kernel.HttpHandlerFactory, httpClient, kernel.LoggerFactory); + ILoggerFactory loggerFactory = kernel.GetService(); + + var client = HttpClientProvider.GetHttpClient(kernel.Services.GetService()); var runner = new GrpcOperationRunner(client); - ILogger logger = kernel.LoggerFactory.CreateLogger(typeof(KernelGrpcExtensions)); + ILogger logger = loggerFactory.CreateLogger(typeof(KernelGrpcExtensions)); foreach (var operation in operations) { try { logger.LogTrace("Registering gRPC function {0}.{1}", pluginName, operation.Name); - plugin.AddFunction(CreateGrpcFunction(runner, operation, kernel.LoggerFactory)); + plugin.AddFunction(CreateGrpcFunction(runner, operation, loggerFactory)); } catch (Exception ex) when (!ex.IsCriticalException()) { diff --git a/dotnet/src/Functions/Functions.Markdown/Extensions/KernelFunctionsMarkdownExtensions.cs b/dotnet/src/Functions/Functions.Markdown/Extensions/KernelFunctionsMarkdownExtensions.cs index 5e62dbcce24c..329b449ec4fd 100644 --- a/dotnet/src/Functions/Functions.Markdown/Extensions/KernelFunctionsMarkdownExtensions.cs +++ b/dotnet/src/Functions/Functions.Markdown/Extensions/KernelFunctionsMarkdownExtensions.cs @@ -28,7 +28,7 @@ public static KernelFunction CreateFunctionFromMarkdownResource( IPromptTemplateFactory? promptTemplateFactory = null) { functionName ??= Path.GetFileNameWithoutExtension(resourceName); - return KernelFunctionMarkdown.FromPromptMarkdownResource(resourceName, functionName, pluginName, promptTemplateFactory, kernel.LoggerFactory); + return KernelFunctionMarkdown.FromPromptMarkdownResource(resourceName, functionName, pluginName, promptTemplateFactory, kernel.GetService()); } /// @@ -49,6 +49,6 @@ public static KernelFunction CreateFunctionFromMarkdown( IPromptTemplateFactory? promptTemplateFactory = null, ILoggerFactory? loggerFactory = null) { - return KernelFunctionMarkdown.FromPromptMarkdown(text, functionName, pluginName, promptTemplateFactory, kernel.LoggerFactory); + return KernelFunctionMarkdown.FromPromptMarkdown(text, functionName, pluginName, promptTemplateFactory, kernel.GetService()); } } diff --git a/dotnet/src/Functions/Functions.OpenAPI/Extensions/KernelOpenApiPluginExtensions.cs b/dotnet/src/Functions/Functions.OpenAPI/Extensions/KernelOpenApiPluginExtensions.cs index 9c3fa9f7e1a6..c9dfb7c98db5 100644 --- a/dotnet/src/Functions/Functions.OpenAPI/Extensions/KernelOpenApiPluginExtensions.cs +++ b/dotnet/src/Functions/Functions.OpenAPI/Extensions/KernelOpenApiPluginExtensions.cs @@ -9,6 +9,7 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.SemanticKernel.Functions.OpenAPI.Model; @@ -107,12 +108,12 @@ public static async Task CreatePluginFromOpenApiAsync( Verify.ValidPluginName(pluginName, kernel.Plugins); #pragma warning disable CA2000 // Dispose objects before losing scope. No need to dispose the Http client here. It can either be an internal client using NonDisposableHttpClientHandler or an external client managed by the calling code, which should handle its disposal. - var httpClient = HttpClientProvider.GetHttpClient(kernel.HttpHandlerFactory, executionParameters?.HttpClient, kernel.LoggerFactory); + var httpClient = HttpClientProvider.GetHttpClient(executionParameters?.HttpClient ?? kernel.Services.GetService()); #pragma warning restore CA2000 var openApiSpec = await DocumentLoader.LoadDocumentFromFilePathAsync( filePath, - kernel.LoggerFactory.CreateLogger(typeof(KernelOpenApiPluginExtensions)), + kernel.GetService().CreateLogger(typeof(KernelOpenApiPluginExtensions)), cancellationToken).ConfigureAwait(false); return await CreateOpenApiPluginAsync( @@ -144,12 +145,12 @@ public static async Task CreatePluginFromOpenApiAsync( Verify.ValidPluginName(pluginName, kernel.Plugins); #pragma warning disable CA2000 // Dispose objects before losing scope. No need to dispose the Http client here. It can either be an internal client using NonDisposableHttpClientHandler or an external client managed by the calling code, which should handle its disposal. - var httpClient = HttpClientProvider.GetHttpClient(kernel.HttpHandlerFactory, executionParameters?.HttpClient, kernel.LoggerFactory); + var httpClient = HttpClientProvider.GetHttpClient(executionParameters?.HttpClient ?? kernel.Services.GetService()); #pragma warning restore CA2000 var openApiSpec = await DocumentLoader.LoadDocumentFromUriAsync( uri, - kernel.LoggerFactory.CreateLogger(typeof(KernelOpenApiPluginExtensions)), + kernel.GetService().CreateLogger(typeof(KernelOpenApiPluginExtensions)), httpClient, executionParameters?.AuthCallback, executionParameters?.UserAgent, @@ -185,7 +186,7 @@ public static async Task CreatePluginFromOpenApiAsync( Verify.ValidPluginName(pluginName, kernel.Plugins); #pragma warning disable CA2000 // Dispose objects before losing scope. No need to dispose the Http client here. It can either be an internal client using NonDisposableHttpClientHandler or an external client managed by the calling code, which should handle its disposal. - var httpClient = HttpClientProvider.GetHttpClient(kernel.HttpHandlerFactory, executionParameters?.HttpClient, kernel.LoggerFactory); + var httpClient = HttpClientProvider.GetHttpClient(executionParameters?.HttpClient ?? kernel.Services.GetService()); #pragma warning restore CA2000 var openApiSpec = await DocumentLoader.LoadDocumentFromStreamAsync(stream).ConfigureAwait(false); @@ -212,7 +213,9 @@ private static async Task CreateOpenApiPluginAsync( { using var documentStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(pluginJson)); - var parser = new OpenApiDocumentParser(kernel.LoggerFactory); + ILoggerFactory loggerFactory = kernel.GetService(); + + var parser = new OpenApiDocumentParser(loggerFactory); var operations = await parser.ParseAsync( documentStream, @@ -229,13 +232,13 @@ private static async Task CreateOpenApiPluginAsync( KernelPlugin plugin = new(pluginName); - ILogger logger = kernel.LoggerFactory.CreateLogger(typeof(KernelOpenApiPluginExtensions)); + ILogger logger = loggerFactory.CreateLogger(typeof(KernelOpenApiPluginExtensions)); foreach (var operation in operations) { try { logger.LogTrace("Registering Rest function {0}.{1}", pluginName, operation.Id); - plugin.AddFunction(CreateRestApiFunction(pluginName, runner, operation, executionParameters, documentUri, kernel.LoggerFactory, cancellationToken)); + plugin.AddFunction(CreateRestApiFunction(pluginName, runner, operation, executionParameters, documentUri, loggerFactory, cancellationToken)); } catch (Exception ex) when (!ex.IsCriticalException()) { diff --git a/dotnet/src/Functions/Functions.OpenAPI/OpenAI/KernelOpenAIPluginExtensions.cs b/dotnet/src/Functions/Functions.OpenAPI/OpenAI/KernelOpenAIPluginExtensions.cs index e03daa548f02..9ee8003f7b5b 100644 --- a/dotnet/src/Functions/Functions.OpenAPI/OpenAI/KernelOpenAIPluginExtensions.cs +++ b/dotnet/src/Functions/Functions.OpenAPI/OpenAI/KernelOpenAIPluginExtensions.cs @@ -2,11 +2,13 @@ using System; using System.IO; +using System.Net.Http; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.Functions.OpenAPI.Extensions; @@ -109,7 +111,7 @@ public static async Task CreatePluginFromOpenAIAsync( var openAIManifest = await DocumentLoader.LoadDocumentFromFilePathAsync( filePath, - kernel.LoggerFactory.CreateLogger(typeof(KernelOpenAIPluginExtensions)), + kernel.GetService().CreateLogger(typeof(KernelOpenAIPluginExtensions)), cancellationToken).ConfigureAwait(false); return await CreateAsync( @@ -140,12 +142,12 @@ public static async Task CreatePluginFromOpenAIAsync( Verify.ValidPluginName(pluginName, kernel.Plugins); #pragma warning disable CA2000 // Dispose objects before losing scope. No need to dispose the Http client here. It can either be an internal client using NonDisposableHttpClientHandler or an external client managed by the calling code, which should handle its disposal. - var httpClient = HttpClientProvider.GetHttpClient(kernel.HttpHandlerFactory, executionParameters?.HttpClient, kernel.LoggerFactory); + var httpClient = HttpClientProvider.GetHttpClient(executionParameters?.HttpClient ?? kernel.Services.GetService()); #pragma warning restore CA2000 var openAIManifest = await DocumentLoader.LoadDocumentFromUriAsync( uri, - kernel.LoggerFactory.CreateLogger(typeof(KernelOpenAIPluginExtensions)), + kernel.GetService().CreateLogger(typeof(KernelOpenAIPluginExtensions)), httpClient, null, // auth is not needed when loading the manifest executionParameters?.UserAgent, diff --git a/dotnet/src/Functions/Functions.UnitTests/Markdown/Functions/KernelFunctionMarkdownTests.cs b/dotnet/src/Functions/Functions.UnitTests/Markdown/Functions/KernelFunctionMarkdownTests.cs index a4dbd05096b0..1e71f2aced9e 100644 --- a/dotnet/src/Functions/Functions.UnitTests/Markdown/Functions/KernelFunctionMarkdownTests.cs +++ b/dotnet/src/Functions/Functions.UnitTests/Markdown/Functions/KernelFunctionMarkdownTests.cs @@ -28,7 +28,7 @@ public void ItShouldCreateSemanticFunctionConfigFromMarkdown() public void ItShouldCreateSemanticFunctionFromMarkdown() { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); // Act var function = KernelFunctionMarkdown.CreateFromPromptMarkdown(this._markdown, "TellMeAbout"); diff --git a/dotnet/src/Functions/Functions.UnitTests/OpenAPI/Extensions/KernelOpenApiPluginExtensionsTests.cs b/dotnet/src/Functions/Functions.UnitTests/OpenAPI/Extensions/KernelOpenApiPluginExtensionsTests.cs index b50b127a9029..d301f46365c0 100644 --- a/dotnet/src/Functions/Functions.UnitTests/OpenAPI/Extensions/KernelOpenApiPluginExtensionsTests.cs +++ b/dotnet/src/Functions/Functions.UnitTests/OpenAPI/Extensions/KernelOpenApiPluginExtensionsTests.cs @@ -40,7 +40,7 @@ public sealed class KernelOpenApiPluginExtensionsTests : IDisposable /// public KernelOpenApiPluginExtensionsTests() { - this._kernel = KernelBuilder.Create(); + this._kernel = new Kernel(); this._openApiDocument = ResourcePluginsProvider.LoadFromResource("documentV2_0.json"); @@ -198,7 +198,7 @@ public async Task ItShouldRespectRunAsyncCancellationTokenOnExecutionAsync() var openApiPlugins = await this._kernel.ImportPluginFromOpenApiAsync("fakePlugin", this._openApiDocument, executionParameters, registerCancellationToken.Token); - var kernel = KernelBuilder.Create(); + var kernel = new Kernel(); var arguments = new ContextVariables { diff --git a/dotnet/src/Functions/Functions.UnitTests/OpenAPI/OpenAI/KernelOpenAIPluginExtensionsTests.cs b/dotnet/src/Functions/Functions.UnitTests/OpenAPI/OpenAI/KernelOpenAIPluginExtensionsTests.cs index 169d507f49c8..024213b20ab2 100644 --- a/dotnet/src/Functions/Functions.UnitTests/OpenAPI/OpenAI/KernelOpenAIPluginExtensionsTests.cs +++ b/dotnet/src/Functions/Functions.UnitTests/OpenAPI/OpenAI/KernelOpenAIPluginExtensionsTests.cs @@ -33,7 +33,7 @@ public sealed class KernelOpenAIPluginExtensionsTests : IDisposable /// public KernelOpenAIPluginExtensionsTests() { - this._kernel = KernelBuilder.Create(); + this._kernel = new Kernel(); this._openApiDocument = ResourcePluginsProvider.LoadFromResource("documentV2_0.json"); } diff --git a/dotnet/src/Functions/Functions.Yaml/Extensions/KernelFunctionsPromptYamlExtensions.cs b/dotnet/src/Functions/Functions.Yaml/Extensions/KernelFunctionsPromptYamlExtensions.cs index 6a96c9383594..73869e67b2c4 100644 --- a/dotnet/src/Functions/Functions.Yaml/Extensions/KernelFunctionsPromptYamlExtensions.cs +++ b/dotnet/src/Functions/Functions.Yaml/Extensions/KernelFunctionsPromptYamlExtensions.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.Functions.Yaml.Functions; #pragma warning disable IDE0130 @@ -24,7 +25,7 @@ public static KernelFunction CreateFunctionFromPromptYamlResource( string resourceName, IPromptTemplateFactory? promptTemplateFactory = null) { - return KernelFunctionYaml.FromPromptYamlResource(resourceName, promptTemplateFactory, kernel.LoggerFactory); + return KernelFunctionYaml.FromPromptYamlResource(resourceName, promptTemplateFactory, kernel.GetService()); } /// @@ -41,6 +42,6 @@ public static KernelFunction CreateFunctionFromPromptYaml( string? pluginName = null, IPromptTemplateFactory? promptTemplateFactory = null) { - return KernelFunctionYaml.FromPromptYaml(text, promptTemplateFactory, kernel.LoggerFactory); + return KernelFunctionYaml.FromPromptYaml(text, promptTemplateFactory, kernel.GetService()); } } diff --git a/dotnet/src/IntegrationTests/Connectors/OpenAI/AzureOpenAICompletionTests.cs b/dotnet/src/IntegrationTests/Connectors/OpenAI/AzureOpenAICompletionTests.cs deleted file mode 100644 index 21becf17e77f..000000000000 --- a/dotnet/src/IntegrationTests/Connectors/OpenAI/AzureOpenAICompletionTests.cs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Threading.Tasks; -using Azure; -using Azure.AI.OpenAI; -using Microsoft.Extensions.Configuration; -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.Connectors.AI.OpenAI; -using Microsoft.SemanticKernel.Http; -using Microsoft.SemanticKernel.Reliability.Basic; -using SemanticKernel.IntegrationTests.TestSettings; -using Xunit; -using Xunit.Abstractions; - -namespace SemanticKernel.IntegrationTests.Connectors.OpenAI; - -public sealed class AzureOpenAICompletionTests : IDisposable -{ - private readonly IConfigurationRoot _configuration; - private readonly XunitLogger _logger; - private readonly RedirectOutput _testOutputHelper; - - public AzureOpenAICompletionTests(ITestOutputHelper output) - { - this._logger = new XunitLogger(output); - this._testOutputHelper = new RedirectOutput(output); - Console.SetOut(this._testOutputHelper); - - // Load configuration - this._configuration = new ConfigurationBuilder() - .AddJsonFile(path: "testsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile(path: "testsettings.development.json", optional: true, reloadOnChange: true) - .AddEnvironmentVariables() - .AddUserSecrets() - .Build(); - } - - [Theory] - [InlineData("Where is the most famous fish market in Seattle, Washington, USA?")] - public async Task AzureOpenAIChatNoHttpRetryPolicyTestShouldThrowAsync(string prompt) - { - // Arrange - var configuration = this._configuration.GetSection("AzureOpenAI").Get(); - Assert.NotNull(configuration); - - var httpRetryConfig = new BasicRetryConfig { MaxRetryCount = 0 }; - BasicHttpRetryHandlerFactory defaultHttpRetryHandlerFactory = new(httpRetryConfig); - - var target = new KernelBuilder() - .WithLoggerFactory(this._logger) - .WithAzureOpenAIChatCompletionService(configuration.ChatDeploymentName!, configuration.Endpoint, configuration.ApiKey) - .WithHttpHandlerFactory(defaultHttpRetryHandlerFactory) - .Build(); - - // Act - var func = target.CreateFunctionFromPrompt(prompt); - - var exception = await Assert.ThrowsAsync(() => func.InvokeAsync(target, string.Empty, executionSettings: new OpenAIPromptExecutionSettings() { MaxTokens = 1000000, Temperature = 0.5, TopP = 0.5 })); - - // Assert - Assert.NotNull(exception); - } - - [Theory] - [InlineData("Where is the most famous fish market in Seattle, Washington, USA?")] - public async Task AzureOpenAIChatNoHttpRetryPolicyCustomClientShouldThrowAsync(string prompt) - { - // Arrange - var configuration = this._configuration.GetSection("AzureOpenAI").Get(); - Assert.NotNull(configuration); - - var clientOptions = new OpenAIClientOptions(); - clientOptions.Retry.MaxRetries = 0; - clientOptions.Retry.NetworkTimeout = TimeSpan.FromSeconds(10); - - var openAIClient = new OpenAIClient(new Uri(configuration.Endpoint), new AzureKeyCredential(configuration.ApiKey), clientOptions); - - var target = new KernelBuilder() - .WithLoggerFactory(this._logger) - .WithAzureOpenAIChatCompletionService(configuration.ChatDeploymentName!, openAIClient) - .Build(); - - // Act - var func = target.CreateFunctionFromPrompt(prompt); - - var exception = await Assert.ThrowsAsync(() => func.InvokeAsync(target, string.Empty, executionSettings: new OpenAIPromptExecutionSettings() { MaxTokens = 1000000, Temperature = 0.5, TopP = 0.5 })); - - // Assert - Assert.NotNull(exception); - } - - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - ~AzureOpenAICompletionTests() - { - this.Dispose(false); - } - - private void Dispose(bool disposing) - { - if (disposing) - { - this._logger.Dispose(); - this._testOutputHelper.Dispose(); - } - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs b/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs index fd23115ad2ae..e92d95ef7df6 100644 --- a/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs @@ -7,12 +7,13 @@ using System.Text; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Http.Resilience; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.Connectors.AI.OpenAI; using Microsoft.SemanticKernel.Http; using Microsoft.SemanticKernel.Orchestration; -using Microsoft.SemanticKernel.Reliability.Basic; using SemanticKernel.IntegrationTests.TestSettings; using Xunit; using Xunit.Abstractions; @@ -41,7 +42,6 @@ public OpenAICompletionTests(ITestOutputHelper output) .Build(); this._kernelBuilder = new KernelBuilder(); - this._kernelBuilder.WithRetryBasic(); } [Theory(Skip = "OpenAI will often throttle requests. This test is for manual verification.")] @@ -54,11 +54,10 @@ public async Task OpenAITestAsync(string prompt, string expectedAnswerContains) Kernel target = this._kernelBuilder .WithLoggerFactory(this._logger) - .WithOpenAITextCompletionService( + .WithOpenAITextCompletion( serviceId: openAIConfiguration.ServiceId, modelId: openAIConfiguration.ModelId, - apiKey: openAIConfiguration.ApiKey, - setAsDefault: true) + apiKey: openAIConfiguration.ApiKey) .Build(); IReadOnlyKernelPluginCollection plugins = TestHelpers.ImportSamplePlugins(target, "ChatPlugin"); @@ -172,24 +171,26 @@ public async Task AzureOpenAITestAsync(bool useChatModel, string prompt, string // If the test fails, please note that SK retry logic may not be fully integrated into the underlying code using Azure SDK [Theory] - [InlineData("Where is the most famous fish market in Seattle, Washington, USA?", - "Error executing action [attempt 1 of 1]. Reason: Unauthorized. Will retry after 2000ms")] + [InlineData("Where is the most famous fish market in Seattle, Washington, USA?", "Resilience event occurred")] public async Task OpenAIHttpRetryPolicyTestAsync(string prompt, string expectedOutput) { - // Arrange - var retryConfig = new BasicRetryConfig(); - retryConfig.RetryableStatusCodes.Add(HttpStatusCode.Unauthorized); - OpenAIConfiguration? openAIConfiguration = this._configuration.GetSection("OpenAI").Get(); Assert.NotNull(openAIConfiguration); Kernel target = this._kernelBuilder .WithLoggerFactory(this._testOutputHelper) - .WithRetryBasic(retryConfig) - .WithOpenAITextCompletionService( + .WithOpenAITextCompletion( serviceId: openAIConfiguration.ServiceId, modelId: openAIConfiguration.ModelId, apiKey: "INVALID_KEY") // Use an invalid API key to force a 401 Unauthorized response + .ConfigureServices(c => c.ConfigureHttpClientDefaults(c => + { + // Use a standard resiliency policy, augmented to retry on 401 Unauthorized for this example + c.AddStandardResilienceHandler().Configure(o => + { + o.Retry.ShouldHandle = args => ValueTask.FromResult(args.Outcome.Result?.StatusCode is HttpStatusCode.Unauthorized); + }); + })) .Build(); IReadOnlyKernelPluginCollection plugins = TestHelpers.ImportSamplePlugins(target, "SummarizePlugin"); @@ -203,27 +204,30 @@ public async Task OpenAIHttpRetryPolicyTestAsync(string prompt, string expectedO // If the test fails, please note that SK retry logic may not be fully integrated into the underlying code using Azure SDK [Theory] - [InlineData("Where is the most famous fish market in Seattle, Washington, USA?", - "Error executing action [attempt 1 of 1]. Reason: Unauthorized. Will retry after 2000ms")] + [InlineData("Where is the most famous fish market in Seattle, Washington, USA?", "Resilience event occurred")] public async Task AzureOpenAIHttpRetryPolicyTestAsync(string prompt, string expectedOutput) { - // Arrange - var retryConfig = new BasicRetryConfig(); - retryConfig.RetryableStatusCodes.Add(HttpStatusCode.Unauthorized); - KernelBuilder builder = this._kernelBuilder - .WithLoggerFactory(this._testOutputHelper) - .WithRetryBasic(retryConfig); + .WithLoggerFactory(this._testOutputHelper); var azureOpenAIConfiguration = this._configuration.GetSection("AzureOpenAI").Get(); Assert.NotNull(azureOpenAIConfiguration); // Use an invalid API key to force a 401 Unauthorized response - builder.WithAzureTextCompletionService( + builder.WithAzureOpenAITextCompletion( deploymentName: azureOpenAIConfiguration.DeploymentName, endpoint: azureOpenAIConfiguration.Endpoint, apiKey: "INVALID_KEY"); + builder.ConfigureServices(c => c.ConfigureHttpClientDefaults(c => + { + // Use a standard resiliency policy, augmented to retry on 401 Unauthorized for this example + c.AddStandardResilienceHandler().Configure(o => + { + o.Retry.ShouldHandle = args => ValueTask.FromResult(args.Outcome.Result?.StatusCode is HttpStatusCode.Unauthorized); + }); + })); + Kernel target = builder.Build(); IReadOnlyKernelPluginCollection plugins = TestHelpers.ImportSamplePlugins(target, "SummarizePlugin"); @@ -244,7 +248,7 @@ public async Task OpenAIHttpInvalidKeyShouldReturnErrorDetailAsync() // Use an invalid API key to force a 401 Unauthorized response Kernel target = this._kernelBuilder - .WithOpenAITextCompletionService( + .WithOpenAITextCompletion( modelId: openAIConfiguration.ModelId, apiKey: "INVALID_KEY", serviceId: openAIConfiguration.ServiceId) @@ -267,7 +271,7 @@ public async Task AzureOpenAIHttpInvalidKeyShouldReturnErrorDetailAsync() Kernel target = this._kernelBuilder .WithLoggerFactory(this._testOutputHelper) - .WithAzureTextCompletionService( + .WithAzureOpenAITextCompletion( deploymentName: azureOpenAIConfiguration.DeploymentName, endpoint: azureOpenAIConfiguration.Endpoint, apiKey: "INVALID_KEY", @@ -291,7 +295,7 @@ public async Task AzureOpenAIHttpExceededMaxTokensShouldReturnErrorDetailAsync() // Arrange Kernel target = this._kernelBuilder .WithLoggerFactory(this._testOutputHelper) - .WithAzureTextCompletionService( + .WithAzureOpenAITextCompletion( deploymentName: azureOpenAIConfiguration.DeploymentName, endpoint: azureOpenAIConfiguration.Endpoint, apiKey: azureOpenAIConfiguration.ApiKey, @@ -439,11 +443,10 @@ private void ConfigureChatOpenAI(KernelBuilder kernelBuilder) Assert.NotNull(openAIConfiguration.ApiKey); Assert.NotNull(openAIConfiguration.ServiceId); - kernelBuilder.WithOpenAIChatCompletionService( + kernelBuilder.WithOpenAIChatCompletion( modelId: openAIConfiguration.ChatModelId, apiKey: openAIConfiguration.ApiKey, - serviceId: openAIConfiguration.ServiceId, - setAsDefault: true); + serviceId: openAIConfiguration.ServiceId); } private void ConfigureAzureOpenAI(KernelBuilder kernelBuilder) @@ -456,12 +459,11 @@ private void ConfigureAzureOpenAI(KernelBuilder kernelBuilder) Assert.NotNull(azureOpenAIConfiguration.ApiKey); Assert.NotNull(azureOpenAIConfiguration.ServiceId); - kernelBuilder.WithAzureTextCompletionService( + kernelBuilder.WithAzureOpenAITextCompletion( deploymentName: azureOpenAIConfiguration.DeploymentName, endpoint: azureOpenAIConfiguration.Endpoint, apiKey: azureOpenAIConfiguration.ApiKey, - serviceId: azureOpenAIConfiguration.ServiceId, - setAsDefault: true); + serviceId: azureOpenAIConfiguration.ServiceId); } private void ConfigureInvalidAzureOpenAI(KernelBuilder kernelBuilder) { @@ -471,12 +473,11 @@ private void ConfigureInvalidAzureOpenAI(KernelBuilder kernelBuilder) Assert.NotNull(azureOpenAIConfiguration.DeploymentName); Assert.NotNull(azureOpenAIConfiguration.Endpoint); - kernelBuilder.WithAzureTextCompletionService( + kernelBuilder.WithAzureOpenAITextCompletion( deploymentName: azureOpenAIConfiguration.DeploymentName, endpoint: azureOpenAIConfiguration.Endpoint, apiKey: "invalid-api-key", - serviceId: $"invalid-{azureOpenAIConfiguration.ServiceId}", - setAsDefault: true); + serviceId: $"invalid-{azureOpenAIConfiguration.ServiceId}"); } private void ConfigureAzureOpenAIChatAsText(KernelBuilder kernelBuilder) @@ -489,7 +490,7 @@ private void ConfigureAzureOpenAIChatAsText(KernelBuilder kernelBuilder) Assert.NotNull(azureOpenAIConfiguration.Endpoint); Assert.NotNull(azureOpenAIConfiguration.ServiceId); - kernelBuilder.WithAzureOpenAIChatCompletionService( + kernelBuilder.WithAzureOpenAIChatCompletion( deploymentName: azureOpenAIConfiguration.ChatDeploymentName, endpoint: azureOpenAIConfiguration.Endpoint, apiKey: azureOpenAIConfiguration.ApiKey, diff --git a/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs b/dotnet/src/IntegrationTests/Extensions/KernelFunctionExtensionsTests.cs similarity index 81% rename from dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs rename to dotnet/src/IntegrationTests/Extensions/KernelFunctionExtensionsTests.cs index 8c03ccb48d40..cc80e0eb2958 100644 --- a/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs +++ b/dotnet/src/IntegrationTests/Extensions/KernelFunctionExtensionsTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.AI.TextCompletion; @@ -25,12 +26,11 @@ public KernelFunctionExtensionsTests(ITestOutputHelper output) [Fact] public async Task ItSupportsFunctionCallsAsync() { - var builder = new KernelBuilder() - .WithAIService(null, new RedirectTextCompletion(), true) - .WithLoggerFactory(this._logger); - Kernel target = builder.Build(); - - var emailFunctions = target.ImportPluginFromObject(); + Kernel target = new KernelBuilder() + .WithLoggerFactory(this._logger) + .ConfigureServices(c => c.AddSingleton(new RedirectTextCompletion())) + .ConfigurePlugins(plugins => plugins.AddPluginFromObject()) + .Build(); var prompt = $"Hey {{{{{nameof(EmailPluginFake)}.GetEmailAddress}}}}"; @@ -44,12 +44,11 @@ public async Task ItSupportsFunctionCallsAsync() [Fact] public async Task ItSupportsFunctionCallsWithInputAsync() { - var builder = new KernelBuilder() - .WithAIService(null, new RedirectTextCompletion(), true) - .WithLoggerFactory(this._logger); - Kernel target = builder.Build(); - - var emailFunctions = target.ImportPluginFromObject(); + Kernel target = new KernelBuilder() + .WithLoggerFactory(this._logger) + .ConfigureServices(c => c.AddSingleton(new RedirectTextCompletion())) + .ConfigurePlugins(plugins => plugins.AddPluginFromObject()) + .Build(); var prompt = $"Hey {{{{{nameof(EmailPluginFake)}.GetEmailAddress \"a person\"}}}}"; diff --git a/dotnet/src/IntegrationTests/IntegrationTests.csproj b/dotnet/src/IntegrationTests/IntegrationTests.csproj index 1d294e85db88..f1bc5fec87f6 100644 --- a/dotnet/src/IntegrationTests/IntegrationTests.csproj +++ b/dotnet/src/IntegrationTests/IntegrationTests.csproj @@ -26,6 +26,8 @@ + + @@ -48,8 +50,6 @@ - - @@ -59,6 +59,12 @@ + + + + + + Always diff --git a/dotnet/src/IntegrationTests/Planners/Handlebars/HandlebarsPlannerTests.cs b/dotnet/src/IntegrationTests/Planners/Handlebars/HandlebarsPlannerTests.cs index fe353f442a20..60acc7d9863d 100644 --- a/dotnet/src/IntegrationTests/Planners/Handlebars/HandlebarsPlannerTests.cs +++ b/dotnet/src/IntegrationTests/Planners/Handlebars/HandlebarsPlannerTests.cs @@ -3,8 +3,6 @@ using System; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Planning.Handlebars; using SemanticKernel.IntegrationTests.Fakes; @@ -19,7 +17,6 @@ public sealed class HandlebarsPlannerTests : IDisposable { public HandlebarsPlannerTests(ITestOutputHelper output) { - this._logger = NullLoggerFactory.Instance; this._testOutputHelper = new RedirectOutput(output); // Load configuration @@ -83,61 +80,38 @@ private Kernel InitializeKernel(bool useEmbeddings = false, bool useChatModel = AzureOpenAIConfiguration? azureOpenAIEmbeddingsConfiguration = this._configuration.GetSection("AzureOpenAIEmbeddings").Get(); Assert.NotNull(azureOpenAIEmbeddingsConfiguration); - var builder = new KernelBuilder().WithLoggerFactory(this._logger); - builder.WithRetryBasic(); - - if (useChatModel) - { - builder.WithAzureOpenAIChatCompletionService( - deploymentName: azureOpenAIConfiguration.ChatDeploymentName!, - endpoint: azureOpenAIConfiguration.Endpoint, - apiKey: azureOpenAIConfiguration.ApiKey); - } - else + return new KernelBuilder().ConfigureServices(c => { - builder.WithAzureTextCompletionService( - deploymentName: azureOpenAIConfiguration.DeploymentName, - endpoint: azureOpenAIConfiguration.Endpoint, - apiKey: azureOpenAIConfiguration.ApiKey); - } + if (useChatModel) + { + c.AddAzureOpenAIChatCompletion( + deploymentName: azureOpenAIConfiguration.ChatDeploymentName!, + endpoint: azureOpenAIConfiguration.Endpoint, + apiKey: azureOpenAIConfiguration.ApiKey); + } + else + { + c.AddAzureOpenAITextCompletion( + deploymentName: azureOpenAIConfiguration.DeploymentName, + endpoint: azureOpenAIConfiguration.Endpoint, + apiKey: azureOpenAIConfiguration.ApiKey); + } - if (useEmbeddings) - { - builder.WithAzureOpenAITextEmbeddingGenerationService( + if (useEmbeddings) + { + c.AddAzureOpenAITextEmbeddingGeneration( deploymentName: azureOpenAIEmbeddingsConfiguration.DeploymentName, endpoint: azureOpenAIEmbeddingsConfiguration.Endpoint, apiKey: azureOpenAIEmbeddingsConfiguration.ApiKey); - } - - var kernel = builder.Build(); - return kernel; + } + }).Build(); } - private readonly ILoggerFactory _logger; private readonly RedirectOutput _testOutputHelper; private readonly IConfigurationRoot _configuration; public void Dispose() { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - ~HandlebarsPlannerTests() - { - this.Dispose(false); - } - - private void Dispose(bool disposing) - { - if (disposing) - { - if (this._logger is IDisposable ld) - { - ld.Dispose(); - } - - this._testOutputHelper.Dispose(); - } + this._testOutputHelper.Dispose(); } } diff --git a/dotnet/src/IntegrationTests/Planners/PlanTests.cs b/dotnet/src/IntegrationTests/Planners/PlanTests.cs index 90d2732bab60..1adcd4483f14 100644 --- a/dotnet/src/IntegrationTests/Planners/PlanTests.cs +++ b/dotnet/src/IntegrationTests/Planners/PlanTests.cs @@ -4,8 +4,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Events; using Microsoft.SemanticKernel.Orchestration; @@ -23,7 +21,6 @@ public sealed class PlanTests : IDisposable { public PlanTests(ITestOutputHelper output) { - this._loggerFactory = NullLoggerFactory.Instance; this._testOutputHelper = new RedirectOutput(output); // Load configuration @@ -567,35 +564,31 @@ private Kernel InitializeKernel(bool useEmbeddings = false, bool useChatModel = AzureOpenAIConfiguration? azureOpenAIEmbeddingsConfiguration = this._configuration.GetSection("AzureOpenAIEmbeddings").Get(); Assert.NotNull(azureOpenAIEmbeddingsConfiguration); - var builder = new KernelBuilder() - .WithLoggerFactory(this._loggerFactory) - .WithRetryBasic(); - - if (useChatModel) - { - builder.WithAzureOpenAIChatCompletionService( - deploymentName: azureOpenAIConfiguration.ChatDeploymentName!, - endpoint: azureOpenAIConfiguration.Endpoint, - apiKey: azureOpenAIConfiguration.ApiKey); - } - else + var kernel = new KernelBuilder().ConfigureServices(c => { - builder.WithAzureTextCompletionService( - deploymentName: azureOpenAIConfiguration.DeploymentName, - endpoint: azureOpenAIConfiguration.Endpoint, - apiKey: azureOpenAIConfiguration.ApiKey); - } + if (useChatModel) + { + c.AddAzureOpenAIChatCompletion( + deploymentName: azureOpenAIConfiguration.ChatDeploymentName!, + endpoint: azureOpenAIConfiguration.Endpoint, + apiKey: azureOpenAIConfiguration.ApiKey); + } + else + { + c.AddAzureOpenAITextCompletion( + deploymentName: azureOpenAIConfiguration.DeploymentName, + endpoint: azureOpenAIConfiguration.Endpoint, + apiKey: azureOpenAIConfiguration.ApiKey); + } - if (useEmbeddings) - { - builder - .WithAzureOpenAITextEmbeddingGenerationService( + if (useEmbeddings) + { + c.AddAzureOpenAITextEmbeddingGeneration( deploymentName: azureOpenAIEmbeddingsConfiguration.DeploymentName, endpoint: azureOpenAIEmbeddingsConfiguration.Endpoint, apiKey: azureOpenAIEmbeddingsConfiguration.ApiKey); - } - - var kernel = builder.Build(); + } + }).Build(); // Import all sample plugins available for demonstration purposes. TestHelpers.ImportAllSamplePlugins(kernel); @@ -604,31 +597,11 @@ private Kernel InitializeKernel(bool useEmbeddings = false, bool useChatModel = return kernel; } - private readonly ILoggerFactory _loggerFactory; private readonly RedirectOutput _testOutputHelper; private readonly IConfigurationRoot _configuration; public void Dispose() { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - ~PlanTests() - { - this.Dispose(false); - } - - private void Dispose(bool disposing) - { - if (disposing) - { - if (this._loggerFactory is IDisposable ld) - { - ld.Dispose(); - } - - this._testOutputHelper.Dispose(); - } + this._testOutputHelper.Dispose(); } } diff --git a/dotnet/src/IntegrationTests/Planners/SequentialPlanner/SequentialPlanParserTests.cs b/dotnet/src/IntegrationTests/Planners/SequentialPlanner/SequentialPlanParserTests.cs index a3fd981746b8..8cb1eb994598 100644 --- a/dotnet/src/IntegrationTests/Planners/SequentialPlanner/SequentialPlanParserTests.cs +++ b/dotnet/src/IntegrationTests/Planners/SequentialPlanner/SequentialPlanParserTests.cs @@ -33,13 +33,11 @@ public void CanCallToPlanFromXml() Assert.NotNull(azureOpenAIConfiguration); Kernel kernel = new KernelBuilder() - .WithRetryBasic() - .WithAzureTextCompletionService( + .WithAzureOpenAITextCompletion( deploymentName: azureOpenAIConfiguration.DeploymentName, endpoint: azureOpenAIConfiguration.Endpoint, apiKey: azureOpenAIConfiguration.ApiKey, - serviceId: azureOpenAIConfiguration.ServiceId, - setAsDefault: true) + serviceId: azureOpenAIConfiguration.ServiceId) .Build(); kernel.ImportPluginFromObject("email"); TestHelpers.ImportSamplePlugins(kernel, "SummarizePlugin", "WriterPlugin"); diff --git a/dotnet/src/IntegrationTests/Planners/SequentialPlanner/SequentialPlannerTests.cs b/dotnet/src/IntegrationTests/Planners/SequentialPlanner/SequentialPlannerTests.cs index 9259cd93d796..52eefd7c61f5 100644 --- a/dotnet/src/IntegrationTests/Planners/SequentialPlanner/SequentialPlannerTests.cs +++ b/dotnet/src/IntegrationTests/Planners/SequentialPlanner/SequentialPlannerTests.cs @@ -3,8 +3,6 @@ using System; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI.Embeddings; using Microsoft.SemanticKernel.Memory; @@ -24,7 +22,6 @@ public sealed class SequentialPlannerTests : IDisposable { public SequentialPlannerTests(ITestOutputHelper output) { - this._logger = NullLoggerFactory.Instance; this._testOutputHelper = new RedirectOutput(output); // Load configuration @@ -119,73 +116,48 @@ private Kernel InitializeKernel(bool useEmbeddings = false, bool useChatModel = AzureOpenAIConfiguration? azureOpenAIEmbeddingsConfiguration = this._configuration.GetSection("AzureOpenAIEmbeddings").Get(); Assert.NotNull(azureOpenAIEmbeddingsConfiguration); - var builder = new KernelBuilder().WithLoggerFactory(this._logger); - builder.WithRetryBasic(); - - if (useChatModel) - { - builder.WithAzureOpenAIChatCompletionService( - deploymentName: azureOpenAIConfiguration.ChatDeploymentName!, - endpoint: azureOpenAIConfiguration.Endpoint, - apiKey: azureOpenAIConfiguration.ApiKey); - } - else - { - builder.WithAzureTextCompletionService( - deploymentName: azureOpenAIConfiguration.DeploymentName, - endpoint: azureOpenAIConfiguration.Endpoint, - apiKey: azureOpenAIConfiguration.ApiKey); - } - - if (useEmbeddings) + return new KernelBuilder().ConfigureServices(c => { - builder.WithAzureOpenAITextEmbeddingGenerationService( - deploymentName: azureOpenAIEmbeddingsConfiguration.DeploymentName, - endpoint: azureOpenAIEmbeddingsConfiguration.Endpoint, - apiKey: azureOpenAIEmbeddingsConfiguration.ApiKey); - } - - var kernel = builder.Build(); + if (useChatModel) + { + c.AddAzureOpenAIChatCompletion( + deploymentName: azureOpenAIConfiguration.ChatDeploymentName!, + endpoint: azureOpenAIConfiguration.Endpoint, + apiKey: azureOpenAIConfiguration.ApiKey); + } + else + { + c.AddAzureOpenAITextCompletion( + deploymentName: azureOpenAIConfiguration.DeploymentName, + endpoint: azureOpenAIConfiguration.Endpoint, + apiKey: azureOpenAIConfiguration.ApiKey); + } - return kernel; + if (useEmbeddings) + { + c.AddAzureOpenAITextEmbeddingGeneration( + deploymentName: azureOpenAIEmbeddingsConfiguration.DeploymentName, + endpoint: azureOpenAIEmbeddingsConfiguration.Endpoint, + apiKey: azureOpenAIEmbeddingsConfiguration.ApiKey); + } + }).Build(); } private ISemanticTextMemory InitializeMemory(ITextEmbeddingGeneration textEmbeddingGeneration) { var builder = new MemoryBuilder(); - builder.WithLoggerFactory(this._logger); builder.WithMemoryStore(new VolatileMemoryStore()); builder.WithTextEmbeddingGeneration(textEmbeddingGeneration); return builder.Build(); } - private readonly ILoggerFactory _logger; private readonly RedirectOutput _testOutputHelper; private readonly IConfigurationRoot _configuration; public void Dispose() { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - ~SequentialPlannerTests() - { - this.Dispose(false); - } - - private void Dispose(bool disposing) - { - if (disposing) - { - if (this._logger is IDisposable ld) - { - ld.Dispose(); - } - - this._testOutputHelper.Dispose(); - } + this._testOutputHelper.Dispose(); } } diff --git a/dotnet/src/IntegrationTests/Planners/StepwisePlanner/FunctionCallingStepwisePlannerTests.cs b/dotnet/src/IntegrationTests/Planners/StepwisePlanner/FunctionCallingStepwisePlannerTests.cs index 3947cf18c372..2ac4702807be 100644 --- a/dotnet/src/IntegrationTests/Planners/StepwisePlanner/FunctionCallingStepwisePlannerTests.cs +++ b/dotnet/src/IntegrationTests/Planners/StepwisePlanner/FunctionCallingStepwisePlannerTests.cs @@ -3,8 +3,6 @@ using System; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Planning; using Microsoft.SemanticKernel.Plugins.Core; @@ -24,7 +22,6 @@ public sealed class FunctionCallingStepwisePlannerTests : IDisposable public FunctionCallingStepwisePlannerTests(ITestOutputHelper output) { - this._loggerFactory = NullLoggerFactory.Instance; this._testOutputHelper = new RedirectOutput(output); // Load configuration @@ -77,52 +74,26 @@ private Kernel InitializeKernel(bool useEmbeddings = false) Assert.NotNull(azureOpenAIEmbeddingsConfiguration); var builder = new KernelBuilder() - .WithLoggerFactory(this._loggerFactory) - .WithRetryBasic(); - - builder.WithAzureOpenAIChatCompletionService( - deploymentName: azureOpenAIConfiguration.ChatDeploymentName!, - endpoint: azureOpenAIConfiguration.Endpoint, - apiKey: azureOpenAIConfiguration.ApiKey); - + .WithAzureOpenAIChatCompletion( + deploymentName: azureOpenAIConfiguration.ChatDeploymentName!, + endpoint: azureOpenAIConfiguration.Endpoint, + apiKey: azureOpenAIConfiguration.ApiKey); if (useEmbeddings) { - builder.WithAzureOpenAITextEmbeddingGenerationService( - deploymentName: azureOpenAIEmbeddingsConfiguration.DeploymentName, - endpoint: azureOpenAIEmbeddingsConfiguration.Endpoint, - apiKey: azureOpenAIEmbeddingsConfiguration.ApiKey); + builder.WithAzureOpenAITextEmbeddingGeneration( + deploymentName: azureOpenAIEmbeddingsConfiguration.DeploymentName, + endpoint: azureOpenAIEmbeddingsConfiguration.Endpoint, + apiKey: azureOpenAIEmbeddingsConfiguration.ApiKey); } - var kernel = builder.Build(); - - return kernel; + return builder.Build(); } - private readonly ILoggerFactory _loggerFactory; private readonly RedirectOutput _testOutputHelper; private readonly IConfigurationRoot _configuration; public void Dispose() { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - ~FunctionCallingStepwisePlannerTests() - { - this.Dispose(false); - } - - private void Dispose(bool disposing) - { - if (disposing) - { - if (this._loggerFactory is IDisposable ld) - { - ld.Dispose(); - } - - this._testOutputHelper.Dispose(); - } + this._testOutputHelper.Dispose(); } } diff --git a/dotnet/src/IntegrationTests/Planners/StepwisePlanner/StepwisePlannerTests.cs b/dotnet/src/IntegrationTests/Planners/StepwisePlanner/StepwisePlannerTests.cs index 40b6fb439b91..340e1799f714 100644 --- a/dotnet/src/IntegrationTests/Planners/StepwisePlanner/StepwisePlannerTests.cs +++ b/dotnet/src/IntegrationTests/Planners/StepwisePlanner/StepwisePlannerTests.cs @@ -3,8 +3,6 @@ using System; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Functions.OpenAPI.OpenAI; using Microsoft.SemanticKernel.Planning; @@ -26,7 +24,6 @@ public sealed class StepwisePlannerTests : IDisposable public StepwisePlannerTests(ITestOutputHelper output) { - this._loggerFactory = NullLoggerFactory.Instance; this._testOutputHelper = new RedirectOutput(output); // Load configuration @@ -148,63 +145,38 @@ private Kernel InitializeKernel(bool useEmbeddings = false, bool useChatModel = AzureOpenAIConfiguration? azureOpenAIEmbeddingsConfiguration = this._configuration.GetSection("AzureOpenAIEmbeddings").Get(); Assert.NotNull(azureOpenAIEmbeddingsConfiguration); - var builder = new KernelBuilder() - .WithLoggerFactory(this._loggerFactory) - .WithRetryBasic(); - - if (useChatModel) - { - builder.WithAzureOpenAIChatCompletionService( - deploymentName: azureOpenAIConfiguration.ChatDeploymentName!, - endpoint: azureOpenAIConfiguration.Endpoint, - apiKey: azureOpenAIConfiguration.ApiKey); - } - else - { - builder.WithAzureTextCompletionService( - deploymentName: azureOpenAIConfiguration.DeploymentName, - endpoint: azureOpenAIConfiguration.Endpoint, - apiKey: azureOpenAIConfiguration.ApiKey); - } - - if (useEmbeddings) + return new KernelBuilder().ConfigureServices(c => { - builder.WithAzureOpenAITextEmbeddingGenerationService( - deploymentName: azureOpenAIEmbeddingsConfiguration.DeploymentName, - endpoint: azureOpenAIEmbeddingsConfiguration.Endpoint, - apiKey: azureOpenAIEmbeddingsConfiguration.ApiKey); - } - - var kernel = builder.Build(); + if (useChatModel) + { + c.AddAzureOpenAIChatCompletion( + deploymentName: azureOpenAIConfiguration.ChatDeploymentName!, + endpoint: azureOpenAIConfiguration.Endpoint, + apiKey: azureOpenAIConfiguration.ApiKey); + } + else + { + c.AddAzureOpenAITextCompletion( + deploymentName: azureOpenAIConfiguration.DeploymentName, + endpoint: azureOpenAIConfiguration.Endpoint, + apiKey: azureOpenAIConfiguration.ApiKey); + } - return kernel; + if (useEmbeddings) + { + c.AddAzureOpenAITextEmbeddingGeneration( + deploymentName: azureOpenAIEmbeddingsConfiguration.DeploymentName, + endpoint: azureOpenAIEmbeddingsConfiguration.Endpoint, + apiKey: azureOpenAIEmbeddingsConfiguration.ApiKey); + } + }).Build(); } - private readonly ILoggerFactory _loggerFactory; private readonly RedirectOutput _testOutputHelper; private readonly IConfigurationRoot _configuration; public void Dispose() { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - ~StepwisePlannerTests() - { - this.Dispose(false); - } - - private void Dispose(bool disposing) - { - if (disposing) - { - if (this._loggerFactory is IDisposable ld) - { - ld.Dispose(); - } - - this._testOutputHelper.Dispose(); - } + this._testOutputHelper.Dispose(); } } diff --git a/dotnet/src/IntegrationTests/Plugins/PluginTests.cs b/dotnet/src/IntegrationTests/Plugins/PluginTests.cs index f8b961747a10..396b7a1c84e3 100644 --- a/dotnet/src/IntegrationTests/Plugins/PluginTests.cs +++ b/dotnet/src/IntegrationTests/Plugins/PluginTests.cs @@ -25,7 +25,7 @@ public async Task QueryKlarnaOpenAIPluginAsync( string countryCode) { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); using HttpClient httpClient = new(); var plugin = await kernel.ImportPluginFromOpenAIAsync( @@ -55,7 +55,7 @@ public async Task QueryKlarnaOpenApiPluginAsync( string countryCode) { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); using HttpClient httpClient = new(); var plugin = await kernel.ImportPluginFromOpenApiAsync( @@ -85,7 +85,7 @@ public async Task QueryKlarnaOpenApiPluginRunAsync( string countryCode) { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); using HttpClient httpClient = new(); var plugin = await kernel.ImportPluginFromOpenApiAsync( @@ -122,7 +122,7 @@ public async Task QueryInstacartPluginAsync( string payload) { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); using HttpClient httpClient = new(); //note that this plugin is not compliant according to the underlying validator in SK @@ -153,7 +153,7 @@ public async Task QueryInstacartPluginFromStreamAsync( // Arrange using (var stream = System.IO.File.OpenRead(pluginFilePath)) { - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); using HttpClient httpClient = new(); //note that this plugin is not compliant according to the underlying validator in SK @@ -183,7 +183,7 @@ public async Task QueryInstacartPluginUsingRelativeFilePathAsync( string payload) { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); using HttpClient httpClient = new(); //note that this plugin is not compliant according to the underlying validator in SK diff --git a/dotnet/src/IntegrationTests/Plugins/SamplePluginsTests.cs b/dotnet/src/IntegrationTests/Plugins/SamplePluginsTests.cs index 6d72a08891c0..5549a743e1f0 100644 --- a/dotnet/src/IntegrationTests/Plugins/SamplePluginsTests.cs +++ b/dotnet/src/IntegrationTests/Plugins/SamplePluginsTests.cs @@ -11,7 +11,7 @@ public class SamplePluginsTests public void CanLoadSamplePluginsRequestSettings() { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); // Act TestHelpers.ImportAllSamplePlugins(kernel); @@ -32,7 +32,7 @@ public void CanLoadSamplePluginsRequestSettings() public void CanLoadSampleSkillsCompletions() { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); // Act TestHelpers.ImportAllSampleSkills(kernel); diff --git a/dotnet/src/InternalUtilities/src/Http/HttpClientProvider.cs b/dotnet/src/InternalUtilities/src/Http/HttpClientProvider.cs index 8acff7db5e04..88402126d0fe 100644 --- a/dotnet/src/InternalUtilities/src/Http/HttpClientProvider.cs +++ b/dotnet/src/InternalUtilities/src/Http/HttpClientProvider.cs @@ -1,8 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. +using System; using System.Net.Http; -using Microsoft.Extensions.Logging; -using Microsoft.SemanticKernel.Http; +using Microsoft.Extensions.DependencyInjection; + +#pragma warning disable CA2215 // Dispose methods should call base class dispose /// /// Provides functionality for retrieving instances of HttpClient. @@ -12,19 +14,55 @@ internal static class HttpClientProvider /// /// Retrieves an instance of HttpClient. /// - /// The to be used when the HttpClient is not provided already - /// An optional pre-existing instance of HttpClient. - /// The to use for logging. If null, no logging will be performed. /// An instance of HttpClient. - public static HttpClient GetHttpClient(IDelegatingHandlerFactory httpHandlerFactory, HttpClient? httpClient, ILoggerFactory? loggerFactory) + public static HttpClient GetHttpClient() => new(NonDisposableHttpClientHandler.Instance, disposeHandler: false); + + /// + /// Retrieves an instance of HttpClient. + /// + /// An instance of HttpClient. + public static HttpClient GetHttpClient(HttpClient? httpClient = null) => httpClient ?? GetHttpClient(); + + /// + /// Retrieves an instance of HttpClient. + /// + /// An instance of HttpClient. + public static HttpClient GetHttpClient(IServiceProvider? serviceProvider = null) => GetHttpClient(serviceProvider?.GetService()); + + /// + /// Retrieves an instance of HttpClient. + /// + /// An instance of HttpClient. + public static HttpClient GetHttpClient(HttpClient? httpClient, IServiceProvider serviceProvider) => httpClient ?? GetHttpClient(serviceProvider?.GetService()); + + /// + /// Represents a singleton implementation of that is not disposable. + /// + private sealed class NonDisposableHttpClientHandler : HttpClientHandler { - if (httpClient is null) + /// + /// Private constructor to prevent direct instantiation of the class. + /// + private NonDisposableHttpClientHandler() { - var providedHttpHandler = httpHandlerFactory.Create(loggerFactory); - providedHttpHandler.InnerHandler = NonDisposableHttpClientHandler.Instance; - return new HttpClient(providedHttpHandler, false); // We should refrain from disposing the underlying SK default HttpClient handler as it would impact other HTTP clients that utilize the same handler. + this.CheckCertificateRevocationList = true; } - return httpClient; + /// + /// Gets the singleton instance of . + /// + public static NonDisposableHttpClientHandler Instance { get; } = new(); + + /// + /// Disposes the underlying resources held by the . + /// This implementation does nothing to prevent unintended disposal, as it may affect all references. + /// + /// True if called from , false if called from a finalizer. + protected override void Dispose(bool disposing) + { + // Do nothing if called explicitly from Dispose, as it may unintentionally affect all references. + // The base.Dispose(disposing) is not called to avoid invoking the disposal of HttpClientHandler resources. + // This implementation assumes that the HttpClientHandler is being used as a singleton and should not be disposed directly. + } } } diff --git a/dotnet/src/InternalUtilities/src/Http/NonDisposableHttpClientHandler.cs b/dotnet/src/InternalUtilities/src/Http/NonDisposableHttpClientHandler.cs deleted file mode 100644 index c2167f286937..000000000000 --- a/dotnet/src/InternalUtilities/src/Http/NonDisposableHttpClientHandler.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -using System.Net.Http; - -/// -/// Represents a singleton implementation of that is not disposable. -/// -internal sealed class NonDisposableHttpClientHandler : HttpClientHandler -{ - /// - /// Private constructor to prevent direct instantiation of the class. - /// - private NonDisposableHttpClientHandler() - { - this.CheckCertificateRevocationList = true; - } - - /// - /// Gets the singleton instance of . - /// - public static NonDisposableHttpClientHandler Instance { get; } = new(); - - /// - /// Disposes the underlying resources held by the . - /// This implementation does nothing to prevent unintended disposal, as it may affect all references. - /// - /// True if called from , false if called from a finalizer. -#pragma warning disable CA2215 // Dispose methods should call base class dispose - protected override void Dispose(bool disposing) -#pragma warning restore CA2215 // Dispose methods should call base class dispose - { - // Do nothing if called explicitly from Dispose, as it may unintentionally affect all references. - // The base.Dispose(disposing) is not called to avoid invoking the disposal of HttpClientHandler resources. - // This implementation assumes that the HttpClientHandler is being used as a singleton and should not be disposed directly. - } -} diff --git a/dotnet/src/InternalUtilities/test/FunctionHelpers.cs b/dotnet/src/InternalUtilities/test/FunctionHelpers.cs index 4e70607927d8..21cf9a0f7001 100644 --- a/dotnet/src/InternalUtilities/test/FunctionHelpers.cs +++ b/dotnet/src/InternalUtilities/test/FunctionHelpers.cs @@ -17,7 +17,7 @@ public static Task CallViaKernelAsync( string methodName, params (string Name, object Value)[] variables) { - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); IKernelPlugin plugin = kernel.ImportPluginFromObject(pluginInstance); diff --git a/dotnet/src/Planners/Planners.Core.UnitTests/Action/ActionPlannerTests.cs b/dotnet/src/Planners/Planners.Core.UnitTests/Action/ActionPlannerTests.cs index 013ecf3fb838..0a4d8dff942f 100644 --- a/dotnet/src/Planners/Planners.Core.UnitTests/Action/ActionPlannerTests.cs +++ b/dotnet/src/Planners/Planners.Core.UnitTests/Action/ActionPlannerTests.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. +using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Orchestration; -using Microsoft.SemanticKernel.Services; using Moq; using Xunit; @@ -179,9 +179,10 @@ private Kernel CreateKernel(string testPlanString, KernelPluginCollection? plugi .Setup(ss => ss.SelectAIService(It.IsAny(), It.IsAny(), It.IsAny())) .Returns((textCompletion.Object, new PromptExecutionSettings())); - var serviceProvider = new Mock(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(serviceSelector.Object); - return new Kernel(serviceProvider.Object, plugins, serviceSelector.Object); + return new Kernel(serviceCollection.BuildServiceProvider(), plugins); } private KernelPluginCollection CreatePluginCollection() diff --git a/dotnet/src/Planners/Planners.Core.UnitTests/Extensions/ReadOnlyFunctionCollectionExtensionsTests.cs b/dotnet/src/Planners/Planners.Core.UnitTests/Extensions/ReadOnlyFunctionCollectionExtensionsTests.cs index 61e637be4247..58d6d482ed6c 100644 --- a/dotnet/src/Planners/Planners.Core.UnitTests/Extensions/ReadOnlyFunctionCollectionExtensionsTests.cs +++ b/dotnet/src/Planners/Planners.Core.UnitTests/Extensions/ReadOnlyFunctionCollectionExtensionsTests.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using Microsoft.SemanticKernel.Memory; -using Microsoft.SemanticKernel.Services; using Moq; using Xunit; @@ -35,7 +34,7 @@ public async Task CanCallGetAvailableFunctionsWithNoFunctionsAsync(Type t) // Arrange var plugins = new KernelPluginCollection(); var cancellationToken = default(CancellationToken); - var kernel = new Kernel(new Mock().Object, plugins); + var kernel = new Kernel(new Mock().Object, plugins); // Arrange Mock Memory and Result var memory = new Mock(); @@ -54,7 +53,7 @@ public async Task CanCallGetAvailableFunctionsWithNoFunctionsAsync(Type t) x.SearchAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(asyncEnumerable); - var serviceProvider = new Mock(); + var serviceProvider = new Mock(); var serviceSelector = new Mock(); // Arrange GetAvailableFunctionsAsync parameters @@ -114,7 +113,7 @@ public async Task CanCallGetAvailableFunctionsWithFunctionsAsync(Type t) var functionView = new KernelFunctionMetadata(plugins["pluginName"]["functionName"].Metadata) { PluginName = "pluginName" }; var nativeFunctionView = new KernelFunctionMetadata(plugins["pluginName"]["nativeFunctionName"].Metadata) { PluginName = "pluginName" }; - var kernel = new Kernel(new Mock().Object, plugins); + var kernel = new Kernel(new Mock().Object, plugins); var memoryQueryResult = new MemoryQueryResult( @@ -133,7 +132,7 @@ public async Task CanCallGetAvailableFunctionsWithFunctionsAsync(Type t) x.SearchAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(asyncEnumerable); - var serviceProvider = new Mock(); + var serviceProvider = new Mock(); var serviceSelector = new Mock(); // Arrange GetAvailableFunctionsAsync parameters @@ -181,7 +180,7 @@ public async Task CanCallGetAvailableFunctionsWithFunctionsWithRelevancyAsync(Ty }), }; - var kernel = new Kernel(new Mock().Object, plugins); + var kernel = new Kernel(new Mock().Object, plugins); var functionView = new KernelFunctionMetadata(plugins["pluginName"]["functionName"].Metadata) { PluginName = "pluginName" }; var nativeFunctionView = new KernelFunctionMetadata(plugins["pluginName"]["nativeFunctionName"].Metadata) { PluginName = "pluginName" }; @@ -203,7 +202,7 @@ public async Task CanCallGetAvailableFunctionsWithFunctionsWithRelevancyAsync(Ty x.SearchAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) .Returns(asyncEnumerable); - var serviceProvider = new Mock(); + var serviceProvider = new Mock(); var serviceSelector = new Mock(); // Arrange GetAvailableFunctionsAsync parameters @@ -239,13 +238,13 @@ public async Task CanCallGetAvailableFunctionsWithFunctionsWithRelevancyAsync(Ty public async Task CanCallGetAvailableFunctionsAsyncWithDefaultRelevancyAsync(Type t) { // Arrange - var serviceProvider = new Mock(); + var serviceProvider = new Mock(); var serviceSelector = new Mock(); var plugins = new KernelPluginCollection(); var cancellationToken = default(CancellationToken); - var kernel = new Kernel(new Mock().Object, plugins); + var kernel = new Kernel(new Mock().Object, plugins); // Arrange Mock Memory and Result var memory = new Mock(); diff --git a/dotnet/src/Planners/Planners.Core.UnitTests/Planning/PlanSerializationTests.cs b/dotnet/src/Planners/Planners.Core.UnitTests/Planning/PlanSerializationTests.cs index 7e3291389227..a021208b1bcb 100644 --- a/dotnet/src/Planners/Planners.Core.UnitTests/Planning/PlanSerializationTests.cs +++ b/dotnet/src/Planners/Planners.Core.UnitTests/Planning/PlanSerializationTests.cs @@ -2,7 +2,6 @@ using Microsoft.SemanticKernel.Orchestration; using Microsoft.SemanticKernel.Planning; -using Microsoft.SemanticKernel.Services; using Moq; using Xunit; @@ -10,7 +9,7 @@ namespace Microsoft.SemanticKernel.Planners.UnitTests.Planning; public sealed class PlanSerializationTests { - private readonly Kernel _kernel = new(new Mock().Object); + private readonly Kernel _kernel = new(new Mock().Object); [Fact] public void CanSerializePlan() diff --git a/dotnet/src/Planners/Planners.Core.UnitTests/Planning/PlanTests.cs b/dotnet/src/Planners/Planners.Core.UnitTests/Planning/PlanTests.cs index 90e00c90d14d..9c640e0c7845 100644 --- a/dotnet/src/Planners/Planners.Core.UnitTests/Planning/PlanTests.cs +++ b/dotnet/src/Planners/Planners.Core.UnitTests/Planning/PlanTests.cs @@ -5,7 +5,6 @@ using Microsoft.SemanticKernel.Events; using Microsoft.SemanticKernel.Orchestration; using Microsoft.SemanticKernel.Planning; -using Microsoft.SemanticKernel.Services; using Moq; using Xunit; @@ -733,7 +732,7 @@ public async Task CanPlanStepsTriggerKernelEventsAsync() plan.AddSteps(functions.ToArray()); var expectedInvocations = 2; - var sut = new KernelBuilder().Build(); + var sut = new Kernel(); // 1 - Plan - Write poem and send email goal // 2 - Plan - Step 1 - WritePoem @@ -1096,7 +1095,7 @@ void FunctionInvoked(object? sender, FunctionInvokedEventArgs e) private void PrepareKernelAndPlan(out Kernel kernel, out Plan plan) { - kernel = new KernelBuilder().Build(); + kernel = new Kernel(); plan = new Plan("Write a poem or joke and send it in an e-mail to Kai."); plan.AddSteps(new[] @@ -1115,12 +1114,12 @@ private static MethodInfo Method(Delegate method) return method.Method; } - private (Kernel kernel, Mock serviceProviderMock, Mock serviceSelectorMock) SetupKernel(IEnumerable? plugins = null) + private (Kernel kernel, Mock serviceProviderMock, Mock serviceSelectorMock) SetupKernel(IEnumerable? plugins = null) { - var serviceProvider = new Mock(); + var serviceProvider = new Mock(); var serviceSelector = new Mock(); - var kernel = new Kernel(serviceProvider.Object, plugins); + var kernel = new Kernel(serviceProvider.Object, plugins is not null ? new KernelPluginCollection(plugins) : null); return (kernel, serviceProvider, serviceSelector); } diff --git a/dotnet/src/Planners/Planners.Core.UnitTests/Sequential/SequentialPlanParserTests.cs b/dotnet/src/Planners/Planners.Core.UnitTests/Sequential/SequentialPlanParserTests.cs index 8409610b3404..f13e2150e3cd 100644 --- a/dotnet/src/Planners/Planners.Core.UnitTests/Sequential/SequentialPlanParserTests.cs +++ b/dotnet/src/Planners/Planners.Core.UnitTests/Sequential/SequentialPlanParserTests.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. +using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Orchestration; -using Microsoft.SemanticKernel.Services; using Moq; using Xunit; using Xunit.Abstractions; @@ -367,8 +367,9 @@ private Kernel CreateKernel(string testPlanString, KernelPluginCollection? plugi .Setup(ss => ss.SelectAIService(It.IsAny(), It.IsAny(), It.IsAny())) .Returns((textCompletion.Object, new PromptExecutionSettings())); - var serviceProvider = new Mock(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(serviceSelector.Object); - return new Kernel(serviceProvider.Object, plugins, serviceSelector.Object); + return new Kernel(serviceCollection.BuildServiceProvider(), plugins); } } diff --git a/dotnet/src/Planners/Planners.Core.UnitTests/Sequential/SequentialPlannerTests.cs b/dotnet/src/Planners/Planners.Core.UnitTests/Sequential/SequentialPlannerTests.cs index 33210de29c30..4ef2189e4900 100644 --- a/dotnet/src/Planners/Planners.Core.UnitTests/Sequential/SequentialPlannerTests.cs +++ b/dotnet/src/Planners/Planners.Core.UnitTests/Sequential/SequentialPlannerTests.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. +using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Orchestration; -using Microsoft.SemanticKernel.Services; using Moq; using Xunit; @@ -107,9 +107,10 @@ private Kernel CreateKernel(string testPlanString, KernelPluginCollection? plugi .Setup(ss => ss.SelectAIService(It.IsAny(), It.IsAny(), It.IsAny())) .Returns((textCompletion.Object, new PromptExecutionSettings())); - var serviceProvider = new Mock(); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(serviceSelector.Object); - return new Kernel(serviceProvider.Object, plugins, serviceSelector.Object); + return new Kernel(serviceCollection.BuildServiceProvider(), plugins); } private KernelPluginCollection CreatePluginCollection() diff --git a/dotnet/src/Planners/Planners.Core.UnitTests/Stepwise/ParseResultTests.cs b/dotnet/src/Planners/Planners.Core.UnitTests/Stepwise/ParseResultTests.cs index b965b6d41e93..eaa857d43c38 100644 --- a/dotnet/src/Planners/Planners.Core.UnitTests/Stepwise/ParseResultTests.cs +++ b/dotnet/src/Planners/Planners.Core.UnitTests/Stepwise/ParseResultTests.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -using Microsoft.SemanticKernel.Services; using Moq; using Xunit; @@ -24,7 +23,7 @@ public sealed class ParseResultTests public void WhenInputIsFinalAnswerReturnsFinalAnswer(string input, string expected) { // Arrange - var kernel = new Kernel(new Mock().Object); + var kernel = new Kernel(new Mock().Object); var planner = new StepwisePlanner(kernel); @@ -75,7 +74,7 @@ public void ParseActionReturnsAction(string input, string expectedThought, strin } // Arrange - var kernel = new Kernel(new Mock().Object); + var kernel = new Kernel(new Mock().Object); var planner = new StepwisePlanner(kernel); diff --git a/dotnet/src/Planners/Planners.Core.UnitTests/Stepwise/StepwisePlannerTests.cs b/dotnet/src/Planners/Planners.Core.UnitTests/Stepwise/StepwisePlannerTests.cs index 126e710816e6..1c77d43b42d9 100644 --- a/dotnet/src/Planners/Planners.Core.UnitTests/Stepwise/StepwisePlannerTests.cs +++ b/dotnet/src/Planners/Planners.Core.UnitTests/Stepwise/StepwisePlannerTests.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -using Microsoft.SemanticKernel.Services; using Moq; using Xunit; @@ -14,7 +13,7 @@ public sealed class StepwisePlannerTests public void UsesPromptDelegateWhenProvided() { // Arrange - var kernel = new Kernel(new Mock().Object); + var kernel = new Kernel(new Mock().Object); var getPromptTemplateMock = new Mock>(); var config = new StepwisePlannerConfig() diff --git a/dotnet/src/Planners/Planners.Core/Action/ActionPlanner.cs b/dotnet/src/Planners/Planners.Core/Action/ActionPlanner.cs index a6affbfaafc9..87498b41ae32 100644 --- a/dotnet/src/Planners/Planners.Core/Action/ActionPlanner.cs +++ b/dotnet/src/Planners/Planners.Core/Action/ActionPlanner.cs @@ -88,7 +88,7 @@ public ActionPlanner( // Create context and logger this._contextVariables = new ContextVariables(); - this._logger = this._kernel.LoggerFactory.CreateLogger(this.GetType()); + this._logger = kernel.GetService().CreateLogger(this.GetType()); } /// Creates a plan for the specified goal. diff --git a/dotnet/src/Planners/Planners.Core/Sequential/SequentialPlanner.cs b/dotnet/src/Planners/Planners.Core/Sequential/SequentialPlanner.cs index 6dbe64ef61a9..a2a783b9c6fe 100644 --- a/dotnet/src/Planners/Planners.Core/Sequential/SequentialPlanner.cs +++ b/dotnet/src/Planners/Planners.Core/Sequential/SequentialPlanner.cs @@ -53,7 +53,7 @@ public SequentialPlanner( }); this._kernel = kernel; - this._logger = this._kernel.LoggerFactory.CreateLogger(this.GetType()); + this._logger = kernel.GetService().CreateLogger(this.GetType()); } /// Creates a plan for the specified goal. diff --git a/dotnet/src/Planners/Planners.Core/Stepwise/StepwisePlanner.cs b/dotnet/src/Planners/Planners.Core/Stepwise/StepwisePlanner.cs index 5f03a33bc0b8..cae7061e19c3 100644 --- a/dotnet/src/Planners/Planners.Core/Stepwise/StepwisePlanner.cs +++ b/dotnet/src/Planners/Planners.Core/Stepwise/StepwisePlanner.cs @@ -57,14 +57,16 @@ public StepwisePlanner( // Set MaxTokens for the prompt config this._promptConfig.SetMaxTokens(this.Config.MaxCompletionTokens); + ILoggerFactory loggerFactory = this._kernel.GetService(); + // Initialize prompt renderer - this._promptTemplateFactory = new KernelPromptTemplateFactory(this._kernel.LoggerFactory); + this._promptTemplateFactory = new KernelPromptTemplateFactory(loggerFactory); // Import native functions this._nativeFunctions = this._kernel.ImportPluginFromObject(this, RestrictedPluginName); // Create context and logger - this._logger = this._kernel.LoggerFactory.CreateLogger(this.GetType()); + this._logger = loggerFactory.CreateLogger(this.GetType()); } /// Creates a plan for the specified goal. @@ -349,8 +351,7 @@ private ChatHistory CreateChatHistory(Kernel kernel, out IAIService aiService) } else { - var textCompletion = this._kernel.GetService(); - aiService = textCompletion; + aiService = this._kernel.GetService(); chatHistory = new ChatHistory(); } diff --git a/dotnet/src/Planners/Planners.Handlebars.UnitTests/Handlebars/HandlebarsTemplateEngineExtensionsTests.cs b/dotnet/src/Planners/Planners.Handlebars.UnitTests/Handlebars/HandlebarsTemplateEngineExtensionsTests.cs index 4c45234a9889..8ab1982555b0 100644 --- a/dotnet/src/Planners/Planners.Handlebars.UnitTests/Handlebars/HandlebarsTemplateEngineExtensionsTests.cs +++ b/dotnet/src/Planners/Planners.Handlebars.UnitTests/Handlebars/HandlebarsTemplateEngineExtensionsTests.cs @@ -251,11 +251,7 @@ public void ShouldThrowExceptionWhenFunctionHelperIsNotDefined() Assert.Throws(() => HandlebarsTemplateEngineExtensions.Render(kernel, contextVariables, template, variables)); } - private Kernel InitializeKernel() - { - Kernel kernel = new KernelBuilder().Build(); - return kernel; - } + private Kernel InitializeKernel() => new(); private sealed class Foo { diff --git a/dotnet/src/Planners/Planners.Handlebars/Handlebars/HandlebarsPlanner.cs b/dotnet/src/Planners/Planners.Handlebars/Handlebars/HandlebarsPlanner.cs index 6bd5b685358e..cc2b5ee0a34b 100644 --- a/dotnet/src/Planners/Planners.Handlebars/Handlebars/HandlebarsPlanner.cs +++ b/dotnet/src/Planners/Planners.Handlebars/Handlebars/HandlebarsPlanner.cs @@ -38,7 +38,7 @@ public HandlebarsPlanner(Kernel kernel, HandlebarsPlannerConfig? config = defaul { this._kernel = kernel; this._config = config ?? new HandlebarsPlannerConfig(); - this._logger = kernel.LoggerFactory.CreateLogger(this.GetType()); + this._logger = kernel.GetService().CreateLogger(this.GetType()); } /// Creates a plan for the specified goal. diff --git a/dotnet/src/Planners/Planners.OpenAI/Stepwise/FunctionCallingStepwisePlanner.cs b/dotnet/src/Planners/Planners.OpenAI/Stepwise/FunctionCallingStepwisePlanner.cs index 72960461007a..55b579bfe325 100644 --- a/dotnet/src/Planners/Planners.OpenAI/Stepwise/FunctionCallingStepwisePlanner.cs +++ b/dotnet/src/Planners/Planners.OpenAI/Stepwise/FunctionCallingStepwisePlanner.cs @@ -37,8 +37,10 @@ public FunctionCallingStepwisePlanner( this._kernel = kernel; this._chatCompletion = kernel.GetService(); + ILoggerFactory loggerFactory = kernel.GetService(); + // Initialize prompt renderer - this._promptTemplateFactory = new KernelPromptTemplateFactory(this._kernel.LoggerFactory); + this._promptTemplateFactory = new KernelPromptTemplateFactory(loggerFactory); // Set up Config with default values and excluded plugins this.Config = config ?? new(); @@ -48,7 +50,7 @@ public FunctionCallingStepwisePlanner( this._stepPrompt = this.Config.GetStepPromptTemplate?.Invoke() ?? EmbeddedResource.Read("Stepwise.StepPrompt.txt"); // Create context and logger - this._logger = this._kernel.LoggerFactory.CreateLogger(this.GetType()); + this._logger = loggerFactory.CreateLogger(this.GetType()); } /// diff --git a/dotnet/src/Plugins/Plugins.Core/HttpPlugin.cs b/dotnet/src/Plugins/Plugins.Core/HttpPlugin.cs index a0df6f29a646..25eff62fb289 100644 --- a/dotnet/src/Plugins/Plugins.Core/HttpPlugin.cs +++ b/dotnet/src/Plugins/Plugins.Core/HttpPlugin.cs @@ -29,7 +29,7 @@ public sealed class HttpPlugin /// /// Initializes a new instance of the class. /// - public HttpPlugin() : this(new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false)) + public HttpPlugin() : this(HttpClientProvider.GetHttpClient()) { } diff --git a/dotnet/src/Plugins/Plugins.Memory/MemoryBuilder.cs b/dotnet/src/Plugins/Plugins.Memory/MemoryBuilder.cs index c32cf280e62f..419f0a2ce0a0 100644 --- a/dotnet/src/Plugins/Plugins.Memory/MemoryBuilder.cs +++ b/dotnet/src/Plugins/Plugins.Memory/MemoryBuilder.cs @@ -1,10 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using System.Net.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.SemanticKernel.AI.Embeddings; -using Microsoft.SemanticKernel.Http; using Microsoft.SemanticKernel.Memory; namespace Microsoft.SemanticKernel.Plugins.Memory; @@ -16,7 +16,7 @@ public sealed class MemoryBuilder { private Func? _memoryStoreFactory = null; private Func? _embeddingGenerationFactory = null; - private IDelegatingHandlerFactory _httpHandlerFactory = NullHttpHandlerFactory.Instance; + private HttpClient? _httpClient; private ILoggerFactory _loggerFactory = NullLoggerFactory.Instance; /// @@ -47,14 +47,14 @@ public MemoryBuilder WithLoggerFactory(ILoggerFactory loggerFactory) } /// - /// Add a http handler factory. + /// Add an HttpClient. /// - /// Http handler factory to add. - /// Updated Memory builder including the http handler factory. - public MemoryBuilder WithHttpHandlerFactory(IDelegatingHandlerFactory httpHandlerFactory) + /// to add. + /// Updated Memory builder including the client. + public MemoryBuilder WithHttpClient(HttpClient httpClient) { - Verify.NotNull(httpHandlerFactory); - this._httpHandlerFactory = httpHandlerFactory; + Verify.NotNull(httpClient); + this._httpClient = httpClient; return this; } @@ -87,10 +87,10 @@ public MemoryBuilder WithMemoryStore(Func factor /// /// The store factory. /// Updated Memory builder including the memory store. - public MemoryBuilder WithMemoryStore(Func factory) where TStore : IMemoryStore + public MemoryBuilder WithMemoryStore(Func factory) where TStore : IMemoryStore { Verify.NotNull(factory); - this._memoryStoreFactory = () => factory(this._loggerFactory, this._httpHandlerFactory); + this._memoryStoreFactory = () => factory(this._loggerFactory, this._httpClient); return this; } @@ -112,10 +112,10 @@ public MemoryBuilder WithTextEmbeddingGeneration(ITextEmbeddingGeneration textEm /// The text embedding generation factory. /// Updated Memory builder including the text embedding generation. public MemoryBuilder WithTextEmbeddingGeneration( - Func factory) where TEmbeddingGeneration : ITextEmbeddingGeneration + Func factory) where TEmbeddingGeneration : ITextEmbeddingGeneration { Verify.NotNull(factory); - this._embeddingGenerationFactory = () => factory(this._loggerFactory, this._httpHandlerFactory); + this._embeddingGenerationFactory = () => factory(this._loggerFactory, this._httpClient); return this; } } diff --git a/dotnet/src/Plugins/Plugins.UnitTests/Core/FileIOPluginTests.cs b/dotnet/src/Plugins/Plugins.UnitTests/Core/FileIOPluginTests.cs index 92e1b25e37bf..4d0cc5a60bb9 100644 --- a/dotnet/src/Plugins/Plugins.UnitTests/Core/FileIOPluginTests.cs +++ b/dotnet/src/Plugins/Plugins.UnitTests/Core/FileIOPluginTests.cs @@ -21,14 +21,8 @@ public void ItCanBeInstantiated() [Fact] public void ItCanBeImported() { - // Arrange - var kernel = new KernelBuilder().Build(); - - // Act - var functions = kernel.ImportPluginFromObject("fileIO"); - - // Assert no exception occurs e.g. due to reflection - Assert.NotNull(functions); + // Act - Assert no exception occurs e.g. due to reflection + Assert.NotNull(KernelPluginFactory.CreateFromObject("fileIO")); } [Fact] diff --git a/dotnet/src/Plugins/Plugins.UnitTests/Core/HttpPluginTests.cs b/dotnet/src/Plugins/Plugins.UnitTests/Core/HttpPluginTests.cs index c9d7ef4ef8a7..b19f806363a0 100644 --- a/dotnet/src/Plugins/Plugins.UnitTests/Core/HttpPluginTests.cs +++ b/dotnet/src/Plugins/Plugins.UnitTests/Core/HttpPluginTests.cs @@ -34,12 +34,8 @@ public void ItCanBeInstantiated() [Fact] public void ItCanBeImported() { - // Arrange - var kernel = KernelBuilder.Create(); - var plugin = new HttpPlugin(); - // Act - Assert no exception occurs e.g. due to reflection - kernel.ImportPluginFromObject(plugin, "http"); + Assert.NotNull(KernelPluginFactory.CreateFromObject("http")); } [Fact] diff --git a/dotnet/src/Plugins/Plugins.UnitTests/Core/MathPluginTests.cs b/dotnet/src/Plugins/Plugins.UnitTests/Core/MathPluginTests.cs index 27e0b487581a..6db5a7e91ed2 100644 --- a/dotnet/src/Plugins/Plugins.UnitTests/Core/MathPluginTests.cs +++ b/dotnet/src/Plugins/Plugins.UnitTests/Core/MathPluginTests.cs @@ -21,11 +21,8 @@ public void ItCanBeInstantiated() [Fact] public void ItCanBeImported() { - // Arrange - var kernel = new KernelBuilder().Build(); - // Act - Assert no exception occurs e.g. due to reflection - kernel.ImportPluginFromObject("math"); + Assert.NotNull(KernelPluginFactory.CreateFromObject("math")); } [Theory] diff --git a/dotnet/src/Plugins/Plugins.UnitTests/Core/TextPluginTests.cs b/dotnet/src/Plugins/Plugins.UnitTests/Core/TextPluginTests.cs index 525f78c9c68b..4bd1beb027ae 100644 --- a/dotnet/src/Plugins/Plugins.UnitTests/Core/TextPluginTests.cs +++ b/dotnet/src/Plugins/Plugins.UnitTests/Core/TextPluginTests.cs @@ -18,11 +18,8 @@ public void ItCanBeInstantiated() [Fact] public void ItCanBeImported() { - // Arrange - var kernel = new KernelBuilder().Build(); - // Act - Assert no exception occurs e.g. due to reflection - kernel.ImportPluginFromObject("text"); + Assert.NotNull(KernelPluginFactory.CreateFromObject("text")); } [Fact] diff --git a/dotnet/src/Plugins/Plugins.UnitTests/Core/TimePluginTests.cs b/dotnet/src/Plugins/Plugins.UnitTests/Core/TimePluginTests.cs index 55f8e70dfc71..6460f7739633 100644 --- a/dotnet/src/Plugins/Plugins.UnitTests/Core/TimePluginTests.cs +++ b/dotnet/src/Plugins/Plugins.UnitTests/Core/TimePluginTests.cs @@ -24,11 +24,8 @@ public void ItCanBeInstantiated() [Fact] public void ItCanBeImported() { - // Arrange - var kernel = new KernelBuilder().Build(); - // Act - Assert no exception occurs e.g. due to reflection - kernel.ImportPluginFromObject("time"); + Assert.NotNull(KernelPluginFactory.CreateFromObject("time")); } [Fact] diff --git a/dotnet/src/Plugins/Plugins.UnitTests/Core/WaitPluginTests.cs b/dotnet/src/Plugins/Plugins.UnitTests/Core/WaitPluginTests.cs index 3d9774b0734f..9c37ef48a4b3 100644 --- a/dotnet/src/Plugins/Plugins.UnitTests/Core/WaitPluginTests.cs +++ b/dotnet/src/Plugins/Plugins.UnitTests/Core/WaitPluginTests.cs @@ -23,11 +23,8 @@ public void ItCanBeInstantiated() [Fact] public void ItCanBeImported() { - // Arrange - var kernel = new KernelBuilder().Build(); - // Act - Assert no exception occurs e.g. due to reflection - kernel.ImportPluginFromObject("wait"); + Assert.NotNull(KernelPluginFactory.CreateFromObject("wait")); } [Theory] diff --git a/dotnet/src/Plugins/Plugins.UnitTests/Memory/MemoryBuilderTests.cs b/dotnet/src/Plugins/Plugins.UnitTests/Memory/MemoryBuilderTests.cs index 8c79e08a7b88..1ce77015573e 100644 --- a/dotnet/src/Plugins/Plugins.UnitTests/Memory/MemoryBuilderTests.cs +++ b/dotnet/src/Plugins/Plugins.UnitTests/Memory/MemoryBuilderTests.cs @@ -1,9 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. +using System.Net.Http; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI.Embeddings; -using Microsoft.SemanticKernel.Http; using Microsoft.SemanticKernel.Memory; using Microsoft.SemanticKernel.Plugins.Memory; using Moq; @@ -75,7 +75,7 @@ public void ItUsesProvidedLoggerFactory() return Mock.Of(); }) - .WithTextEmbeddingGeneration((loggerFactory, httpHandlerFactory) => + .WithTextEmbeddingGeneration((loggerFactory, httpClient) => { Assert.Same(loggerFactoryUsed, loggerFactory); Assert.NotSame(loggerFactoryUnused, loggerFactory); @@ -86,26 +86,26 @@ public void ItUsesProvidedLoggerFactory() } [Fact] - public void ItUsesProvidedHttpHandlerFactory() + public void ItUsesProvidedHttpClientFactory() { // Arrange - var httpHandlerFactoryUsed = Mock.Of(); - var httpHandlerFactoryUnused = Mock.Of(); + using var httpClientUsed = new HttpClient(); + using var httpClientUnused = new HttpClient(); // Act & Assert var builder = new MemoryBuilder() - .WithHttpHandlerFactory(httpHandlerFactoryUsed) - .WithMemoryStore((loggerFactory, httpHandlerFactory) => + .WithHttpClient(httpClientUsed) + .WithMemoryStore((loggerFactory, httpClient) => { - Assert.Same(httpHandlerFactoryUsed, httpHandlerFactory); - Assert.NotSame(httpHandlerFactoryUnused, httpHandlerFactory); + Assert.Same(httpClientUsed, httpClient); + Assert.NotSame(httpClientUnused, httpClient); return Mock.Of(); }) - .WithTextEmbeddingGeneration((loggerFactory, httpHandlerFactory) => + .WithTextEmbeddingGeneration((loggerFactory, httpClient) => { - Assert.Same(httpHandlerFactoryUsed, httpHandlerFactory); - Assert.NotSame(httpHandlerFactoryUnused, httpHandlerFactory); + Assert.Same(httpClientUsed, httpClient); + Assert.NotSame(httpClientUnused, httpClient); return Mock.Of(); }) diff --git a/dotnet/src/Plugins/Plugins.UnitTests/Web/SearchUrlSkillTests.cs b/dotnet/src/Plugins/Plugins.UnitTests/Web/SearchUrlSkillTests.cs index 4edde2d92f10..63d971fb0ede 100644 --- a/dotnet/src/Plugins/Plugins.UnitTests/Web/SearchUrlSkillTests.cs +++ b/dotnet/src/Plugins/Plugins.UnitTests/Web/SearchUrlSkillTests.cs @@ -22,11 +22,8 @@ public void ItCanBeInstantiated() [Fact] public void ItCanBeImported() { - // Arrange - Kernel kernel = new KernelBuilder().Build(); - // Act - Assert no exception occurs e.g. due to reflection - kernel.ImportPluginFromObject("search"); + Assert.NotNull(KernelPluginFactory.CreateFromObject("search")); } [Fact] diff --git a/dotnet/src/Plugins/Plugins.Web/Bing/BingConnector.cs b/dotnet/src/Plugins/Plugins.Web/Bing/BingConnector.cs index 952026fc665c..b534920a4d03 100644 --- a/dotnet/src/Plugins/Plugins.Web/Bing/BingConnector.cs +++ b/dotnet/src/Plugins/Plugins.Web/Bing/BingConnector.cs @@ -30,7 +30,7 @@ public sealed class BingConnector : IWebSearchEngineConnector /// The API key to authenticate the connector. /// The to use for logging. If null, no logging will be performed. public BingConnector(string apiKey, ILoggerFactory? loggerFactory = null) : - this(apiKey, new HttpClient(NonDisposableHttpClientHandler.Instance, false), loggerFactory) + this(apiKey, HttpClientProvider.GetHttpClient(), loggerFactory) { } diff --git a/dotnet/src/Plugins/Plugins.Web/WebFileDownloadPlugin.cs b/dotnet/src/Plugins/Plugins.Web/WebFileDownloadPlugin.cs index eec8400c0d9d..652016220393 100644 --- a/dotnet/src/Plugins/Plugins.Web/WebFileDownloadPlugin.cs +++ b/dotnet/src/Plugins/Plugins.Web/WebFileDownloadPlugin.cs @@ -30,7 +30,7 @@ public sealed class WebFileDownloadPlugin /// /// The to use for logging. If null, no logging will be performed. public WebFileDownloadPlugin(ILoggerFactory? loggerFactory = null) : - this(new HttpClient(NonDisposableHttpClientHandler.Instance, false), loggerFactory) + this(HttpClientProvider.GetHttpClient(), loggerFactory) { } diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatCompletionServiceExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatCompletionServiceExtensions.cs deleted file mode 100644 index 51bc3c6560d9..000000000000 --- a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatCompletionServiceExtensions.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.SemanticKernel.AI.ChatCompletion; -using Microsoft.SemanticKernel.Services; - -// Use base namespace for better discoverability and to avoid conflicts with other extensions. -#pragma warning disable IDE0130 // Namespace does not match folder structure -namespace Microsoft.SemanticKernel; -#pragma warning restore IDE0130 // Namespace does not match folder structure - -/// -/// Provides extension methods for working with chat completion services. -/// -public static class ChatCompletionServiceExtensions -{ - /// - /// Get the matching the given , or - /// the default if is not provided or not found. - /// - /// The service provider. - /// Optional identifier of the desired service. - /// The completion service id matching the given id or the default. - /// Thrown when no suitable service is found. - public static IChatCompletion GetChatCompletionService( - this IAIServiceProvider services, - string? serviceId = null) => services.GetService(serviceId) - ?? throw new KernelException("Chat completion service not found"); - - /// - /// Returns true if a exist with the specified ID. - /// - /// The service provider. - /// The service ID to search for. If null, it will look for a default service. - /// True if the service ID is registered, false otherwise. - public static bool HasChatCompletionService( - this IAIServiceProvider services, - string? serviceId = null) - => services.TryGetService(serviceId, out _); -} diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/TextEmbeddingServiceExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/TextEmbeddingServiceExtensions.cs deleted file mode 100644 index 4f795c80c465..000000000000 --- a/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/TextEmbeddingServiceExtensions.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.SemanticKernel.AI.Embeddings; -using Microsoft.SemanticKernel.Services; - -// Use base namespace for better discoverability and to avoid conflicts with other extensions. -#pragma warning disable IDE0130 // Namespace does not match folder structure -namespace Microsoft.SemanticKernel; -#pragma warning restore IDE0130 // Namespace does not match folder structure - -/// -/// Provides extension methods for working with text embedding services. -/// -public static class TextEmbeddingServiceExtensions -{ - /// - /// Get the matching the given , or the default - /// if the is not provided or not found. - /// - /// The service provider. - /// Optional identifier of the desired service. - /// The embedding service matching the given id or the default service. - /// Thrown when no suitable service is found. - public static ITextEmbeddingGeneration GetTextEmbeddingService( - this IAIServiceProvider services, - string? serviceId = null) - => services.GetService(serviceId) - ?? throw new KernelException("Text embedding service not found"); - - /// - /// Returns true if a exist with the specified ID. - /// - /// The service provider. - /// The service ID to search for. If null, it will look for a default service. - /// True if the service ID is registered, false otherwise. - public static bool HasTextEmbeddingService( - this IAIServiceProvider services, - string? serviceId = null) - => services.TryGetService(serviceId, out _); -} diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/ImageGeneration/ImageGenerationServiceExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/AI/ImageGeneration/ImageGenerationServiceExtensions.cs deleted file mode 100644 index 685f0db4c657..000000000000 --- a/dotnet/src/SemanticKernel.Abstractions/AI/ImageGeneration/ImageGenerationServiceExtensions.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.SemanticKernel.AI.ImageGeneration; -using Microsoft.SemanticKernel.Services; - -// Use base namespace for better discoverability and to avoid conflicts with other extensions. -#pragma warning disable IDE0130 // Namespace does not match folder structure -namespace Microsoft.SemanticKernel; -#pragma warning restore IDE0130 // Namespace does not match folder structure - -/// -/// Provides extension methods for working with services. -/// -public static class ImageGenerationServiceExtensions -{ - /// - /// Get the matching the given , or the default - /// if the is not provided or not found. - /// - /// The service provider. - /// Optional identifier of the desired service. - /// The id matching the given id or the default. - /// Thrown when no suitable service is found. - public static IImageGeneration GetImageGenerationService( - this IAIServiceProvider services, - string? serviceId = null) => services.GetService(serviceId) - ?? throw new KernelException("Image generation service not found"); - - /// - /// Returns true if a exist with the specified ID. - /// - /// The service provider. - /// The service ID to search for. If null, it will look for a default service. - /// True if the service ID is registered, false otherwise. - public static bool HasImageGenerationService( - this IAIServiceProvider services, - string? serviceId = null) - => services.TryGetService(serviceId, out _); -} diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/TextCompletion/TextCompletionServiceExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/AI/TextCompletion/TextCompletionServiceExtensions.cs deleted file mode 100644 index 0eb190f196a1..000000000000 --- a/dotnet/src/SemanticKernel.Abstractions/AI/TextCompletion/TextCompletionServiceExtensions.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.SemanticKernel.AI.TextCompletion; -using Microsoft.SemanticKernel.Services; - -// Use base namespace for better discoverability and to avoid conflicts with other extensions. -#pragma warning disable IDE0130 // Namespace does not match folder structure -namespace Microsoft.SemanticKernel; -#pragma warning restore IDE0130 // Namespace does not match folder structure - -/// -/// Provides extension methods for working with services. -/// -public static class TextCompletionServiceExtensions -{ - /// - /// Get the matching the given , or the default - /// if the is not provided or not found. - /// - /// The service provider. - /// Optional identifier of the desired service. - /// The text completion service id matching the given ID or the default. - /// Thrown when no suitable service is found. - public static ITextCompletion GetTextCompletionServiceOrDefault( - this IAIServiceProvider services, - string? serviceId = null) => services.GetService(serviceId) - ?? throw new KernelException("Text completion service not found"); - - /// - /// Returns true if a exist with the specified ID. - /// - /// The service provider. - /// The service ID to search for. If null, it will look for a default service. - /// True if the service ID is registered, false otherwise. - public static bool HasTextCompletionService( - this IAIServiceProvider services, - string? serviceId = null) - => services.TryGetService(serviceId, out _); -} diff --git a/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs b/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs index 4ab84e2288fd..e886fcbc0eb7 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Functions/KernelFunction.cs @@ -102,7 +102,7 @@ public async Task InvokeAsync( CancellationToken cancellationToken = default) { using var activity = s_activitySource.StartActivity(this.Name); - ILogger logger = kernel.LoggerFactory.CreateLogger(this.Name); + ILogger logger = kernel.GetService().CreateLogger(this.Name); logger.LogTrace("Function invoking."); @@ -179,7 +179,7 @@ public async IAsyncEnumerable InvokeStreamingAsync( [EnumeratorCancellation] CancellationToken cancellationToken = default) { using var activity = s_activitySource.StartActivity(this.Name); - ILogger logger = kernel.LoggerFactory.CreateLogger(this.Name); + ILogger logger = kernel.GetService().CreateLogger(this.Name); logger.LogInformation("Function streaming invoking."); diff --git a/dotnet/src/SemanticKernel.Abstractions/Http/HttpHandlerFactory{THandler}.cs b/dotnet/src/SemanticKernel.Abstractions/Http/HttpHandlerFactory{THandler}.cs deleted file mode 100644 index 918f58717773..000000000000 --- a/dotnet/src/SemanticKernel.Abstractions/Http/HttpHandlerFactory{THandler}.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Net.Http; -using Microsoft.Extensions.Logging; - -namespace Microsoft.SemanticKernel.Http; - -/// -/// A factory for creating instances of . -/// -/// -public abstract class HttpHandlerFactory : IDelegatingHandlerFactory where THandler : DelegatingHandler -{ - /// - /// Creates a new instance of . - /// - /// - /// - public virtual DelegatingHandler Create(ILoggerFactory? loggerFactory = null) - { - return (DelegatingHandler)Activator.CreateInstance(typeof(THandler), loggerFactory)!; - } -} diff --git a/dotnet/src/SemanticKernel.Abstractions/Http/IDelegatingHandlerFactory.cs b/dotnet/src/SemanticKernel.Abstractions/Http/IDelegatingHandlerFactory.cs deleted file mode 100644 index fbb19a834015..000000000000 --- a/dotnet/src/SemanticKernel.Abstractions/Http/IDelegatingHandlerFactory.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Net.Http; -using Microsoft.Extensions.Logging; - -namespace Microsoft.SemanticKernel.Http; - -/// -/// Factory for creating instances. -/// -public interface IDelegatingHandlerFactory -{ - /// - /// Creates a new instance with the specified logger. - /// - /// The to use for logging. If null, no logging will be performed. - /// A new instance. - DelegatingHandler Create(ILoggerFactory? loggerFactory); -} diff --git a/dotnet/src/SemanticKernel.Abstractions/Http/NullHttpHandler.cs b/dotnet/src/SemanticKernel.Abstractions/Http/NullHttpHandler.cs deleted file mode 100644 index 3ed5113f26ae..000000000000 --- a/dotnet/src/SemanticKernel.Abstractions/Http/NullHttpHandler.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Net.Http; - -namespace Microsoft.SemanticKernel.Http; - -/// -/// A http retry handler that does nothing. -/// -public sealed class NullHttpHandler : DelegatingHandler -{ -} diff --git a/dotnet/src/SemanticKernel.Abstractions/Http/NullHttpHandlerFactory.cs b/dotnet/src/SemanticKernel.Abstractions/Http/NullHttpHandlerFactory.cs deleted file mode 100644 index 07c5d5ccd73a..000000000000 --- a/dotnet/src/SemanticKernel.Abstractions/Http/NullHttpHandlerFactory.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Net.Http; -using Microsoft.Extensions.Logging; - -namespace Microsoft.SemanticKernel.Http; - -/// -/// Implementation of that creates instances. -/// -public sealed class NullHttpHandlerFactory : IDelegatingHandlerFactory -{ - /// - /// Gets the singleton instance of . - /// - public static NullHttpHandlerFactory Instance => new(); - - /// - /// Creates a new instance. - /// - /// The logger factory to use. - /// A new instance. - public DelegatingHandler Create(ILoggerFactory? loggerFactory) - { - return new NullHttpHandler(); - } -} diff --git a/dotnet/src/SemanticKernel.Abstractions/Kernel.cs b/dotnet/src/SemanticKernel.Abstractions/Kernel.cs index 4c8c94ffed66..0d2bb19d1419 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Kernel.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Kernel.cs @@ -4,13 +4,13 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Globalization; -using System.Net.Http; +using System.Linq; using System.Threading; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.Events; -using Microsoft.SemanticKernel.Http; using Microsoft.SemanticKernel.Orchestration; using Microsoft.SemanticKernel.Services; @@ -25,30 +25,72 @@ namespace Microsoft.SemanticKernel; /// public sealed class Kernel { + /// Key used by KernelBuilder to store type information into the service provider. + internal const string KernelServiceTypeToKeyMappingsKey = nameof(KernelServiceTypeToKeyMappingsKey); + + /// Dictionary containing ambient data stored in the kernel, lazily-initialized on first access. + private Dictionary? _data; + /// to be used by any operations that need access to the culture, a format provider, etc. + private CultureInfo _culture = CultureInfo.CurrentCulture; + /// The collection of plugins, initialized via the constructor or lazily-initialized on first access via . + private KernelPluginCollection? _plugins; + /// - /// Gets the culture currently associated with this context. + /// Initializes a new instance of . /// + /// The used to query for services available through the kernel. + /// + /// The collection of plugins available through the kernel. If null, an empty collection will be used. + /// If non-null, the supplied collection instance is used, not a copy; if it's desired for the + /// to have a copy, the caller is responsible for supplying it. + /// /// - /// The culture defaults to if not explicitly set. - /// It may be set to another culture, such as , - /// and any functions invoked within the context can consult this property for use in - /// operations like formatting and parsing. + /// The KernelBuilder class provides a fluent API for constructing a instance. /// - [AllowNull] - public CultureInfo Culture + public Kernel( + IServiceProvider? services = null, + KernelPluginCollection? plugins = null) { - get => this._culture; - set => this._culture = value ?? CultureInfo.CurrentCulture; + this.Services = services ?? EmptyServiceProvider.Instance; + this._plugins = plugins; } /// - /// Gets the to use for logging. + /// Clone the object to create a new instance that may be mutated without affecting the current instance. /// /// - /// If no logging is provided, this will be an instance that ignores all logging operations. + /// The current instance is unmodified by this operation. The new will be initialized with: + /// + /// + /// The same reference as is returned by the current instance's . + /// + /// + /// A new instance initialized with the same instances as are stored by the current instance's collection. + /// Changes to the new instance's plugin collection will not affect the current instance's plugin collection, and vice versa. + /// + /// + /// All of the delegates registered with each event. Delegates are immutable (every time an additional delegate is added or removed, a new one is created), + /// so changes to the new instance's event delegates will not affect the current instance's event delegates, and vice versa. + /// + /// + /// A new containing all of the key/value pairs from the current instance's dictionary. + /// Any changes made to the new instance's dictionary will not affect the current instance's dictionary, and vice versa. + /// + /// The same reference as is returned by the current instance's . + /// /// - public ILoggerFactory LoggerFactory { get; } + public Kernel Clone() => + new(this.Services, this._plugins is { Count: > 0 } ? new KernelPluginCollection(this._plugins) : null) + { + FunctionInvoking = this.FunctionInvoking, + FunctionInvoked = this.FunctionInvoked, + PromptRendering = this.PromptRendering, + PromptRendered = this.PromptRendered, + _data = this._data is { Count: > 0 } ? new Dictionary(this._data) : null, + _culture = this._culture, + }; + #region Core State: Plugins and Services /// /// Gets the collection of plugins available through the kernel. /// @@ -60,25 +102,36 @@ public CultureInfo Culture /// /// Gets the service provider used to query for services available through the kernel. /// - public IAIServiceProvider ServiceProvider { get; } + public IServiceProvider Services { get; } + #endregion + #region Additional Transient State /// - /// Gets the used to select between multiple AI services. + /// Gets the culture currently associated with this context. /// - internal IAIServiceSelector ServiceSelector => - this._serviceSelector ?? - Interlocked.CompareExchange(ref this._serviceSelector, new OrderedIAIServiceSelector(), null) ?? - this._serviceSelector; + /// + /// The culture defaults to if not explicitly set. + /// It may be set to another culture, such as , + /// and any functions invoked within the context can consult this property for use in + /// operations like formatting and parsing. + /// + [AllowNull] + public CultureInfo Culture + { + get => this._culture; + set => this._culture = value ?? CultureInfo.CurrentCulture; + } /// - /// Gets the to use when constructing - /// instances for use in HTTP requests. + /// Gets a dictionary for ambient data associated with the kernel. /// /// - /// This is typically only used as part of creating plugins and functions, as that is typically - /// when such clients are constructed. + /// This may be used to flow arbitrary data in and out of operations performed with this kernel instance. /// - public IDelegatingHandlerFactory HttpHandlerFactory { get; } + public IDictionary Data => + this._data ?? + Interlocked.CompareExchange(ref this._data, new Dictionary(), null) ?? + this._data; /// /// Provides an event that's raised prior to a function's invocation. @@ -99,155 +152,151 @@ public CultureInfo Culture /// Provides an event that's raised after a prompt is rendered. /// public event EventHandler? PromptRendered; + #endregion - /// - /// Initializes a new instance of . - /// - /// The used to query for services available through the kernel. - /// The collection of plugins available through the kernel. If null, an empty collection will be used. - /// The used to select between multiple AI services. - /// The to use when constructing instances for use in HTTP requests. - /// The to use for logging. If null, no logging will be performed. + #region Helpers on top of Plugins and Services + /// Gets a service from the collection. + /// Specifies the type of the service to get. + /// An object that specifies the key of the service to get. + /// The found service instance. + /// A service of the specified type and name could not be found. /// - /// The KernelBuilder class provides a fluent API for constructing a instance. + /// The behavior of this method is not the same as that of + /// on the exposed . Rather, it is opinionated view around it. If a + /// is provided, it will attempt to find a service registered with that key. If no + /// is provided, it will attempt to find any service registered, regardless of whether it was registered with + /// with a key. If multiple services meet the criteria, it will return one of those registered, but no guarantee + /// on exactly which. For certain services, like , it will also return a default implementation + /// if no key was specified and no service was found. If it's able to find the specified service, that service is returned. + /// Otherwise, an exception is thrown. /// - public Kernel( - IAIServiceProvider aiServiceProvider, - IEnumerable? plugins = null, - IAIServiceSelector? serviceSelector = null, - IDelegatingHandlerFactory? httpHandlerFactory = null, - ILoggerFactory? loggerFactory = null) + public T GetService(string? serviceId = null) where T : class { - Verify.NotNull(aiServiceProvider); + T? service = null; - this.ServiceProvider = aiServiceProvider; - this._plugins = plugins is not null ? new KernelPluginCollection(plugins) : null; - this._serviceSelector = serviceSelector; - this.HttpHandlerFactory = httpHandlerFactory ?? NullHttpHandlerFactory.Instance; - this.LoggerFactory = loggerFactory ?? NullLoggerFactory.Instance; + if (serviceId is not null) + { + if (this.Services is IKeyedServiceProvider) + { + // We were given a service ID, so we need to use the keyed service lookup. + service = this.Services.GetKeyedService(serviceId); + } + } + else + { + // No ID was given. We first want to use non-keyed lookup, in order to match against + // a service registered without an ID. If we can't find one, then we try to match with + // a service registered with an ID. In both cases, if there were multiple, this will match + // with whichever was registered last. + service = this.Services.GetService(); + if (service is null && this.Services is IKeyedServiceProvider) + { + // Get the last to approximate the same behavior as GetKeyedService when there are multiple identical keys. + service = this.GetAllServices().LastOrDefault(); + } + + // If no service could be found, special-case specific services to provide a default. + if (service is null) + { + if (typeof(T) == typeof(ILoggerFactory) || typeof(T) == typeof(NullLoggerFactory)) + { + return (T)(object)NullLoggerFactory.Instance; + } + + if (typeof(T) == typeof(IAIServiceSelector) || typeof(T) == typeof(OrderedIAIServiceSelector)) + { + return (T)(object)OrderedIAIServiceSelector.Instance; + } + } + } + + // If we couldn't find the service, throw an exception. + if (service is null) + { + string message = + serviceId is null ? $"Service of type '{typeof(T)}' not registered." : + this.Services is not IKeyedServiceProvider ? $"Key '{serviceId}' specified but service provider '{this.Services}' is not a {nameof(IKeyedServiceProvider)}." : + $"Service of type '{typeof(T)}' and key '{serviceId}' not registered."; + + throw new KernelException(message); + } + + // Return the found service. + return service; } - /// - /// Clone the object to create a new instance that may be mutated without affecting the current instance. - /// - /// - /// The current instance is unmodified by this operation. The new will be initialized with: - /// - /// - /// The same reference as is returned by the current instance's . - /// The same reference as is returned by the current instance's . - /// The same reference as is returned by the current instance's . - /// The same reference as is returned by the current instance's . - /// - /// A new instance initialized with the same instances as are stored by the current instance's collection. - /// Changes to the new instance's plugin collection will not affect the current instance's plugin collection, and vice versa. - /// - /// - /// All of the delegates registered with each event. Delegates are immutable (every time an additional delegate is added or removed, a new one is created), - /// so changes to the new instance's event delegates will not affect the current instance's event delegates, and vice versa. - /// - /// - /// A new containing all of the key/value pairs from the current instance's dictionary. - /// Any changes made to the new instance's dictionary will not affect the current instance's dictionary, and vice versa. - /// - /// The same reference as is returned by the current instance's . - /// - /// - public Kernel Clone() => - new(this.ServiceProvider, - this.Plugins is { Count: > 0 } ? new KernelPluginCollection(this.Plugins) : null, - this.ServiceSelector, - this.HttpHandlerFactory, - this.LoggerFactory) + /// Gets all services of the specified type. + /// Specifies the type of the services to retrieve. + /// An enumerable of all instances of the specified service that are registered. + public IEnumerable GetAllServices() where T : class + { + if (this.Services is IKeyedServiceProvider) { - FunctionInvoking = this.FunctionInvoking, - FunctionInvoked = this.FunctionInvoked, - _data = this._data is { Count: > 0 } ? new Dictionary(this._data) : null, - _culture = this._culture, - }; + // M.E.DI doesn't support querying for a service without a key, and it also doesn't + // support AnyKey currently: https://github.com/dotnet/runtime/issues/91466 + // As a workaround, KernelBuilder injects a service containing the type-to-all-keys + // mapping. We can query for that service and and then use it to try to get a service. + if (this.Services.GetKeyedService>>(KernelServiceTypeToKeyMappingsKey) is { } typeToKeyMappings && + typeToKeyMappings.TryGetValue(typeof(T), out List keys)) + { + return keys.Select(key => this.Services.GetKeyedService(key)).Where(s => s is not null)!; + } - /// - /// Gets a configured service from the service provider. - /// - /// Specifies the type of the service being requested. - /// The name of the registered service. If a name is not provided, the default service for the specified is returned. - /// The instance of the service. - /// The specified service was not registered. - public T GetService(string? name = null) where T : IAIService => - this.ServiceProvider.GetService(name) ?? - throw new KernelException($"Service of type {typeof(T)} and name {name ?? ""} not registered."); + return Enumerable.Empty(); + } - /// - /// Gets a dictionary for ambient data associated with the kernel. - /// - /// - /// This may be used to flow arbitrary data in and out of operations performed with this kernel instance. - /// - public IDictionary Data => - this._data ?? - Interlocked.CompareExchange(ref this._data, new Dictionary(), null) ?? - this._data; + return this.Services.GetServices(); + } + + #endregion - #region internal =============================================================================== + #region Helpers internal FunctionInvokingEventArgs? OnFunctionInvoking(KernelFunction function, ContextVariables variables) { - var functionInvoking = this.FunctionInvoking; - if (functionInvoking is null) + FunctionInvokingEventArgs? eventArgs = null; + if (this.FunctionInvoking is { } functionInvoking) { - return null; + eventArgs = new(function, variables); + functionInvoking.Invoke(this, eventArgs); } - var eventArgs = new FunctionInvokingEventArgs(function, variables); - functionInvoking.Invoke(this, eventArgs); return eventArgs; } internal FunctionInvokedEventArgs? OnFunctionInvoked(KernelFunction function, FunctionResult result) { - var functionInvoked = this.FunctionInvoked; - if (functionInvoked is null) + FunctionInvokedEventArgs? eventArgs = null; + if (this.FunctionInvoked is { } functionInvoked) { - return null; + eventArgs = new(function, result); + functionInvoked.Invoke(this, eventArgs); } - var eventArgs = new FunctionInvokedEventArgs(function, result); - functionInvoked.Invoke(this, eventArgs); return eventArgs; } internal PromptRenderingEventArgs? OnPromptRendering(KernelFunction function, ContextVariables variables, PromptExecutionSettings? executionSettings) { - var promptRendering = this.PromptRendering; - if (promptRendering is null) + PromptRenderingEventArgs? eventArgs = null; + if (this.PromptRendering is { } promptRendering) { - return null; + eventArgs = new(function, variables, executionSettings); + promptRendering.Invoke(this, eventArgs); } - var eventArgs = new PromptRenderingEventArgs(function, variables, executionSettings); - promptRendering.Invoke(this, eventArgs); return eventArgs; } internal PromptRenderedEventArgs? OnPromptRendered(KernelFunction function, ContextVariables variables, string renderedPrompt) { - var promptRendered = this.PromptRendered; - if (promptRendered is null) + PromptRenderedEventArgs? eventArgs = null; + if (this.PromptRendered is { } promptRendered) { - return null; + eventArgs = new(function, variables, renderedPrompt); + promptRendered.Invoke(this, eventArgs); } - var eventArgs = new PromptRenderedEventArgs(function, variables, renderedPrompt); - promptRendered.Invoke(this, eventArgs); return eventArgs; } #endregion - - #region private ================================================================================ - - private Dictionary? _data; - private CultureInfo _culture = CultureInfo.CurrentCulture; - private KernelPluginCollection? _plugins; - private IAIServiceSelector? _serviceSelector; - - #endregion } diff --git a/dotnet/src/SemanticKernel.Abstractions/SemanticKernel.Abstractions.csproj b/dotnet/src/SemanticKernel.Abstractions/SemanticKernel.Abstractions.csproj index 5fd881c68f03..aadec42d9cbc 100644 --- a/dotnet/src/SemanticKernel.Abstractions/SemanticKernel.Abstractions.csproj +++ b/dotnet/src/SemanticKernel.Abstractions/SemanticKernel.Abstractions.csproj @@ -18,6 +18,7 @@ + diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/EmptyServiceProvider.cs b/dotnet/src/SemanticKernel.Abstractions/Services/EmptyServiceProvider.cs new file mode 100644 index 000000000000..7ea2fac739bf --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/Services/EmptyServiceProvider.cs @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using Microsoft.Extensions.DependencyInjection; + +#pragma warning disable IDE0130 // Namespace does not match folder structure + +namespace Microsoft.SemanticKernel; + +/// Empty implementation that returns null from all calls. +internal sealed class EmptyServiceProvider : IServiceProvider, IKeyedServiceProvider +{ + /// Singleton instance of . + public static IServiceProvider Instance { get; } = new EmptyServiceProvider(); + + /// + public object? GetService(Type serviceType) => null; + + /// + public object? GetKeyedService(Type serviceType, object? serviceKey) => null; + + /// + public object GetRequiredKeyedService(Type serviceType, object? serviceKey) => + throw new InvalidOperationException(serviceKey is null ? + $"No service for type '{serviceType}' has been registered." : + $"No service for type '{serviceType}' and service key '{serviceKey}' has been registered."); +} diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs b/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs index 05e9619f909d..e9c0fa5f45ab 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs @@ -5,7 +5,7 @@ namespace Microsoft.SemanticKernel.Services; /// -/// Represents an empty interface for AI services. +/// Represents an AI service. /// public interface IAIService { diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/IAIServiceProvider.cs b/dotnet/src/SemanticKernel.Abstractions/Services/IAIServiceProvider.cs deleted file mode 100644 index 293973f6ed14..000000000000 --- a/dotnet/src/SemanticKernel.Abstractions/Services/IAIServiceProvider.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -namespace Microsoft.SemanticKernel.Services; - -/// -/// Represents an interface for AI service providers that implements the INamedServiceProvider interface. -/// -public interface IAIServiceProvider : INamedServiceProvider -{ -} diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/IAIServiceSelector.cs b/dotnet/src/SemanticKernel.Abstractions/Services/IAIServiceSelector.cs index ddcefbc69e9b..8fe3fe31a081 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Services/IAIServiceSelector.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Services/IAIServiceSelector.cs @@ -9,7 +9,7 @@ namespace Microsoft.SemanticKernel; #pragma warning restore IDE0130 /// -/// Selector which will return a tuple containing instances of and from the specified provider based on the model settings. +/// Represents a selector which will return a tuple containing instances of and from the specified provider based on the model settings. /// public interface IAIServiceSelector { @@ -23,6 +23,6 @@ public interface IAIServiceSelector /// Semantic Kernel callable function interface /// #pragma warning disable CA1716 // Identifiers should not match keywords - (T?, PromptExecutionSettings?) SelectAIService(Kernel kernel, ContextVariables variables, KernelFunction function) where T : IAIService; + (T?, PromptExecutionSettings?) SelectAIService(Kernel kernel, ContextVariables variables, KernelFunction function) where T : class, IAIService; #pragma warning restore CA1716 } diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/INamedServiceProvider.cs b/dotnet/src/SemanticKernel.Abstractions/Services/INamedServiceProvider.cs deleted file mode 100644 index ed003cd2e360..000000000000 --- a/dotnet/src/SemanticKernel.Abstractions/Services/INamedServiceProvider.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; - -namespace Microsoft.SemanticKernel.Services; - -/// -/// Represents a named service provider that can retrieve services by type and name. -/// -/// The base type of the services provided by this provider. -public interface INamedServiceProvider -{ - /// - /// Gets the service of the specified type and name, or null if not found. - /// - /// The type of the service. - /// The name of the service, or null for the default service. - /// The service instance, or null if not found. - T? GetService(string? name = null) where T : TService; - - /// - /// Gets all services of the specified type, or an empty collection of none are found. - /// - /// The type of the service. - /// Collection of services of the specified type, or an empty collection of none are found - ICollection GetServices() where T : TService; -} diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/OrderedIAIServiceSelector.cs b/dotnet/src/SemanticKernel.Abstractions/Services/OrderedIAIServiceSelector.cs index bd0dc3704cd3..2829a7905903 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Services/OrderedIAIServiceSelector.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Services/OrderedIAIServiceSelector.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. +using System; using System.Linq; +using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.Orchestration; @@ -10,16 +12,19 @@ namespace Microsoft.SemanticKernel.Services; /// Implementation of that selects the AI service based on the order of the model settings. /// Uses the service id to select the preferred service provider and then returns the service and associated model settings. /// -internal class OrderedIAIServiceSelector : IAIServiceSelector +internal sealed class OrderedIAIServiceSelector : IAIServiceSelector { + public static OrderedIAIServiceSelector Instance { get; } = new(); + /// - public (T?, PromptExecutionSettings?) SelectAIService(Kernel kernel, ContextVariables variables, KernelFunction function) where T : IAIService + public (T?, PromptExecutionSettings?) SelectAIService(Kernel kernel, ContextVariables variables, KernelFunction function) where T : class, IAIService { - var serviceProvider = kernel.ServiceProvider; - var modelSettings = function.ExecutionSettings; - if (modelSettings is null || !modelSettings.Any()) + var executionSettings = function.ExecutionSettings; + if (executionSettings is null || !executionSettings.Any()) { - var service = serviceProvider.GetService(null); + var service = kernel.Services is IKeyedServiceProvider ? + kernel.GetAllServices().LastOrDefault() : // see comments in Kernel/KernelBuilder for why we can't use GetKeyedService + kernel.Services.GetService(); if (service is not null) { return (service, null); @@ -28,11 +33,13 @@ internal class OrderedIAIServiceSelector : IAIServiceSelector else { PromptExecutionSettings? defaultRequestSettings = null; - foreach (var model in modelSettings) + foreach (var model in executionSettings) { if (!string.IsNullOrEmpty(model.ServiceId)) { - var service = serviceProvider.GetService(model.ServiceId); + var service = kernel.Services is IKeyedServiceProvider ? + kernel.Services.GetKeyedService(model.ServiceId) : + null; if (service is not null) { return (service, model); @@ -40,7 +47,7 @@ internal class OrderedIAIServiceSelector : IAIServiceSelector } else if (!string.IsNullOrEmpty(model.ModelId)) { - var service = this.GetServiceByModelId(serviceProvider, model.ModelId!); + var service = this.GetServiceByModelId(kernel.Services, model.ModelId!); if (service is not null) { return (service, model); @@ -55,19 +62,17 @@ internal class OrderedIAIServiceSelector : IAIServiceSelector if (defaultRequestSettings is not null) { - var service = serviceProvider.GetService(null); - if (service is not null) - { - return (service, defaultRequestSettings); - } + return (kernel.GetService(), defaultRequestSettings); } } - var names = modelSettings is not null ? string.Join("|", modelSettings.Select(model => model.ServiceId).ToArray()) : null; - throw new KernelException($"Service of type {typeof(T)} and name {names ?? ""} not registered."); + var names = executionSettings is not null ? string.Join("|", executionSettings.Select(model => model.ServiceId).ToArray()) : null; + throw new KernelException(string.IsNullOrWhiteSpace(names) ? + $"Service of type {typeof(T)} not registered." : + $"Service of type {typeof(T)} and names {names} not registered."); } - private T? GetServiceByModelId(IAIServiceProvider serviceProvider, string modelId) where T : IAIService + private T? GetServiceByModelId(IServiceProvider serviceProvider, string modelId) where T : IAIService { var services = serviceProvider.GetServices(); foreach (var service in services) diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/ServiceExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/Services/ServiceExtensions.cs deleted file mode 100644 index 16f10184d248..000000000000 --- a/dotnet/src/SemanticKernel.Abstractions/Services/ServiceExtensions.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Diagnostics.CodeAnalysis; - -namespace Microsoft.SemanticKernel.Services; - -internal static class AIServiceProviderExtensions -{ - /// - /// Tries to get the service of the specified type and name, and returns a value indicating whether the operation succeeded. - /// - /// The type of the service. - /// The service provider. - /// The output parameter to receive the service instance, or null if not found. - /// True if the service was found, false otherwise. - public static bool TryGetService(this IAIServiceProvider serviceProvider, - [NotNullWhen(true)] out T? service) where T : IAIService - { - service = serviceProvider.GetService(); - return service != null; - } - - /// - /// Tries to get the service of the specified type and name, and returns a value indicating whether the operation succeeded. - /// - /// The type of the service. - /// The service provider. - /// The name of the service, or null for the default service. - /// The output parameter to receive the service instance, or null if not found. - /// True if the service was found, false otherwise. - public static bool TryGetService(this IAIServiceProvider serviceProvider, - string? name, [NotNullWhen(true)] out T? service) where T : IAIService - { - service = serviceProvider.GetService(name); - return service != null; - } -} diff --git a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs index febfa9cb9348..61925c7c3bc3 100644 --- a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs +++ b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromMethod.cs @@ -286,8 +286,8 @@ private static (Func, Kern { TrackUniqueParameterType(ref hasLoggerParam, method, $"At most one {nameof(ILogger)}/{nameof(ILoggerFactory)} parameter is permitted."); return type == typeof(ILogger) ? - ((Kernel kernel, ContextVariables context, CancellationToken _) => kernel.LoggerFactory.CreateLogger(method?.DeclaringType ?? typeof(KernelFunctionFromPrompt)), null) : - ((Kernel kernel, ContextVariables context, CancellationToken _) => kernel.LoggerFactory, null); + ((Kernel kernel, ContextVariables context, CancellationToken _) => kernel.GetService().CreateLogger(method?.DeclaringType ?? typeof(KernelFunctionFromPrompt)), null) : + ((Kernel kernel, ContextVariables context, CancellationToken _) => kernel.GetService(), null); } if (type == typeof(CultureInfo) || type == typeof(IFormatProvider)) diff --git a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromPrompt.cs b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromPrompt.cs index 496a654fe69a..518f5904b4f8 100644 --- a/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromPrompt.cs +++ b/dotnet/src/SemanticKernel.Core/Functions/KernelFunctionFromPrompt.cs @@ -227,7 +227,7 @@ private void AddDefaultValues(ContextVariables variables) private async Task<(ITextCompletion, PromptExecutionSettings?, string, PromptRenderedEventArgs?)> RenderPromptAsync(Kernel kernel, ContextVariables variables, PromptExecutionSettings? executionSettings, CancellationToken cancellationToken) { - var serviceSelector = kernel.ServiceSelector; + var serviceSelector = kernel.GetService(); (var textCompletion, var defaultRequestSettings) = serviceSelector.SelectAIService(kernel, variables, this); Verify.NotNull(textCompletion); diff --git a/dotnet/src/SemanticKernel.Core/KernelBuilder.cs b/dotnet/src/SemanticKernel.Core/KernelBuilder.cs index 3b4eec72c2ae..203403839d83 100644 --- a/dotnet/src/SemanticKernel.Core/KernelBuilder.cs +++ b/dotnet/src/SemanticKernel.Core/KernelBuilder.cs @@ -1,50 +1,87 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using System.Collections.Generic; using System.Globalization; +using System.Net.Http; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.SemanticKernel.Http; -using Microsoft.SemanticKernel.Services; + +#pragma warning disable CA1200 // Avoid using cref tags with a prefix namespace Microsoft.SemanticKernel; -/// -/// A builder for Semantic Kernel. -/// +/// Provides a builder for constructing instances of . +/// +/// A is primarily a collection of services and plugins. Services are represented +/// via the standard interface, and plugins via a . +/// makes it easy to compose those services and plugins via a fluent +/// interface. In particular, allows for extension methods off of +/// to be used to register services, and +/// allows for plugins to be constructed and added to the collection, having been handed a reference +/// to the , in case it's needed for resolving services, e.g. a +/// or that might be used by the plugin. Once composed, the builder's +/// method produces a new instance. +/// public sealed class KernelBuilder { - private ILoggerFactory _loggerFactory = NullLoggerFactory.Instance; - private IDelegatingHandlerFactory _httpHandlerFactory = NullHttpHandlerFactory.Instance; - private readonly AIServiceCollection _aiServices = new(); - private IAIServiceSelector? _serviceSelector; + /// The collection of services to be available through the . + private ServiceCollection? _services; + /// Multicast delegate of configuration callbacks for creating plugins. + private Action>? _pluginCallbacks; + /// The initial culture to be stored in the . private CultureInfo? _culture; - /// - /// Create a new kernel instance - /// - /// New kernel instance - public static Kernel Create() - { - var builder = new KernelBuilder(); - return builder.Build(); - } + /// Initializes a new instance of the . + public KernelBuilder() { } - /// - /// Build a new kernel instance using the settings passed so far. - /// - /// Kernel instance + /// Constructs a new instance of using all of the settings configured on the builder. + /// The new instance. + /// + /// Every call to produces a new instance. The resulting + /// instances will not share the same plugins collection or services provider (unless there are no services). + /// public Kernel Build() { -#pragma warning disable CS8604 // Possible null reference argument. - var instance = new Kernel( - this._aiServices.Build(), - new KernelPluginCollection(), - this._serviceSelector, - this._httpHandlerFactory, - this._loggerFactory - ); -#pragma warning restore CS8604 // Possible null reference argument. + IServiceProvider serviceProvider; + if (this._services is { Count: > 0 } services) + { + // This is a workaround for Microsoft.Extensions.DependencyInjection's GetKeyedServices not currently supporting + // enumerating all services for a given type regardless of key. + // https://github.com/dotnet/runtime/issues/91466 + // We need this support to, for example, allow IServiceSelector to pick from multiple named instances of an AI + // service based on their characteristics. Until that is addressed, we work around it by injecting as a service all + // of the keys used for a given type, such that Kernel can then query for this dictionary and enumerate it. This means + // that such functionality will work when KernelBuilder is used to build the kernel but not when the IServiceProvider + // is created via other means, such as if Kernel is directly created by DI. However, it allows us to create the APIs + // the way we want them for the longer term and then subsequently fix the implementation when M.E.DI is fixed. + Dictionary> typeToKeyMappings = new(); + foreach (ServiceDescriptor serviceDescriptor in services) + { + if (!typeToKeyMappings.TryGetValue(serviceDescriptor.ServiceType, out List? keys)) + { + typeToKeyMappings[serviceDescriptor.ServiceType] = keys = new(); + } + + keys.Add(serviceDescriptor.ServiceKey); + } + services.AddKeyedSingleton(Kernel.KernelServiceTypeToKeyMappingsKey, typeToKeyMappings); + + serviceProvider = services.BuildServiceProvider(); + } + else + { + serviceProvider = EmptyServiceProvider.Instance; + } + + KernelPluginCollection? plugins = null; + if (this._pluginCallbacks is { } pluginCallbacks) + { + plugins = new KernelPluginCollection(); + pluginCallbacks(serviceProvider, plugins); + } + + var instance = new Kernel(serviceProvider, plugins); if (this._culture != null) { @@ -55,110 +92,84 @@ public Kernel Build() } /// - /// Add a logger to the kernel to be built. + /// Configures the services to be available through the . /// - /// The to use for logging. If null, no logging will be performed. - /// Updated kernel builder including the logger. - public KernelBuilder WithLoggerFactory(ILoggerFactory loggerFactory) + /// Callback invoked as part of this call. It's passed the service collection to manipulate. + /// This instance. + /// The callback will be invoked synchronously as part of the call to . + public KernelBuilder ConfigureServices(Action configureServices) { - Verify.NotNull(loggerFactory); - this._loggerFactory = loggerFactory; - return this; - } + Verify.NotNull(configureServices); - /// - /// Add a http handler factory to the kernel to be built. - /// - /// Http handler factory to add. - /// Updated kernel builder including the http handler factory. - public KernelBuilder WithHttpHandlerFactory(IDelegatingHandlerFactory httpHandlerFactory) - { - Verify.NotNull(httpHandlerFactory); - this._httpHandlerFactory = httpHandlerFactory; - return this; - } + this._services ??= new(); + configureServices(this._services); - /// - /// Adds a instance to the services collection - /// - /// The instance. - public KernelBuilder WithDefaultAIService(TService instance) where TService : IAIService - { - this._aiServices.SetService(instance); return this; } /// - /// Adds a factory method to the services collection + /// Configures the plugins to be available through the . /// - /// The factory method that creates the AI service instances of type . - public KernelBuilder WithDefaultAIService(Func factory) where TService : IAIService + /// Callback to invoke to add plugins. + /// This instance. + /// The callback will be invoked as part of each call to . + public KernelBuilder ConfigurePlugins(Action> configurePlugins) { - this._aiServices.SetService(() => factory(this._loggerFactory)); - return this; - } + Verify.NotNull(configurePlugins); - /// - /// Adds a instance to the services collection - /// - /// The service ID - /// The instance. - /// Optional: set as the default AI service for type - public KernelBuilder WithAIService( - string? serviceId, - TService instance, - bool setAsDefault = false) where TService : IAIService - { - this._aiServices.SetService(serviceId, instance, setAsDefault); - return this; - } + this._pluginCallbacks += (_, plugins) => configurePlugins(plugins); - /// - /// Adds a factory method to the services collection - /// - /// The service ID - /// The factory method that creates the AI service instances of type . - /// Optional: set as the default AI service for type - public KernelBuilder WithAIService( - string? serviceId, - Func factory, - bool setAsDefault = false) where TService : IAIService - { - this._aiServices.SetService(serviceId, () => factory(this._loggerFactory), setAsDefault); return this; } /// - /// Adds a factory method to the services collection + /// Configures the plugins to be available through the . /// - /// The service ID - /// The factory method that creates the AI service instances of type . - /// Optional: set as the default AI service for type - public KernelBuilder WithAIService( - string? serviceId, - Func factory, - bool setAsDefault = false) where TService : IAIService + /// Callback to invoke to add plugins. + /// This instance. + /// + /// The callback will be invoked as part of each call to . It is passed the same + /// that will be provided to the so that the callback can resolve services necessary to create plugins. + /// + public KernelBuilder ConfigurePlugins(Action> configurePlugins) { - this._aiServices.SetService(serviceId, () => factory(this._loggerFactory, this._httpHandlerFactory), setAsDefault); + Verify.NotNull(configurePlugins); + + this._pluginCallbacks += configurePlugins; + return this; } - /// - /// Adds a to the builder - /// - public KernelBuilder WithAIServiceSelector(IAIServiceSelector serviceSelector) + /// Sets a culture to be used by the . + /// The culture. + public KernelBuilder WithCulture(CultureInfo? culture) { - this._serviceSelector = serviceSelector; + this._culture = culture; + return this; } - /// - /// Sets a culture to be used by the kernel. - /// - /// The culture. - public KernelBuilder WithCulture(CultureInfo culture) + /// Configures the services to contain the specified singleton . + /// The to use to select an AI service from those registered in the kernel. + /// This instance. + public KernelBuilder WithAIServiceSelector(IAIServiceSelector? aiServiceSelector) => this.WithSingleton(aiServiceSelector); + + /// Configures the services to contain the specified singleton . + /// The logger factory. If null, no logger factory will be registered. + /// This instance. + public KernelBuilder WithLoggerFactory(ILoggerFactory? loggerFactory) => this.WithSingleton(loggerFactory); + + /// Configures the services to contain the specified singleton. + /// Specifies the service type. + /// The singleton instance. + /// This instance. + private KernelBuilder WithSingleton(T? instance) where T : class { - this._culture = culture; + if (instance is not null) + { + (this._services ??= new()).AddSingleton(instance); + } + return this; } } diff --git a/dotnet/src/SemanticKernel.Core/KernelExtensions.cs b/dotnet/src/SemanticKernel.Core/KernelExtensions.cs index 9aee7d018436..7a9c6fe5e04e 100644 --- a/dotnet/src/SemanticKernel.Core/KernelExtensions.cs +++ b/dotnet/src/SemanticKernel.Core/KernelExtensions.cs @@ -8,6 +8,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.Orchestration; @@ -39,7 +40,7 @@ public static KernelFunction CreateFunctionFromMethod( { Verify.NotNull(kernel); - return KernelFunctionFactory.CreateFromMethod(method.Method, method.Target, functionName, description, parameters, returnParameter, kernel.LoggerFactory); + return KernelFunctionFactory.CreateFromMethod(method.Method, method.Target, functionName, description, parameters, returnParameter, kernel.GetService()); } /// @@ -65,7 +66,7 @@ public static KernelFunction CreateFunctionFromMethod( { Verify.NotNull(kernel); - return KernelFunctionFactory.CreateFromMethod(method, target, functionName, description, parameters, returnParameter, kernel.LoggerFactory); + return KernelFunctionFactory.CreateFromMethod(method, target, functionName, description, parameters, returnParameter, kernel.GetService()); } #endregion @@ -94,7 +95,7 @@ public static KernelFunction CreateFunctionFromPrompt( { Verify.NotNull(kernel); - return KernelFunctionFactory.CreateFromPrompt(promptTemplate, executionSettings, functionName, description, promptTemplateFactory, kernel.LoggerFactory); + return KernelFunctionFactory.CreateFromPrompt(promptTemplate, executionSettings, functionName, description, promptTemplateFactory, kernel.GetService()); } /// @@ -111,7 +112,7 @@ public static KernelFunction CreateFunctionFromPrompt( { Verify.NotNull(kernel); - return KernelFunctionFactory.CreateFromPrompt(promptConfig, promptTemplateFactory, kernel.LoggerFactory); + return KernelFunctionFactory.CreateFromPrompt(promptConfig, promptTemplateFactory, kernel.GetService()); } /// @@ -130,7 +131,7 @@ public static KernelFunction CreateFunctionFromPrompt( { Verify.NotNull(kernel); - return KernelFunctionFactory.CreateFromPrompt(promptTemplate, promptConfig, kernel.LoggerFactory); + return KernelFunctionFactory.CreateFromPrompt(promptTemplate, promptConfig, kernel.GetService()); } #endregion @@ -149,7 +150,7 @@ public static IKernelPlugin CreatePluginFromObject(this Kernel kernel, string { Verify.NotNull(kernel); - return KernelPluginFactory.CreateFromObject(pluginName, kernel.LoggerFactory); + return KernelPluginFactory.CreateFromObject(pluginName, kernel.GetService()); } /// Creates a plugin that wraps the specified target object. @@ -165,7 +166,7 @@ public static IKernelPlugin CreatePluginFromObject(this Kernel kernel, object ta { Verify.NotNull(kernel); - return KernelPluginFactory.CreateFromObject(target, pluginName, kernel.LoggerFactory); + return KernelPluginFactory.CreateFromObject(target, pluginName, kernel.GetService()); } #endregion @@ -187,6 +188,24 @@ public static IKernelPlugin ImportPluginFromObject(this Kernel kernel, string return plugin; } + /// Creates a plugin that wraps a new instance of the specified type and adds it into the plugin collection. + /// Specifies the type of the object to wrap. + /// The plugin collection to which the new plugin should be added. + /// + /// Name of the plugin for function collection and prompt templates. If the value is null, a plugin name is derived from the type of the . + /// + /// Service provider from which to resolve dependencies, such as . + /// + /// Public methods that have the attribute will be included in the plugin. + /// + public static IKernelPlugin AddPluginFromObject(this ICollection plugins, string? pluginName = null, IServiceProvider? serviceProvider = null) + where T : new() + { + IKernelPlugin plugin = KernelPluginFactory.CreateFromObject(pluginName, serviceProvider?.GetService()); + plugins.Add(plugin); + return plugin; + } + /// Creates a plugin that wraps the specified target object and imports it into the 's plugin collection. /// The kernel. /// The instance of the class to be wrapped. @@ -202,6 +221,23 @@ public static IKernelPlugin ImportPluginFromObject(this Kernel kernel, object ta kernel.Plugins.Add(plugin); return plugin; } + + /// Creates a plugin that wraps the specified target object and adds it into the plugin collection. + /// The plugin collection to which the new plugin should be added. + /// The instance of the class to be wrapped. + /// + /// Name of the plugin for function collection and prompt templates. If the value is null, a plugin name is derived from the type of the . + /// + /// Service provider from which to resolve dependencies, such as . + /// + /// Public methods that have the attribute will be included in the plugin. + /// + public static IKernelPlugin AddPluginFromObject(this ICollection plugins, object target, string? pluginName = null, IServiceProvider? serviceProvider = null) + { + IKernelPlugin plugin = KernelPluginFactory.CreateFromObject(target, pluginName, serviceProvider?.GetService()); + plugins.Add(plugin); + return plugin; + } #endregion /// Creates a plugin containing one function per child directory of the specified . @@ -248,10 +284,10 @@ public static IKernelPlugin CreatePluginFromPromptDirectory( Verify.ValidPluginName(pluginName, kernel.Plugins); Verify.DirectoryExists(pluginDirectory); - var factory = promptTemplateFactory ?? new KernelPromptTemplateFactory(kernel.LoggerFactory); + var factory = promptTemplateFactory ?? new KernelPromptTemplateFactory(kernel.GetService()); KernelPlugin plugin = new(pluginName); - ILogger logger = kernel.LoggerFactory.CreateLogger(typeof(Kernel)); + ILogger logger = kernel.GetService().CreateLogger(typeof(Kernel)); foreach (string functionDirectory in Directory.EnumerateDirectories(pluginDirectory)) { diff --git a/dotnet/src/SemanticKernel.Core/Reliability/NullHttpRetryHandler.cs b/dotnet/src/SemanticKernel.Core/Reliability/NullHttpRetryHandler.cs deleted file mode 100644 index a12528df0526..000000000000 --- a/dotnet/src/SemanticKernel.Core/Reliability/NullHttpRetryHandler.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Net.Http; -using Microsoft.Extensions.Logging; -using Microsoft.SemanticKernel.Http; - -namespace Microsoft.SemanticKernel.Reliability; - -/// -/// A factory for creating instances of . -/// -public class NullHttpRetryHandlerFactory : IDelegatingHandlerFactory -{ - /// - /// Creates a new instance of . - /// - /// The to use for logging. If null, no logging will be performed. - /// A new instance of . - public DelegatingHandler Create(ILoggerFactory? loggerFactory) - { - return new NullHttpRetryHandler(); - } -} - -/// -/// A HTTP retry handler that does not retry. -/// -/// -/// This handler is useful when you want to disable retry functionality in your HTTP requests. -/// -public class NullHttpRetryHandler : DelegatingHandler -{ -} diff --git a/dotnet/src/SemanticKernel.Core/SemanticKernel.Core.csproj b/dotnet/src/SemanticKernel.Core/SemanticKernel.Core.csproj index 98d59c8947a3..3ecbbe8bc79d 100644 --- a/dotnet/src/SemanticKernel.Core/SemanticKernel.Core.csproj +++ b/dotnet/src/SemanticKernel.Core/SemanticKernel.Core.csproj @@ -26,6 +26,10 @@ + + + + diff --git a/dotnet/src/SemanticKernel.Core/Services/AIServiceCollection.cs b/dotnet/src/SemanticKernel.Core/Services/AIServiceCollection.cs deleted file mode 100644 index 4f65f8f3d545..000000000000 --- a/dotnet/src/SemanticKernel.Core/Services/AIServiceCollection.cs +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Microsoft.SemanticKernel.Services; - -/// -/// A collection of AI services that can be registered and built into an . -/// -[System.Diagnostics.CodeAnalysis.SuppressMessage("Naming", "CA1711:Identifiers should not have incorrect suffix")] -public class AIServiceCollection -{ - // A constant key for the default service - private const string DefaultKey = "__DEFAULT__"; - - // A dictionary that maps a service type to a nested dictionary of names and service instances or factories - private readonly Dictionary>> _services = new(); - - // A dictionary that maps a service type to the name of the default service - private readonly Dictionary _defaultIds = new(); - - /// - /// Registers a singleton service instance with the default name. - /// - /// The type of the service. - /// The service instance. - /// The service instance is null. - public void SetService(T service) where T : IAIService - => this.SetService(DefaultKey, service, true); - - /// - /// Registers a singleton service instance with an optional name and default flag. - /// - /// The type of the service. - /// The name of the service, or null for the default service. - /// The service instance. - /// Whether the service should be the default for its type. - /// The service instance is null. - /// The name is empty or whitespace. - public void SetService(string? name, T service, bool setAsDefault = false) where T : IAIService - => this.SetService(name, (() => service), setAsDefault); - - /// - /// Registers a transient service factory with the default name. - /// - /// The type of the service. - /// The factory function to create the service instance. - /// The factory function is null. - public void SetService(Func factory) where T : IAIService - => this.SetService(DefaultKey, factory, true); - - /// - /// Registers a transient service factory with an optional name and default flag. - /// - /// The type of the service. - /// The name of the service, or null for the default service. - /// The factory function to create the service instance. - /// Whether the service should be the default for its type. - /// The factory function is null. - /// The name is empty or whitespace. - public void SetService(string? name, Func factory, bool setAsDefault = false) where T : IAIService - { - // Validate the factory function - Verify.NotNull(factory); - - // Get or create the nested dictionary for the service type - var type = typeof(T); - if (!this._services.TryGetValue(type, out var namedServices)) - { - namedServices = new(); - this._services[type] = namedServices; - } - - // Set as the default if the name is empty, or the default flag is true, - // or there is no default name for the service type. - if (name == null || setAsDefault || !this.HasDefault()) - { - // Update the default name for the service type - this._defaultIds[type] = name ?? DefaultKey; - } - - var objectFactory = factory as Func; - - // Register the factory with the given name - namedServices[name ?? DefaultKey] = objectFactory - ?? throw new InvalidOperationException("Service factory is an invalid format"); - } - - /// - /// Builds an from the registered services and default names. - /// - /// An containing the registered services. - public IAIServiceProvider Build() - { - // Create a clone of the services and defaults Dictionaries to prevent further changes - // by the services provider. - var servicesClone = this._services.ToDictionary( - typeCollection => typeCollection.Key, - typeCollection => typeCollection.Value.ToDictionary( - service => service.Key, - service => service.Value)); - - var defaultsClone = this._defaultIds.ToDictionary( - typeDefault => typeDefault.Key, - typeDefault => typeDefault.Value); - - return new AIServiceProvider(servicesClone, defaultsClone); - } - - private bool HasDefault() where T : IAIService - => this._defaultIds.TryGetValue(typeof(T), out var defaultName) - && !string.IsNullOrEmpty(defaultName); -} diff --git a/dotnet/src/SemanticKernel.Core/Services/AIServiceProvider.cs b/dotnet/src/SemanticKernel.Core/Services/AIServiceProvider.cs deleted file mode 100644 index d3cec01a3031..000000000000 --- a/dotnet/src/SemanticKernel.Core/Services/AIServiceProvider.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; - -namespace Microsoft.SemanticKernel.Services; -/// -/// Provides AI services by managing a collection of named service instances. -/// -public class AIServiceProvider : NamedServiceProvider, IAIServiceProvider -{ - /// - /// Initializes a new instance of the class. - /// - /// A dictionary of service types and their corresponding named instances. - /// A dictionary of service types and their default instance names. - public AIServiceProvider(Dictionary>> services, Dictionary defaultIds) - : base(services, defaultIds) - { - } -} diff --git a/dotnet/src/SemanticKernel.Core/Services/NamedServiceProvider.cs b/dotnet/src/SemanticKernel.Core/Services/NamedServiceProvider.cs deleted file mode 100644 index 8036fae2a785..000000000000 --- a/dotnet/src/SemanticKernel.Core/Services/NamedServiceProvider.cs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Microsoft.SemanticKernel.Services; - -/// -/// Provides named services of type . Allows for the registration and retrieval of services by name. -/// -/// The type of service provided by this provider. -public class NamedServiceProvider : INamedServiceProvider -{ - // A dictionary that maps a service type to a nested dictionary of names and service instances or factories - private readonly Dictionary>> _services; - - // A dictionary that maps a service type to the name of the default service - private readonly Dictionary _defaultIds; - - /// - /// Initializes a new instance of the class. - /// - /// A dictionary that maps a service type to a nested dictionary of names and service instances or factories. - /// A dictionary that maps a service type to the name of the default service. - public NamedServiceProvider( - Dictionary>> services, - Dictionary defaultIds) - { - this._services = services; - this._defaultIds = defaultIds; - } - - /// - public T? GetService(string? name = null) where T : TService - { - // Return the service, casting or invoking the factory if needed - var factory = this.GetServiceFactory(name); - if (factory is Func) - { - return factory.Invoke(); - } - - return default; - } - - /// - private string? GetDefaultServiceName() where T : TService - { - // Returns the name of the default service for the given type, or null if none - var type = typeof(T); - if (this._defaultIds.TryGetValue(type, out var name)) - { - return name; - } - - return null; - } - - /// - public ICollection GetServices() where T : TService - { - if (typeof(T) == typeof(TService)) - { - return this.GetAllServices(); - } - - if (this._services.TryGetValue(typeof(T), out var namedServices)) - { - return namedServices.Values.Select(f => f.Invoke()).Cast().ToList(); - } - - return Array.Empty(); - } - - private HashSet GetAllServices() - { - HashSet services = new(); - foreach (var namedServices in this._services.Values) - { - services.UnionWith(namedServices.Values.Select(f => f.Invoke()).Cast()); - } - - return services; - } - - private Func? GetServiceFactory(string? name = null) where T : TService - { - // Get the nested dictionary for the service type - if (this._services.TryGetValue(typeof(T), out var namedServices)) - { - Func? serviceFactory = null; - - // If the name is not specified, try to load the default factory - name ??= this.GetDefaultServiceName(); - if (name != null) - { - // Check if there is a service registered with the given name - namedServices.TryGetValue(name, out serviceFactory); - } - - return serviceFactory as Func; - } - - return null; - } -} diff --git a/dotnet/src/SemanticKernel.Core/TemplateEngine/Blocks/CodeBlock.cs b/dotnet/src/SemanticKernel.Core/TemplateEngine/Blocks/CodeBlock.cs index 6b331030b9b3..272b3850d058 100644 --- a/dotnet/src/SemanticKernel.Core/TemplateEngine/Blocks/CodeBlock.cs +++ b/dotnet/src/SemanticKernel.Core/TemplateEngine/Blocks/CodeBlock.cs @@ -22,7 +22,7 @@ internal sealed class CodeBlock : Block, ICodeRendering /// /// Block content /// The to use for logging. If null, no logging will be performed. - public CodeBlock(string? content, ILoggerFactory? loggerFactory) + public CodeBlock(string? content, ILoggerFactory? loggerFactory = null) : this(new CodeTokenizer(loggerFactory).Tokenize(content), content?.Trim(), loggerFactory) { } @@ -33,7 +33,7 @@ public CodeBlock(string? content, ILoggerFactory? loggerFactory) /// A list of blocks /// Block content /// The to use for logging. If null, no logging will be performed. - public CodeBlock(List tokens, string? content, ILoggerFactory? loggerFactory) + public CodeBlock(List tokens, string? content, ILoggerFactory? loggerFactory = null) : base(content?.Trim(), loggerFactory) { this._tokens = tokens; diff --git a/dotnet/src/SemanticKernel.MetaPackage/SemanticKernel.MetaPackage.csproj b/dotnet/src/SemanticKernel.MetaPackage/SemanticKernel.MetaPackage.csproj index 809160b90cb9..0b3fb585b7a7 100644 --- a/dotnet/src/SemanticKernel.MetaPackage/SemanticKernel.MetaPackage.csproj +++ b/dotnet/src/SemanticKernel.MetaPackage/SemanticKernel.MetaPackage.csproj @@ -12,7 +12,6 @@ Empowers app owners to integrate cutting-edge LLM technology quickly and easily into their apps. - diff --git a/dotnet/src/SemanticKernel.UnitTests/AI/ChatCompletion/ChatCompletionServiceExtensionTests.cs b/dotnet/src/SemanticKernel.UnitTests/AI/ChatCompletion/ChatCompletionServiceExtensionTests.cs deleted file mode 100644 index 86788477d4e7..000000000000 --- a/dotnet/src/SemanticKernel.UnitTests/AI/ChatCompletion/ChatCompletionServiceExtensionTests.cs +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.AI.ChatCompletion; -using Microsoft.SemanticKernel.Services; -using Moq; -using Xunit; - -namespace SemanticKernel.UnitTests.AI.ChatCompletion; - -/// -/// Unit tests of . -/// -public class ChatCompletionServiceExtensionsTests -{ - [Fact] - public void ItCanAddChatCompletionServiceInstance() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId = "test"; - var instance = Mock.Of(); - - // Act - services.SetService(serviceId, instance); - var provider = services.Build(); - - // Assert - Assert.True(provider.TryGetService(serviceId, out var instanceRetrieved)); - Assert.Same(instance, instanceRetrieved); - } - - [Fact] - public void ItCanAddChatCompletionServiceFactory() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId = "test"; - var instance = Mock.Of(); - var factory = new Func(() => instance); - - // Act - services.SetService(serviceId, factory); - var provider = services.Build(); - - // Assert - Assert.True(provider.TryGetService(serviceId, out _)); - } - - [Fact] - public void ItCanSetDefaultChatCompletionService() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId1 = "test1"; - var serviceId2 = "test2"; - var instance1 = Mock.Of(); - var instance2 = Mock.Of(); - services.SetService(serviceId1, instance1); - - // Act - services.SetService(serviceId2, instance2, true); - var provider = services.Build(); - - // Assert - Assert.True(provider.TryGetService(out var instanceRetrieved)); - Assert.Same(instance2, instanceRetrieved); - } - - [Fact] - public void ItReturnsFalseIfNoDefaultChatCompletionServiceIsSet() - { - // Arrange - var services = new AIServiceCollection(); - var provider = services.Build(); - - // Assert - Assert.False(provider.TryGetService(out var instanceRetrieved)); - } - - [Fact] - public void ItReturnsTrueIfHasChatCompletionServiceWithValidId() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId = "test"; - var instance = Mock.Of(); - services.SetService(serviceId, instance); - - // Act - var provider = services.Build(); - var result = provider.HasChatCompletionService(serviceId); - - // Assert - Assert.True(result); - } - - [Fact] - public void ItReturnsFalseIfHasChatCompletionServiceWithInvalidId() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId1 = "test1"; - var serviceId2 = "test2"; - var instance = Mock.Of(); - services.SetService(serviceId1, instance); - var provider = services.Build(); - - // Act - var result = provider.HasChatCompletionService(serviceId2); - - // Assert - Assert.False(result); - } - - [Fact] - public void ItReturnsTrueIfHasChatCompletionServiceWithNullIdAndDefaultIsSet() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId = "test"; - var instance = Mock.Of(); - services.SetService(serviceId, instance, setAsDefault: true); - var provider = services.Build(); - - // Act - var result = provider.HasChatCompletionService(); - - // Assert - Assert.True(result); - } - - [Fact] - public void ItReturnsFalseIfHasChatCompletionServiceWithNullIdAndNoDefaultExists() - { - // Arrange - var services = new AIServiceCollection(); - var provider = services.Build(); - - // Act - var result = provider.HasChatCompletionService(); - - // Assert - Assert.False(result); - } -} diff --git a/dotnet/src/SemanticKernel.UnitTests/AI/Embeddings/TextEmbeddingServiceExtensionTests.cs b/dotnet/src/SemanticKernel.UnitTests/AI/Embeddings/TextEmbeddingServiceExtensionTests.cs deleted file mode 100644 index 0638ce0085d3..000000000000 --- a/dotnet/src/SemanticKernel.UnitTests/AI/Embeddings/TextEmbeddingServiceExtensionTests.cs +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.AI.Embeddings; -using Microsoft.SemanticKernel.Services; -using Moq; -using Xunit; - -namespace SemanticKernel.UnitTests.AI.Embeddings; - -/// -/// Unit tests of . -/// -public class TextEmbeddingServiceExtensionsTests -{ - [Fact] - public void ItCanAddTextEmbeddingServiceInstance() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId = "test"; - var instance = Mock.Of(); - - // Act - services.SetService(serviceId, instance); - var provider = services.Build(); - - // Assert - Assert.True(provider.TryGetService(serviceId, out var instanceRetrieved)); - Assert.Same(instance, instanceRetrieved); - } - - [Fact] - public void ItCanAddTextEmbeddingServiceFactory() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId = "test"; - var instance = Mock.Of(); - var factory = new Func(() => instance); - - // Act - services.SetService(serviceId, factory); - var provider = services.Build(); - - // Assert - Assert.True(provider.TryGetService(serviceId, out _)); - } - - [Fact] - public void ItCanSetDefaultTextEmbeddingService() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId1 = "test1"; - var serviceId2 = "test2"; - var instance1 = Mock.Of(); - var instance2 = Mock.Of(); - services.SetService(serviceId1, instance1); - - // Act - services.SetService(serviceId2, instance2, setAsDefault: true); - var provider = services.Build(); - - // Assert - Assert.True(provider.TryGetService(out var instanceRetrieved)); - Assert.Same(instance2, instanceRetrieved); - } - - [Fact] - public void ItReturnsFalseIfNoDefaultTextEmbeddingServiceIsSet() - { - // Arrange - var services = new AIServiceCollection(); - var provider = services.Build(); - - // Assert - Assert.False(provider.TryGetService(out var instanceRetrieved)); - } - - [Fact] - public void ItReturnsTrueIfHasTextEmbeddingServiceWithValidId() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId = "test"; - var instance = Mock.Of(); - services.SetService(serviceId, instance); - var provider = services.Build(); - - // Act - var result = provider.HasTextEmbeddingService(serviceId); - - // Assert - Assert.True(result); - } - - [Fact] - public void ItReturnsFalseIfHasTextEmbeddingServiceWithInvalidId() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId1 = "test1"; - var serviceId2 = "test2"; - var instance = Mock.Of(); - services.SetService(serviceId1, instance); - var provider = services.Build(); - - // Act - var result = provider.HasTextEmbeddingService(serviceId2); - - // Assert - Assert.False(result); - } - - [Fact] - public void ItReturnsTrueIfHasTextEmbeddingServiceWithNullIdAndDefaultIsSet() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId = "test"; - var instance = Mock.Of(); - services.SetService(serviceId, instance, setAsDefault: true); - var provider = services.Build(); - - // Act - var result = provider.HasTextEmbeddingService(); - - // Assert - Assert.True(result); - } - - [Fact] - public void ItReturnsFalseIfHasTextEmbeddingServiceWithNullIdAndNoDefaultExists() - { - // Arrange - var services = new AIServiceCollection(); - var provider = services.Build(); - - // Act - var result = provider.HasTextEmbeddingService(); - - // Assert - Assert.False(result); - } -} diff --git a/dotnet/src/SemanticKernel.UnitTests/AI/ImageGeneration/ImageCompletionServiceExtensionTests.cs b/dotnet/src/SemanticKernel.UnitTests/AI/ImageGeneration/ImageCompletionServiceExtensionTests.cs deleted file mode 100644 index 165f5e636d76..000000000000 --- a/dotnet/src/SemanticKernel.UnitTests/AI/ImageGeneration/ImageCompletionServiceExtensionTests.cs +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.AI.ImageGeneration; -using Microsoft.SemanticKernel.Services; -using Moq; -using Xunit; - -namespace SemanticKernel.UnitTests.AI.ImageGeneration; - -/// -/// Unit tests of . -/// -public class ImageGenerationServiceExtensionsTests -{ - [Fact] - public void ItCanSetServiceImageGenerationInstance() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId = "test"; - var instance = Mock.Of(); - - // Act - services.SetService(serviceId, instance); - var provider = services.Build(); - - // Assert - Assert.True(provider.TryGetService(serviceId, out var instanceRetrieved)); - Assert.Same(instance, instanceRetrieved); - } - - [Fact] - public void ItCanSetServiceImageGenerationFactory() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId = "test"; - var instance = Mock.Of(); - var factory = new Func(() => instance); - - // Act - services.SetService(serviceId, factory); - var provider = services.Build(); - - // Assert - Assert.True(provider.TryGetService(out var instanceRetrieved)); - } - - [Fact] - public void ItCanSetDefaultImageGenerationService() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId1 = "test1"; - var serviceId2 = "test2"; - var instance1 = Mock.Of(); - var instance2 = Mock.Of(); - services.SetService(serviceId1, instance1); - - // Act - services.SetService(serviceId2, instance2, setAsDefault: true); - var provider = services.Build(); - - // Assert - Assert.True(provider.TryGetService(out var instanceRetrieved)); - Assert.Same(instance2, instanceRetrieved); - } - - [Fact] - public void ItReturnsFalseIfNoDefaultImageGenerationServiceIsSet() - { - // Arrange - var services = new AIServiceCollection(); - var provider = services.Build(); - - Assert.False(provider.TryGetService(out var instanceRetrieved)); - } - - [Fact] - public void ItReturnsTrueIfHasImageGenerationServiceWithValidId() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId = "test"; - var instance = Mock.Of(); - services.SetService(serviceId, instance); - var provider = services.Build(); - - // Act - var result = provider.HasImageGenerationService(serviceId); - - // Assert - Assert.True(result); - } - - [Fact] - public void ItReturnsFalseIfHasImageGenerationServiceWithInvalidId() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId1 = "test1"; - var serviceId2 = "test2"; - var instance = Mock.Of(); - services.SetService(serviceId1, instance); - var provider = services.Build(); - - // Act - var result = provider.HasImageGenerationService(serviceId2); - - // Assert - Assert.False(result); - } - - [Fact] - public void ItReturnsTrueIfHasImageGenerationServiceWithNullIdAndDefaultIsSet() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId = "test"; - var instance = Mock.Of(); - services.SetService(serviceId, instance, setAsDefault: true); - var provider = services.Build(); - - // Act - var result = provider.HasImageGenerationService(); - - // Assert - Assert.True(result); - } - - [Fact] - public void ItReturnsFalseIfHasImageGenerationServiceWithNullIdAndNoDefaultExists() - { - // Arrange - var services = new AIServiceCollection(); - var provider = services.Build(); - - // Act - var result = provider.HasImageGenerationService(); - - // Assert - Assert.False(result); - } -} diff --git a/dotnet/src/SemanticKernel.UnitTests/AI/TextCompletion/TextCompletionServiceExtensionTests.cs b/dotnet/src/SemanticKernel.UnitTests/AI/TextCompletion/TextCompletionServiceExtensionTests.cs deleted file mode 100644 index 689b88d115dd..000000000000 --- a/dotnet/src/SemanticKernel.UnitTests/AI/TextCompletion/TextCompletionServiceExtensionTests.cs +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using Microsoft.SemanticKernel; -using Microsoft.SemanticKernel.AI.TextCompletion; -using Microsoft.SemanticKernel.Services; -using Moq; -using Xunit; - -namespace SemanticKernel.UnitTests.AI.TextCompletion; - -/// -/// Unit tests of . -/// -public class TextCompletionServiceExtensionsTests -{ - [Fact] - public void ItCanAddTextCompletionServiceInstance() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId = "test"; - var instance = Mock.Of(); - - // Act - services.SetService(serviceId, instance); - var provider = services.Build(); - - // Assert - Assert.True(provider.TryGetService(serviceId, out var instanceRetrieved)); - Assert.Same(instance, instanceRetrieved); - } - - [Fact] - public void ItCanAddTextCompletionServiceFactory() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId = "test"; - var instance = Mock.Of(); - var factory = new Func(() => instance); - - // Act - services.SetService(serviceId, factory); - var provider = services.Build(); - - // Assert - Assert.True(provider.TryGetService(serviceId, out var _)); - } - - [Fact] - public void ItCanSetDefaultTextCompletionService() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId1 = "test1"; - var serviceId2 = "test2"; - var instance1 = Mock.Of(); - var instance2 = Mock.Of(); - services.SetService(serviceId1, instance1); - - // Act - services.SetService(serviceId2, instance2, setAsDefault: true); - var provider = services.Build(); - - // Assert - Assert.True(provider.TryGetService(out var instanceRetrieved)); - Assert.Same(instance2, instanceRetrieved); - } - - [Fact] - public void ItReturnsFalseIfNoDefaultTextCompletionServiceIsSet() - { - // Arrange - var services = new AIServiceCollection(); - var provider = services.Build(); - - // Assert - Assert.False(provider.TryGetService(out var _)); - } - - [Fact] - public void ItReturnsTrueIfHasTextCompletionServiceWithValidId() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId = "test"; - var instance = Mock.Of(); - services.SetService(serviceId, instance); - var provider = services.Build(); - - // Act - var result = provider.HasTextCompletionService(serviceId); - - // Assert - Assert.True(result); - } - - [Fact] - public void ItReturnsFalseIfHasTextCompletionServiceWithInvalidId() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId1 = "test1"; - var serviceId2 = "test2"; - var instance = Mock.Of(); - services.SetService(serviceId1, instance); - var provider = services.Build(); - - // Act - var result = provider.HasTextCompletionService(serviceId2); - - // Assert - Assert.False(result); - } - - [Fact] - public void ItReturnsTrueIfHasTextCompletionServiceWithNullIdAndDefaultIsSet() - { - // Arrange - var services = new AIServiceCollection(); - var serviceId = "test"; - var instance = Mock.Of(); - services.SetService(serviceId, instance, setAsDefault: true); - var provider = services.Build(); - - // Act - var result = provider.HasTextCompletionService(); - - // Assert - Assert.True(result); - } - - [Fact] - public void ItReturnsFalseIfHasTextCompletionServiceWithNullIdAndNoDefaultExists() - { - // Arrange - var services = new AIServiceCollection(); - var provider = services.Build(); - - // Act - var result = provider.HasTextCompletionService(); - - // Assert - Assert.False(result); - } -} diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/FunctionFromMethodTests.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/FunctionFromMethodTests.cs index 2dc09813452e..fc4eef1aa6d8 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Functions/FunctionFromMethodTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Functions/FunctionFromMethodTests.cs @@ -17,7 +17,7 @@ public class FunctionFromMethodTests public async Task InvokeStreamingAsyncShouldReturnOneChunkFromNonStreamingMethodAsync() { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); var nativeContent = "Full content result"; var sut = KernelFunctionFactory.CreateFromMethod(() => nativeContent); @@ -44,7 +44,7 @@ public async Task InvokeStreamingAsyncShouldReturnOneChunkFromNonStreamingMethod public async Task InvokeStreamingAsyncOnlySupportsInvokingEventAsync() { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); var sut = KernelFunctionFactory.CreateFromMethod(() => "any"); var invokedCalled = false; @@ -75,7 +75,7 @@ public async Task InvokeStreamingAsyncOnlySupportsInvokingEventAsync() public async Task InvokeStreamingAsyncInvokingCancellingShouldRenderNoChunksAsync() { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); var sut = KernelFunctionFactory.CreateFromMethod(() => "any"); kernel.FunctionInvoking += (object? sender, FunctionInvokingEventArgs e) => @@ -98,7 +98,7 @@ public async Task InvokeStreamingAsyncInvokingCancellingShouldRenderNoChunksAsyn public async Task InvokeStreamingAsyncInvokingSkippingShouldRenderNoChunksAsync() { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); var sut = KernelFunctionFactory.CreateFromMethod(() => "any"); kernel.FunctionInvoking += (object? sender, FunctionInvokingEventArgs e) => @@ -121,7 +121,7 @@ public async Task InvokeStreamingAsyncInvokingSkippingShouldRenderNoChunksAsync( public async Task InvokeStreamingAsyncUsingInvokedEventHasNoEffectAsync() { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); var sut = KernelFunctionFactory.CreateFromMethod(() => "any"); kernel.FunctionInvoked += (object? sender, FunctionInvokedEventArgs e) => diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/FunctionFromPromptTests.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/FunctionFromPromptTests.cs index c1ca208ba38e..c6fe5deebda5 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Functions/FunctionFromPromptTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Functions/FunctionFromPromptTests.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.AI.TextCompletion; @@ -23,10 +23,11 @@ public class FunctionFromPromptTests public void ItProvidesAccessToFunctionsViaFunctionCollection() { // Arrange - var factory = new Mock>(); - var kernel = new KernelBuilder() - .WithDefaultAIService(factory.Object) - .Build(); + var factory = new Mock>(); + var kernel = new KernelBuilder().ConfigureServices(c => + { + c.AddSingleton(factory.Object); + }).Build(); kernel.Plugins.Add(new KernelPlugin("jk", functions: new[] { kernel.CreateFunctionFromPrompt(promptTemplate: "Tell me a joke", functionName: "joker", description: "Nice fun") })); @@ -47,9 +48,10 @@ public async Task ItUsesChatSystemPromptWhenProvidedAsync(string providedSystemC mockTextCompletion.Setup(c => c.GetCompletionsAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(new[] { mockCompletionResult.Object }); mockCompletionResult.Setup(cr => cr.GetCompletionAsync(It.IsAny())).ReturnsAsync("llmResult"); - var kernel = new KernelBuilder() - .WithAIService("x", mockTextCompletion.Object) - .Build(); + var kernel = new KernelBuilder().ConfigureServices(c => + { + c.AddKeyedSingleton("x", mockTextCompletion.Object); + }).Build(); var promptConfig = new PromptTemplateConfig(); promptConfig.Template = "template"; @@ -67,35 +69,6 @@ public async Task ItUsesChatSystemPromptWhenProvidedAsync(string providedSystemC mockTextCompletion.Verify(a => a.GetCompletionsAsync("template", It.Is(c => c.ChatSystemPrompt == expectedSystemChatPrompt), It.IsAny()), Times.Once()); } - [Fact] - public async Task ItUsesDefaultServiceWhenSpecifiedAsync() - { - // Arrange - var mockTextCompletion1 = new Mock(); - var mockTextCompletion2 = new Mock(); - var mockCompletionResult = new Mock(); - - mockTextCompletion1.Setup(c => c.GetCompletionsAsync(It.IsAny(), null, It.IsAny())).ReturnsAsync(new[] { mockCompletionResult.Object }); - mockTextCompletion2.Setup(c => c.GetCompletionsAsync(It.IsAny(), null, It.IsAny())).ReturnsAsync(new[] { mockCompletionResult.Object }); - mockCompletionResult.Setup(cr => cr.GetCompletionAsync(It.IsAny())).ReturnsAsync("llmResult"); - - var kernel = new KernelBuilder() - .WithAIService("service1", mockTextCompletion1.Object, false) - .WithAIService("service2", mockTextCompletion2.Object, true) - .Build(); - - var promptConfig = new PromptTemplateConfig(); - promptConfig.Template = "template"; - var func = kernel.CreateFunctionFromPrompt(promptConfig); - - // Act - await kernel.InvokeAsync(func); - - // Assert - mockTextCompletion1.Verify(a => a.GetCompletionsAsync("template", null, It.IsAny()), Times.Never()); - mockTextCompletion2.Verify(a => a.GetCompletionsAsync("template", null, It.IsAny()), Times.Once()); - } - [Fact] public async Task ItUsesServiceIdWhenProvidedAsync() { @@ -108,10 +81,11 @@ public async Task ItUsesServiceIdWhenProvidedAsync() mockTextCompletion2.Setup(c => c.GetCompletionsAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(new[] { mockCompletionResult.Object }); mockCompletionResult.Setup(cr => cr.GetCompletionAsync(It.IsAny())).ReturnsAsync("llmResult"); - var kernel = new KernelBuilder() - .WithAIService("service1", mockTextCompletion1.Object, false) - .WithAIService("service2", mockTextCompletion2.Object, true) - .Build(); + var kernel = new KernelBuilder().ConfigureServices(c => + { + c.AddKeyedSingleton("service1", mockTextCompletion1.Object); + c.AddKeyedSingleton("service2", mockTextCompletion2.Object); + }).Build(); var promptConfig = new PromptTemplateConfig(); promptConfig.Template = "template"; @@ -133,10 +107,11 @@ public async Task ItFailsIfInvalidServiceIdIsProvidedAsync() var mockTextCompletion1 = new Mock(); var mockTextCompletion2 = new Mock(); - var kernel = new KernelBuilder() - .WithAIService("service1", mockTextCompletion1.Object, false) - .WithAIService("service2", mockTextCompletion2.Object, true) - .Build(); + var kernel = new KernelBuilder().ConfigureServices(c => + { + c.AddKeyedSingleton("service1", mockTextCompletion1.Object); + c.AddKeyedSingleton("service2", mockTextCompletion2.Object); + }).Build(); var promptConfig = new PromptTemplateConfig(); promptConfig.Template = "template"; @@ -147,7 +122,7 @@ public async Task ItFailsIfInvalidServiceIdIsProvidedAsync() var exception = await Assert.ThrowsAsync(() => kernel.InvokeAsync(func)); // Assert - Assert.Equal("Service of type Microsoft.SemanticKernel.AI.TextCompletion.ITextCompletion and name service3 not registered.", exception.Message); + Assert.Equal("Service of type Microsoft.SemanticKernel.AI.TextCompletion.ITextCompletion and names service3 not registered.", exception.Message); } [Fact] @@ -155,7 +130,7 @@ public async Task RunAsyncHandlesPreInvocationAsync() { // Arrange var (mockTextResult, mockTextCompletion) = this.SetupMocks(); - var sut = new KernelBuilder().WithAIService(null, mockTextCompletion.Object).Build(); + var sut = new KernelBuilder().ConfigureServices(c => c.AddSingleton(mockTextCompletion.Object)).Build(); var function = KernelFunctionFactory.CreateFromPrompt("Write a simple phrase about UnitTests"); var invoked = 0; @@ -178,7 +153,7 @@ public async Task RunAsyncHandlesPreInvocationWasCancelledAsync() { // Arrange var (mockTextResult, mockTextCompletion) = this.SetupMocks(); - var sut = new KernelBuilder().WithAIService(null, mockTextCompletion.Object).Build(); + var sut = new KernelBuilder().ConfigureServices(c => c.AddSingleton(mockTextCompletion.Object)).Build(); var function = KernelFunctionFactory.CreateFromPrompt("Write a simple phrase about UnitTests"); var input = "Test input"; var invoked = false; @@ -201,7 +176,7 @@ public async Task RunAsyncHandlesPreInvocationCancelationDontRunSubsequentFuncti { // Arrange var (mockTextResult, mockTextCompletion) = this.SetupMocks(); - var sut = new KernelBuilder().WithAIService(null, mockTextCompletion.Object).Build(); + var sut = new KernelBuilder().ConfigureServices(c => c.AddSingleton(mockTextCompletion.Object)).Build(); var function = KernelFunctionFactory.CreateFromPrompt("Write a simple phrase about UnitTests"); var invoked = 0; @@ -224,7 +199,7 @@ public async Task RunAsyncPreInvocationCancelationDontTriggerInvokedHandlerAsync { // Arrange var (mockTextResult, mockTextCompletion) = this.SetupMocks(); - var sut = new KernelBuilder().WithAIService(null, mockTextCompletion.Object).Build(); + var sut = new KernelBuilder().ConfigureServices(c => c.AddSingleton(mockTextCompletion.Object)).Build(); var function = KernelFunctionFactory.CreateFromPrompt("Write a simple phrase about UnitTests"); var invoked = 0; @@ -250,7 +225,7 @@ public async Task RunAsyncPreInvocationSkipDontTriggerInvokedHandlerAsync() { // Arrange var (mockTextResult, mockTextCompletion) = this.SetupMocks(); - var sut = new KernelBuilder().WithAIService(null, mockTextCompletion.Object).Build(); + var sut = new KernelBuilder().ConfigureServices(c => c.AddSingleton(mockTextCompletion.Object)).Build(); var function = KernelFunctionFactory.CreateFromPrompt("Write one phrase about UnitTests", functionName: "SkipMe"); var invoked = 0; var invoking = 0; @@ -285,7 +260,7 @@ public async Task RunAsyncHandlesPostInvocationAsync() { // Arrange var (mockTextResult, mockTextCompletion) = this.SetupMocks(); - var sut = new KernelBuilder().WithAIService(null, mockTextCompletion.Object).Build(); + var sut = new KernelBuilder().ConfigureServices(c => c.AddSingleton(mockTextCompletion.Object)).Build(); var function = KernelFunctionFactory.CreateFromPrompt("Write a simple phrase about UnitTests"); var invoked = 0; @@ -307,7 +282,7 @@ public async Task RunAsyncHandlesPostInvocationAsync() public async Task RunAsyncChangeVariableInvokingHandlerAsync() { var (mockTextResult, mockTextCompletion) = this.SetupMocks(); - var sut = new KernelBuilder().WithAIService(null, mockTextCompletion.Object).Build(); + var sut = new KernelBuilder().ConfigureServices(c => c.AddSingleton(mockTextCompletion.Object)).Build(); var prompt = "Write a simple phrase about UnitTests {{$input}}"; var function = KernelFunctionFactory.CreateFromPrompt(prompt); @@ -330,7 +305,7 @@ public async Task RunAsyncChangeVariableInvokingHandlerAsync() public async Task RunAsyncChangeVariableInvokedHandlerAsync() { var (mockTextResult, mockTextCompletion) = this.SetupMocks(); - var sut = new KernelBuilder().WithAIService(null, mockTextCompletion.Object).Build(); + var sut = new KernelBuilder().ConfigureServices(c => c.AddSingleton(mockTextCompletion.Object)).Build(); var prompt = "Write a simple phrase about UnitTests {{$input}}"; var function = KernelFunctionFactory.CreateFromPrompt(prompt); @@ -356,7 +331,7 @@ public async Task InvokeStreamingAsyncCallsConnectorStreamingApiAsync() var mockTextCompletion = this.SetupStreamingMocks( new TestStreamingContent("chunk1"), new TestStreamingContent("chunk2")); - var kernel = new KernelBuilder().WithAIService(null, mockTextCompletion.Object).Build(); + var kernel = new KernelBuilder().ConfigureServices(c => c.AddSingleton(mockTextCompletion.Object)).Build(); var prompt = "Write a simple phrase about UnitTests {{$input}}"; var sut = KernelFunctionFactory.CreateFromPrompt(prompt); var variables = new ContextVariables("importance"); diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionMetadataTests.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionMetadataTests.cs index 54daaa7c5432..475c22e95d7b 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionMetadataTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionMetadataTests.cs @@ -8,7 +8,6 @@ using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Orchestration; -using Microsoft.SemanticKernel.Services; using Moq; using Xunit; @@ -185,7 +184,7 @@ private static async Task ValidFunctionNameAsync() { var function = KernelFunctionFactory.CreateFromMethod(Method(ValidFunctionName)); var variables = new ContextVariables(string.Empty); - var result = await function.InvokeAsync(new Kernel(new Mock().Object), variables); + var result = await function.InvokeAsync(new Kernel(new Mock().Object), variables); } private static MethodInfo Method(Delegate method) diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionTests2.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionTests2.cs index d81aec493c0c..ecb37f40926d 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionTests2.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionTests2.cs @@ -25,7 +25,7 @@ public sealed class KernelFunctionTests2 public KernelFunctionTests2() { - this._kernel = KernelBuilder.Create(); + this._kernel = new Kernel(); this._logger = new Mock(); s_expected = Guid.NewGuid().ToString("D"); diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionTests3.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionTests3.cs index 2715207a8c97..e5ac184917de 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionTests3.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Functions/KernelFunctionTests3.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Orchestration; -using Microsoft.SemanticKernel.Services; using Moq; using Xunit; @@ -16,7 +15,7 @@ namespace SemanticKernel.UnitTests.Functions; public sealed class KernelFunctionTests3 { - private readonly Kernel _kernel = new(new Mock().Object); + private readonly Kernel _kernel = new(new Mock().Object); [Fact] public void ItDoesntThrowForValidFunctionsViaDelegate() @@ -45,7 +44,7 @@ public void ItDoesNotThrowForValidFunctionsViaPlugin() .Where(m => m.Name is not "GetType" and not "Equals" and not "GetHashCode" and not "ToString") .ToArray(); - KernelFunction[] functions = new KernelBuilder().Build().ImportPluginFromObject(pluginInstance).ToArray(); + KernelFunction[] functions = KernelPluginFactory.CreateFromObject(pluginInstance).ToArray(); // Act Assert.Equal(methods.Length, functions.Length); diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/MultipleModelTests.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/MultipleModelTests.cs index a8f07ec79471..042af05c47b4 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Functions/MultipleModelTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Functions/MultipleModelTests.cs @@ -2,6 +2,7 @@ using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.AI.TextCompletion; @@ -23,10 +24,11 @@ public async Task ItUsesServiceIdWhenProvidedAsync() mockTextCompletion2.Setup(c => c.GetCompletionsAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(new[] { mockCompletionResult.Object }); mockCompletionResult.Setup(cr => cr.GetCompletionAsync(It.IsAny())).ReturnsAsync("llmResult"); - var kernel = new KernelBuilder() - .WithAIService("service1", mockTextCompletion1.Object, false) - .WithAIService("service2", mockTextCompletion2.Object, true) - .Build(); + var kernel = new KernelBuilder().ConfigureServices(c => + { + c.AddKeyedSingleton("service1", mockTextCompletion1.Object); + c.AddKeyedSingleton("service2", mockTextCompletion2.Object); + }).Build(); var promptConfig = new PromptTemplateConfig(); promptConfig.Template = "template"; @@ -48,10 +50,11 @@ public async Task ItFailsIfInvalidServiceIdIsProvidedAsync() var mockTextCompletion1 = new Mock(); var mockTextCompletion2 = new Mock(); - var kernel = new KernelBuilder() - .WithAIService("service1", mockTextCompletion1.Object, false) - .WithAIService("service2", mockTextCompletion2.Object, true) - .Build(); + var kernel = new KernelBuilder().ConfigureServices(c => + { + c.AddKeyedSingleton("service1", mockTextCompletion1.Object); + c.AddKeyedSingleton("service2", mockTextCompletion2.Object); + }).Build(); var promptConfig = new PromptTemplateConfig(); promptConfig.Template = "template"; @@ -62,15 +65,13 @@ public async Task ItFailsIfInvalidServiceIdIsProvidedAsync() var exception = await Assert.ThrowsAsync(() => kernel.InvokeAsync(func)); // Assert - Assert.Equal("Service of type Microsoft.SemanticKernel.AI.TextCompletion.ITextCompletion and name service3 not registered.", exception.Message); + Assert.Equal("Service of type Microsoft.SemanticKernel.AI.TextCompletion.ITextCompletion and names service3 not registered.", exception.Message); } [Theory] - [InlineData(new string[] { "service1" }, 1, new int[] { 1, 0, 0 })] - [InlineData(new string[] { "service2" }, 2, new int[] { 0, 1, 0 })] - [InlineData(new string[] { "service3" }, 0, new int[] { 0, 0, 1 })] - [InlineData(new string[] { "service4", "service1" }, 1, new int[] { 1, 0, 0 })] - public async Task ItUsesServiceIdByOrderAsync(string[] serviceIds, int defaultServiceIndex, int[] callCount) + [InlineData(new string[] { "service1" }, new int[] { 1, 0, 0 })] + [InlineData(new string[] { "service4", "service1" }, new int[] { 1, 0, 0 })] + public async Task ItUsesServiceIdByOrderAsync(string[] serviceIds, int[] callCount) { // Arrange var mockTextCompletion1 = new Mock(); @@ -83,11 +84,12 @@ public async Task ItUsesServiceIdByOrderAsync(string[] serviceIds, int defaultSe mockTextCompletion3.Setup(c => c.GetCompletionsAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(new[] { mockCompletionResult.Object }); mockCompletionResult.Setup(cr => cr.GetCompletionAsync(It.IsAny())).ReturnsAsync("llmResult"); - var kernel = new KernelBuilder() - .WithAIService("service1", mockTextCompletion1.Object, defaultServiceIndex == 0) - .WithAIService("service2", mockTextCompletion2.Object, defaultServiceIndex == 1) - .WithAIService("service3", mockTextCompletion3.Object, defaultServiceIndex == 2) - .Build(); + var kernel = new KernelBuilder().ConfigureServices(c => + { + c.AddKeyedSingleton("service1", mockTextCompletion1.Object); + c.AddKeyedSingleton("service2", mockTextCompletion2.Object); + c.AddKeyedSingleton("service3", mockTextCompletion3.Object); + }).Build(); var promptConfig = new PromptTemplateConfig(); promptConfig.Template = "template"; @@ -120,11 +122,12 @@ public async Task ItUsesServiceIdWithJsonPromptTemplateConfigAsync() mockTextCompletion3.Setup(c => c.GetCompletionsAsync(It.IsAny(), It.IsAny(), It.IsAny())).ReturnsAsync(new[] { mockCompletionResult.Object }); mockCompletionResult.Setup(cr => cr.GetCompletionAsync(It.IsAny())).ReturnsAsync("llmResult"); - var kernel = new KernelBuilder() - .WithAIService("service1", mockTextCompletion1.Object, true) - .WithAIService("service2", mockTextCompletion2.Object, false) - .WithAIService("service3", mockTextCompletion3.Object, false) - .Build(); + var kernel = new KernelBuilder().ConfigureServices(c => + { + c.AddKeyedSingleton("service1", mockTextCompletion1.Object); + c.AddKeyedSingleton("service2", mockTextCompletion2.Object); + c.AddKeyedSingleton("service3", mockTextCompletion3.Object); + }).Build(); var json = @"{ ""template"": ""template"", diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs index 7bce68ec8f7f..0ab52976399f 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.AI.TextCompletion; @@ -18,7 +19,7 @@ public class OrderedIAIServiceConfigurationProviderTests public void ItThrowsAnSKExceptionForNoServices() { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); var function = KernelFunctionFactory.CreateFromPrompt("Hello AI"); var serviceSelector = new OrderedIAIServiceSelector(); @@ -31,7 +32,10 @@ public void ItThrowsAnSKExceptionForNoServices() public void ItGetsAIServiceConfigurationForSingleAIService() { // Arrange - var kernel = new KernelBuilder().WithAIService("service1", new AIService()).Build(); + var kernel = new KernelBuilder().ConfigureServices(c => + { + c.AddKeyedSingleton("service1", new AIService()); + }).Build(); var function = kernel.CreateFunctionFromPrompt("Hello AI"); var serviceSelector = new OrderedIAIServiceSelector(); @@ -47,7 +51,10 @@ public void ItGetsAIServiceConfigurationForSingleAIService() public void ItGetsAIServiceConfigurationForSingleTextCompletion() { // Arrange - var kernel = new KernelBuilder().WithAIService("service1", new TextCompletion()).Build(); + var kernel = new KernelBuilder().ConfigureServices(c => + { + c.AddKeyedSingleton("service1", new TextCompletion()); + }).Build(); var variables = new ContextVariables(); var function = kernel.CreateFunctionFromPrompt("Hello AI"); var serviceSelector = new OrderedIAIServiceSelector(); @@ -64,10 +71,11 @@ public void ItGetsAIServiceConfigurationForSingleTextCompletion() public void ItGetsAIServiceConfigurationForTextCompletionByServiceId() { // Arrange - var kernel = new KernelBuilder() - .WithAIService("service1", new TextCompletion()) - .WithAIService("service2", new TextCompletion()) - .Build(); + var kernel = new KernelBuilder().ConfigureServices(c => + { + c.AddKeyedSingleton("service1", new TextCompletion()); + c.AddKeyedSingleton("service2", new TextCompletion()); + }).Build(); var variables = new ContextVariables(); var executionSettings = new PromptExecutionSettings() { ServiceId = "service2" }; var function = kernel.CreateFunctionFromPrompt("Hello AI", executionSettings: executionSettings); @@ -77,7 +85,7 @@ public void ItGetsAIServiceConfigurationForTextCompletionByServiceId() (var aiService, var defaultExecutionSettings) = serviceSelector.SelectAIService(kernel, variables, function); // Assert - Assert.Equal(kernel.ServiceProvider.GetService("service2"), aiService); + Assert.Equal(kernel.GetService("service2"), aiService); Assert.Equal(executionSettings, defaultExecutionSettings); } @@ -85,10 +93,11 @@ public void ItGetsAIServiceConfigurationForTextCompletionByServiceId() public void ItThrowsAnSKExceptionForNotFoundService() { // Arrange - var kernel = new KernelBuilder() - .WithAIService("service1", new TextCompletion()) - .WithAIService("service2", new TextCompletion()) - .Build(); + var kernel = new KernelBuilder().ConfigureServices(c => + { + c.AddKeyedSingleton("service1", new TextCompletion()); + c.AddKeyedSingleton("service2", new TextCompletion()); + }).Build(); var variables = new ContextVariables(); var executionSettings = new PromptExecutionSettings() { ServiceId = "service3" }; var function = kernel.CreateFunctionFromPrompt("Hello AI", executionSettings: executionSettings); @@ -103,10 +112,11 @@ public void ItThrowsAnSKExceptionForNotFoundService() public void ItUsesDefaultServiceForEmptyModelSettings() { // Arrange - var kernel = new KernelBuilder() - .WithAIService("service1", new TextCompletion()) - .WithAIService("service2", new TextCompletion(), true) - .Build(); + var kernel = new KernelBuilder().ConfigureServices(c => + { + c.AddKeyedSingleton("service1", new TextCompletion()); + c.AddKeyedSingleton("service2", new TextCompletion()); + }).Build(); var variables = new ContextVariables(); var function = kernel.CreateFunctionFromPrompt("Hello AI"); var serviceSelector = new OrderedIAIServiceSelector(); @@ -115,7 +125,7 @@ public void ItUsesDefaultServiceForEmptyModelSettings() (var aiService, var defaultRequestSettings) = serviceSelector.SelectAIService(kernel, variables, function); // Assert - Assert.Equal(kernel.ServiceProvider.GetService("service2"), aiService); + Assert.Equal(kernel.GetService("service2"), aiService); Assert.Null(defaultRequestSettings); } @@ -124,10 +134,11 @@ public void ItUsesDefaultServiceAndSettings() { // Arrange // Arrange - var kernel = new KernelBuilder() - .WithAIService("service1", new TextCompletion()) - .WithAIService("service2", new TextCompletion(), true) - .Build(); + var kernel = new KernelBuilder().ConfigureServices(c => + { + c.AddKeyedSingleton("service1", new TextCompletion()); + c.AddKeyedSingleton("service2", new TextCompletion()); + }).Build(); var variables = new ContextVariables(); var executionSettings = new PromptExecutionSettings(); var function = kernel.CreateFunctionFromPrompt("Hello AI", executionSettings: executionSettings); @@ -137,7 +148,7 @@ public void ItUsesDefaultServiceAndSettings() (var aiService, var defaultRequestSettings) = serviceSelector.SelectAIService(kernel, variables, function); // Assert - Assert.Equal(kernel.ServiceProvider.GetService("service2"), aiService); + Assert.Equal(kernel.GetService("service2"), aiService); Assert.Equal(executionSettings, defaultRequestSettings); } @@ -145,10 +156,11 @@ public void ItUsesDefaultServiceAndSettings() public void ItUsesDefaultServiceAndSettingsEmptyServiceId() { // Arrange - var kernel = new KernelBuilder() - .WithAIService("service1", new TextCompletion()) - .WithAIService("service2", new TextCompletion(), true) - .Build(); + var kernel = new KernelBuilder().ConfigureServices(c => + { + c.AddKeyedSingleton("service1", new TextCompletion()); + c.AddKeyedSingleton("service2", new TextCompletion()); + }).Build(); var variables = new ContextVariables(); var executionSettings = new PromptExecutionSettings() { ServiceId = "" }; var function = kernel.CreateFunctionFromPrompt("Hello AI", executionSettings: executionSettings); @@ -158,7 +170,7 @@ public void ItUsesDefaultServiceAndSettingsEmptyServiceId() (var aiService, var defaultRequestSettings) = serviceSelector.SelectAIService(kernel, variables, function); // Assert - Assert.Equal(kernel.ServiceProvider.GetService("service2"), aiService); + Assert.Equal(kernel.GetService("service2"), aiService); Assert.Equal(executionSettings, defaultRequestSettings); } @@ -170,11 +182,12 @@ public void ItUsesDefaultServiceAndSettingsEmptyServiceId() public void ItGetsAIServiceConfigurationByOrder(string[] serviceIds, string expectedServiceId) { // Arrange - var kernel = new KernelBuilder() - .WithAIService("service1", new TextCompletion()) - .WithAIService("service2", new TextCompletion()) - .WithAIService("service3", new TextCompletion()) - .Build(); + var kernel = new KernelBuilder().ConfigureServices(c => + { + c.AddKeyedSingleton("service1", new TextCompletion()); + c.AddKeyedSingleton("service2", new TextCompletion()); + c.AddKeyedSingleton("service3", new TextCompletion()); + }).Build(); var variables = new ContextVariables(); var executionSettings = new List(); foreach (var serviceId in serviceIds) @@ -188,7 +201,7 @@ public void ItGetsAIServiceConfigurationByOrder(string[] serviceIds, string expe (var aiService, var defaultRequestSettings) = serviceSelector.SelectAIService(kernel, variables, function); // Assert - Assert.Equal(kernel.ServiceProvider.GetService(expectedServiceId), aiService); + Assert.Equal(kernel.GetService(expectedServiceId), aiService); Assert.Equal(expectedServiceId, defaultRequestSettings!.ServiceId); } diff --git a/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs b/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs index c132dea4363a..51c8a22653fc 100644 --- a/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs @@ -4,15 +4,15 @@ using System.ComponentModel; using System.Globalization; using System.Linq; +using System.Net.Http; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Events; -using Microsoft.SemanticKernel.Http; using Microsoft.SemanticKernel.Orchestration; -using Microsoft.SemanticKernel.Services; using Moq; using Xunit; @@ -26,10 +26,11 @@ public class KernelTests public void ItProvidesAccessToFunctionsViaFunctionCollection() { // Arrange - var factory = new Mock>(); - var kernel = new KernelBuilder() - .WithDefaultAIService(factory.Object) - .Build(); + var factory = new Mock>(); + var kernel = new KernelBuilder().ConfigureServices(c => + { + c.AddSingleton(factory.Object); + }).Build(); kernel.ImportPluginFromObject("mySk"); @@ -44,7 +45,7 @@ public void ItProvidesAccessToFunctionsViaFunctionCollection() public async Task RunAsyncDoesNotRunWhenCancelledAsync() { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); var functions = kernel.ImportPluginFromObject(); using CancellationTokenSource cts = new(); @@ -58,7 +59,7 @@ public async Task RunAsyncDoesNotRunWhenCancelledAsync() public async Task RunAsyncRunsWhenNotCancelledAsync() { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); kernel.ImportPluginFromObject("mySk"); using CancellationTokenSource cts = new(); @@ -74,7 +75,7 @@ public async Task RunAsyncRunsWhenNotCancelledAsync() public void ItImportsPluginsNotCaseSensitive() { // Act - IKernelPlugin plugin = new KernelBuilder().Build().ImportPluginFromObject(); + IKernelPlugin plugin = new Kernel().ImportPluginFromObject(); // Assert Assert.Equal(3, plugin.Count()); @@ -87,7 +88,7 @@ public void ItImportsPluginsNotCaseSensitive() public void ItAllowsToImportTheSamePluginMultipleTimes() { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); // Act - Assert no exception occurs kernel.ImportPluginFromObject(); @@ -100,7 +101,7 @@ public void ItAllowsToImportTheSamePluginMultipleTimes() public async Task RunAsyncHandlesPreInvocationAsync() { // Arrange - var sut = new KernelBuilder().Build(); + var sut = new Kernel(); int functionInvocations = 0; var function = KernelFunctionFactory.CreateFromMethod(() => functionInvocations++); @@ -122,7 +123,7 @@ public async Task RunAsyncHandlesPreInvocationAsync() public async Task RunStreamingAsyncHandlesPreInvocationAsync() { // Arrange - var sut = new KernelBuilder().Build(); + var sut = new Kernel(); int functionInvocations = 0; var function = KernelFunctionFactory.CreateFromMethod(() => functionInvocations++); @@ -144,7 +145,7 @@ public async Task RunStreamingAsyncHandlesPreInvocationAsync() public async Task RunStreamingAsyncHandlesPreInvocationWasCancelledAsync() { // Arrange - var sut = new KernelBuilder().Build(); + var sut = new Kernel(); int functionInvocations = 0; var function = KernelFunctionFactory.CreateFromMethod(() => functionInvocations++); @@ -172,7 +173,7 @@ public async Task RunStreamingAsyncHandlesPreInvocationWasCancelledAsync() public async Task RunStreamingAsyncPreInvocationCancelationDontTriggerInvokedHandlerAsync() { // Arrange - var sut = new KernelBuilder().Build(); + var sut = new Kernel(); var functions = sut.ImportPluginFromObject(); var invoked = 0; @@ -199,7 +200,7 @@ public async Task RunStreamingAsyncPreInvocationCancelationDontTriggerInvokedHan public async Task RunStreamingAsyncPreInvocationSkipDontTriggerInvokedHandlerAsync() { // Arrange - var sut = new KernelBuilder().Build(); + var sut = new Kernel(); int funcInvocations = 0; var function = KernelFunctionFactory.CreateFromMethod(() => funcInvocations++, functionName: "func1"); @@ -237,7 +238,7 @@ public async Task RunStreamingAsyncPreInvocationSkipDontTriggerInvokedHandlerAsy public async Task RunStreamingAsyncDoesNotHandlePostInvocationAsync() { // Arrange - var sut = new KernelBuilder().Build(); + var sut = new Kernel(); int functionInvocations = 0; var function = KernelFunctionFactory.CreateFromMethod(() => functionInvocations++); @@ -261,7 +262,7 @@ public async Task RunStreamingAsyncDoesNotHandlePostInvocationAsync() public async Task RunAsyncHandlesPreInvocationWasCancelledAsync() { // Arrange - var sut = new KernelBuilder().Build(); + var sut = new Kernel(); int functionInvocations = 0; var function = KernelFunctionFactory.CreateFromMethod(() => functionInvocations++); @@ -285,7 +286,7 @@ public async Task RunAsyncHandlesPreInvocationWasCancelledAsync() public async Task RunAsyncHandlesPreInvocationCancelationDontRunSubsequentFunctionsInThePipelineAsync() { // Arrange - var sut = new KernelBuilder().Build(); + var sut = new Kernel(); int functionInvocations = 0; var function = KernelFunctionFactory.CreateFromMethod(() => functionInvocations++); @@ -308,7 +309,7 @@ public async Task RunAsyncHandlesPreInvocationCancelationDontRunSubsequentFuncti public async Task RunAsyncPreInvocationCancelationDontTriggerInvokedHandlerAsync() { // Arrange - var sut = new KernelBuilder().Build(); + var sut = new Kernel(); var functions = sut.ImportPluginFromObject(); var invoked = 0; @@ -333,7 +334,7 @@ public async Task RunAsyncPreInvocationCancelationDontTriggerInvokedHandlerAsync public async Task RunAsyncPreInvocationSkipDontTriggerInvokedHandlerAsync() { // Arrange - var sut = new KernelBuilder().Build(); + var sut = new Kernel(); int funcInvocations = 0; var function = KernelFunctionFactory.CreateFromMethod(() => funcInvocations++, functionName: "func1"); @@ -369,7 +370,7 @@ public async Task RunAsyncPreInvocationSkipDontTriggerInvokedHandlerAsync() public async Task RunAsyncHandlesPostInvocationAsync() { // Arrange - var sut = new KernelBuilder().Build(); + var sut = new Kernel(); int functionInvocations = 0; var function = KernelFunctionFactory.CreateFromMethod(() => functionInvocations++); @@ -390,7 +391,7 @@ public async Task RunAsyncHandlesPostInvocationAsync() [Fact] public async Task RunAsyncChangeVariableInvokingHandlerAsync() { - var sut = new KernelBuilder().Build(); + var sut = new Kernel(); var function = KernelFunctionFactory.CreateFromMethod(() => { }); var originalInput = "Importance"; @@ -411,7 +412,7 @@ public async Task RunAsyncChangeVariableInvokingHandlerAsync() [Fact] public async Task RunAsyncChangeVariableInvokedHandlerAsync() { - var sut = new KernelBuilder().Build(); + var sut = new Kernel(); var function = KernelFunctionFactory.CreateFromMethod(() => { }); var originalInput = "Importance"; @@ -433,7 +434,7 @@ public async Task RunAsyncChangeVariableInvokedHandlerAsync() public async Task ItReturnsFunctionResultsCorrectlyAsync() { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); var function = KernelFunctionFactory.CreateFromMethod(() => "Result", "Function1"); @@ -448,7 +449,7 @@ public async Task ItReturnsFunctionResultsCorrectlyAsync() [Fact] public async Task ItReturnsChangedResultsFromFunctionInvokedEventsAsync() { - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); // Arrange var function1 = KernelFunctionFactory.CreateFromMethod(() => "Result1", "Function1"); @@ -471,7 +472,7 @@ public async Task ItReturnsChangedResultsFromFunctionInvokedEventsAsync() public async Task ItReturnsChangedResultsFromFunctionInvokingEventsAsync() { // Arrange - var kernel = new KernelBuilder().Build(); + var kernel = new Kernel(); var function1 = KernelFunctionFactory.CreateFromMethod((string injectedVariable) => injectedVariable, "Function1"); const string ExpectedValue = "injected value"; @@ -493,12 +494,12 @@ public async Task ItReturnsChangedResultsFromFunctionInvokingEventsAsync() public async Task ItCanFindAndRunFunctionAsync() { //Arrange - var serviceProvider = new Mock(); + var serviceProvider = new Mock(); var serviceSelector = new Mock(); var function = KernelFunctionFactory.CreateFromMethod(() => "fake result", "function"); - var kernel = new Kernel(new Mock().Object); + var kernel = new Kernel(new Mock().Object); kernel.Plugins.Add(new KernelPlugin("plugin", new[] { function })); //Act @@ -513,7 +514,7 @@ public async Task ItCanFindAndRunFunctionAsync() public void ItShouldBePossibleToSetAndGetCultureAssociatedWithKernel() { //Arrange - var kernel = KernelBuilder.Create(); + var kernel = new Kernel(); var culture = CultureInfo.GetCultureInfo(28); @@ -528,7 +529,7 @@ public void ItShouldBePossibleToSetAndGetCultureAssociatedWithKernel() public void CurrentCultureShouldBeReturnedIfNoCultureWasAssociatedWithKernel() { //Arrange - var kernel = KernelBuilder.Create(); + var kernel = new Kernel(); //Act var culture = kernel.Culture; @@ -542,20 +543,23 @@ public void CurrentCultureShouldBeReturnedIfNoCultureWasAssociatedWithKernel() public void ItDeepClonesAllRelevantStateInClone() { // Kernel with all properties set - var serviceProvider = new Mock(); var serviceSelector = new Mock(); - var httpHandler = new Mock(); var loggerFactory = new Mock(); + var serviceProvider = new ServiceCollection() + .AddSingleton(serviceSelector.Object) +#pragma warning disable CA2000 // Dispose objects before losing scope + .AddSingleton(new HttpClient()) +#pragma warning restore CA2000 + .AddSingleton(loggerFactory.Object) + .BuildServiceProvider(); var plugin = new KernelPlugin("plugin1"); var plugins = new KernelPluginCollection() { plugin }; - Kernel kernel1 = new(serviceProvider.Object, plugins, serviceSelector.Object, httpHandler.Object, loggerFactory.Object); + Kernel kernel1 = new(serviceProvider, plugins); kernel1.Data["key"] = "value"; // Clone and validate it Kernel kernel2 = kernel1.Clone(); - Assert.Same(kernel1.ServiceProvider, kernel2.ServiceProvider); - Assert.Same(kernel1.ServiceSelector, kernel2.ServiceSelector); - Assert.Same(kernel1.HttpHandlerFactory, kernel2.HttpHandlerFactory); + Assert.Same(kernel1.Services, kernel2.Services); Assert.Same(kernel1.Culture, kernel2.Culture); Assert.NotSame(kernel1.Data, kernel2.Data); Assert.Equal(kernel1.Data.Count, kernel2.Data.Count); @@ -564,13 +568,11 @@ public void ItDeepClonesAllRelevantStateInClone() Assert.Equal(kernel1.Plugins, kernel2.Plugins); // Minimally configured kernel - Kernel kernel3 = new(serviceProvider.Object); + Kernel kernel3 = new(); // Clone and validate it Kernel kernel4 = kernel3.Clone(); - Assert.Same(kernel3.ServiceProvider, kernel4.ServiceProvider); - Assert.Same(kernel3.ServiceSelector, kernel4.ServiceSelector); - Assert.Same(kernel3.HttpHandlerFactory, kernel4.HttpHandlerFactory); + Assert.Same(kernel3.Services, kernel4.Services); Assert.NotSame(kernel3.Data, kernel4.Data); Assert.Empty(kernel4.Data); Assert.NotSame(kernel1.Plugins, kernel2.Plugins); diff --git a/dotnet/src/SemanticKernel.UnitTests/PromptTemplate/KernelPromptTemplateTests.cs b/dotnet/src/SemanticKernel.UnitTests/PromptTemplate/KernelPromptTemplateTests.cs index ba2084338a97..c8f8d437240c 100644 --- a/dotnet/src/SemanticKernel.UnitTests/PromptTemplate/KernelPromptTemplateTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/PromptTemplate/KernelPromptTemplateTests.cs @@ -28,7 +28,7 @@ public KernelPromptTemplateTests(ITestOutputHelper testOutputHelper) this._logger = testOutputHelper; this._factory = new KernelPromptTemplateFactory(TestConsoleLogger.LoggerFactory); this._variables = new ContextVariables(Guid.NewGuid().ToString("X")); - this._kernel = KernelBuilder.Create(); + this._kernel = new Kernel(); } [Fact] diff --git a/dotnet/src/SemanticKernel.UnitTests/Reliability/NullHttpRetryHandlerTests.cs b/dotnet/src/SemanticKernel.UnitTests/Reliability/NullHttpRetryHandlerTests.cs deleted file mode 100644 index 14565f2b877e..000000000000 --- a/dotnet/src/SemanticKernel.UnitTests/Reliability/NullHttpRetryHandlerTests.cs +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.SemanticKernel.Reliability; -using Moq; -using Moq.Protected; -using Xunit; - -namespace SemanticKernel.UnitTests.Reliability; - -public class NullHttpRetryHandlerTests -{ - [Fact] - public async Task ItDoesNotRetryOnExceptionAsync() - { - // Arrange - using var retry = new NullHttpRetryHandler(); - using var mockResponse = new HttpResponseMessage((HttpStatusCode)429); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal((HttpStatusCode)429, response.StatusCode); - } - - [Fact] - public async Task NoExceptionNoRetryAsync() - { - // Arrange - using var retry = new NullHttpRetryHandler(); - using var mockResponse = new HttpResponseMessage(HttpStatusCode.OK); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, CancellationToken.None); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - } - - [Fact] - public async Task TaskCanceledExceptionThrownOnCancellationTokenAsync() - { - // Arrange - using var retry = new NullHttpRetryHandler(); - using var mockResponse = new HttpResponseMessage((HttpStatusCode)429); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - using var cancellationTokenSource = new CancellationTokenSource(); - cancellationTokenSource.Cancel(); - - // Act - await Assert.ThrowsAnyAsync(async () => - await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, cancellationTokenSource.Token)); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); - } - - [Fact] - public async Task ItDoestExecuteOnFalseCancellationTokenAsync() - { - // Arrange - using var retry = new NullHttpRetryHandler(); - using var mockResponse = new HttpResponseMessage((HttpStatusCode)429); - using var testContent = new StringContent("test"); - var mockHandler = GetHttpMessageHandlerMock(mockResponse); - retry.InnerHandler = mockHandler.Object; - using var httpClient = new HttpClient(retry); - - // Act - var response = await httpClient.PostAsync(new Uri("https://www.microsoft.com"), testContent, new CancellationToken(false)); - - // Assert - mockHandler.Protected() - .Verify>("SendAsync", Times.Once(), ItExpr.IsAny(), ItExpr.IsAny()); - Assert.Equal((HttpStatusCode)429, response.StatusCode); - } - - private static Mock GetHttpMessageHandlerMock(HttpResponseMessage mockResponse) - { - var mockHandler = new Mock(); - mockHandler.Protected() - .Setup>("SendAsync", ItExpr.IsAny(), ItExpr.IsAny()) - .Callback((m, ct) => ct.ThrowIfCancellationRequested()) // .NET Framework doesn't include a pre-check for cancellation - .ReturnsAsync(mockResponse); - return mockHandler; - } -} diff --git a/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs b/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs deleted file mode 100644 index 394b977d42ff..000000000000 --- a/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using Microsoft.SemanticKernel.Services; -using Xunit; - -namespace SemanticKernel.UnitTests.Services; - -/// -/// Unit tests of . -/// -public class ServiceRegistryTests -{ - [Fact] - public void ItCanSetAndRetrieveServiceInstance() - { - // Arrange - var services = new AIServiceCollection(); - var service = new TestService(); - - // Act - services.SetService(service); - var provider = services.Build(); - var result = provider.GetService(); - - // Assert - Assert.Same(service, result); - } - - [Fact] - public void ItCanSetAndRetrieveServiceInstanceWithName() - { - // Arrange - var services = new AIServiceCollection(); - var service1 = new TestService(); - var service2 = new TestService(); - - // Act - services.SetService("foo", service1); - services.SetService("bar", service2); - var provider = services.Build(); - - // Assert - Assert.Same(service1, provider.GetService("foo")); - Assert.Same(service2, provider.GetService("bar")); - } - - [Fact] - public void ItCanSetAndRetrieveServiceFactory() - { - // Arrange - var services = new AIServiceCollection(); - var service = new TestService(); - - // Act - services.SetService(() => service); - var provider = services.Build(); - - // Assert - Assert.Same(service, provider.GetService()); - } - - [Fact] - public void ItCanSetAndRetrieveServiceFactoryWithName() - { - // Arrange - var services = new AIServiceCollection(); - var service1 = new TestService(); - var service2 = new TestService(); - - // Act - services.SetService("foo", () => service1); - services.SetService("bar", () => service2); - var provider = services.Build(); - - // Assert - Assert.Same(service1, provider.GetService("foo")); - Assert.Same(service2, provider.GetService("bar")); - } - - [Fact] - public void ItCanSetAndRetrieveServiceFactoryWithServiceProvider() - { - // Arrange - var services = new AIServiceCollection(); - var service = new TestService(); - - // Act - services.SetService(() => service); - var provider = services.Build(); - - // Assert - Assert.Same(service, provider.GetService()); - } - - [Fact] - public void ItCanSetAndRetrieveServiceFactoryWithServiceProviderAndName() - { - // Arrange - var services = new AIServiceCollection(); - var service1 = new TestService(); - var service2 = new TestService(); - - // Act - services.SetService("foo", () => service1); - services.SetService("bar", () => service2); - var provider = services.Build(); - - // Assert - Assert.Same(service1, provider.GetService("foo")); - Assert.Same(service2, provider.GetService("bar")); - } - - [Fact] - public void ItCanSetDefaultService() - { - // Arrange - var services = new AIServiceCollection(); - var service1 = new TestService(); - var service2 = new TestService(); - - // Act - services.SetService("foo", service1); - services.SetService("bar", service2, setAsDefault: true); - var provider = services.Build(); - - // Assert - Assert.Same(service2, provider.GetService()); - } - - [Fact] - public void ItCanSetDefaultServiceFactory() - { - // Arrange - var services = new AIServiceCollection(); - var service1 = new TestService(); - var service2 = new TestService(); - - // Act - services.SetService("foo", () => service1); - services.SetService("bar", () => service2, setAsDefault: true); - var provider = services.Build(); - - // Assert - Assert.Same(service2, provider.GetService()); - } - - [Fact] - public void ItCanSetDefaultServiceFactoryWithServiceProvider() - { - // Arrange - var services = new AIServiceCollection(); - var service1 = new TestService(); - var service2 = new TestService(); - - // Act - services.SetService("foo", () => service1); - services.SetService("bar", () => service2, setAsDefault: true); - var provider = services.Build(); - - // Assert - Assert.Same(service2, provider.GetService()); - } - - [Fact] - public void ItCanTryGetService() - { - // Arrange - var services = new AIServiceCollection(); - var service = new TestService(); - services.SetService(service); - var provider = services.Build(); - - // Act - var result = provider.TryGetService(out IAIService? retrieved); - - // Assert - Assert.True(result); - Assert.Same(service, retrieved); - } - - [Fact] - public void ItCanTryGetServiceWithName() - { - // Arrange - var services = new AIServiceCollection(); - var service = new TestService(); - services.SetService("foo", service); - var provider = services.Build(); - - // Act - var result = provider.TryGetService("foo", out IAIService? retrieved); - - // Assert - Assert.True(result); - Assert.Same(service, retrieved); - } - - [Fact] - public void ItReturnsFalseIfTryGetServiceWithInvalidName() - { - // Arrange - var services = new AIServiceCollection(); - var service = new TestService(); - services.SetService("foo", service); - var provider = services.Build(); - - // Act - var result = provider.TryGetService("bar", out IAIService? retrieved); - - // Assert - Assert.False(result); - Assert.Null(retrieved); - } - - // A test service implementation - private sealed class TestService : IAIService - { - public string? ModelId { get; } - - public IReadOnlyDictionary Attributes => new Dictionary(); - } -} diff --git a/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/CodeBlockTests.cs b/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/CodeBlockTests.cs index 4aba23e8283c..6e66c935f432 100644 --- a/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/CodeBlockTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/CodeBlockTests.cs @@ -3,11 +3,8 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Orchestration; -using Microsoft.SemanticKernel.Services; using Microsoft.SemanticKernel.TemplateEngine.Blocks; using Moq; using Xunit; @@ -16,15 +13,14 @@ namespace SemanticKernel.UnitTests.TemplateEngine.Blocks; public class CodeBlockTests { - private readonly ILoggerFactory _logger = NullLoggerFactory.Instance; - private readonly Kernel _kernel = new(new Mock().Object); + private readonly Kernel _kernel = new(new Mock().Object); [Fact] public async Task ItThrowsIfAFunctionDoesntExistAsync() { // Arrange var variables = new ContextVariables(); - var target = new CodeBlock("functionName", this._logger); + var target = new CodeBlock("functionName"); // Act & Assert await Assert.ThrowsAsync(() => target.RenderCodeAsync(this._kernel, variables)); @@ -41,7 +37,7 @@ public async Task ItThrowsIfAFunctionCallThrowsAsync() this._kernel.Plugins.Add(new KernelPlugin("plugin", new[] { function })); - var target = new CodeBlock("plugin.function", this._logger); + var target = new CodeBlock("plugin.function"); // Act & Assert await Assert.ThrowsAsync(() => target.RenderCodeAsync(this._kernel, variables)); @@ -51,7 +47,7 @@ public async Task ItThrowsIfAFunctionCallThrowsAsync() public void ItHasTheCorrectType() { // Act - var target = new CodeBlock("", NullLoggerFactory.Instance); + var target = new CodeBlock(""); // Assert Assert.Equal(BlockTypes.Code, target.Type); @@ -61,7 +57,7 @@ public void ItHasTheCorrectType() public void ItTrimsSpaces() { // Act + Assert - Assert.Equal("aa", new CodeBlock(" aa ", NullLoggerFactory.Instance).Content); + Assert.Equal("aa", new CodeBlock(" aa ").Content); } [Fact] @@ -73,8 +69,8 @@ public void ItChecksValidityOfInternalBlocks() var invalidBlock = new VarBlock(""); // Act - var codeBlock1 = new CodeBlock(new List { validBlock1, validBlock2 }, "", NullLoggerFactory.Instance); - var codeBlock2 = new CodeBlock(new List { validBlock1, invalidBlock }, "", NullLoggerFactory.Instance); + var codeBlock1 = new CodeBlock(new List { validBlock1, validBlock2 }, ""); + var codeBlock2 = new CodeBlock(new List { validBlock1, invalidBlock }, ""); // Assert Assert.True(codeBlock1.IsValid(out _)); @@ -91,13 +87,13 @@ public void ItRequiresAValidFunctionCall() var namedArgBlock = new NamedArgBlock("varName='foo'"); // Act - var codeBlock1 = new CodeBlock(new List { funcId, valBlock }, "", NullLoggerFactory.Instance); - var codeBlock2 = new CodeBlock(new List { funcId, varBlock }, "", NullLoggerFactory.Instance); - var codeBlock3 = new CodeBlock(new List { funcId, funcId }, "", NullLoggerFactory.Instance); - var codeBlock4 = new CodeBlock(new List { funcId, varBlock, varBlock }, "", NullLoggerFactory.Instance); - var codeBlock5 = new CodeBlock(new List { funcId, varBlock, namedArgBlock }, "", NullLoggerFactory.Instance); - var codeBlock6 = new CodeBlock(new List { varBlock, valBlock }, "", NullLoggerFactory.Instance); - var codeBlock7 = new CodeBlock(new List { namedArgBlock }, "", NullLoggerFactory.Instance); + var codeBlock1 = new CodeBlock(new List { funcId, valBlock }, ""); + var codeBlock2 = new CodeBlock(new List { funcId, varBlock }, ""); + var codeBlock3 = new CodeBlock(new List { funcId, funcId }, ""); + var codeBlock4 = new CodeBlock(new List { funcId, varBlock, varBlock }, ""); + var codeBlock5 = new CodeBlock(new List { funcId, varBlock, namedArgBlock }, ""); + var codeBlock6 = new CodeBlock(new List { varBlock, valBlock }, ""); + var codeBlock7 = new CodeBlock(new List { namedArgBlock }, ""); // Assert Assert.True(codeBlock1.IsValid(out _)); @@ -131,7 +127,7 @@ public async Task ItRendersCodeBlockConsistingOfJustAVarBlock1Async() var variables = new ContextVariables { ["varName"] = "foo" }; // Act - var codeBlock = new CodeBlock("$varName", NullLoggerFactory.Instance); + var codeBlock = new CodeBlock("$varName"); var result = await codeBlock.RenderCodeAsync(this._kernel, variables); // Assert @@ -146,7 +142,7 @@ public async Task ItRendersCodeBlockConsistingOfJustAVarBlock2Async() var varBlock = new VarBlock("$varName"); // Act - var codeBlock = new CodeBlock(new List { varBlock }, "", NullLoggerFactory.Instance); + var codeBlock = new CodeBlock(new List { varBlock }, ""); var result = await codeBlock.RenderCodeAsync(this._kernel, variables); // Assert @@ -160,7 +156,7 @@ public async Task ItRendersCodeBlockConsistingOfJustAValBlock1Async() var variables = new ContextVariables(); // Act - var codeBlock = new CodeBlock("'ciao'", NullLoggerFactory.Instance); + var codeBlock = new CodeBlock("'ciao'"); var result = await codeBlock.RenderCodeAsync(this._kernel, variables); // Assert @@ -175,7 +171,7 @@ public async Task ItRendersCodeBlockConsistingOfJustAValBlock2Async() var valBlock = new ValBlock("'arrivederci'"); // Act - var codeBlock = new CodeBlock(new List { valBlock }, "", NullLoggerFactory.Instance); + var codeBlock = new CodeBlock(new List { valBlock }, ""); var result = await codeBlock.RenderCodeAsync(this._kernel, variables); // Assert @@ -208,7 +204,7 @@ public async Task ItInvokesFunctionCloningAllVariablesAsync() this._kernel.Plugins.Add(new KernelPlugin("plugin", new[] { function })); // Act - var codeBlock = new CodeBlock(new List { funcBlock }, "", NullLoggerFactory.Instance); + var codeBlock = new CodeBlock(new List { funcBlock }, ""); string result = await codeBlock.RenderCodeAsync(this._kernel, variables); // Assert - Values are received @@ -244,7 +240,7 @@ public async Task ItInvokesFunctionWithCustomVariableAsync() this._kernel.Plugins.Add(new KernelPlugin("plugin", new[] { function })); // Act - var codeBlock = new CodeBlock(new List { funcId, varBlock }, "", NullLoggerFactory.Instance); + var codeBlock = new CodeBlock(new List { funcId, varBlock }, ""); string result = await codeBlock.RenderCodeAsync(this._kernel, variables); // Assert @@ -273,7 +269,7 @@ public async Task ItInvokesFunctionWithCustomValueAsync() this._kernel.Plugins.Add(new KernelPlugin("plugin", new[] { function })); // Act - var codeBlock = new CodeBlock(new List { funcBlock, valBlock }, "", NullLoggerFactory.Instance); + var codeBlock = new CodeBlock(new List { funcBlock, valBlock }, ""); string result = await codeBlock.RenderCodeAsync(this._kernel, context); // Assert @@ -310,7 +306,7 @@ public async Task ItInvokesFunctionWithNamedArgsAsync() this._kernel.Plugins.Add(new KernelPlugin("plugin", new[] { function })); // Act - var codeBlock = new CodeBlock(new List { funcId, namedArgBlock1, namedArgBlock2 }, "", NullLoggerFactory.Instance); + var codeBlock = new CodeBlock(new List { funcId, namedArgBlock1, namedArgBlock2 }, ""); string result = await codeBlock.RenderCodeAsync(this._kernel, variables); // Assert diff --git a/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/FunctionIdBlockTests.cs b/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/FunctionIdBlockTests.cs index 73ea94347264..aba5d26d1b03 100644 --- a/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/FunctionIdBlockTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/FunctionIdBlockTests.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.TemplateEngine.Blocks; using Xunit; @@ -13,7 +12,7 @@ public class FunctionIdBlockTests public void ItHasTheCorrectType() { // Act - var target = new FunctionIdBlock("", NullLoggerFactory.Instance); + var target = new FunctionIdBlock(""); // Assert Assert.Equal(BlockTypes.FunctionId, target.Type); @@ -23,7 +22,7 @@ public void ItHasTheCorrectType() public void ItTrimsSpaces() { // Act + Assert - Assert.Equal("aa", new FunctionIdBlock(" aa ", NullLoggerFactory.Instance).Content); + Assert.Equal("aa", new FunctionIdBlock(" aa ").Content); } [Theory] diff --git a/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/NamedArgBlockTests.cs b/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/NamedArgBlockTests.cs index 75ee2836585e..89b46fa70f22 100644 --- a/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/NamedArgBlockTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/TemplateEngine/Blocks/NamedArgBlockTests.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -using Microsoft.Extensions.Logging.Abstractions; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.Orchestration; using Microsoft.SemanticKernel.TemplateEngine.Blocks; @@ -14,7 +13,7 @@ public class NamedArgBlockTests public void ItHasTheCorrectType() { // Act - var target = new NamedArgBlock("a=$b", NullLoggerFactory.Instance); + var target = new NamedArgBlock("a=$b"); // Assert Assert.Equal(BlockTypes.NamedArg, target.Type); @@ -30,7 +29,7 @@ public void ItHasTheCorrectType() public void ItTrimsSpaces(string input, string expected) { // Act + Assert - Assert.Equal(expected, new NamedArgBlock(input, NullLoggerFactory.Instance).Content); + Assert.Equal(expected, new NamedArgBlock(input).Content); } [Theory]