From 01279decd5ae375d28431bfd9978a74354f08440 Mon Sep 17 00:00:00 2001 From: name Date: Sun, 7 May 2023 20:13:19 -0700 Subject: [PATCH 01/15] AI Service Provider --- .../Connectors.AI.OpenAI.csproj | 4 +- ...ns.cs => OpenAIKernelBuilderExtensions.cs} | 265 +++++++++--------- .../AzureTextEmbeddingGeneration.cs | 2 +- .../OpenAITextEmbeddingGeneration.cs | 2 +- .../QdrantVectorRecord.cs | 1 - .../SqliteMemoryStore.cs | 1 - .../OpenAI/AIServicesOpenAIExtensionsTests.cs | 60 ++++ .../KernelConfigOpenAIExtensionsTests.cs | 129 --------- .../OpenAI/OpenAICompletionTests.cs | 116 ++++---- .../IntegrationTests/Planning/PlanTests.cs | 28 +- .../SequentialPlanParserTests.cs | 13 +- .../SequentialPlannerTests.cs | 28 +- .../ChatCompletionServiceExtensions.cs | 34 +++ .../AI/ChatCompletion/IChatCompletion.cs | 3 +- .../EmbeddingGenerationServiceExtensions.cs | 31 ++ .../AI/Embeddings/IEmbeddingGeneration.cs | 25 -- .../AI/Embeddings/ITextEmbeddingGeneration.cs | 12 + .../TextEmbeddingServiceExtensions.cs | 35 +++ .../AI/ImageGeneration/IImageGeneration.cs | 3 +- .../ImageGenerationServiceExtensions.cs | 34 +++ .../AI/TextCompletion/ITextCompletion.cs | 3 +- .../TextCompletionServiceExtensions.cs | 34 +++ .../Diagnostics/NullableAttributes.cs | 4 +- .../SemanticKernel.Abstractions/IKernel.cs | 3 +- .../KernelConfig.cs | 222 --------------- .../SemanticKernel.Abstractions.csproj | 20 +- .../Services/IAIService.cs | 10 + .../Services/IAIServiceProvider.cs | 7 + .../Services/INamedServiceProvider.cs | 14 + .../Services/ServiceExtensions.cs | 57 ++++ .../ChatCompletionServiceExtensionTests.cs | 148 ++++++++++ .../TextEmbeddingServiceExtensionTests.cs | 148 ++++++++++ .../ImageCompletionServiceExtensionTests.cs | 146 ++++++++++ .../TextCompletionServiceExtensionTests.cs | 147 ++++++++++ .../CoreSkills/FileIOSkillTests.cs | 2 +- .../CoreSkills/HttpSkillTests.cs | 2 +- .../CoreSkills/MathSkillTests.cs | 2 +- .../CoreSkills/TextSkillTests.cs | 2 +- .../CoreSkills/TimeSkillTests.cs | 2 +- .../CoreSkills/WaitSkillTests.cs | 2 +- .../KernelConfigTests.cs | 22 -- .../SemanticKernel.UnitTests/KernelTests.cs | 39 ++- .../SemanticKernel.UnitTests.csproj | 1 + .../Services/ServiceRegistryTests.cs | 219 +++++++++++++++ .../SkillDefinition/SKContextTests.cs | 2 +- .../HuggingFaceTextCompletion.cs | 1 - .../HuggingFaceTextEmbeddingGeneration.cs | 2 +- dotnet/src/SemanticKernel/Kernel.cs | 69 +---- dotnet/src/SemanticKernel/KernelBuilder.cs | 73 ++++- .../Memory/MemoryConfiguration.cs | 4 +- .../Memory/SemanticTextMemory.cs | 4 +- .../Services/AIServiceCollection.cs | 116 ++++++++ .../Services/NamedServiceProvider.cs | 70 +++++ .../SkillDefinition/SKFunction.cs | 8 +- .../src/Skills/Skills.MsGraph/EmailSkill.cs | 1 - .../Extensions/RestApiOperationExtensions.cs | 1 - .../Web/SearchUrlSkillTests.cs | 2 +- .../webapi/SemanticKernelExtensions.cs | 94 +++---- samples/dotnet/KernelBuilder/Program.cs | 44 +-- .../KernelHttpServer/SemanticKernelFactory.cs | 67 +++-- .../MsGraphSkillsExample.csproj | 1 + samples/dotnet/graph-api-skills/Program.cs | 46 +-- ...xample04_CombineLLMPromptsAndNativeCode.cs | 11 +- .../Example05_InlineFunctionDefinition.cs | 8 +- .../Example06_TemplateLanguage.cs | 6 +- .../Example07_BingAndGoogleSkills.cs | 7 +- .../Example08_RetryHandler.cs | 22 +- .../Example09_FunctionTypes.cs | 6 +- ...Example10_DescribeAllSkillsAndFunctions.cs | 5 +- .../Example12_SequentialPlanner.cs | 38 +-- .../Example13_ConversationSummarySkill.cs | 12 +- .../Example14_SemanticMemory.cs | 2 +- .../Example15_MemorySkill.cs | 7 +- .../Example16_CustomLLM.cs | 13 +- .../Example17_ChatGPT.cs | 8 +- .../kernel-syntax-examples/Example18_DallE.cs | 14 +- .../Example19_Qdrant.cs | 7 +- .../Example20_HuggingFace.cs | 9 +- .../Example26_AADAuth.cs | 15 +- ...Example27_SemanticFunctionsUsingChatGPT.cs | 9 +- .../Example28_ActionPlanner.cs | 8 +- .../Example30_ChatWithPrompts.cs | 6 +- .../Example31_CustomPlanner.cs | 21 +- .../Example32_StreamingCompletion.cs | 18 +- 84 files changed, 1903 insertions(+), 1036 deletions(-) rename dotnet/src/Connectors/Connectors.AI.OpenAI/{KernelConfigOpenAIExtensions.cs => OpenAIKernelBuilderExtensions.cs} (66%) create mode 100644 dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs delete mode 100644 dotnet/src/Connectors/Connectors.UnitTests/OpenAI/KernelConfigOpenAIExtensionsTests.cs create mode 100644 dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatCompletionServiceExtensions.cs create mode 100644 dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/EmbeddingGenerationServiceExtensions.cs create mode 100644 dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/ITextEmbeddingGeneration.cs create mode 100644 dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/TextEmbeddingServiceExtensions.cs create mode 100644 dotnet/src/SemanticKernel.Abstractions/AI/ImageGeneration/ImageGenerationServiceExtensions.cs create mode 100644 dotnet/src/SemanticKernel.Abstractions/AI/TextCompletion/TextCompletionServiceExtensions.cs create mode 100644 dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs create mode 100644 dotnet/src/SemanticKernel.Abstractions/Services/IAIServiceProvider.cs create mode 100644 dotnet/src/SemanticKernel.Abstractions/Services/INamedServiceProvider.cs create mode 100644 dotnet/src/SemanticKernel.Abstractions/Services/ServiceExtensions.cs create mode 100644 dotnet/src/SemanticKernel.UnitTests/AI/ChatCompletion/ChatCompletionServiceExtensionTests.cs create mode 100644 dotnet/src/SemanticKernel.UnitTests/AI/Embeddings/TextEmbeddingServiceExtensionTests.cs create mode 100644 dotnet/src/SemanticKernel.UnitTests/AI/ImageGeneration/ImageCompletionServiceExtensionTests.cs create mode 100644 dotnet/src/SemanticKernel.UnitTests/AI/TextCompletion/TextCompletionServiceExtensionTests.cs create mode 100644 dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs create mode 100644 dotnet/src/SemanticKernel/Services/AIServiceCollection.cs create mode 100644 dotnet/src/SemanticKernel/Services/NamedServiceProvider.cs diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/Connectors.AI.OpenAI.csproj b/dotnet/src/Connectors/Connectors.AI.OpenAI/Connectors.AI.OpenAI.csproj index 87dfd74874a8..ae687fe4709c 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/Connectors.AI.OpenAI.csproj +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/Connectors.AI.OpenAI.csproj @@ -1,4 +1,4 @@ - + @@ -19,7 +19,7 @@ - + diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/KernelConfigOpenAIExtensions.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs similarity index 66% rename from dotnet/src/Connectors/Connectors.AI.OpenAI/KernelConfigOpenAIExtensions.cs rename to dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs index 3e8b327bea5d..184bc984ff03 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/KernelConfigOpenAIExtensions.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs @@ -5,20 +5,18 @@ 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.ChatCompletion; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.ImageGeneration; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; -using Microsoft.SemanticKernel.Reliability; #pragma warning disable IDE0130 // ReSharper disable once CheckNamespace - Using NS of KernelConfig namespace Microsoft.SemanticKernel; #pragma warning restore IDE0130 -public static class KernelConfigOpenAIExtensions +public static class OpenAKernelBuilderExtensions { #region Text Completion @@ -26,96 +24,101 @@ public static class KernelConfigOpenAIExtensions /// Adds an Azure OpenAI text completion service to the list. /// See https://learn.microsoft.com/azure/cognitive-services/openai for service details. /// - /// The kernel config instance + /// The instance /// 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 + /// Whether the service should be the default for its type. /// Custom for HTTP requests. /// Application logger /// Self instance - public static KernelConfig AddAzureTextCompletionService(this KernelConfig config, + public static KernelBuilder AddAzureTextCompletionService(this KernelBuilder builder, string deploymentName, string endpoint, string apiKey, string? serviceId = null, + bool setAsDefault = false, HttpClient? httpClient = null, ILogger? logger = null) { - ITextCompletion Factory(IKernel kernel) => new AzureTextCompletion( - deploymentName, - endpoint, - apiKey, - httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), - logger ?? kernel.Log); - - config.AddTextCompletionService(Factory, serviceId); + builder.WithAIService(serviceId, () => + new AzureTextCompletion( + deploymentName, + endpoint, + apiKey, + httpClient, + logger), + setAsDefault); - return config; + return builder; } /// /// Adds an Azure OpenAI text completion service to the list. /// See https://learn.microsoft.com/azure/cognitive-services/openai for service details. /// - /// The kernel config instance + /// The instance /// 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 + /// Whether the service should be the default for its type. /// Custom for HTTP requests. /// Application logger /// Self instance - public static KernelConfig AddAzureTextCompletionService(this KernelConfig config, + public static KernelBuilder AddAzureTextCompletionService(this KernelBuilder builder, string deploymentName, string endpoint, TokenCredential credentials, string? serviceId = null, + bool setAsDefault = false, HttpClient? httpClient = null, ILogger? logger = null) { - ITextCompletion Factory(IKernel kernel) => new AzureTextCompletion( - deploymentName, - endpoint, - credentials, - httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), - logger ?? kernel.Log); - - config.AddTextCompletionService(Factory, serviceId); + builder.WithAIService(serviceId, () => + new AzureTextCompletion( + deploymentName, + endpoint, + credentials, + httpClient, + logger), + setAsDefault); - return config; + return builder; } /// /// Adds the OpenAI text completion service to the list. /// See https://platform.openai.com/docs for service details. /// - /// The kernel config instance + /// The instance /// 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. /// Application logger /// Self instance - public static KernelConfig AddOpenAITextCompletionService(this KernelConfig config, + public static KernelBuilder AddOpenAITextCompletionService(this KernelBuilder builder, string modelId, string apiKey, string? orgId = null, string? serviceId = null, + bool setAsDefault = false, HttpClient? httpClient = null, ILogger? logger = null) { - ITextCompletion Factory(IKernel kernel) => new OpenAITextCompletion( - modelId, - apiKey, - orgId, - httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), - logger ?? kernel.Log); - - config.AddTextCompletionService(Factory, serviceId); - - return config; + builder.WithAIService(serviceId, () => + new OpenAITextCompletion( + modelId, + apiKey, + orgId, + httpClient, + logger), + setAsDefault); + return builder; } #endregion @@ -126,96 +129,99 @@ public static KernelConfig AddOpenAITextCompletionService(this KernelConfig conf /// Adds an Azure OpenAI text embeddings service to the list. /// See https://learn.microsoft.com/azure/cognitive-services/openai for service details. /// - /// The kernel config instance + /// The instance /// 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 + /// Whether the service should be the default for its type. /// Custom for HTTP requests. /// Application logger /// Self instance - public static KernelConfig AddAzureTextEmbeddingGenerationService(this KernelConfig config, + public static KernelBuilder AddAzureTextEmbeddingGenerationService(this KernelBuilder builder, string deploymentName, string endpoint, string apiKey, string? serviceId = null, + bool setAsDefault = false, HttpClient? httpClient = null, ILogger? logger = null) { - IEmbeddingGeneration Factory(IKernel kernel) => new AzureTextEmbeddingGeneration( - deploymentName, - endpoint, - apiKey, - httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), - logger ?? kernel.Log); - - config.AddTextEmbeddingGenerationService(Factory, serviceId); - - return config; + builder.WithAIService(serviceId, () => + new AzureTextEmbeddingGeneration( + deploymentName, + endpoint, + apiKey, + httpClient, + logger), + setAsDefault); + return builder; } /// /// Adds an Azure OpenAI text embeddings service to the list. /// See https://learn.microsoft.com/azure/cognitive-services/openai for service details. /// - /// The kernel config instance + /// The instance /// 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. + /// Token credentials, e.g. DefaultAzureCredential, ManagedIdentityCredential, EnvironmentCredential, etc. + /// Whether the service should be the default for its type. /// A local identifier for the given AI service /// Custom for HTTP requests. /// Application logger /// Self instance - public static KernelConfig AddAzureTextEmbeddingGenerationService(this KernelConfig config, + public static KernelBuilder AddAzureTextEmbeddingGenerationService(this KernelBuilder builder, string deploymentName, string endpoint, - TokenCredential credentials, + TokenCredential credential, string? serviceId = null, + bool setAsDefault = false, HttpClient? httpClient = null, ILogger? logger = null) { - IEmbeddingGeneration Factory(IKernel kernel) => new AzureTextEmbeddingGeneration( - deploymentName, - endpoint, - credentials, - httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), - logger ?? kernel.Log); - - config.AddTextEmbeddingGenerationService(Factory, serviceId); - - return config; + builder.WithAIService(serviceId, () => + new AzureTextEmbeddingGeneration( + deploymentName, + endpoint, + credential, + httpClient, + logger), + setAsDefault); + return builder; } /// /// Adds the OpenAI text embeddings service to the list. /// See https://platform.openai.com/docs for service details. /// - /// The kernel config instance + /// The instance /// 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. + /// Whether the service should be the default for its type. /// A local identifier for the given AI service /// Custom for HTTP requests. /// Application logger /// Self instance - public static KernelConfig AddOpenAITextEmbeddingGenerationService(this KernelConfig config, + public static KernelBuilder AddOpenAITextEmbeddingGenerationService(this KernelBuilder builder, string modelId, string apiKey, string? orgId = null, string? serviceId = null, + bool setAsDefault = false, HttpClient? httpClient = null, ILogger? logger = null) { - IEmbeddingGeneration Factory(IKernel kernel) => new OpenAITextEmbeddingGeneration( - modelId, - apiKey, - orgId, - httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), - logger ?? kernel.Log); - - config.AddTextEmbeddingGenerationService(Factory, serviceId); - - return config; + builder.WithAIService(serviceId, () => + new OpenAITextEmbeddingGeneration( + modelId, + apiKey, + orgId, + httpClient, + logger), + setAsDefault); + return builder; } #endregion @@ -226,137 +232,126 @@ public static KernelConfig AddOpenAITextEmbeddingGenerationService(this KernelCo /// Adds the Azure OpenAI ChatGPT completion service to the list. /// See https://platform.openai.com/docs for service details. /// - /// The kernel config instance + /// The instance /// 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 + /// Whether the service should be the default for its type. /// Custom for HTTP requests. /// Application logger /// Self instance - public static KernelConfig AddAzureChatCompletionService(this KernelConfig config, + public static KernelBuilder AddAzureChatCompletionService(this KernelBuilder builder, string deploymentName, string endpoint, string apiKey, bool alsoAsTextCompletion = true, string? serviceId = null, + bool setAsDefault = false, HttpClient? httpClient = null, ILogger? logger = null) { - IChatCompletion Factory(IKernel kernel) => new AzureChatCompletion( - deploymentName, endpoint, apiKey, kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), kernel.Log); + AzureChatCompletion Factory() => new AzureChatCompletion( + deploymentName, + endpoint, + apiKey, + httpClient, + logger); - config.AddChatCompletionService(Factory, serviceId); + 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(AzureChatCompletion))) { - ITextCompletion TextServiceFactory(IKernel kernel) => new AzureChatCompletion( - deploymentName, - endpoint, - apiKey, - httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), - logger ?? kernel.Log); - - config.AddTextCompletionService(TextServiceFactory, serviceId); + builder.WithAIService(serviceId, Factory, setAsDefault); } - return config; + return builder; } /// /// Adds the Azure OpenAI ChatGPT completion service to the list. /// See https://platform.openai.com/docs for service details. /// - /// The kernel config instance + /// The instance /// 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 + /// Whether the service should be the default for its type. /// Custom for HTTP requests. /// Application logger /// Self instance - public static KernelConfig AddAzureChatCompletionService(this KernelConfig config, + public static KernelBuilder AddAzureChatCompletionService(this KernelBuilder builder, string deploymentName, string endpoint, TokenCredential credentials, bool alsoAsTextCompletion = true, string? serviceId = null, + bool setAsDefault = false, HttpClient? httpClient = null, ILogger? logger = null) { - IChatCompletion Factory(IKernel kernel) => new AzureChatCompletion( + AzureChatCompletion Factory() => new AzureChatCompletion( deploymentName, endpoint, credentials, - httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), - logger ?? kernel.Log); + httpClient, + logger); - config.AddChatCompletionService(Factory, serviceId); + 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(AzureChatCompletion))) { - ITextCompletion TextServiceFactory(IKernel kernel) => new AzureChatCompletion( - deploymentName, - endpoint, - credentials, - httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), - logger ?? kernel.Log); - - config.AddTextCompletionService(TextServiceFactory, serviceId); + builder.WithAIService(serviceId, Factory, setAsDefault); } - return config; + return builder; } /// /// Adds the OpenAI ChatGPT completion service to the list. /// See https://platform.openai.com/docs for service details. /// - /// The kernel config instance + /// The instance /// 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. /// 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. /// Application logger /// Self instance - public static KernelConfig AddOpenAIChatCompletionService(this KernelConfig config, + public static KernelBuilder AddOpenAIChatCompletionService(this KernelBuilder builder, string modelId, string apiKey, string? orgId = null, - bool alsoAsTextCompletion = true, string? serviceId = null, + bool alsoAsTextCompletion = true, + bool setAsDefault = false, HttpClient? httpClient = null, ILogger? logger = null) { - IChatCompletion Factory(IKernel kernel) => new OpenAIChatCompletion( + OpenAIChatCompletion Factory() => new OpenAIChatCompletion( modelId, apiKey, orgId, - httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), - logger ?? kernel.Log); + httpClient, + logger); - config.AddChatCompletionService(Factory, serviceId); + 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(OpenAIChatCompletion))) { - ITextCompletion TextServiceFactory(IKernel kernel) => new OpenAIChatCompletion( - modelId, - apiKey, - orgId, - httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), - logger ?? kernel.Log); - - config.AddTextCompletionService(TextServiceFactory, serviceId); + builder.WithAIService(serviceId, Factory, setAsDefault); } - return config; + return builder; } #endregion @@ -366,38 +361,32 @@ public static KernelConfig AddOpenAIChatCompletionService(this KernelConfig conf /// /// Add the OpenAI DallE image generation service to the list /// - /// The kernel config instance + /// 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. /// A local identifier for the given AI service + /// Whether the service should be the default for its type. /// Custom for HTTP requests. /// Application logger /// Self instance - public static KernelConfig AddOpenAIImageGenerationService(this KernelConfig config, + public static KernelBuilder AddOpenAIImageGenerationService(this KernelBuilder builder, string apiKey, string? orgId = null, string? serviceId = null, + bool setAsDefault = false, HttpClient? httpClient = null, ILogger? logger = null) { - IImageGeneration Factory(IKernel kernel) => new OpenAIImageGeneration( - apiKey, - orgId, - httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), - logger ?? kernel.Log); - - config.AddImageGenerationService(Factory, serviceId); + builder.WithAIService(serviceId, () => + new OpenAIImageGeneration( + apiKey, + orgId, + httpClient, + logger), + setAsDefault); - return config; + return builder; } #endregion - - private static HttpClient CreateHttpClient(this IDelegatingHandlerFactory handlerFactory, - ILogger? logger) - { - var retryHandler = handlerFactory.Create(logger); - retryHandler.InnerHandler = new HttpClientHandler { CheckCertificateRevocationList = true }; - return new HttpClient(retryHandler); - } } diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureTextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureTextEmbeddingGeneration.cs index 985d278e0540..bb1c5bed47be 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureTextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureTextEmbeddingGeneration.cs @@ -14,7 +14,7 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; /// /// Azure OpenAI text embedding service. /// -public sealed class AzureTextEmbeddingGeneration : AzureOpenAIClientBase, IEmbeddingGeneration +public sealed class AzureTextEmbeddingGeneration : AzureOpenAIClientBase, ITextEmbeddingGeneration { /// /// Creates a new AzureTextCompletion client instance using API Key auth diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs index 53a36e4e0687..b93fdeff679e 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs @@ -13,7 +13,7 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; /// /// OpenAI text embedding service. /// -public sealed class OpenAITextEmbeddingGeneration : OpenAIClientBase, IEmbeddingGeneration +public sealed class OpenAITextEmbeddingGeneration : OpenAIClientBase, ITextEmbeddingGeneration { /// /// Create an instance of the OpenAI text embedding connector diff --git a/dotnet/src/Connectors/Connectors.Memory.Qdrant/QdrantVectorRecord.cs b/dotnet/src/Connectors/Connectors.Memory.Qdrant/QdrantVectorRecord.cs index afefb46ad510..1a006d57aa71 100644 --- a/dotnet/src/Connectors/Connectors.Memory.Qdrant/QdrantVectorRecord.cs +++ b/dotnet/src/Connectors/Connectors.Memory.Qdrant/QdrantVectorRecord.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Text.Json; using System.Text.Json.Serialization; using Microsoft.SemanticKernel.Connectors.Memory.Qdrant.Diagnostics; diff --git a/dotnet/src/Connectors/Connectors.Memory.Sqlite/SqliteMemoryStore.cs b/dotnet/src/Connectors/Connectors.Memory.Sqlite/SqliteMemoryStore.cs index 89fb1cacf138..9a57fc6d2211 100644 --- a/dotnet/src/Connectors/Connectors.Memory.Sqlite/SqliteMemoryStore.cs +++ b/dotnet/src/Connectors/Connectors.Memory.Sqlite/SqliteMemoryStore.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; diff --git a/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs new file mode 100644 index 000000000000..c684714537b2 --- /dev/null +++ b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.AI.Embeddings; +using Microsoft.SemanticKernel.AI.TextCompletion; +using Xunit; + +namespace SemanticKernel.Connectors.UnitTests.OpenAI; + +/// +/// Unit tests of . +/// +public class AIServicesOpenAIExtensionsTests +{ + [Fact] + public void ItSucceedsWhenAddingDifferentServiceTypeWithSameId() + { + KernelBuilder targetBuilder = Kernel.Builder; + targetBuilder.AddAzureTextCompletionService("depl", "https://url", "key", "azure"); + targetBuilder.AddAzureTextEmbeddingGenerationService("depl2", "https://url", "key", "azure"); + + IKernel targetKernel = targetBuilder.Build(); + Assert.NotNull(targetKernel.GetService("azure")); + Assert.NotNull(targetKernel.GetService("azure")); + } + + [Fact] + public void ItTellsIfAServiceIsAvailable() + { + KernelBuilder targetBuilder = Kernel.Builder; + targetBuilder.AddAzureTextCompletionService("azure", "depl", "https://url", "key"); + targetBuilder.AddOpenAITextCompletionService("oai", "model", "apikey"); + targetBuilder.AddAzureTextEmbeddingGenerationService("azure", "depl2", "https://url2", "key"); + targetBuilder.AddOpenAITextEmbeddingGenerationService("oai2", "model2", "apikey2"); + + // Assert + IKernel targetKernel = targetBuilder.Build(); + Assert.NotNull(targetKernel.GetService("azure")); + Assert.NotNull(targetKernel.GetService("oai")); + Assert.NotNull(targetKernel.GetService("azure")); + Assert.NotNull(targetKernel.GetService("oai")); + } + + [Fact] + public void ItCanOverwriteServices() + { + // Arrange + KernelBuilder targetBuilder = Kernel.Builder; + + // Act - Assert no exception occurs + targetBuilder.AddAzureTextCompletionService("one", "dep", "https://localhost", "key"); + targetBuilder.AddAzureTextCompletionService("one", "dep", "https://localhost", "key"); + targetBuilder.AddOpenAITextCompletionService("one", "model", "key"); + targetBuilder.AddOpenAITextCompletionService("one", "model", "key"); + targetBuilder.AddAzureTextEmbeddingGenerationService("one", "dep", "https://localhost", "key"); + targetBuilder.AddAzureTextEmbeddingGenerationService("one", "dep", "https://localhost", "key"); + targetBuilder.AddOpenAITextEmbeddingGenerationService("one", "model", "key"); + targetBuilder.AddOpenAITextEmbeddingGenerationService("one", "model", "key"); + } +} diff --git a/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/KernelConfigOpenAIExtensionsTests.cs b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/KernelConfigOpenAIExtensionsTests.cs deleted file mode 100644 index 1d0c27a64a25..000000000000 --- a/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/KernelConfigOpenAIExtensionsTests.cs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using Microsoft.SemanticKernel; -using Xunit; - -namespace SemanticKernel.Connectors.UnitTests.OpenAI; - -/// -/// Unit tests of . -/// -public class KernelConfigOpenAIExtensionsTests -{ - [Fact] - public void ItSucceedsWhenAddingDifferentServiceTypeWithSameId() - { - var target = new KernelConfig(); - target.AddAzureTextCompletionService("depl", "https://url", "key", serviceId: "azure"); - target.AddAzureTextEmbeddingGenerationService("depl2", "https://url", "key", serviceId: "azure"); - - Assert.True(target.TextCompletionServices.ContainsKey("azure")); - Assert.True(target.TextEmbeddingGenerationServices.ContainsKey("azure")); - } - - [Fact] - public void ItTellsIfAServiceIsAvailable() - { - // Arrange - var target = new KernelConfig(); - target.AddAzureTextCompletionService("deployment1", "https://url", "key", serviceId: "azure"); - target.AddOpenAITextCompletionService("model", "apikey", serviceId: "oai"); - target.AddAzureTextEmbeddingGenerationService("deployment2", "https://url2", "key", serviceId: "azure"); - target.AddOpenAITextEmbeddingGenerationService("model2", "apikey2", serviceId: "oai2"); - - // Assert - Assert.True(target.TextCompletionServices.ContainsKey("azure")); - Assert.True(target.TextCompletionServices.ContainsKey("oai")); - Assert.True(target.TextEmbeddingGenerationServices.ContainsKey("azure")); - Assert.True(target.TextEmbeddingGenerationServices.ContainsKey("oai2")); - - Assert.False(target.TextCompletionServices.ContainsKey("azure2")); - Assert.False(target.TextCompletionServices.ContainsKey("oai2")); - Assert.False(target.TextEmbeddingGenerationServices.ContainsKey("azure1")); - Assert.False(target.TextEmbeddingGenerationServices.ContainsKey("oai")); - } - - [Fact] - public void ItCanOverwriteServices() - { - // Arrange - var target = new KernelConfig(); - - // Act - Assert no exception occurs - target.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); - target.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); - target.AddOpenAITextCompletionService("model", "key", serviceId: "one"); - target.AddOpenAITextCompletionService("model", "key", serviceId: "one"); - target.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "one"); - target.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "one"); - target.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "one"); - target.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "one"); - } - - [Fact] - public void ItCanRemoveAllServices() - { - // Arrange - var target = new KernelConfig(); - target.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); - target.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "2"); - target.AddOpenAITextCompletionService("model", "key", serviceId: "3"); - target.AddOpenAITextCompletionService("model", "key", serviceId: "4"); - target.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "5"); - target.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "6"); - target.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "7"); - target.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "8"); - - // Act - target.RemoveAllTextCompletionServices(); - target.RemoveAllTextEmbeddingGenerationServices(); - - // Assert - Assert.Empty(target.TextEmbeddingGenerationServices); - Assert.Empty(target.TextCompletionServices); - } - - [Fact] - public void ItCanRemoveAllTextCompletionServices() - { - // Arrange - var target = new KernelConfig(); - target.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); - target.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "2"); - target.AddOpenAITextCompletionService("model", "key", serviceId: "3"); - target.AddOpenAITextCompletionService("model", "key", serviceId: "4"); - - target.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "5"); - target.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "6"); - target.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "7"); - target.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "8"); - - // Act - target.RemoveAllTextCompletionServices(); - - // Assert (+1 for the default) - Assert.Equal(4 + 1, target.TextEmbeddingGenerationServices.Count); - } - - [Fact] - public void ItCanRemoveAllTextEmbeddingGenerationServices() - { - // Arrange - var target = new KernelConfig(); - target.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); - target.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "2"); - target.AddOpenAITextCompletionService("model", "key", serviceId: "3"); - target.AddOpenAITextCompletionService("model", "key", serviceId: "4"); - target.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "5"); - target.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "6"); - target.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "7"); - target.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "8"); - - // Act - target.RemoveAllTextEmbeddingGenerationServices(); - - // Assert (+1 for the default) - Assert.Equal(4 + 1, target.TextCompletionServices.Count); - Assert.Empty(target.TextEmbeddingGenerationServices); - } -} diff --git a/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs b/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs index 933af65e131a..8db915de2d3a 100644 --- a/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs @@ -3,13 +3,11 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Net; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.Orchestration; -using Microsoft.SemanticKernel.Reliability; using Microsoft.SemanticKernel.SkillDefinition; using SemanticKernel.IntegrationTests.TestSettings; using Xunit; @@ -34,9 +32,6 @@ public OpenAICompletionTests(ITestOutputHelper output) .AddEnvironmentVariables() .AddUserSecrets() .Build(); - - this._serviceConfiguration.Add(AIServiceType.OpenAI, this.ConfigureOpenAI); - this._serviceConfiguration.Add(AIServiceType.AzureOpenAI, this.ConfigureAzureOpenAI); } [Theory(Skip = "OpenAI will often throttle requests. This test is for manual verification.")] @@ -44,9 +39,17 @@ public OpenAICompletionTests(ITestOutputHelper output) public async Task OpenAITestAsync(string prompt, string expectedAnswerContains) { // Arrange - IKernel target = Kernel.Builder.WithLogger(this._logger).Build(); + var openAIConfiguration = this._configuration.GetSection("OpenAI").Get(); + Assert.NotNull(openAIConfiguration); - this.ConfigureOpenAI(target); + IKernel target = Kernel.Builder + .WithLogger(this._logger) + .AddOpenAITextCompletionService( + serviceId: openAIConfiguration.ServiceId, + modelId: openAIConfiguration.ModelId, + apiKey: openAIConfiguration.ApiKey, + setAsDefault: true) + .Build(); IDictionary skill = TestHelpers.GetSkills(target, "ChatSkill"); @@ -62,9 +65,18 @@ public async Task OpenAITestAsync(string prompt, string expectedAnswerContains) public async Task AzureOpenAITestAsync(string prompt, string expectedAnswerContains) { // Arrange - IKernel target = Kernel.Builder.WithLogger(this._logger).Build(); + var azureOpenAIConfiguration = this._configuration.GetSection("AzureOpenAI").Get(); + Assert.NotNull(azureOpenAIConfiguration); - this.ConfigureAzureOpenAI(target); + IKernel target = Kernel.Builder + .WithLogger(this._logger) + .AddAzureTextCompletionService( + serviceId: azureOpenAIConfiguration.ServiceId, + deploymentName: azureOpenAIConfiguration.DeploymentName, + endpoint: azureOpenAIConfiguration.Endpoint, + apiKey: azureOpenAIConfiguration.ApiKey, + setAsDefault: true) + .Build(); IDictionary skill = TestHelpers.GetSkills(target, "ChatSkill"); @@ -83,17 +95,15 @@ public async Task AzureOpenAITestAsync(string prompt, string expectedAnswerConta public async Task OpenAIHttpRetryPolicyTestAsync(string prompt, string expectedOutput) { // Arrange - var retryConfig = new HttpRetryConfig(); - retryConfig.RetryableStatusCodes.Add(HttpStatusCode.Unauthorized); - IKernel target = Kernel.Builder.WithLogger(this._testOutputHelper).Configure(c => c.SetDefaultHttpRetryConfig(retryConfig)).Build(); - OpenAIConfiguration? openAIConfiguration = this._configuration.GetSection("OpenAI").Get(); Assert.NotNull(openAIConfiguration); - // Use an invalid API key to force a 401 Unauthorized response - target.Config.AddOpenAITextCompletionService( - modelId: openAIConfiguration.ModelId, - apiKey: "INVALID_KEY"); + IKernel target = Kernel.Builder + .AddOpenAITextCompletionService( + serviceId: openAIConfiguration.ServiceId, + modelId: openAIConfiguration.ModelId, + apiKey: "INVALID_KEY") // Use an invalid API key to force a 401 Unauthorized response + .Build(); IDictionary skill = TestHelpers.GetSkills(target, "SummarizeSkill"); @@ -108,15 +118,16 @@ public async Task OpenAIHttpRetryPolicyTestAsync(string prompt, string expectedO public async Task OpenAIHttpInvalidKeyShouldReturnErrorDetailAsync() { // Arrange - IKernel target = Kernel.Builder.WithLogger(this._testOutputHelper).Build(); - OpenAIConfiguration? openAIConfiguration = this._configuration.GetSection("OpenAI").Get(); Assert.NotNull(openAIConfiguration); // Use an invalid API key to force a 401 Unauthorized response - target.Config.AddOpenAITextCompletionService( - modelId: openAIConfiguration.ModelId, - apiKey: "INVALID_KEY"); + IKernel target = Kernel.Builder + .AddOpenAITextCompletionService( + modelId: openAIConfiguration.ModelId, + apiKey: "INVALID_KEY", + serviceId: openAIConfiguration.ServiceId) + .Build(); IDictionary skill = TestHelpers.GetSkills(target, "SummarizeSkill"); @@ -133,16 +144,17 @@ public async Task OpenAIHttpInvalidKeyShouldReturnErrorDetailAsync() public async Task AzureOpenAIHttpInvalidKeyShouldReturnErrorDetailAsync() { // Arrange - IKernel target = Kernel.Builder.WithLogger(this._testOutputHelper).Build(); - var azureOpenAIConfiguration = this._configuration.GetSection("AzureOpenAI").Get(); - Assert.NotNull(azureOpenAIConfiguration); - target.Config.AddAzureTextCompletionService( - deploymentName: azureOpenAIConfiguration.DeploymentName, - endpoint: azureOpenAIConfiguration.Endpoint, - apiKey: "INVALID_KEY"); + IKernel target = Kernel.Builder + .WithLogger(this._testOutputHelper) + .AddAzureTextCompletionService( + deploymentName: azureOpenAIConfiguration.DeploymentName, + endpoint: azureOpenAIConfiguration.Endpoint, + apiKey: "INVALID_KEY", + serviceId: azureOpenAIConfiguration.ServiceId) + .Build(); IDictionary skill = TestHelpers.GetSkills(target, "SummarizeSkill"); @@ -158,17 +170,18 @@ public async Task AzureOpenAIHttpInvalidKeyShouldReturnErrorDetailAsync() [Fact] public async Task AzureOpenAIHttpExceededMaxTokensShouldReturnErrorDetailAsync() { - // Arrange - IKernel target = Kernel.Builder.WithLogger(this._testOutputHelper).Build(); - var azureOpenAIConfiguration = this._configuration.GetSection("AzureOpenAI").Get(); - Assert.NotNull(azureOpenAIConfiguration); - target.Config.AddAzureTextCompletionService( - deploymentName: azureOpenAIConfiguration.DeploymentName, - endpoint: azureOpenAIConfiguration.Endpoint, - apiKey: azureOpenAIConfiguration.ApiKey); + // Arrange + IKernel target = Kernel.Builder + .WithLogger(this._testOutputHelper) + .AddAzureTextCompletionService( + deploymentName: azureOpenAIConfiguration.DeploymentName, + endpoint: azureOpenAIConfiguration.Endpoint, + apiKey: azureOpenAIConfiguration.ApiKey, + serviceId: azureOpenAIConfiguration.ServiceId) + .Build(); IDictionary skill = TestHelpers.GetSkills(target, "SummarizeSkill"); @@ -236,34 +249,5 @@ private void Dispose(bool disposing) } } - private void ConfigureOpenAI(IKernel kernel) - { - var openAIConfiguration = this._configuration.GetSection("OpenAI").Get(); - - Assert.NotNull(openAIConfiguration); - - kernel.Config.AddOpenAITextCompletionService( - modelId: openAIConfiguration.ModelId, - apiKey: openAIConfiguration.ApiKey, - serviceId: openAIConfiguration.ServiceId); - - kernel.Config.SetDefaultTextCompletionService(openAIConfiguration.ServiceId); - } - - private void ConfigureAzureOpenAI(IKernel kernel) - { - var azureOpenAIConfiguration = this._configuration.GetSection("AzureOpenAI").Get(); - - Assert.NotNull(azureOpenAIConfiguration); - - kernel.Config.AddAzureTextCompletionService( - deploymentName: azureOpenAIConfiguration.DeploymentName, - endpoint: azureOpenAIConfiguration.Endpoint, - apiKey: azureOpenAIConfiguration.ApiKey, - serviceId: azureOpenAIConfiguration.ServiceId); - - kernel.Config.SetDefaultTextCompletionService(azureOpenAIConfiguration.ServiceId); - } - #endregion } diff --git a/dotnet/src/IntegrationTests/Planning/PlanTests.cs b/dotnet/src/IntegrationTests/Planning/PlanTests.cs index fe41e15732f4..b4714d1599a9 100644 --- a/dotnet/src/IntegrationTests/Planning/PlanTests.cs +++ b/dotnet/src/IntegrationTests/Planning/PlanTests.cs @@ -458,25 +458,21 @@ private IKernel InitializeKernel(bool useEmbeddings = false) var builder = Kernel.Builder .WithLogger(this._logger) - .Configure(config => - { - config.AddAzureTextCompletionService( - deploymentName: azureOpenAIConfiguration.DeploymentName, - endpoint: azureOpenAIConfiguration.Endpoint, - apiKey: azureOpenAIConfiguration.ApiKey); - - if (useEmbeddings) - { - config.AddAzureTextEmbeddingGenerationService( - deploymentName: azureOpenAIEmbeddingsConfiguration.DeploymentName, - endpoint: azureOpenAIEmbeddingsConfiguration.Endpoint, - apiKey: azureOpenAIEmbeddingsConfiguration.ApiKey); - } - }); + .AddAzureTextCompletionService( + deploymentName: azureOpenAIConfiguration.DeploymentName, + endpoint: azureOpenAIConfiguration.Endpoint, + apiKey: azureOpenAIConfiguration.ApiKey, + setAsDefault: true); + if (useEmbeddings) { - builder = builder.WithMemoryStorage(new VolatileMemoryStore()); + builder + .AddAzureTextEmbeddingGenerationService( + deploymentName: azureOpenAIEmbeddingsConfiguration.DeploymentName, + endpoint: azureOpenAIEmbeddingsConfiguration.Endpoint, + apiKey: azureOpenAIEmbeddingsConfiguration.ApiKey) + .WithMemoryStorage(new VolatileMemoryStore()); } var kernel = builder.Build(); diff --git a/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlanParserTests.cs b/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlanParserTests.cs index 5546a3007bcb..03c0a51421cb 100644 --- a/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlanParserTests.cs +++ b/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlanParserTests.cs @@ -32,13 +32,12 @@ public void CanCallToPlanFromXml() Assert.NotNull(azureOpenAIConfiguration); IKernel kernel = Kernel.Builder - .Configure(config => - { - config.AddAzureTextCompletionService( - deploymentName: azureOpenAIConfiguration.DeploymentName, - endpoint: azureOpenAIConfiguration.Endpoint, - apiKey: azureOpenAIConfiguration.ApiKey); - }) + .AddAzureTextCompletionService( + deploymentName: azureOpenAIConfiguration.DeploymentName, + endpoint: azureOpenAIConfiguration.Endpoint, + apiKey: azureOpenAIConfiguration.ApiKey, + serviceId: azureOpenAIConfiguration.ServiceId, + setAsDefault: true) .Build(); kernel.ImportSkill(new EmailSkillFake(), "email"); TestHelpers.GetSkills(kernel, "SummarizeSkill", "WriterSkill"); diff --git a/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlannerTests.cs b/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlannerTests.cs index 8b4ed677a04a..681de268350f 100644 --- a/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlannerTests.cs +++ b/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlannerTests.cs @@ -108,21 +108,19 @@ private IKernel InitializeKernel(bool useEmbeddings = false) var builder = Kernel.Builder .WithLogger(this._logger) - .Configure(config => - { - config.AddAzureTextCompletionService( - deploymentName: azureOpenAIConfiguration.DeploymentName, - endpoint: azureOpenAIConfiguration.Endpoint, - apiKey: azureOpenAIConfiguration.ApiKey); - - if (useEmbeddings) - { - config.AddAzureTextEmbeddingGenerationService( - deploymentName: azureOpenAIEmbeddingsConfiguration.DeploymentName, - endpoint: azureOpenAIEmbeddingsConfiguration.Endpoint, - apiKey: azureOpenAIEmbeddingsConfiguration.ApiKey); - } - }); + .AddAzureTextCompletionService( + deploymentName: azureOpenAIConfiguration.DeploymentName, + endpoint: azureOpenAIConfiguration.Endpoint, + apiKey: azureOpenAIConfiguration.ApiKey, + setAsDefault: true); + + if (useEmbeddings) + { + builder.AddAzureTextEmbeddingGenerationService( + deploymentName: azureOpenAIEmbeddingsConfiguration.DeploymentName, + endpoint: azureOpenAIEmbeddingsConfiguration.Endpoint, + apiKey: azureOpenAIEmbeddingsConfiguration.ApiKey); + } if (useEmbeddings) { diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatCompletionServiceExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatCompletionServiceExtensions.cs new file mode 100644 index 000000000000..60503fd521e8 --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatCompletionServiceExtensions.cs @@ -0,0 +1,34 @@ +// 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. +namespace Microsoft.SemanticKernel; + +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 GetChatCompletionServiceOrDefault( + this IAIServiceProvider services, + string? serviceId = null) => services.GetNamedServiceOrDefault(serviceId) + ?? throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, "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/ChatCompletion/IChatCompletion.cs b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/IChatCompletion.cs index f890fbb0b497..e6b207eedc6a 100644 --- a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/IChatCompletion.cs +++ b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/IChatCompletion.cs @@ -2,10 +2,11 @@ using System.Threading; using System.Threading.Tasks; +using Microsoft.SemanticKernel.Services; namespace Microsoft.SemanticKernel.AI.ChatCompletion; -public interface IChatCompletion +public interface IChatCompletion : IAIService { /// /// Generate a new chat message diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/EmbeddingGenerationServiceExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/EmbeddingGenerationServiceExtensions.cs new file mode 100644 index 000000000000..a15e7678904b --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/EmbeddingGenerationServiceExtensions.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SemanticKernel.Diagnostics; + +namespace Microsoft.SemanticKernel.AI.Embeddings; + +/// +/// Provides a collection of static methods for operating on objects. +/// +public static class EmbeddingGenerationExtensions +{ + /// + /// Generates an embedding from the given . + /// + /// The type from which embeddings will be generated. + /// The numeric type of the embedding data. + /// The embedding generator. + /// A value from which an will be generated. + /// Cancellation token + /// A list of structs representing the input . + public static async Task> GenerateEmbeddingAsync + (this IEmbeddingGeneration generator, TValue value, CancellationToken cancellationToken = default) + where TEmbedding : unmanaged + { + Verify.NotNull(generator); + return (await generator.GenerateEmbeddingsAsync(new[] { value }, cancellationToken).ConfigureAwait(false)).FirstOrDefault(); + } +} diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/IEmbeddingGeneration.cs b/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/IEmbeddingGeneration.cs index c3128b921aa7..33d3e1ec7f2b 100644 --- a/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/IEmbeddingGeneration.cs +++ b/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/IEmbeddingGeneration.cs @@ -1,10 +1,8 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; -using Microsoft.SemanticKernel.Diagnostics; namespace Microsoft.SemanticKernel.AI.Embeddings; @@ -24,26 +22,3 @@ public interface IEmbeddingGeneration /// List of embeddings Task>> GenerateEmbeddingsAsync(IList data, CancellationToken cancellationToken = default); } - -/// -/// Provides a collection of static methods for operating on objects. -/// -public static class EmbeddingGenerationExtensions -{ - /// - /// Generates an embedding from the given . - /// - /// The type from which embeddings will be generated. - /// The numeric type of the embedding data. - /// The embedding generator. - /// A value from which an will be generated. - /// The to monitor for cancellation requests. The default is . - /// A list of structs representing the input . - public static async Task> GenerateEmbeddingAsync - (this IEmbeddingGeneration generator, TValue value, CancellationToken cancellationToken = default) - where TEmbedding : unmanaged - { - Verify.NotNull(generator); - return (await generator.GenerateEmbeddingsAsync(new[] { value }, cancellationToken).ConfigureAwait(false)).FirstOrDefault(); - } -} diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/ITextEmbeddingGeneration.cs b/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/ITextEmbeddingGeneration.cs new file mode 100644 index 000000000000..372a2a2b512a --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/ITextEmbeddingGeneration.cs @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.SemanticKernel.Services; + +namespace Microsoft.SemanticKernel.AI.Embeddings; + +/// +/// Represents a generator of text embeddings of type float. +/// +public interface ITextEmbeddingGeneration : IEmbeddingGeneration, IAIService +{ +} diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/TextEmbeddingServiceExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/TextEmbeddingServiceExtensions.cs new file mode 100644 index 000000000000..6854b6db15f6 --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/TextEmbeddingServiceExtensions.cs @@ -0,0 +1,35 @@ +// 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. +namespace Microsoft.SemanticKernel; + +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 GetTextEmbeddingServiceOrDefault( + this IAIServiceProvider services, + string? serviceId = null) + => services.GetNamedServiceOrDefault(serviceId) + ?? throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, "Text embedding service not available"); + + /// + /// 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/IImageGeneration.cs b/dotnet/src/SemanticKernel.Abstractions/AI/ImageGeneration/IImageGeneration.cs index 4336506c439a..ded72f7855c7 100644 --- a/dotnet/src/SemanticKernel.Abstractions/AI/ImageGeneration/IImageGeneration.cs +++ b/dotnet/src/SemanticKernel.Abstractions/AI/ImageGeneration/IImageGeneration.cs @@ -2,10 +2,11 @@ using System.Threading; using System.Threading.Tasks; +using Microsoft.SemanticKernel.Services; namespace Microsoft.SemanticKernel.AI.ImageGeneration; -public interface IImageGeneration +public interface IImageGeneration : IAIService { /// /// Generate an image matching the given description diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/ImageGeneration/ImageGenerationServiceExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/AI/ImageGeneration/ImageGenerationServiceExtensions.cs new file mode 100644 index 000000000000..28488ad72624 --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/AI/ImageGeneration/ImageGenerationServiceExtensions.cs @@ -0,0 +1,34 @@ +// 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. +namespace Microsoft.SemanticKernel; + +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 GetImageGenerationServiceOrDefault( + this IAIServiceProvider services, + string? serviceId = null) => services.GetNamedServiceOrDefault(serviceId) + ?? throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, "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/ITextCompletion.cs b/dotnet/src/SemanticKernel.Abstractions/AI/TextCompletion/ITextCompletion.cs index 1ec4778d4eec..d1d38de2b2e6 100644 --- a/dotnet/src/SemanticKernel.Abstractions/AI/TextCompletion/ITextCompletion.cs +++ b/dotnet/src/SemanticKernel.Abstractions/AI/TextCompletion/ITextCompletion.cs @@ -3,13 +3,14 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.SemanticKernel.Services; namespace Microsoft.SemanticKernel.AI.TextCompletion; /// /// Interface for text completion services /// -public interface ITextCompletion +public interface ITextCompletion : IAIService { /// /// Creates a completion for the prompt and settings. diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/TextCompletion/TextCompletionServiceExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/AI/TextCompletion/TextCompletionServiceExtensions.cs new file mode 100644 index 000000000000..b60f833be1fa --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/AI/TextCompletion/TextCompletionServiceExtensions.cs @@ -0,0 +1,34 @@ +// 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. +namespace Microsoft.SemanticKernel; + +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() + ?? throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, "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/Diagnostics/NullableAttributes.cs b/dotnet/src/SemanticKernel.Abstractions/Diagnostics/NullableAttributes.cs index 18fc100ef3ce..9c9efa3a097d 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Diagnostics/NullableAttributes.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Diagnostics/NullableAttributes.cs @@ -1,6 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. +// Copyright (c) Microsoft. All rights reserved. // This was copied from https://github.com/dotnet/runtime/blob/39b9607807f29e48cae4652cd74735182b31182e/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs // and updated to have the scope of the attributes be internal. diff --git a/dotnet/src/SemanticKernel.Abstractions/IKernel.cs b/dotnet/src/SemanticKernel.Abstractions/IKernel.cs index bae440660a82..d9a70aa0dcd5 100644 --- a/dotnet/src/SemanticKernel.Abstractions/IKernel.cs +++ b/dotnet/src/SemanticKernel.Abstractions/IKernel.cs @@ -7,6 +7,7 @@ using Microsoft.SemanticKernel.Memory; using Microsoft.SemanticKernel.Orchestration; using Microsoft.SemanticKernel.SemanticFunctions; +using Microsoft.SemanticKernel.Services; using Microsoft.SemanticKernel.SkillDefinition; using Microsoft.SemanticKernel.TemplateEngine; @@ -170,5 +171,5 @@ Task RunAsync( /// Optional name. If the name is not provided, returns the default T available /// Service type /// Instance of T - T GetService(string? name = null); + T GetService(string? name = null) where T : IAIService; } diff --git a/dotnet/src/SemanticKernel.Abstractions/KernelConfig.cs b/dotnet/src/SemanticKernel.Abstractions/KernelConfig.cs index 4ca40d688cd8..ae22a158059f 100644 --- a/dotnet/src/SemanticKernel.Abstractions/KernelConfig.cs +++ b/dotnet/src/SemanticKernel.Abstractions/KernelConfig.cs @@ -1,11 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -using System; -using System.Collections.Generic; -using Microsoft.SemanticKernel.AI.ChatCompletion; -using Microsoft.SemanticKernel.AI.Embeddings; -using Microsoft.SemanticKernel.AI.ImageGeneration; -using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Reliability; namespace Microsoft.SemanticKernel; @@ -26,148 +20,6 @@ public sealed class KernelConfig /// public HttpRetryConfig DefaultHttpRetryConfig { get; private set; } = new(); - /// - /// Text completion service factories - /// - public Dictionary> TextCompletionServices { get; } = new(); - - /// - /// Chat completion service factories - /// - public Dictionary> ChatCompletionServices { get; } = new(); - - /// - /// Text embedding generation service factories - /// - public Dictionary>> TextEmbeddingGenerationServices { get; } = new(); - - /// - /// Image generation service factories - /// - public Dictionary> ImageGenerationServices { get; } = new(); - - /// - /// Default name used when binding services if the user doesn't provide a custom value - /// - internal string DefaultServiceId => "__SK_DEFAULT"; - - /// - /// Add to the list a service for text completion, e.g. Azure OpenAI Text Completion. - /// - /// Function used to instantiate the service object - /// Id used to identify the service - /// Current object instance - /// Failure if a service with the same id already exists - public KernelConfig AddTextCompletionService( - Func serviceFactory, - string? serviceId = null) - { - if (serviceId != null && serviceId.Equals(this.DefaultServiceId, StringComparison.OrdinalIgnoreCase)) - { - throw new KernelException( - KernelException.ErrorCodes.InvalidServiceConfiguration, - $"The service id '{serviceId}' is reserved, please use a different name"); - } - - serviceId ??= this.DefaultServiceId; - - this.TextCompletionServices[serviceId] = serviceFactory; - if (this.TextCompletionServices.Count == 1) - { - this.TextCompletionServices[this.DefaultServiceId] = serviceFactory; - } - - return this; - } - - /// - /// Add to the list a service for chat completion, e.g. OpenAI ChatGPT. - /// - /// Function used to instantiate the service object - /// Id used to identify the service - /// Current object instance - /// Failure if a service with the same id already exists - public KernelConfig AddChatCompletionService( - Func serviceFactory, - string? serviceId = null) - { - if (serviceId != null && serviceId.Equals(this.DefaultServiceId, StringComparison.OrdinalIgnoreCase)) - { - throw new KernelException( - KernelException.ErrorCodes.InvalidServiceConfiguration, - $"The service id '{serviceId}' is reserved, please use a different name"); - } - - serviceId ??= this.DefaultServiceId; - - this.ChatCompletionServices[serviceId] = serviceFactory; - if (this.ChatCompletionServices.Count == 1) - { - this.ChatCompletionServices[this.DefaultServiceId] = serviceFactory; - } - - return this; - } - - /// - /// Add to the list a service for text embedding generation, e.g. Azure OpenAI Text Embedding. - /// - /// Function used to instantiate the service object - /// Id used to identify the service - /// Current object instance - /// Failure if a service with the same id already exists - public KernelConfig AddTextEmbeddingGenerationService( - Func> serviceFactory, - string? serviceId = null) - { - if (serviceId != null && serviceId.Equals(this.DefaultServiceId, StringComparison.OrdinalIgnoreCase)) - { - throw new KernelException( - KernelException.ErrorCodes.InvalidServiceConfiguration, - $"The service id '{serviceId}' is reserved, please use a different name"); - } - - serviceId ??= this.DefaultServiceId; - - this.TextEmbeddingGenerationServices[serviceId] = serviceFactory; - if (this.TextEmbeddingGenerationServices.Count == 1) - { - this.TextEmbeddingGenerationServices[this.DefaultServiceId] = serviceFactory; - } - - return this; - } - - /// - /// Add to the list a service for image generation, e.g. OpenAI DallE. - /// - /// Function used to instantiate the service object - /// Id used to identify the service - /// Current object instance - /// Failure if a service with the same id already exists - public KernelConfig AddImageGenerationService( - Func serviceFactory, - string? serviceId = null) - { - if (serviceId != null && serviceId.Equals(this.DefaultServiceId, StringComparison.OrdinalIgnoreCase)) - { - throw new KernelException( - KernelException.ErrorCodes.InvalidServiceConfiguration, - $"The service id '{serviceId}' is reserved, please use a different name"); - } - - serviceId ??= this.DefaultServiceId; - - this.ImageGenerationServices[serviceId] = serviceFactory; - if (this.ImageGenerationServices.Count == 1) - { - this.ImageGenerationServices[this.DefaultServiceId] = serviceFactory; - } - - return this; - } - - #region Set /// /// Set the http retry handler factory to use for the kernel. @@ -194,78 +46,4 @@ public KernelConfig SetDefaultHttpRetryConfig(HttpRetryConfig? httpRetryConfig) return this; } - - /// - /// Set the default completion service to use for the kernel. - /// - /// Identifier of completion service to use. - /// The updated kernel configuration. - /// Thrown if the requested service doesn't exist. - public KernelConfig SetDefaultTextCompletionService(string serviceId) - { - if (!this.TextCompletionServices.ContainsKey(serviceId)) - { - throw new KernelException( - KernelException.ErrorCodes.ServiceNotFound, - $"A text completion service id '{serviceId}' doesn't exist"); - } - - this.TextCompletionServices[this.DefaultServiceId] = this.TextCompletionServices[serviceId]; - return this; - } - - /// - /// Set the default embedding service to use for the kernel. - /// - /// Identifier of text embedding service to use. - /// The updated kernel configuration. - /// Thrown if the requested service doesn't exist. - public KernelConfig SetDefaultTextEmbeddingGenerationService(string serviceId) - { - if (!this.TextEmbeddingGenerationServices.ContainsKey(serviceId)) - { - throw new KernelException( - KernelException.ErrorCodes.ServiceNotFound, - $"A text embedding generation service id '{serviceId}' doesn't exist"); - } - - this.TextEmbeddingGenerationServices[this.DefaultServiceId] = this.TextEmbeddingGenerationServices[serviceId]; - return this; - } - - #endregion - - #region Remove - - /// - /// Remove all text completion services. - /// - /// The updated kernel configuration. - public KernelConfig RemoveAllTextCompletionServices() - { - this.TextCompletionServices.Clear(); - return this; - } - - /// - /// Remove all chat completion services. - /// - /// The updated kernel configuration. - public KernelConfig RemoveAllChatCompletionServices() - { - this.ChatCompletionServices.Clear(); - return this; - } - - /// - /// Remove all text embedding generation services. - /// - /// The updated kernel configuration. - public KernelConfig RemoveAllTextEmbeddingGenerationServices() - { - this.TextEmbeddingGenerationServices.Clear(); - return this; - } - - #endregion } diff --git a/dotnet/src/SemanticKernel.Abstractions/SemanticKernel.Abstractions.csproj b/dotnet/src/SemanticKernel.Abstractions/SemanticKernel.Abstractions.csproj index 5d49eb5d598a..46a980cadc87 100644 --- a/dotnet/src/SemanticKernel.Abstractions/SemanticKernel.Abstractions.csproj +++ b/dotnet/src/SemanticKernel.Abstractions/SemanticKernel.Abstractions.csproj @@ -23,15 +23,15 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs b/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs new file mode 100644 index 000000000000..3d2ab690bfb5 --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.SemanticKernel.Services; + +[SuppressMessage("Design", "CA1040:Avoid empty interfaces")] +public interface IAIService +{ +} diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/IAIServiceProvider.cs b/dotnet/src/SemanticKernel.Abstractions/Services/IAIServiceProvider.cs new file mode 100644 index 000000000000..5f4ff9a507cf --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/Services/IAIServiceProvider.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.SemanticKernel.Services; + +public interface IAIServiceProvider : INamedServiceProvider +{ +} diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/INamedServiceProvider.cs b/dotnet/src/SemanticKernel.Abstractions/Services/INamedServiceProvider.cs new file mode 100644 index 000000000000..ca0ec11eb29c --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/Services/INamedServiceProvider.cs @@ -0,0 +1,14 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.SemanticKernel.Services; + +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; +} diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/ServiceExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/Services/ServiceExtensions.cs new file mode 100644 index 000000000000..8b81fb01b2db --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/Services/ServiceExtensions.cs @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.SemanticKernel.Services; +internal static class AIServiceProviderExtensions +{ + /// + /// Get the service matching the given id or the default if an id is not provided or not found. + /// + /// The type of the service. + /// The service provider. + /// Optional identifier of the desired service. + /// The service matching the given id, the default service, or null. + public static T? GetNamedServiceOrDefault( + this IAIServiceProvider serviceProvider, + string? serviceId = null) where T : IAIService + { + if ((serviceId != null) + && serviceProvider.TryGetService(serviceId, out var namedService)) + { + return namedService; + } + + return serviceProvider.GetService(); + } + + + /// + /// 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.UnitTests/AI/ChatCompletion/ChatCompletionServiceExtensionTests.cs b/dotnet/src/SemanticKernel.UnitTests/AI/ChatCompletion/ChatCompletionServiceExtensionTests.cs new file mode 100644 index 000000000000..bcb6b6da910f --- /dev/null +++ b/dotnet/src/SemanticKernel.UnitTests/AI/ChatCompletion/ChatCompletionServiceExtensionTests.cs @@ -0,0 +1,148 @@ +// 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 new file mode 100644 index 000000000000..0740a7f81fea --- /dev/null +++ b/dotnet/src/SemanticKernel.UnitTests/AI/Embeddings/TextEmbeddingServiceExtensionTests.cs @@ -0,0 +1,148 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.AI.ChatCompletion; +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 new file mode 100644 index 000000000000..165f5e636d76 --- /dev/null +++ b/dotnet/src/SemanticKernel.UnitTests/AI/ImageGeneration/ImageCompletionServiceExtensionTests.cs @@ -0,0 +1,146 @@ +// 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 new file mode 100644 index 000000000000..689b88d115dd --- /dev/null +++ b/dotnet/src/SemanticKernel.UnitTests/AI/TextCompletion/TextCompletionServiceExtensionTests.cs @@ -0,0 +1,147 @@ +// 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/CoreSkills/FileIOSkillTests.cs b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/FileIOSkillTests.cs index f617856a5930..0dfe8b2d53a0 100644 --- a/dotnet/src/SemanticKernel.UnitTests/CoreSkills/FileIOSkillTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/FileIOSkillTests.cs @@ -27,7 +27,7 @@ public void ItCanBeInstantiated() public void ItCanBeImported() { // Arrange - var kernel = KernelBuilder.Create(); + var kernel = Kernel.Builder.Build(); // Act - Assert no exception occurs e.g. due to reflection _ = kernel.ImportSkill(new FileIOSkill(), "fileIO"); diff --git a/dotnet/src/SemanticKernel.UnitTests/CoreSkills/HttpSkillTests.cs b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/HttpSkillTests.cs index 5fd1471f6bcd..d843764b196d 100644 --- a/dotnet/src/SemanticKernel.UnitTests/CoreSkills/HttpSkillTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/HttpSkillTests.cs @@ -39,7 +39,7 @@ public void ItCanBeInstantiated() public void ItCanBeImported() { // Arrange - var kernel = KernelBuilder.Create(); + var kernel = Kernel.Builder.Build(); using var skill = new HttpSkill(); // Act - Assert no exception occurs e.g. due to reflection diff --git a/dotnet/src/SemanticKernel.UnitTests/CoreSkills/MathSkillTests.cs b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/MathSkillTests.cs index 6f4239bcd3f4..1c3cc2f40557 100644 --- a/dotnet/src/SemanticKernel.UnitTests/CoreSkills/MathSkillTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/MathSkillTests.cs @@ -26,7 +26,7 @@ public void ItCanBeInstantiated() public void ItCanBeImported() { // Arrange - var kernel = KernelBuilder.Create(); + var kernel = Kernel.Builder.Build(); // Act - Assert no exception occurs e.g. due to reflection kernel.ImportSkill(new MathSkill(), "math"); diff --git a/dotnet/src/SemanticKernel.UnitTests/CoreSkills/TextSkillTests.cs b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/TextSkillTests.cs index 300339d2929a..138e17982b46 100644 --- a/dotnet/src/SemanticKernel.UnitTests/CoreSkills/TextSkillTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/TextSkillTests.cs @@ -24,7 +24,7 @@ public void ItCanBeInstantiated() public void ItCanBeImported() { // Arrange - var kernel = KernelBuilder.Create(); + var kernel = Kernel.Builder.Build(); // Act - Assert no exception occurs e.g. due to reflection kernel.ImportSkill(new TextSkill(), "text"); diff --git a/dotnet/src/SemanticKernel.UnitTests/CoreSkills/TimeSkillTests.cs b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/TimeSkillTests.cs index b6a22b2aedc2..9f63f9cdfa91 100644 --- a/dotnet/src/SemanticKernel.UnitTests/CoreSkills/TimeSkillTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/TimeSkillTests.cs @@ -20,7 +20,7 @@ public void ItCanBeInstantiated() public void ItCanBeImported() { // Arrange - var kernel = KernelBuilder.Create(); + var kernel = Kernel.Builder.Build(); // Act - Assert no exception occurs e.g. due to reflection kernel.ImportSkill(new TimeSkill(), "time"); diff --git a/dotnet/src/SemanticKernel.UnitTests/CoreSkills/WaitSkillTests.cs b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/WaitSkillTests.cs index b9c669d34d34..1911a96ecdee 100644 --- a/dotnet/src/SemanticKernel.UnitTests/CoreSkills/WaitSkillTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/WaitSkillTests.cs @@ -23,7 +23,7 @@ public void ItCanBeInstantiated() public void ItCanBeImported() { // Arrange - var kernel = KernelBuilder.Create(); + var kernel = Kernel.Builder.Build(); // Act - Assert no exception occurs e.g. due to reflection kernel.ImportSkill(new WaitSkill(), "wait"); diff --git a/dotnet/src/SemanticKernel.UnitTests/KernelConfigTests.cs b/dotnet/src/SemanticKernel.UnitTests/KernelConfigTests.cs index 50ecb9ce1ae3..bafd04ca17bd 100644 --- a/dotnet/src/SemanticKernel.UnitTests/KernelConfigTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/KernelConfigTests.cs @@ -74,26 +74,4 @@ public void HttpRetryHandlerFactoryIsSetToDefaultHttpRetryHandlerFactoryIfNotSet // Assert Assert.IsType(config.HttpHandlerFactory); } - - [Fact] - public void ItFailsWhenSetNonExistentTextCompletionService() - { - var target = new KernelConfig(); - var exception = Assert.Throws(() => - { - target.SetDefaultTextCompletionService("azure"); - }); - Assert.Equal(KernelException.ErrorCodes.ServiceNotFound, exception.ErrorCode); - } - - [Fact] - public void ItFailsWhenSetNonExistentEmbeddingService() - { - var target = new KernelConfig(); - var exception = Assert.Throws(() => - { - target.SetDefaultTextEmbeddingGenerationService("azure"); - }); - Assert.Equal(KernelException.ErrorCodes.ServiceNotFound, exception.ErrorCode); - } } diff --git a/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs b/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs index 491775458ceb..a831493f01f8 100644 --- a/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs @@ -21,9 +21,10 @@ public class KernelTests public void ItProvidesAccessToFunctionsViaSkillCollection() { // Arrange - var kernel = KernelBuilder.Create(); - var factory = new Mock>(); - kernel.Config.AddTextCompletionService(factory.Object); + var factory = new Mock>(); + var kernel = Kernel.Builder + .WithDefaultAIService(factory.Object) + .Build(); var nativeSkill = new MySkill(); kernel.CreateSemanticFunction(promptTemplate: "Tell me a joke", functionName: "joker", skillName: "jk", description: "Nice fun"); @@ -49,9 +50,11 @@ public void ItProvidesAccessToFunctionsViaSkillCollection() public async Task ItProvidesAccessToFunctionsViaSKContextAsync() { // Arrange - var kernel = KernelBuilder.Create(); - var factory = new Mock>(); - kernel.Config.AddTextCompletionService(factory.Object); + var factory = new Mock>(); + var kernel = Kernel.Builder + .WithAIService("x", factory.Object) + .Build(); + var nativeSkill = new MySkill(); kernel.CreateSemanticFunction("Tell me a joke", functionName: "joker", skillName: "jk", description: "Nice fun"); @@ -73,11 +76,11 @@ public async Task ItProvidesAccessToFunctionsViaSKContextAsync() public async Task RunAsyncDoesNotRunWhenCancelledAsync() { // Arrange - var kernel = KernelBuilder.Create(); + var kernel = Kernel.Builder.Build(); var nativeSkill = new MySkill(); var skill = kernel.ImportSkill(nativeSkill, "mySk"); - using CancellationTokenSource cts = new CancellationTokenSource(); + using CancellationTokenSource cts = new(); cts.Cancel(); // Act @@ -93,11 +96,11 @@ public async Task RunAsyncDoesNotRunWhenCancelledAsync() public async Task RunAsyncRunsWhenNotCancelledAsync() { // Arrange - var kernel = KernelBuilder.Create(); + var kernel = Kernel.Builder.Build(); var nativeSkill = new MySkill(); kernel.ImportSkill(nativeSkill, "mySk"); - using CancellationTokenSource cts = new CancellationTokenSource(); + using CancellationTokenSource cts = new(); // Act SKContext result = await kernel.RunAsync(cts.Token, kernel.Func("mySk", "GetAnyValue")); @@ -112,7 +115,7 @@ public async Task RunAsyncRunsWhenNotCancelledAsync() public void ItImportsSkillsNotCaseSensitive() { // Act - IDictionary skill = KernelBuilder.Create().ImportSkill(new MySkill(), "test"); + IDictionary skill = Kernel.Builder.Build().ImportSkill(new MySkill(), "test"); // Assert Assert.Equal(3, skill.Count); @@ -125,7 +128,7 @@ public void ItImportsSkillsNotCaseSensitive() public void ItAllowsToImportSkillsInTheGlobalNamespace() { // Arrange - var kernel = KernelBuilder.Create(); + var kernel = Kernel.Builder.Build(); // Act IDictionary skill = kernel.ImportSkill(new MySkill()); @@ -140,7 +143,7 @@ public void ItAllowsToImportSkillsInTheGlobalNamespace() public void ItAllowsToImportTheSameSkillMultipleTimes() { // Arrange - var kernel = KernelBuilder.Create(); + var kernel = Kernel.Builder.Build(); // Act - Assert no exception occurs kernel.ImportSkill(new MySkill()); @@ -148,16 +151,6 @@ public void ItAllowsToImportTheSameSkillMultipleTimes() kernel.ImportSkill(new MySkill()); } - [Fact] - public void ItFailsIfTextCompletionServiceConfigIsNotSet() - { - // Arrange - var kernel = KernelBuilder.Create(); - - var exception = Assert.Throws( - () => kernel.CreateSemanticFunction(promptTemplate: "Tell me a joke", functionName: "joker", skillName: "jk", description: "Nice fun")); - } - public class MySkill { [SKFunction("Return any value.")] diff --git a/dotnet/src/SemanticKernel.UnitTests/SemanticKernel.UnitTests.csproj b/dotnet/src/SemanticKernel.UnitTests/SemanticKernel.UnitTests.csproj index fc8c771216e4..51e33658c774 100644 --- a/dotnet/src/SemanticKernel.UnitTests/SemanticKernel.UnitTests.csproj +++ b/dotnet/src/SemanticKernel.UnitTests/SemanticKernel.UnitTests.csproj @@ -25,6 +25,7 @@ + diff --git a/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs b/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs new file mode 100644 index 000000000000..346d402bbccc --- /dev/null +++ b/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs @@ -0,0 +1,219 @@ +// Copyright (c) Microsoft. All rights reserved. + +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 + { + } +} diff --git a/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKContextTests.cs b/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKContextTests.cs index 5455530119d5..8a98957a4f33 100644 --- a/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKContextTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/SkillDefinition/SKContextTests.cs @@ -51,7 +51,7 @@ public void ItHasHelpersForContextVariables() public async Task ItHasHelpersForSkillCollectionAsync() { // Arrange - IDictionary skill = KernelBuilder.Create().ImportSkill(new Parrot(), "test"); + IDictionary skill = Kernel.Builder.Build().ImportSkill(new Parrot(), "test"); this._skills.Setup(x => x.GetNativeFunction("func")).Returns(skill["say"]); var target = new SKContext(new ContextVariables(), NullMemory.Instance, this._skills.Object, this._log.Object); Assert.NotNull(target.Skills); diff --git a/dotnet/src/SemanticKernel/Connectors/HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs b/dotnet/src/SemanticKernel/Connectors/HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs index 742da1aa5530..e09fa498cc18 100644 --- a/dotnet/src/SemanticKernel/Connectors/HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs +++ b/dotnet/src/SemanticKernel/Connectors/HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; -using System.Runtime.CompilerServices; using System.Text.Json; using System.Threading; using System.Threading.Tasks; diff --git a/dotnet/src/SemanticKernel/Connectors/HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs b/dotnet/src/SemanticKernel/Connectors/HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs index 29bceced7d53..16d247355262 100644 --- a/dotnet/src/SemanticKernel/Connectors/HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs +++ b/dotnet/src/SemanticKernel/Connectors/HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs @@ -16,7 +16,7 @@ namespace Microsoft.SemanticKernel.Connectors.HuggingFace.TextEmbedding; /// /// HuggingFace embedding generation service. /// -public sealed class HuggingFaceTextEmbeddingGeneration : IEmbeddingGeneration, IDisposable +public sealed class HuggingFaceTextEmbeddingGeneration : ITextEmbeddingGeneration, IDisposable { private const string HttpUserAgent = "Microsoft Semantic Kernel"; diff --git a/dotnet/src/SemanticKernel/Kernel.cs b/dotnet/src/SemanticKernel/Kernel.cs index 062bb68ec8fd..9ec428bab067 100644 --- a/dotnet/src/SemanticKernel/Kernel.cs +++ b/dotnet/src/SemanticKernel/Kernel.cs @@ -7,14 +7,12 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.AI; -using Microsoft.SemanticKernel.AI.ChatCompletion; -using Microsoft.SemanticKernel.AI.Embeddings; -using Microsoft.SemanticKernel.AI.ImageGeneration; using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Diagnostics; using Microsoft.SemanticKernel.Memory; using Microsoft.SemanticKernel.Orchestration; using Microsoft.SemanticKernel.SemanticFunctions; +using Microsoft.SemanticKernel.Services; using Microsoft.SemanticKernel.SkillDefinition; using Microsoft.SemanticKernel.TemplateEngine; @@ -58,12 +56,14 @@ public sealed class Kernel : IKernel, IDisposable /// Kernel constructor. See KernelBuilder for an easier and less error prone approach to create kernel instances. /// /// + /// /// /// /// /// public Kernel( ISkillCollection skillCollection, + IAIServiceProvider aiServiceProvider, IPromptTemplateEngine promptTemplateEngine, ISemanticTextMemory memory, KernelConfig config, @@ -72,6 +72,7 @@ public Kernel( this._log = log; this._config = config; this._memory = memory; + this._aiServiceProvider = aiServiceProvider; this._promptTemplateEngine = promptTemplateEngine; this._skillCollection = skillCollection; } @@ -221,64 +222,11 @@ public SKContext CreateNewContext() } /// - public T GetService(string? name = null) + public T GetService(string? name = null) where T : IAIService { - // TODO: use .NET ServiceCollection (will require a lot of changes) - // TODO: support Connectors, IHttpFactory and IDelegatingHandlerFactory - - if (typeof(T) == typeof(ITextCompletion)) - { - name ??= this.Config.DefaultServiceId; - - if (!this.Config.TextCompletionServices.TryGetValue(name, out Func factory)) - { - throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, $"'{name}' text completion service not available"); - } - - var service = factory.Invoke(this); - return (T)service; - } - - if (typeof(T) == typeof(IEmbeddingGeneration)) - { - name ??= this.Config.DefaultServiceId; - - if (!this.Config.TextEmbeddingGenerationServices.TryGetValue(name, out Func> factory)) - { - throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, $"'{name}' text embedding service not available"); - } - - var service = factory.Invoke(this); - return (T)service; - } - - if (typeof(T) == typeof(IChatCompletion)) - { - name ??= this.Config.DefaultServiceId; - - if (!this.Config.ChatCompletionServices.TryGetValue(name, out Func factory)) - { - throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, $"'{name}' chat completion service not available"); - } - - var service = factory.Invoke(this); - return (T)service; - } - - if (typeof(T) == typeof(IImageGeneration)) - { - name ??= this.Config.DefaultServiceId; - - if (!this.Config.ImageGenerationServices.TryGetValue(name, out Func factory)) - { - throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, $"'{name}' image generation service not available"); - } - - var service = factory.Invoke(this); - return (T)service; - } - - throw new NotSupportedException("The kernel service collection doesn't support the type " + typeof(T).FullName); + return this._aiServiceProvider.GetService(name) ?? + throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, + $"Service of type {typeof(T)} and name {name ?? ""} not registered."); } /// @@ -300,6 +248,7 @@ public void Dispose() private readonly ISkillCollection _skillCollection; private ISemanticTextMemory _memory; private readonly IPromptTemplateEngine _promptTemplateEngine; + private readonly IAIServiceProvider _aiServiceProvider; private ISKFunction CreateSemanticFunction( string skillName, diff --git a/dotnet/src/SemanticKernel/KernelBuilder.cs b/dotnet/src/SemanticKernel/KernelBuilder.cs index 3e32ea04620b..79b41b5c124f 100644 --- a/dotnet/src/SemanticKernel/KernelBuilder.cs +++ b/dotnet/src/SemanticKernel/KernelBuilder.cs @@ -7,6 +7,7 @@ using Microsoft.SemanticKernel.Diagnostics; using Microsoft.SemanticKernel.Memory; using Microsoft.SemanticKernel.Reliability; +using Microsoft.SemanticKernel.Services; using Microsoft.SemanticKernel.SkillDefinition; using Microsoft.SemanticKernel.TemplateEngine; @@ -23,16 +24,7 @@ public sealed class KernelBuilder private ILogger _log = NullLogger.Instance; private IMemoryStore? _memoryStorage = null; private IDelegatingHandlerFactory? _httpHandlerFactory = null; - - /// - /// Create a new kernel instance - /// - /// New kernel instance - public static IKernel Create() - { - var builder = new KernelBuilder(); - return builder.Build(); - } + private readonly AIServiceCollection _aiServices = new(); /// /// Build a new kernel instance using the settings passed so far. @@ -47,10 +39,11 @@ public IKernel Build() var instance = new Kernel( new SkillCollection(this._log), + this._aiServices.Build(), new PromptTemplateEngine(this._log), this._memory, this._config, - this._log + this._log ?? NullLogger.Instance ); // TODO: decouple this from 'UseMemory' kernel extension @@ -105,7 +98,7 @@ public KernelBuilder WithMemoryStorage(IMemoryStore storage) /// Embedding generator to add. /// Updated kernel builder including the memory storage and embedding generator. public KernelBuilder WithMemoryStorageAndTextEmbeddingGeneration( - IMemoryStore storage, IEmbeddingGeneration embeddingGenerator) + IMemoryStore storage, ITextEmbeddingGeneration embeddingGenerator) { Verify.NotNull(storage); Verify.NotNull(embeddingGenerator); @@ -148,4 +141,60 @@ public KernelBuilder Configure(Action configure) configure.Invoke(this._config); return this; } + + /// + /// Adds a instance to the services collection + /// + /// The instance. + /// Optional: set as the default AI service for type + public KernelBuilder WithDefaultAIService( + TService instance, + bool setAsDefault = false) where TService : IAIService + { + this._aiServices.SetService(instance); + return this; + } + + /// + /// 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; + } + + /// + /// Adds a factory method to the services collection + /// + /// The factory method that creates the AI service instances of type . + /// Optional: set as the default AI service for type + public KernelBuilder WithDefaultAIService( + Func factory, + bool setAsDefault = false) where TService : IAIService + { + this._aiServices.SetService(factory); + return this; + } + + /// + /// 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, setAsDefault); + return this; + } } diff --git a/dotnet/src/SemanticKernel/Memory/MemoryConfiguration.cs b/dotnet/src/SemanticKernel/Memory/MemoryConfiguration.cs index f3247c5a98f8..caa177670924 100644 --- a/dotnet/src/SemanticKernel/Memory/MemoryConfiguration.cs +++ b/dotnet/src/SemanticKernel/Memory/MemoryConfiguration.cs @@ -23,7 +23,7 @@ public static class MemoryConfiguration /// Kernel service id for embedding generation public static void UseMemory(this IKernel kernel, IMemoryStore storage, string? embeddingsServiceId = null) { - var embeddingGenerator = kernel.GetService>(embeddingsServiceId); + var embeddingGenerator = kernel.GetService(embeddingsServiceId); UseMemory(kernel, embeddingGenerator, storage); } @@ -35,7 +35,7 @@ public static void UseMemory(this IKernel kernel, IMemoryStore storage, string? /// Embedding generator /// Memory storage [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "The embeddingGenerator object is disposed by the kernel")] - public static void UseMemory(this IKernel kernel, IEmbeddingGeneration embeddingGenerator, IMemoryStore storage) + public static void UseMemory(this IKernel kernel, ITextEmbeddingGeneration embeddingGenerator, IMemoryStore storage) { Verify.NotNull(storage); Verify.NotNull(embeddingGenerator); diff --git a/dotnet/src/SemanticKernel/Memory/SemanticTextMemory.cs b/dotnet/src/SemanticKernel/Memory/SemanticTextMemory.cs index 7535c285beeb..57b94fc8262d 100644 --- a/dotnet/src/SemanticKernel/Memory/SemanticTextMemory.cs +++ b/dotnet/src/SemanticKernel/Memory/SemanticTextMemory.cs @@ -15,12 +15,12 @@ namespace Microsoft.SemanticKernel.Memory; /// public sealed class SemanticTextMemory : ISemanticTextMemory, IDisposable { - private readonly IEmbeddingGeneration _embeddingGenerator; + private readonly ITextEmbeddingGeneration _embeddingGenerator; private readonly IMemoryStore _storage; public SemanticTextMemory( IMemoryStore storage, - IEmbeddingGeneration embeddingGenerator) + ITextEmbeddingGeneration embeddingGenerator) { this._embeddingGenerator = embeddingGenerator; this._storage = storage; diff --git a/dotnet/src/SemanticKernel/Services/AIServiceCollection.cs b/dotnet/src/SemanticKernel/Services/AIServiceCollection.cs new file mode 100644 index 000000000000..a99234ab0149 --- /dev/null +++ b/dotnet/src/SemanticKernel/Services/AIServiceCollection.cs @@ -0,0 +1,116 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.SemanticKernel.Services; + +[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(); + 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 + if (factory == null) + { + throw new ArgumentNullException(nameof(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. + /// + /// + 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 (IAIServiceProvider)new NamedServiceProvider(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/Services/NamedServiceProvider.cs b/dotnet/src/SemanticKernel/Services/NamedServiceProvider.cs new file mode 100644 index 000000000000..19373e13ab1e --- /dev/null +++ b/dotnet/src/SemanticKernel/Services/NamedServiceProvider.cs @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Collections.Generic; + +namespace Microsoft.SemanticKernel.Services; +internal 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 = new(); + private readonly Dictionary>> _services; + + // A dictionary that maps a service type to the name of the default service + private readonly Dictionary _defaultIds; + + internal 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; + } + + 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/SkillDefinition/SKFunction.cs b/dotnet/src/SemanticKernel/SkillDefinition/SKFunction.cs index b4fbca91c419..39186dcfa8bc 100644 --- a/dotnet/src/SemanticKernel/SkillDefinition/SKFunction.cs +++ b/dotnet/src/SemanticKernel/SkillDefinition/SKFunction.cs @@ -207,7 +207,7 @@ public ISKFunction SetAIService(Func serviceFactory) { Verify.NotNull(serviceFactory); this.VerifyIsSemantic(); - this._aiService = serviceFactory.Invoke(); + this._aiService = new Lazy(serviceFactory); return this; } @@ -243,7 +243,7 @@ public void Dispose() private readonly Delegate _function; private readonly ILogger _log; private IReadOnlySkillCollection? _skillCollection; - private ITextCompletion? _aiService = null; + private Lazy? _aiService = null; private CompleteRequestSettings _aiRequestSettings = new(); private struct MethodDetails @@ -309,7 +309,7 @@ internal SKFunction( private void ReleaseUnmanagedResources() { - if (this._aiService is not IDisposable disposable) { return; } + if (this._aiService?.Value is not IDisposable disposable) { return; } disposable.Dispose(); } @@ -338,7 +338,7 @@ private async Task InvokeSemanticAsync(SKContext context, CompleteReq settings ??= this._aiRequestSettings; var callable = (Func>)this._function; - context.Variables.Update((await callable(this._aiService, settings, context).ConfigureAwait(false)).Variables); + context.Variables.Update((await callable(this._aiService?.Value, settings, context).ConfigureAwait(false)).Variables); return context; } diff --git a/dotnet/src/Skills/Skills.MsGraph/EmailSkill.cs b/dotnet/src/Skills/Skills.MsGraph/EmailSkill.cs index 3272cb9fce08..f64a21542528 100644 --- a/dotnet/src/Skills/Skills.MsGraph/EmailSkill.cs +++ b/dotnet/src/Skills/Skills.MsGraph/EmailSkill.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Graph; using Microsoft.SemanticKernel.Orchestration; using Microsoft.SemanticKernel.SkillDefinition; using Microsoft.SemanticKernel.Skills.MsGraph.Diagnostics; diff --git a/dotnet/src/Skills/Skills.OpenAPI/Extensions/RestApiOperationExtensions.cs b/dotnet/src/Skills/Skills.OpenAPI/Extensions/RestApiOperationExtensions.cs index 4edba885e597..88e0a5f15e99 100644 --- a/dotnet/src/Skills/Skills.OpenAPI/Extensions/RestApiOperationExtensions.cs +++ b/dotnet/src/Skills/Skills.OpenAPI/Extensions/RestApiOperationExtensions.cs @@ -5,7 +5,6 @@ using System.Net.Http; using System.Text.RegularExpressions; using Microsoft.SemanticKernel.Connectors.WebApi.Rest.Model; -using Microsoft.SemanticKernel.Text; #pragma warning disable IDE0130 // ReSharper disable once CheckNamespace diff --git a/dotnet/src/Skills/Skills.UnitTests/Web/SearchUrlSkillTests.cs b/dotnet/src/Skills/Skills.UnitTests/Web/SearchUrlSkillTests.cs index 041137150b8c..0536c2ca67a5 100644 --- a/dotnet/src/Skills/Skills.UnitTests/Web/SearchUrlSkillTests.cs +++ b/dotnet/src/Skills/Skills.UnitTests/Web/SearchUrlSkillTests.cs @@ -23,7 +23,7 @@ public void ItCanBeInstantiated() public void ItCanBeImported() { // Arrange - IKernel kernel = KernelBuilder.Create(); + IKernel kernel = Kernel.Builder.Build(); // Act - Assert no exception occurs e.g. due to reflection kernel.ImportSkill(new SearchUrlSkill(), "search"); diff --git a/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs b/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs index e24833208dc0..02c644d23c05 100644 --- a/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs +++ b/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs @@ -8,8 +8,6 @@ using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; using Microsoft.SemanticKernel.Connectors.Memory.Qdrant; using Microsoft.SemanticKernel.Memory; -using Microsoft.SemanticKernel.SkillDefinition; -using Microsoft.SemanticKernel.TemplateEngine; using SemanticKernel.Service.Config; using SemanticKernel.Service.Skills; @@ -68,88 +66,86 @@ internal static IServiceCollection AddSemanticKernelServices(this IServiceCollec .ToTextEmbeddingsService(logger: serviceProvider.GetRequiredService>()))); // Add the planner. - services.AddScoped(sp => + services.AddScoped(serviceProvider => { - // Create a kernel for the planner with the same contexts as the chat's kernel except with no skills and its own completion backend. - // This allows the planner to use only the skills that are available at call time. - IKernel chatKernel = sp.GetRequiredService(); - IOptions plannerOptions = sp.GetRequiredService>(); - IKernel plannerKernel = new Kernel( - new SkillCollection(), - chatKernel.PromptTemplateEngine, - chatKernel.Memory, - new KernelConfig().AddCompletionBackend(plannerOptions.Value.AIService!), - sp.GetRequiredService>()); - return new CopilotChatPlanner(plannerKernel, plannerOptions); + return new CopilotChatPlanner( + serviceProvider.GetRequiredService(), + serviceProvider.GetRequiredService>()); }); // Add the Semantic Kernel - services.AddSingleton(); - services.AddScoped(); - services.AddScoped(serviceProvider => new KernelConfig() - .AddCompletionBackend(serviceProvider.GetRequiredService>() - .Get(AIServiceOptions.CompletionPropertyName)) - .AddEmbeddingBackend(serviceProvider.GetRequiredService>() - .Get(AIServiceOptions.EmbeddingPropertyName))); - services.AddScoped(); + services.AddScoped(serviceProvider => + new KernelBuilder() + .AddCompletionBackend(serviceProvider.GetRequiredService>()) + .AddEmbeddingBackend(serviceProvider.GetRequiredService>()) + .WithMemory(serviceProvider.GetRequiredService()) + .Build()); return services; } /// - /// Add the completion backend to the kernel config + /// Add the completion backend to the kernel builder. /// - internal static KernelConfig AddCompletionBackend(this KernelConfig kernelConfig, AIServiceOptions aiServiceOptions) + internal static KernelBuilder AddCompletionBackend(this KernelBuilder kernelBuilder, IOptionsSnapshot aiServiceOptions) { - switch (aiServiceOptions.AIService) + AIServiceOptions config = aiServiceOptions.Get(AIServiceOptions.CompletionPropertyName); + + switch (config.AIService) { case AIServiceOptions.AIServiceType.AzureOpenAI: - kernelConfig.AddAzureChatCompletionService( - deploymentName: aiServiceOptions.DeploymentOrModelId, - endpoint: aiServiceOptions.Endpoint, - apiKey: aiServiceOptions.Key); + kernelBuilder.AddAzureChatCompletionService( + serviceId: config.Label, + deploymentName: config.DeploymentOrModelId, + endpoint: config.Endpoint, + apiKey: config.Key, + alsoAsTextCompletion: true); break; case AIServiceOptions.AIServiceType.OpenAI: - kernelConfig.AddOpenAIChatCompletionService( - modelId: aiServiceOptions.DeploymentOrModelId, - apiKey: aiServiceOptions.Key); + kernelBuilder.AddOpenAIChatCompletionService( + serviceId: config.Label, + modelId: config.DeploymentOrModelId, + apiKey: config.Key, + alsoAsTextCompletion: true); break; default: - throw new ArgumentException($"Invalid {nameof(aiServiceOptions.AIService)} value in '{AIServiceOptions.CompletionPropertyName}' settings."); + throw new ArgumentException($"Invalid {nameof(config.AIService)} value in '{AIServiceOptions.CompletionPropertyName}' settings."); } - return kernelConfig; + return kernelBuilder; } /// - /// Add the embedding backend to the kernel config + /// Add the embedding backend to the kernel builder. /// - internal static KernelConfig AddEmbeddingBackend(this KernelConfig kernelConfig, AIServiceOptions aiServiceOptions) + internal static KernelBuilder AddEmbeddingBackend(this KernelBuilder kernelBuilder, IOptionsSnapshot aiServiceOptions) { - switch (aiServiceOptions.AIService) + AIServiceOptions config = aiServiceOptions.Get(AIServiceOptions.EmbeddingPropertyName); + + switch (config.AIService) { case AIServiceOptions.AIServiceType.AzureOpenAI: - kernelConfig.AddAzureTextEmbeddingGenerationService( - deploymentName: aiServiceOptions.DeploymentOrModelId, - endpoint: aiServiceOptions.Endpoint, - apiKey: aiServiceOptions.Key, - serviceId: aiServiceOptions.Label); + kernelBuilder.AddAzureTextEmbeddingGenerationService( + serviceId: config.Label, + deploymentName: config.DeploymentOrModelId, + endpoint: config.Endpoint, + apiKey: config.Key); break; case AIServiceOptions.AIServiceType.OpenAI: - kernelConfig.AddOpenAITextEmbeddingGenerationService( - modelId: aiServiceOptions.DeploymentOrModelId, - apiKey: aiServiceOptions.Key, - serviceId: aiServiceOptions.Label); + kernelBuilder.AddOpenAITextEmbeddingGenerationService( + serviceId: config.Label, + modelId: config.DeploymentOrModelId, + apiKey: config.Key); break; default: - throw new ArgumentException($"Invalid {nameof(aiServiceOptions.AIService)} value in '{AIServiceOptions.EmbeddingPropertyName}' settings."); + throw new ArgumentException($"Invalid {nameof(config.AIService)} value in '{AIServiceOptions.EmbeddingPropertyName}' settings."); } - return kernelConfig; + return kernelBuilder; } /// @@ -158,7 +154,7 @@ internal static KernelConfig AddEmbeddingBackend(this KernelConfig kernelConfig, /// The service configuration /// Custom for HTTP requests. /// Application logger - internal static IEmbeddingGeneration ToTextEmbeddingsService(this AIServiceOptions serviceConfig, + internal static ITextEmbeddingGeneration ToTextEmbeddingsService(this AIServiceOptions serviceConfig, HttpClient? httpClient = null, ILogger? logger = null) { diff --git a/samples/dotnet/KernelBuilder/Program.cs b/samples/dotnet/KernelBuilder/Program.cs index 25d0db7c052d..768e3f0b7a64 100644 --- a/samples/dotnet/KernelBuilder/Program.cs +++ b/samples/dotnet/KernelBuilder/Program.cs @@ -7,8 +7,9 @@ using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; +using Microsoft.SemanticKernel.Services; -IKernel kernel1 = KernelBuilder.Create(); +IKernel kernel1 = Kernel.Builder.Build(); IKernel kernel2 = Kernel.Builder.Build(); @@ -46,13 +47,16 @@ var skills = new SkillCollection(); var templateEngine = new PromptTemplateEngine(logger); var config = new KernelConfig(); + var httpHandler = new DefaultHttpRetryHandler(new HttpRetryConfig(), logger); var httpClient = new HttpClient(httpHandler); -ITextCompletion Factory(IKernel kernel) => new AzureTextCompletion("deploymentName", "https://...", "apiKey", httpClient, logger); -config.AddTextCompletionService(Factory); +var aiServices = new AIServiceCollection(); +ITextCompletion Factory() => new AzureTextCompletion("deploymentName", "https://...", "apiKey", httpClient, logger); +aiServices.SetService("foo", Factory); +IAIServiceProvider aiServiceProvider = aiServices.Build(); // Create kernel manually injecting all the dependencies -var kernel3 = new Kernel(skills, templateEngine, memory, config, logger); +var kernel3 = new Kernel(skills, aiServiceProvider, templateEngine, memory, config, logger); // ========================================================================================================== // The kernel builder purpose is to simplify this process, automating how dependencies @@ -62,10 +66,7 @@ var kernel4 = Kernel.Builder .WithLogger(NullLogger.Instance) .WithMemory(memory) - .Configure(c => - { - c.AddAzureTextCompletionService("deploymentName", "https://...", "apiKey"); - }) + .AddAzureTextCompletionService("deploymentName", "https://...", "apiKey") .Build(); // Example: how to use a custom memory storage and custom embedding generator @@ -78,36 +79,17 @@ var kernel6 = Kernel.Builder .WithLogger(NullLogger.Instance) .WithMemoryStorage(memoryStorage) // Custom memory storage - .Configure(c => - { - // This will be used when using AI completions - c.AddAzureTextCompletionService("completionDeploymentName", "https://...", "apiKey"); - - // This will be used when indexing memory records - c.AddAzureTextEmbeddingGenerationService("embeddingsDeploymentName", "https://...", "apiKey", serviceId: "myName3"); - }) + .AddAzureTextCompletionService("myName1", "completionDeploymentName", "https://...", "apiKey") // This will be used when using AI completions + .AddAzureTextEmbeddingGenerationService("myName2", "embeddingsDeploymentName", "https://...", "apiKey") // This will be used when indexing memory records .Build(); // ========================================================================================================== -// The kernel configuration can be defined with the builder, but can also be managed -// when the kernel instance is already created. +// The AI services are defined with the builder var kernel7 = Kernel.Builder - .Configure(c => - { - c.AddAzureTextCompletionService("completionDeploymentName", "https://...", "apiKey"); - }) - .Configure(c => - { - c.SetDefaultTextEmbeddingGenerationService("myName3"); - }) + .AddAzureTextCompletionService("myName1", "completionDeploymentName", "https://...", "apiKey", true) .Build(); -kernel7.Config - .AddAzureTextEmbeddingGenerationService("embeddingsDeploymentName1", "https://...", "apiKey", serviceId: "myName2") - .AddAzureTextEmbeddingGenerationService("embeddingsDeploymentName2", "https://...", "apiKey", serviceId: "myName3") - .AddOpenAITextCompletionService("text-davinci-003", "sk-..."); - // ========================================================================================================== // 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 diff --git a/samples/dotnet/KernelHttpServer/SemanticKernelFactory.cs b/samples/dotnet/KernelHttpServer/SemanticKernelFactory.cs index ea62c4019381..c074dbfc025e 100644 --- a/samples/dotnet/KernelHttpServer/SemanticKernelFactory.cs +++ b/samples/dotnet/KernelHttpServer/SemanticKernelFactory.cs @@ -43,51 +43,50 @@ internal static class SemanticKernelFactory private static KernelBuilder _ConfigureKernelBuilder(ApiKeyConfig config, KernelBuilder builder, IMemoryStore? memoryStore) { - return builder.Configure(c => + switch (config.CompletionConfig.AIService) { - switch (config.CompletionConfig.AIService) + case AIService.OpenAI: + builder.AddOpenAITextCompletionService( + config.CompletionConfig.DeploymentOrModelId, + config.CompletionConfig.Key, + config.CompletionConfig.ServiceId); + break; + case AIService.AzureOpenAI: + builder.AddAzureTextCompletionService( + config.CompletionConfig.DeploymentOrModelId, + config.CompletionConfig.Endpoint, + config.CompletionConfig.Key, + serviceId: config.CompletionConfig.ServiceId); + break; + default: + break; + } + + if (memoryStore != null && config.EmbeddingConfig.IsValid()) + { + switch (config.EmbeddingConfig.AIService) { case AIService.OpenAI: - c.AddOpenAITextCompletionService( - config.CompletionConfig.DeploymentOrModelId, - config.CompletionConfig.Key, - serviceId: config.CompletionConfig.ServiceId); + builder.AddOpenAITextEmbeddingGenerationService( + config.EmbeddingConfig.DeploymentOrModelId, + config.EmbeddingConfig.Key, + serviceId: config.EmbeddingConfig.ServiceId); break; case AIService.AzureOpenAI: - c.AddAzureTextCompletionService( - config.CompletionConfig.DeploymentOrModelId, - config.CompletionConfig.Endpoint, - config.CompletionConfig.Key, - serviceId: config.CompletionConfig.ServiceId); + builder.AddAzureTextEmbeddingGenerationService( + config.EmbeddingConfig.DeploymentOrModelId, + config.EmbeddingConfig.Endpoint, + config.EmbeddingConfig.Key, + serviceId: config.EmbeddingConfig.ServiceId); break; default: break; } - if (memoryStore != null && config.EmbeddingConfig.IsValid()) - { - switch (config.EmbeddingConfig.AIService) - { - case AIService.OpenAI: - c.AddOpenAITextEmbeddingGenerationService( - config.EmbeddingConfig.DeploymentOrModelId, - config.EmbeddingConfig.Key, - serviceId: config.EmbeddingConfig.ServiceId); - break; - case AIService.AzureOpenAI: - c.AddAzureTextEmbeddingGenerationService( - config.EmbeddingConfig.DeploymentOrModelId, - config.EmbeddingConfig.Endpoint, - config.EmbeddingConfig.Key, - serviceId: config.EmbeddingConfig.ServiceId); - break; - default: - break; - } + builder.WithMemoryStorage(memoryStore); + } - builder.WithMemoryStorage(memoryStore); - } - }); + return builder; } private static IKernel _CompleteKernelSetup(HttpRequestData req, KernelBuilder builder, ILogger logger, IEnumerable? skillsToLoad = null) diff --git a/samples/dotnet/graph-api-skills/MsGraphSkillsExample.csproj b/samples/dotnet/graph-api-skills/MsGraphSkillsExample.csproj index 6da99a0ff87a..2fc07836aedb 100644 --- a/samples/dotnet/graph-api-skills/MsGraphSkillsExample.csproj +++ b/samples/dotnet/graph-api-skills/MsGraphSkillsExample.csproj @@ -27,6 +27,7 @@ + diff --git a/samples/dotnet/graph-api-skills/Program.cs b/samples/dotnet/graph-api-skills/Program.cs index 2b25bf5c4b77..099185cacd8c 100644 --- a/samples/dotnet/graph-api-skills/Program.cs +++ b/samples/dotnet/graph-api-skills/Program.cs @@ -63,6 +63,18 @@ public static async Task Main() MsGraphConfiguration graphApiConfiguration = candidateGraphApiConfig; + string? defaultCompletionServiceId = configuration["DefaultCompletionServiceId"]; + if (string.IsNullOrWhiteSpace(defaultCompletionServiceId)) + { + throw new InvalidOperationException("'DefaultCompletionServiceId' is not set in configuration."); + } + + string? currentAssemblyDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + if (string.IsNullOrWhiteSpace(currentAssemblyDirectory)) + { + throw new InvalidOperationException("Unable to determine current assembly directory."); + } + #endregion // Initialize the Graph API client with interactive authentication and local caching. @@ -93,22 +105,20 @@ public static async Task Main() EmailSkill outlookSkill = new(new OutlookMailConnector(graphServiceClient), loggerFactory.CreateLogger()); // Initialize the Semantic Kernel and and register connections with OpenAI/Azure OpenAI instances. - IKernel sk = Kernel.Builder.WithLogger(loggerFactory.CreateLogger()).Build(); - - var onedrive = sk.ImportSkill(oneDriveSkill, "onedrive"); - var todo = sk.ImportSkill(todoSkill, "todo"); - var outlook = sk.ImportSkill(outlookSkill, "outlook"); + KernelBuilder builder = Kernel.Builder + .WithLogger(loggerFactory.CreateLogger()); if (configuration.GetSection("AzureOpenAI:ServiceId").Value != null) { AzureOpenAIConfiguration? azureOpenAIConfiguration = configuration.GetSection("AzureOpenAI").Get(); if (azureOpenAIConfiguration != null) { - sk.Config.AddAzureTextCompletionService( + builder.AddAzureTextCompletionService( deploymentName: azureOpenAIConfiguration.DeploymentName, endpoint: azureOpenAIConfiguration.Endpoint, apiKey: azureOpenAIConfiguration.ApiKey, - serviceId: azureOpenAIConfiguration.ServiceId); + serviceId: azureOpenAIConfiguration.ServiceId, + setAsDefault: azureOpenAIConfiguration.ServiceId == defaultCompletionServiceId); } } @@ -117,25 +127,19 @@ public static async Task Main() OpenAIConfiguration? openAIConfiguration = configuration.GetSection("OpenAI").Get(); if (openAIConfiguration != null) { - sk.Config.AddOpenAITextCompletionService( + builder.AddOpenAITextCompletionService( modelId: openAIConfiguration.ModelId, - apiKey: openAIConfiguration.ApiKey); + apiKey: openAIConfiguration.ApiKey, + serviceId: openAIConfiguration.ServiceId, + setAsDefault: openAIConfiguration.ServiceId == defaultCompletionServiceId); } } - string? defaultCompletionServiceId = configuration["DefaultCompletionServiceId"]; - if (string.IsNullOrWhiteSpace(defaultCompletionServiceId)) - { - throw new InvalidOperationException("'DefaultCompletionServiceId' is not set in configuration."); - } - - sk.Config.SetDefaultTextCompletionService(defaultCompletionServiceId); + IKernel sk = builder.Build(); - string? currentAssemblyDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - if (string.IsNullOrWhiteSpace(currentAssemblyDirectory)) - { - throw new InvalidOperationException("Unable to determine current assembly directory."); - } + var onedrive = sk.ImportSkill(oneDriveSkill, "onedrive"); + var todo = sk.ImportSkill(todoSkill, "todo"); + var outlook = sk.ImportSkill(outlookSkill, "outlook"); string skillParentDirectory = Path.GetFullPath(Path.Combine(currentAssemblyDirectory, "../../../../skills")); diff --git a/samples/dotnet/kernel-syntax-examples/Example04_CombineLLMPromptsAndNativeCode.cs b/samples/dotnet/kernel-syntax-examples/Example04_CombineLLMPromptsAndNativeCode.cs index 2766458f13e5..ed3aa422714f 100644 --- a/samples/dotnet/kernel-syntax-examples/Example04_CombineLLMPromptsAndNativeCode.cs +++ b/samples/dotnet/kernel-syntax-examples/Example04_CombineLLMPromptsAndNativeCode.cs @@ -14,12 +14,11 @@ public static async Task RunAsync() { Console.WriteLine("======== LLMPrompts ========"); - IKernel kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); - - // OpenAI settings - kernel.Config.AddOpenAITextCompletionService("text-davinci-002", Env.Var("OPENAI_API_KEY"), serviceId: "text-davinci-002"); - kernel.Config.AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY"), serviceId: "text-davinci-003"); - kernel.Config.SetDefaultTextCompletionService("text-davinci-003"); + IKernel kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + .AddOpenAITextCompletionService("text-davinci-002", Env.Var("OPENAI_API_KEY"), serviceId: "text-davinci-002") + .AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) + .Build(); // Load native skill using var bingConnector = new BingConnector(Env.Var("BING_API_KEY")); diff --git a/samples/dotnet/kernel-syntax-examples/Example05_InlineFunctionDefinition.cs b/samples/dotnet/kernel-syntax-examples/Example05_InlineFunctionDefinition.cs index 458a5987f676..102756db2830 100644 --- a/samples/dotnet/kernel-syntax-examples/Example05_InlineFunctionDefinition.cs +++ b/samples/dotnet/kernel-syntax-examples/Example05_InlineFunctionDefinition.cs @@ -18,10 +18,10 @@ public static async Task RunAsync() * function inline if you like. */ - IKernel kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); - - // OpenAI settings - kernel.Config.AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")); + IKernel kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + .AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) + .Build(); // Function defined using few-shot design pattern const string FunctionDefinition = @" diff --git a/samples/dotnet/kernel-syntax-examples/Example06_TemplateLanguage.cs b/samples/dotnet/kernel-syntax-examples/Example06_TemplateLanguage.cs index c6d050e2bd35..94ff2d0cf903 100644 --- a/samples/dotnet/kernel-syntax-examples/Example06_TemplateLanguage.cs +++ b/samples/dotnet/kernel-syntax-examples/Example06_TemplateLanguage.cs @@ -18,8 +18,10 @@ public static async Task RunAsync() { Console.WriteLine("======== TemplateLanguage ========"); - IKernel kernel = Kernel.Builder.WithLogger(ConsoleLogger.Log).Build(); - kernel.Config.AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")); + IKernel kernel = Kernel.Builder + .WithLogger(ConsoleLogger.Log) + .AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) + .Build(); // Load native skill into the kernel skill collection, sharing its functions with prompt templates // Functions loaded here are available as "time.*" diff --git a/samples/dotnet/kernel-syntax-examples/Example07_BingAndGoogleSkills.cs b/samples/dotnet/kernel-syntax-examples/Example07_BingAndGoogleSkills.cs index 13552261bbb8..59b3866b03b7 100644 --- a/samples/dotnet/kernel-syntax-examples/Example07_BingAndGoogleSkills.cs +++ b/samples/dotnet/kernel-syntax-examples/Example07_BingAndGoogleSkills.cs @@ -20,9 +20,10 @@ public static class Example07_BingAndGoogleSkills { public static async Task RunAsync() { - IKernel kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); - - kernel.Config.AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")); + IKernel kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + .AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) + .Build(); // Load Bing skill using var bingConnector = new BingConnector(Env.Var("BING_API_KEY")); diff --git a/samples/dotnet/kernel-syntax-examples/Example08_RetryHandler.cs b/samples/dotnet/kernel-syntax-examples/Example08_RetryHandler.cs index f95991077e52..2664e24427af 100644 --- a/samples/dotnet/kernel-syntax-examples/Example08_RetryHandler.cs +++ b/samples/dotnet/kernel-syntax-examples/Example08_RetryHandler.cs @@ -47,7 +47,7 @@ private static async Task RunRetryHandlerConfigAsync(HttpRetryConfig? config = n kernelBuilder = kernelBuilder.Configure(c => c.DefaultHttpRetryConfig.RetryableStatusCodes.Add(HttpStatusCode.Unauthorized)); // OpenAI settings - you can set the OPENAI_API_KEY to an invalid value to see the retry policy in play - kernelBuilder = kernelBuilder.Configure(c => c.AddOpenAITextCompletionService("text-davinci-003", "BAD_KEY")); + kernelBuilder = kernelBuilder.AddOpenAITextCompletionService("text-davinci-003", "BAD_KEY"); var kernel = kernelBuilder.Build(); @@ -56,9 +56,11 @@ private static async Task RunRetryHandlerConfigAsync(HttpRetryConfig? config = n private static IKernel InitializeKernel() { - var kernel = Kernel.Builder.WithLogger(InfoLogger.Log).Build(); - // OpenAI settings - you can set the OPENAI_API_KEY to an invalid value to see the retry policy in play - kernel.Config.AddOpenAITextCompletionService("text-davinci-003", "BAD_KEY"); + var kernel = Kernel.Builder + .WithLogger(InfoLogger.Log) + // OpenAI settings - you can set the OPENAI_API_KEY to an invalid value to see the retry policy in play + .AddOpenAITextCompletionService("text-davinci-003", "BAD_KEY") + .Build(); return kernel; } @@ -71,13 +73,11 @@ private static async Task RunRetryPolicyAsync(IKernel kernel, IDelegatingHandler private static async Task RunRetryPolicyBuilderAsync(Type retryHandlerFactoryType) { - var kernelBuilder = Kernel.Builder.WithLogger(InfoLogger.Log) - .WithRetryHandlerFactory((Activator.CreateInstance(retryHandlerFactoryType) as IDelegatingHandlerFactory)!); - - // OpenAI settings - you can set the OPENAI_API_KEY to an invalid value to see the retry policy in play - kernelBuilder = kernelBuilder.Configure(c => c.AddOpenAITextCompletionService("text-davinci-003", "BAD_KEY")); - - var kernel = kernelBuilder.Build(); + var kernel = Kernel.Builder.WithLogger(InfoLogger.Log) + .WithRetryHandlerFactory((Activator.CreateInstance(retryHandlerFactoryType) as IDelegatingHandlerFactory)!) + // OpenAI settings - you can set the OPENAI_API_KEY to an invalid value to see the retry policy in play + .AddOpenAITextCompletionService("text-davinci-003", "BAD_KEY") + .Build(); await ImportAndExecuteSkillAsync(kernel); } diff --git a/samples/dotnet/kernel-syntax-examples/Example09_FunctionTypes.cs b/samples/dotnet/kernel-syntax-examples/Example09_FunctionTypes.cs index c5da4da3103e..6d5214cecb14 100644 --- a/samples/dotnet/kernel-syntax-examples/Example09_FunctionTypes.cs +++ b/samples/dotnet/kernel-syntax-examples/Example09_FunctionTypes.cs @@ -19,8 +19,10 @@ public static async Task RunAsync() var fakeContext = new SKContext(new ContextVariables(), NullMemory.Instance, null, ConsoleLogger.Log); - var kernel = Kernel.Builder.WithLogger(ConsoleLogger.Log).Build(); - kernel.Config.AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")); + var kernel = Kernel.Builder + .WithLogger(ConsoleLogger.Log) + .AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) + .Build(); // Load native skill into the kernel skill collection, sharing its functions with prompt templates var test = kernel.ImportSkill(new LocalExampleSkill(), "test"); diff --git a/samples/dotnet/kernel-syntax-examples/Example10_DescribeAllSkillsAndFunctions.cs b/samples/dotnet/kernel-syntax-examples/Example10_DescribeAllSkillsAndFunctions.cs index b0680a583b04..4288de0b716b 100644 --- a/samples/dotnet/kernel-syntax-examples/Example10_DescribeAllSkillsAndFunctions.cs +++ b/samples/dotnet/kernel-syntax-examples/Example10_DescribeAllSkillsAndFunctions.cs @@ -20,8 +20,9 @@ public static void Run() { Console.WriteLine("======== Describe all skills and functions ========"); - var kernel = KernelBuilder.Create(); - kernel.Config.AddOpenAITextCompletionService("text-davinci-003", "none"); + var kernel = Kernel.Builder + .AddOpenAITextCompletionService("text-davinci-003", "none") + .Build(); // Import a native skill var skill1 = new StaticTextSkill(); diff --git a/samples/dotnet/kernel-syntax-examples/Example12_SequentialPlanner.cs b/samples/dotnet/kernel-syntax-examples/Example12_SequentialPlanner.cs index 49243c7ad5f4..77ea2c601513 100644 --- a/samples/dotnet/kernel-syntax-examples/Example12_SequentialPlanner.cs +++ b/samples/dotnet/kernel-syntax-examples/Example12_SequentialPlanner.cs @@ -25,11 +25,14 @@ public static async Task RunAsync() private static async Task PoetrySamplesAsync() { Console.WriteLine("======== Sequential Planner - Create and Execute Poetry Plan ========"); - var kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); - kernel.Config.AddAzureTextCompletionService( - Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), - Env.Var("AZURE_OPENAI_ENDPOINT"), - Env.Var("AZURE_OPENAI_KEY")); + var kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + .AddAzureTextCompletionService( + Env.Var("AZURE_OPENAI_SERVICE_ID"), + Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), + Env.Var("AZURE_OPENAI_ENDPOINT"), + Env.Var("AZURE_OPENAI_KEY")) + .Build(); string folder = RepoFiles.SampleSkillsPath(); kernel.ImportSemanticSkillFromDirectory(folder, @@ -135,19 +138,14 @@ private static async Task MemorySampleAsync() var kernel = new KernelBuilder() .WithLogger(ConsoleLogger.Log) - .Configure( - config => - { - config.AddAzureTextCompletionService( + .AddAzureTextCompletionService( Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), Env.Var("AZURE_OPENAI_ENDPOINT"), - Env.Var("AZURE_OPENAI_KEY")); - - config.AddAzureTextEmbeddingGenerationService( + Env.Var("AZURE_OPENAI_KEY")) + .AddAzureTextEmbeddingGenerationService( Env.Var("AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME"), Env.Var("AZURE_OPENAI_EMBEDDINGS_ENDPOINT"), - Env.Var("AZURE_OPENAI_EMBEDDINGS_KEY")); - }) + Env.Var("AZURE_OPENAI_EMBEDDINGS_KEY")) .WithMemoryStorage(new VolatileMemoryStore()) .Build(); @@ -182,11 +180,13 @@ private static async Task MemorySampleAsync() private static IKernel InitializeKernelAndPlanner(out SequentialPlanner planner, int maxTokens = 1024) { - var kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); - kernel.Config.AddAzureTextCompletionService( - Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), - Env.Var("AZURE_OPENAI_ENDPOINT"), - Env.Var("AZURE_OPENAI_KEY")); + var kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + .AddAzureTextCompletionService( + Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), + Env.Var("AZURE_OPENAI_ENDPOINT"), + Env.Var("AZURE_OPENAI_KEY")) + .Build(); planner = new SequentialPlanner(kernel, new SequentialPlannerConfig { MaxTokens = maxTokens }); diff --git a/samples/dotnet/kernel-syntax-examples/Example13_ConversationSummarySkill.cs b/samples/dotnet/kernel-syntax-examples/Example13_ConversationSummarySkill.cs index 52c42d36de29..948a4a0c14a7 100644 --- a/samples/dotnet/kernel-syntax-examples/Example13_ConversationSummarySkill.cs +++ b/samples/dotnet/kernel-syntax-examples/Example13_ConversationSummarySkill.cs @@ -178,11 +178,13 @@ private static async Task GetConversationTopicsAsync() private static IKernel InitializeKernel() { - IKernel kernel = Kernel.Builder.WithLogger(ConsoleLogger.Log).Build(); - kernel.Config.AddAzureTextCompletionService( - Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), - Env.Var("AZURE_OPENAI_ENDPOINT"), - Env.Var("AZURE_OPENAI_KEY")); + IKernel kernel = Kernel.Builder + .WithLogger(ConsoleLogger.Log) + .AddAzureTextCompletionService( + Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), + Env.Var("AZURE_OPENAI_ENDPOINT"), + Env.Var("AZURE_OPENAI_KEY")) + .Build(); return kernel; } diff --git a/samples/dotnet/kernel-syntax-examples/Example14_SemanticMemory.cs b/samples/dotnet/kernel-syntax-examples/Example14_SemanticMemory.cs index 00f034396fd0..90ae64419961 100644 --- a/samples/dotnet/kernel-syntax-examples/Example14_SemanticMemory.cs +++ b/samples/dotnet/kernel-syntax-examples/Example14_SemanticMemory.cs @@ -56,7 +56,7 @@ public static async Task RunAsync() var kernelWithCustomDb = Kernel.Builder .WithLogger(ConsoleLogger.Log) - .Configure(c => c.AddOpenAITextEmbeddingGenerationService("ada", "text-embedding-ada-002", Env.Var("OPENAI_API_KEY"))) + .AddOpenAITextEmbeddingGenerationService("ada", "text-embedding-ada-002", Env.Var("OPENAI_API_KEY")) .WithMemoryStorage(new VolatileMemoryStore()) .Build(); diff --git a/samples/dotnet/kernel-syntax-examples/Example15_MemorySkill.cs b/samples/dotnet/kernel-syntax-examples/Example15_MemorySkill.cs index 95909e1db444..e68c7cdfde9c 100644 --- a/samples/dotnet/kernel-syntax-examples/Example15_MemorySkill.cs +++ b/samples/dotnet/kernel-syntax-examples/Example15_MemorySkill.cs @@ -16,11 +16,8 @@ public static async Task RunAsync() { var kernel = Kernel.Builder .WithLogger(ConsoleLogger.Log) - .Configure(c => - { - c.AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")); - c.AddOpenAITextEmbeddingGenerationService("text-embedding-ada-002", Env.Var("OPENAI_API_KEY")); - }) + .AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) + .AddOpenAITextEmbeddingGenerationService("text-embedding-ada-002", Env.Var("OPENAI_API_KEY")) .WithMemoryStorage(new VolatileMemoryStore()) .Build(); diff --git a/samples/dotnet/kernel-syntax-examples/Example16_CustomLLM.cs b/samples/dotnet/kernel-syntax-examples/Example16_CustomLLM.cs index 252aed80cd4a..72a8ac0fc313 100644 --- a/samples/dotnet/kernel-syntax-examples/Example16_CustomLLM.cs +++ b/samples/dotnet/kernel-syntax-examples/Example16_CustomLLM.cs @@ -71,12 +71,13 @@ private static async Task CustomTextCompletionWithSKFunctionAsync() { Console.WriteLine("======== Custom LLM - Text Completion - SKFunction ========"); - IKernel kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); - - ITextCompletion Factory(IKernel k) => new MyTextCompletionService(); - - // Add your text completion service - kernel.Config.AddTextCompletionService(Factory); + IKernel kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + // Add your text completion service as a singleton instance + .WithAIService("myService1", new MyTextCompletionService()) + // Add your text completion service as a factory method + .WithAIService("myService2", () => new MyTextCompletionService()) + .Build(); const string FunctionDefinition = "Does the text contain grammar errors (Y/N)? Text: {{$input}}"; diff --git a/samples/dotnet/kernel-syntax-examples/Example17_ChatGPT.cs b/samples/dotnet/kernel-syntax-examples/Example17_ChatGPT.cs index ddc5353cd2af..00f87c0bdb67 100644 --- a/samples/dotnet/kernel-syntax-examples/Example17_ChatGPT.cs +++ b/samples/dotnet/kernel-syntax-examples/Example17_ChatGPT.cs @@ -19,10 +19,10 @@ public static async Task RunAsync() { Console.WriteLine("======== SK with ChatGPT ========"); - IKernel kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); - - // Add your chat completion service - kernel.Config.AddOpenAIChatCompletionService("gpt-3.5-turbo", Env.Var("OPENAI_API_KEY")); + IKernel kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + .AddOpenAIChatCompletionService("gpt-3.5-turbo", Env.Var("OPENAI_API_KEY")) + .Build(); IChatCompletion chatGPT = kernel.GetService(); var chatHistory = (OpenAIChatHistory)chatGPT.CreateNewChat("You are a librarian, expert about books"); diff --git a/samples/dotnet/kernel-syntax-examples/Example18_DallE.cs b/samples/dotnet/kernel-syntax-examples/Example18_DallE.cs index 62b69deb16ec..8c9a0f409644 100644 --- a/samples/dotnet/kernel-syntax-examples/Example18_DallE.cs +++ b/samples/dotnet/kernel-syntax-examples/Example18_DallE.cs @@ -19,10 +19,13 @@ public static async Task RunAsync() { Console.WriteLine("======== Dall-E 2 Image Generation ========"); - IKernel kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); - - // Add your image generation service - kernel.Config.AddOpenAIImageGenerationService(Env.Var("OPENAI_API_KEY")); + IKernel kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + // Add your image generation service + .AddOpenAIImageGenerationService("dallE", Env.Var("OPENAI_API_KEY")) + // Add your chat completion service + .AddOpenAIChatCompletionService("gpt-3.5-turbo", Env.Var("OPENAI_API_KEY")) + .Build(); IImageGeneration dallE = kernel.GetService(); @@ -41,9 +44,6 @@ A cute baby sea otter Console.WriteLine("======== Chat with images ========"); - // Add your chat completion service - kernel.Config.AddOpenAIChatCompletionService("gpt-3.5-turbo", Env.Var("OPENAI_API_KEY")); - IChatCompletion chatGPT = kernel.GetService(); var chatHistory = (OpenAIChatHistory)chatGPT.CreateNewChat( "You're chatting with a user. Instead of replying directly to the user" + diff --git a/samples/dotnet/kernel-syntax-examples/Example19_Qdrant.cs b/samples/dotnet/kernel-syntax-examples/Example19_Qdrant.cs index 528a41cf1cff..10d7fc89072e 100644 --- a/samples/dotnet/kernel-syntax-examples/Example19_Qdrant.cs +++ b/samples/dotnet/kernel-syntax-examples/Example19_Qdrant.cs @@ -19,11 +19,8 @@ public static async Task RunAsync() QdrantMemoryStore memoryStore = new QdrantMemoryStore(Env.Var("QDRANT_ENDPOINT"), qdrantPort, vectorSize: 1536, ConsoleLogger.Log); IKernel kernel = Kernel.Builder .WithLogger(ConsoleLogger.Log) - .Configure(c => - { - c.AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")); - c.AddOpenAITextEmbeddingGenerationService("text-embedding-ada-002", Env.Var("OPENAI_API_KEY")); - }) + .AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) + .AddOpenAITextEmbeddingGenerationService("text-embedding-ada-002", Env.Var("OPENAI_API_KEY")) .WithMemoryStorage(memoryStore) .Build(); diff --git a/samples/dotnet/kernel-syntax-examples/Example20_HuggingFace.cs b/samples/dotnet/kernel-syntax-examples/Example20_HuggingFace.cs index 06806a011d24..2128d8be65b5 100644 --- a/samples/dotnet/kernel-syntax-examples/Example20_HuggingFace.cs +++ b/samples/dotnet/kernel-syntax-examples/Example20_HuggingFace.cs @@ -17,10 +17,11 @@ public static async Task RunAsync() { Console.WriteLine("======== HuggingFace text completion AI ========"); - IKernel kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); - - // Add HuggingFace text completion service - kernel.Config.AddTextCompletionService(_ => new HuggingFaceTextCompletion(Env.Var("HF_API_KEY"), model: "gpt2")); + IKernel kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + // Add HuggingFace text completion service as a factory methods + .WithDefaultAIService(() => new HuggingFaceTextCompletion(Env.Var("HF_API_KEY"), "gpt2")) + .Build(); const string FunctionDefinition = "Question: {{$input}}; Answer:"; diff --git a/samples/dotnet/kernel-syntax-examples/Example26_AADAuth.cs b/samples/dotnet/kernel-syntax-examples/Example26_AADAuth.cs index 9b6b7486a3b1..78601698258e 100644 --- a/samples/dotnet/kernel-syntax-examples/Example26_AADAuth.cs +++ b/samples/dotnet/kernel-syntax-examples/Example26_AADAuth.cs @@ -28,8 +28,6 @@ public static async Task RunAsync() { Console.WriteLine("======== SK with AAD Auth ========"); - IKernel kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); - // Optional: choose which authentication to support var authOptions = new DefaultAzureCredentialOptions { @@ -42,11 +40,14 @@ public static async Task RunAsync() ExcludeInteractiveBrowserCredential = true, }; - // Add Azure chat completion service using DefaultAzureCredential AAD auth - kernel.Config.AddAzureChatCompletionService( - "gpt-35-turbo", - "https://....openai.azure.com/", - new DefaultAzureCredential(authOptions)); + IKernel kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + // Add Azure chat completion service using DefaultAzureCredential AAD auth + .AddAzureChatCompletionService( + "gpt-35-turbo", + "https://....openai.azure.com/", + new DefaultAzureCredential(authOptions)) + .Build(); IChatCompletion chatGPT = kernel.GetService(); var chatHistory = (OpenAIChatHistory)chatGPT.CreateNewChat(); diff --git a/samples/dotnet/kernel-syntax-examples/Example27_SemanticFunctionsUsingChatGPT.cs b/samples/dotnet/kernel-syntax-examples/Example27_SemanticFunctionsUsingChatGPT.cs index 89d401c28e12..e5997f138843 100644 --- a/samples/dotnet/kernel-syntax-examples/Example27_SemanticFunctionsUsingChatGPT.cs +++ b/samples/dotnet/kernel-syntax-examples/Example27_SemanticFunctionsUsingChatGPT.cs @@ -16,10 +16,11 @@ public static async Task RunAsync() { Console.WriteLine("======== Using Chat GPT model for text completion ========"); - IKernel kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); - - // Note: we use Chat Completion and GPT 3.5 Turbo - kernel.Config.AddAzureChatCompletionService("gpt-35-turbo", "https://....openai.azure.com/", "...API KEY..."); + IKernel kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + // Note: we use Chat Completion and GPT 3.5 Turbo + .AddAzureChatCompletionService("gpt-35-turbo", "https://....openai.azure.com/", "...API KEY...") + .Build(); var func = kernel.CreateSemanticFunction( "List the two planets closest to '{{$input}}', excluding moons, using bullet points."); diff --git a/samples/dotnet/kernel-syntax-examples/Example28_ActionPlanner.cs b/samples/dotnet/kernel-syntax-examples/Example28_ActionPlanner.cs index 7a795671729b..8635015a7506 100644 --- a/samples/dotnet/kernel-syntax-examples/Example28_ActionPlanner.cs +++ b/samples/dotnet/kernel-syntax-examples/Example28_ActionPlanner.cs @@ -13,10 +13,10 @@ public static class Example28_ActionPlanner public static async Task RunAsync() { Console.WriteLine("======== Action Planner ========"); - var kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); - - // Note: Action Planner works with old models like text-davinci-002 - kernel.Config.AddOpenAITextCompletionService("text-davinci-002", Env.Var("OPENAI_API_KEY")); + var kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + .AddOpenAITextCompletionService("text-davinci-002", Env.Var("OPENAI_API_KEY"))// Note: Action Planner works with old models like text-davinci-002 + .Build(); string folder = RepoFiles.SampleSkillsPath(); kernel.ImportSemanticSkillFromDirectory(folder, "SummarizeSkill"); diff --git a/samples/dotnet/kernel-syntax-examples/Example30_ChatWithPrompts.cs b/samples/dotnet/kernel-syntax-examples/Example30_ChatWithPrompts.cs index 6d935322ed19..c03907d8298a 100644 --- a/samples/dotnet/kernel-syntax-examples/Example30_ChatWithPrompts.cs +++ b/samples/dotnet/kernel-syntax-examples/Example30_ChatWithPrompts.cs @@ -63,8 +63,10 @@ public static async Task RunAsync() var userPromptTemplate = EmbeddedResource.Read("30-user-prompt.txt"); // Usual kernel initialization, with GPT 3.5 Turbo - IKernel kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); - kernel.Config.AddOpenAIChatCompletionService("gpt-3.5-turbo", Env.Var("OPENAI_API_KEY")); + IKernel kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + .AddOpenAIChatCompletionService("gpt-3.5-turbo", Env.Var("OPENAI_API_KEY"), serviceId: "chat") + .Build(); // As an example, we import the time skill, which is used in system prompt to read the current date. // We could also use a variable, this is just to show that the prompt can invoke functions. diff --git a/samples/dotnet/kernel-syntax-examples/Example31_CustomPlanner.cs b/samples/dotnet/kernel-syntax-examples/Example31_CustomPlanner.cs index 9fed55893016..6f871628bc8e 100644 --- a/samples/dotnet/kernel-syntax-examples/Example31_CustomPlanner.cs +++ b/samples/dotnet/kernel-syntax-examples/Example31_CustomPlanner.cs @@ -120,19 +120,14 @@ private static IKernel InitializeKernel() { return new KernelBuilder() .WithLogger(ConsoleLogger.Log) - .Configure( - config => - { - config.AddAzureTextCompletionService( - Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), - Env.Var("AZURE_OPENAI_ENDPOINT"), - Env.Var("AZURE_OPENAI_KEY")); - - config.AddAzureTextEmbeddingGenerationService( - Env.Var("AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME"), - Env.Var("AZURE_OPENAI_EMBEDDINGS_ENDPOINT"), - Env.Var("AZURE_OPENAI_EMBEDDINGS_KEY")); - }) + .AddAzureTextCompletionService( + Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), + Env.Var("AZURE_OPENAI_ENDPOINT"), + Env.Var("AZURE_OPENAI_KEY")) + .AddAzureTextEmbeddingGenerationService( + Env.Var("AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME"), + Env.Var("AZURE_OPENAI_EMBEDDINGS_ENDPOINT"), + Env.Var("AZURE_OPENAI_EMBEDDINGS_KEY")) .WithMemoryStorage(new VolatileMemoryStore()) .Build(); } diff --git a/samples/dotnet/kernel-syntax-examples/Example32_StreamingCompletion.cs b/samples/dotnet/kernel-syntax-examples/Example32_StreamingCompletion.cs index 88dc5b35c755..857a050e76a7 100644 --- a/samples/dotnet/kernel-syntax-examples/Example32_StreamingCompletion.cs +++ b/samples/dotnet/kernel-syntax-examples/Example32_StreamingCompletion.cs @@ -22,11 +22,13 @@ private static async Task AzureOpenAITextCompletionStreamAsync() { Console.WriteLine("======== Azure OpenAI - Text Completion - Raw Streaming ========"); - IKernel kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); - kernel.Config.AddAzureTextCompletionService( - Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), - Env.Var("AZURE_OPENAI_ENDPOINT"), - Env.Var("AZURE_OPENAI_KEY")); + IKernel kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + .AddAzureTextCompletionService( + Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), + Env.Var("AZURE_OPENAI_ENDPOINT"), + Env.Var("AZURE_OPENAI_KEY")) + .Build(); ITextCompletion textCompletion = kernel.GetService(); @@ -37,8 +39,10 @@ private static async Task OpenAITextCompletionStreamAsync() { Console.WriteLine("======== Open AI - Text Completion - Raw Streaming ========"); - IKernel kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); - kernel.Config.AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY"), serviceId: "text-davinci-003"); + IKernel kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + .AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY"), serviceId: "text-davinci-003") + .Build(); ITextCompletion textCompletion = kernel.GetService(); From 08971819a1dcd34f48f05cb1be8704d989120b68 Mon Sep 17 00:00:00 2001 From: name Date: Sun, 7 May 2023 20:59:46 -0700 Subject: [PATCH 02/15] bug and UT fixes --- .../OpenAI/AIServicesOpenAIExtensionsTests.cs | 24 +++++++++---------- .../TextEmbeddingServiceExtensionTests.cs | 2 +- .../Services/AIServiceCollection.cs | 2 +- .../Services/AIServiceProvider.cs | 13 ++++++++++ 4 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 dotnet/src/SemanticKernel/Services/AIServiceProvider.cs diff --git a/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs index c684714537b2..6a4ac9125b84 100644 --- a/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs +++ b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs @@ -28,10 +28,10 @@ public void ItSucceedsWhenAddingDifferentServiceTypeWithSameId() public void ItTellsIfAServiceIsAvailable() { KernelBuilder targetBuilder = Kernel.Builder; - targetBuilder.AddAzureTextCompletionService("azure", "depl", "https://url", "key"); - targetBuilder.AddOpenAITextCompletionService("oai", "model", "apikey"); - targetBuilder.AddAzureTextEmbeddingGenerationService("azure", "depl2", "https://url2", "key"); - targetBuilder.AddOpenAITextEmbeddingGenerationService("oai2", "model2", "apikey2"); + targetBuilder.AddAzureTextCompletionService("depl", "https://url", "key", serviceId: "azure"); + targetBuilder.AddOpenAITextCompletionService("model", "apikey", serviceId: "oai"); + targetBuilder.AddAzureTextEmbeddingGenerationService("depl2", "https://url2", "key", serviceId: "azure"); + targetBuilder.AddOpenAITextEmbeddingGenerationService("model2", "apikey2", serviceId: "oai2"); // Assert IKernel targetKernel = targetBuilder.Build(); @@ -48,13 +48,13 @@ public void ItCanOverwriteServices() KernelBuilder targetBuilder = Kernel.Builder; // Act - Assert no exception occurs - targetBuilder.AddAzureTextCompletionService("one", "dep", "https://localhost", "key"); - targetBuilder.AddAzureTextCompletionService("one", "dep", "https://localhost", "key"); - targetBuilder.AddOpenAITextCompletionService("one", "model", "key"); - targetBuilder.AddOpenAITextCompletionService("one", "model", "key"); - targetBuilder.AddAzureTextEmbeddingGenerationService("one", "dep", "https://localhost", "key"); - targetBuilder.AddAzureTextEmbeddingGenerationService("one", "dep", "https://localhost", "key"); - targetBuilder.AddOpenAITextEmbeddingGenerationService("one", "model", "key"); - targetBuilder.AddOpenAITextEmbeddingGenerationService("one", "model", "key"); + targetBuilder.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); + targetBuilder.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); + targetBuilder.AddOpenAITextCompletionService("model", "key, serviceId: \"one\""); + targetBuilder.AddOpenAITextCompletionService("model", "key", serviceId: "one"); + targetBuilder.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "one"); + targetBuilder.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "one"); + targetBuilder.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "one"); + targetBuilder.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "one"); } } diff --git a/dotnet/src/SemanticKernel.UnitTests/AI/Embeddings/TextEmbeddingServiceExtensionTests.cs b/dotnet/src/SemanticKernel.UnitTests/AI/Embeddings/TextEmbeddingServiceExtensionTests.cs index 0740a7f81fea..122c9395e889 100644 --- a/dotnet/src/SemanticKernel.UnitTests/AI/Embeddings/TextEmbeddingServiceExtensionTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/AI/Embeddings/TextEmbeddingServiceExtensionTests.cs @@ -46,7 +46,7 @@ public void ItCanAddTextEmbeddingServiceFactory() var provider = services.Build(); // Assert - Assert.True(provider.TryGetService(serviceId, out _)); + Assert.True(provider.TryGetService(serviceId, out _)); } [Fact] diff --git a/dotnet/src/SemanticKernel/Services/AIServiceCollection.cs b/dotnet/src/SemanticKernel/Services/AIServiceCollection.cs index a99234ab0149..dc61b4bda16d 100644 --- a/dotnet/src/SemanticKernel/Services/AIServiceCollection.cs +++ b/dotnet/src/SemanticKernel/Services/AIServiceCollection.cs @@ -107,7 +107,7 @@ public IAIServiceProvider Build() typeDefault => typeDefault.Key, typeDefault => typeDefault.Value); - return (IAIServiceProvider)new NamedServiceProvider(servicesClone, defaultsClone); + return new AIServiceProvider(servicesClone, defaultsClone); } private bool HasDefault() where T : IAIService diff --git a/dotnet/src/SemanticKernel/Services/AIServiceProvider.cs b/dotnet/src/SemanticKernel/Services/AIServiceProvider.cs new file mode 100644 index 000000000000..13c98bd26e22 --- /dev/null +++ b/dotnet/src/SemanticKernel/Services/AIServiceProvider.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Collections.Generic; + +namespace Microsoft.SemanticKernel.Services; +internal class AIServiceProvider : NamedServiceProvider, IAIServiceProvider +{ + public AIServiceProvider(Dictionary>> services, Dictionary defaultIds) + : base(services, defaultIds) + { + } +} From e9162a96f5bf7097fd5f99328dff15b9830e6677 Mon Sep 17 00:00:00 2001 From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> Date: Mon, 8 May 2023 19:16:47 +0100 Subject: [PATCH 03/15] Improvements/fixes for service registry (#857) ### Description Series of improvements to align service registration extension methods with the other kernel builder ones and remove the "default" suffix from service provider Get*ServiceOrDefault method name to not expose unnecessary details with code that may use it. --- .../OpenAIKernelBuilderExtensions.cs | 22 +++++++------- .../OpenAI/AIServicesOpenAIExtensionsTests.cs | 30 +++++++++---------- .../OpenAI/OpenAICompletionTests.cs | 12 ++++---- .../IntegrationTests/Planning/PlanTests.cs | 4 +-- .../SequentialPlanParserTests.cs | 2 +- .../SequentialPlannerTests.cs | 4 +-- .../ChatCompletionServiceExtensions.cs | 4 +-- .../TextEmbeddingServiceExtensions.cs | 4 +-- .../ImageGenerationServiceExtensions.cs | 4 +-- .../Services/ServiceExtensions.cs | 21 ------------- dotnet/src/SemanticKernel/KernelBuilder.cs | 10 ++----- .../webapi/SemanticKernelExtensions.cs | 8 ++--- samples/dotnet/KernelBuilder/Program.cs | 8 ++--- .../KernelHttpServer/SemanticKernelFactory.cs | 8 ++--- samples/dotnet/graph-api-skills/Program.cs | 4 +-- ...xample04_CombineLLMPromptsAndNativeCode.cs | 4 +-- .../Example05_InlineFunctionDefinition.cs | 2 +- .../Example06_TemplateLanguage.cs | 2 +- .../Example07_BingAndGoogleSkills.cs | 2 +- .../Example08_RetryHandler.cs | 6 ++-- .../Example09_FunctionTypes.cs | 2 +- ...Example10_DescribeAllSkillsAndFunctions.cs | 2 +- .../Example12_SequentialPlanner.cs | 8 ++--- .../Example13_ConversationSummarySkill.cs | 2 +- .../Example14_SemanticMemory.cs | 2 +- .../Example15_MemorySkill.cs | 4 +-- .../Example17_ChatGPT.cs | 2 +- .../kernel-syntax-examples/Example18_DallE.cs | 4 +-- .../Example19_Qdrant.cs | 4 +-- .../Example26_AADAuth.cs | 2 +- ...Example27_SemanticFunctionsUsingChatGPT.cs | 2 +- .../Example28_ActionPlanner.cs | 2 +- .../Example30_ChatWithPrompts.cs | 2 +- .../Example31_CustomPlanner.cs | 4 +-- .../Example32_StreamingCompletion.cs | 4 +-- 35 files changed, 90 insertions(+), 117 deletions(-) diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs index 184bc984ff03..614b1d119453 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs @@ -16,7 +16,7 @@ namespace Microsoft.SemanticKernel; #pragma warning restore IDE0130 -public static class OpenAKernelBuilderExtensions +public static class OpenAIKernelBuilderExtensions { #region Text Completion @@ -33,7 +33,7 @@ public static class OpenAKernelBuilderExtensions /// Custom for HTTP requests. /// Application logger /// Self instance - public static KernelBuilder AddAzureTextCompletionService(this KernelBuilder builder, + public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder builder, string deploymentName, string endpoint, string apiKey, @@ -67,7 +67,7 @@ public static KernelBuilder AddAzureTextCompletionService(this KernelBuilder bui /// Custom for HTTP requests. /// Application logger /// Self instance - public static KernelBuilder AddAzureTextCompletionService(this KernelBuilder builder, + public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder builder, string deploymentName, string endpoint, TokenCredential credentials, @@ -101,7 +101,7 @@ public static KernelBuilder AddAzureTextCompletionService(this KernelBuilder bui /// Custom for HTTP requests. /// Application logger /// Self instance - public static KernelBuilder AddOpenAITextCompletionService(this KernelBuilder builder, + public static KernelBuilder WithOpenAITextCompletionService(this KernelBuilder builder, string modelId, string apiKey, string? orgId = null, @@ -138,7 +138,7 @@ public static KernelBuilder AddOpenAITextCompletionService(this KernelBuilder bu /// Custom for HTTP requests. /// Application logger /// Self instance - public static KernelBuilder AddAzureTextEmbeddingGenerationService(this KernelBuilder builder, + public static KernelBuilder WithAzureTextEmbeddingGenerationService(this KernelBuilder builder, string deploymentName, string endpoint, string apiKey, @@ -171,7 +171,7 @@ public static KernelBuilder AddAzureTextEmbeddingGenerationService(this KernelBu /// Custom for HTTP requests. /// Application logger /// Self instance - public static KernelBuilder AddAzureTextEmbeddingGenerationService(this KernelBuilder builder, + public static KernelBuilder WithAzureTextEmbeddingGenerationService(this KernelBuilder builder, string deploymentName, string endpoint, TokenCredential credential, @@ -204,7 +204,7 @@ public static KernelBuilder AddAzureTextEmbeddingGenerationService(this KernelBu /// Custom for HTTP requests. /// Application logger /// Self instance - public static KernelBuilder AddOpenAITextEmbeddingGenerationService(this KernelBuilder builder, + public static KernelBuilder WithOpenAITextEmbeddingGenerationService(this KernelBuilder builder, string modelId, string apiKey, string? orgId = null, @@ -242,7 +242,7 @@ public static KernelBuilder AddOpenAITextEmbeddingGenerationService(this KernelB /// Custom for HTTP requests. /// Application logger /// Self instance - public static KernelBuilder AddAzureChatCompletionService(this KernelBuilder builder, + public static KernelBuilder WithAzureChatCompletionService(this KernelBuilder builder, string deploymentName, string endpoint, string apiKey, @@ -284,7 +284,7 @@ public static KernelBuilder AddAzureChatCompletionService(this KernelBuilder bui /// Custom for HTTP requests. /// Application logger /// Self instance - public static KernelBuilder AddAzureChatCompletionService(this KernelBuilder builder, + public static KernelBuilder WithAzureChatCompletionService(this KernelBuilder builder, string deploymentName, string endpoint, TokenCredential credentials, @@ -326,7 +326,7 @@ public static KernelBuilder AddAzureChatCompletionService(this KernelBuilder bui /// Custom for HTTP requests. /// Application logger /// Self instance - public static KernelBuilder AddOpenAIChatCompletionService(this KernelBuilder builder, + public static KernelBuilder WithOpenAIChatCompletionService(this KernelBuilder builder, string modelId, string apiKey, string? orgId = null, @@ -369,7 +369,7 @@ public static KernelBuilder AddOpenAIChatCompletionService(this KernelBuilder bu /// Custom for HTTP requests. /// Application logger /// Self instance - public static KernelBuilder AddOpenAIImageGenerationService(this KernelBuilder builder, + public static KernelBuilder WithOpenAIImageGenerationService(this KernelBuilder builder, string apiKey, string? orgId = null, string? serviceId = null, diff --git a/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs index 6a4ac9125b84..12fcdf952c14 100644 --- a/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs +++ b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs @@ -8,7 +8,7 @@ namespace SemanticKernel.Connectors.UnitTests.OpenAI; /// -/// Unit tests of . +/// Unit tests of . /// public class AIServicesOpenAIExtensionsTests { @@ -16,8 +16,8 @@ public class AIServicesOpenAIExtensionsTests public void ItSucceedsWhenAddingDifferentServiceTypeWithSameId() { KernelBuilder targetBuilder = Kernel.Builder; - targetBuilder.AddAzureTextCompletionService("depl", "https://url", "key", "azure"); - targetBuilder.AddAzureTextEmbeddingGenerationService("depl2", "https://url", "key", "azure"); + targetBuilder.WithAzureTextCompletionService("depl", "https://url", "key", "azure"); + targetBuilder.WithAzureTextEmbeddingGenerationService("depl2", "https://url", "key", "azure"); IKernel targetKernel = targetBuilder.Build(); Assert.NotNull(targetKernel.GetService("azure")); @@ -28,10 +28,10 @@ public void ItSucceedsWhenAddingDifferentServiceTypeWithSameId() public void ItTellsIfAServiceIsAvailable() { KernelBuilder targetBuilder = Kernel.Builder; - targetBuilder.AddAzureTextCompletionService("depl", "https://url", "key", serviceId: "azure"); - targetBuilder.AddOpenAITextCompletionService("model", "apikey", serviceId: "oai"); - targetBuilder.AddAzureTextEmbeddingGenerationService("depl2", "https://url2", "key", serviceId: "azure"); - targetBuilder.AddOpenAITextEmbeddingGenerationService("model2", "apikey2", serviceId: "oai2"); + targetBuilder.WithAzureTextCompletionService("depl", "https://url", "key", serviceId: "azure"); + targetBuilder.WithOpenAITextCompletionService("model", "apikey", serviceId: "oai"); + targetBuilder.WithAzureTextEmbeddingGenerationService("depl2", "https://url2", "key", serviceId: "azure"); + targetBuilder.WithOpenAITextEmbeddingGenerationService("model2", "apikey2", serviceId: "oai2"); // Assert IKernel targetKernel = targetBuilder.Build(); @@ -48,13 +48,13 @@ public void ItCanOverwriteServices() KernelBuilder targetBuilder = Kernel.Builder; // Act - Assert no exception occurs - targetBuilder.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); - targetBuilder.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); - targetBuilder.AddOpenAITextCompletionService("model", "key, serviceId: \"one\""); - targetBuilder.AddOpenAITextCompletionService("model", "key", serviceId: "one"); - targetBuilder.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "one"); - targetBuilder.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "one"); - targetBuilder.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "one"); - targetBuilder.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "one"); + targetBuilder.WithAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); + targetBuilder.WithAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); + targetBuilder.WithOpenAITextCompletionService("model", "key, serviceId: \"one\""); + targetBuilder.WithOpenAITextCompletionService("model", "key", serviceId: "one"); + targetBuilder.WithAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "one"); + targetBuilder.WithAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "one"); + targetBuilder.WithOpenAITextEmbeddingGenerationService("model", "key", serviceId: "one"); + targetBuilder.WithOpenAITextEmbeddingGenerationService("model", "key", serviceId: "one"); } } diff --git a/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs b/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs index 8db915de2d3a..b2564977bb3b 100644 --- a/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs @@ -44,7 +44,7 @@ public async Task OpenAITestAsync(string prompt, string expectedAnswerContains) IKernel target = Kernel.Builder .WithLogger(this._logger) - .AddOpenAITextCompletionService( + .WithOpenAITextCompletionService( serviceId: openAIConfiguration.ServiceId, modelId: openAIConfiguration.ModelId, apiKey: openAIConfiguration.ApiKey, @@ -70,7 +70,7 @@ public async Task AzureOpenAITestAsync(string prompt, string expectedAnswerConta IKernel target = Kernel.Builder .WithLogger(this._logger) - .AddAzureTextCompletionService( + .WithAzureTextCompletionService( serviceId: azureOpenAIConfiguration.ServiceId, deploymentName: azureOpenAIConfiguration.DeploymentName, endpoint: azureOpenAIConfiguration.Endpoint, @@ -99,7 +99,7 @@ public async Task OpenAIHttpRetryPolicyTestAsync(string prompt, string expectedO Assert.NotNull(openAIConfiguration); IKernel target = Kernel.Builder - .AddOpenAITextCompletionService( + .WithOpenAITextCompletionService( serviceId: openAIConfiguration.ServiceId, modelId: openAIConfiguration.ModelId, apiKey: "INVALID_KEY") // Use an invalid API key to force a 401 Unauthorized response @@ -123,7 +123,7 @@ public async Task OpenAIHttpInvalidKeyShouldReturnErrorDetailAsync() // Use an invalid API key to force a 401 Unauthorized response IKernel target = Kernel.Builder - .AddOpenAITextCompletionService( + .WithOpenAITextCompletionService( modelId: openAIConfiguration.ModelId, apiKey: "INVALID_KEY", serviceId: openAIConfiguration.ServiceId) @@ -149,7 +149,7 @@ public async Task AzureOpenAIHttpInvalidKeyShouldReturnErrorDetailAsync() IKernel target = Kernel.Builder .WithLogger(this._testOutputHelper) - .AddAzureTextCompletionService( + .WithAzureTextCompletionService( deploymentName: azureOpenAIConfiguration.DeploymentName, endpoint: azureOpenAIConfiguration.Endpoint, apiKey: "INVALID_KEY", @@ -176,7 +176,7 @@ public async Task AzureOpenAIHttpExceededMaxTokensShouldReturnErrorDetailAsync() // Arrange IKernel target = Kernel.Builder .WithLogger(this._testOutputHelper) - .AddAzureTextCompletionService( + .WithAzureTextCompletionService( deploymentName: azureOpenAIConfiguration.DeploymentName, endpoint: azureOpenAIConfiguration.Endpoint, apiKey: azureOpenAIConfiguration.ApiKey, diff --git a/dotnet/src/IntegrationTests/Planning/PlanTests.cs b/dotnet/src/IntegrationTests/Planning/PlanTests.cs index b4714d1599a9..4c34f911a08e 100644 --- a/dotnet/src/IntegrationTests/Planning/PlanTests.cs +++ b/dotnet/src/IntegrationTests/Planning/PlanTests.cs @@ -458,7 +458,7 @@ private IKernel InitializeKernel(bool useEmbeddings = false) var builder = Kernel.Builder .WithLogger(this._logger) - .AddAzureTextCompletionService( + .WithAzureTextCompletionService( deploymentName: azureOpenAIConfiguration.DeploymentName, endpoint: azureOpenAIConfiguration.Endpoint, apiKey: azureOpenAIConfiguration.ApiKey, @@ -468,7 +468,7 @@ private IKernel InitializeKernel(bool useEmbeddings = false) if (useEmbeddings) { builder - .AddAzureTextEmbeddingGenerationService( + .WithAzureTextEmbeddingGenerationService( deploymentName: azureOpenAIEmbeddingsConfiguration.DeploymentName, endpoint: azureOpenAIEmbeddingsConfiguration.Endpoint, apiKey: azureOpenAIEmbeddingsConfiguration.ApiKey) diff --git a/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlanParserTests.cs b/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlanParserTests.cs index 03c0a51421cb..fa78fd901fcd 100644 --- a/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlanParserTests.cs +++ b/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlanParserTests.cs @@ -32,7 +32,7 @@ public void CanCallToPlanFromXml() Assert.NotNull(azureOpenAIConfiguration); IKernel kernel = Kernel.Builder - .AddAzureTextCompletionService( + .WithAzureTextCompletionService( deploymentName: azureOpenAIConfiguration.DeploymentName, endpoint: azureOpenAIConfiguration.Endpoint, apiKey: azureOpenAIConfiguration.ApiKey, diff --git a/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlannerTests.cs b/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlannerTests.cs index 681de268350f..7eac6dbc8b8d 100644 --- a/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlannerTests.cs +++ b/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlannerTests.cs @@ -108,7 +108,7 @@ private IKernel InitializeKernel(bool useEmbeddings = false) var builder = Kernel.Builder .WithLogger(this._logger) - .AddAzureTextCompletionService( + .WithAzureTextCompletionService( deploymentName: azureOpenAIConfiguration.DeploymentName, endpoint: azureOpenAIConfiguration.Endpoint, apiKey: azureOpenAIConfiguration.ApiKey, @@ -116,7 +116,7 @@ private IKernel InitializeKernel(bool useEmbeddings = false) if (useEmbeddings) { - builder.AddAzureTextEmbeddingGenerationService( + builder.WithAzureTextEmbeddingGenerationService( deploymentName: azureOpenAIEmbeddingsConfiguration.DeploymentName, endpoint: azureOpenAIEmbeddingsConfiguration.Endpoint, apiKey: azureOpenAIEmbeddingsConfiguration.ApiKey); diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatCompletionServiceExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatCompletionServiceExtensions.cs index 60503fd521e8..4df0db13d3e4 100644 --- a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatCompletionServiceExtensions.cs +++ b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatCompletionServiceExtensions.cs @@ -16,9 +16,9 @@ public static class ChatCompletionServiceExtensions /// 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 GetChatCompletionServiceOrDefault( + public static IChatCompletion GetChatCompletionService( this IAIServiceProvider services, - string? serviceId = null) => services.GetNamedServiceOrDefault(serviceId) + string? serviceId = null) => services.GetService(serviceId) ?? throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, "Chat completion service not found"); /// diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/TextEmbeddingServiceExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/TextEmbeddingServiceExtensions.cs index 6854b6db15f6..45fe761b4546 100644 --- a/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/TextEmbeddingServiceExtensions.cs +++ b/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/TextEmbeddingServiceExtensions.cs @@ -16,10 +16,10 @@ public static class TextEmbeddingServiceExtensions /// 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 GetTextEmbeddingServiceOrDefault( + public static ITextEmbeddingGeneration GetTextEmbeddingService( this IAIServiceProvider services, string? serviceId = null) - => services.GetNamedServiceOrDefault(serviceId) + => services.GetService(serviceId) ?? throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, "Text embedding service not available"); /// diff --git a/dotnet/src/SemanticKernel.Abstractions/AI/ImageGeneration/ImageGenerationServiceExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/AI/ImageGeneration/ImageGenerationServiceExtensions.cs index 28488ad72624..a368757c6c60 100644 --- a/dotnet/src/SemanticKernel.Abstractions/AI/ImageGeneration/ImageGenerationServiceExtensions.cs +++ b/dotnet/src/SemanticKernel.Abstractions/AI/ImageGeneration/ImageGenerationServiceExtensions.cs @@ -16,9 +16,9 @@ public static class ImageGenerationServiceExtensions /// 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 GetImageGenerationServiceOrDefault( + public static IImageGeneration GetImageGenerationService( this IAIServiceProvider services, - string? serviceId = null) => services.GetNamedServiceOrDefault(serviceId) + string? serviceId = null) => services.GetService(serviceId) ?? throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, "Image generation service not found"); /// diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/ServiceExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/Services/ServiceExtensions.cs index 8b81fb01b2db..05c0ec77f3e3 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Services/ServiceExtensions.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Services/ServiceExtensions.cs @@ -5,27 +5,6 @@ namespace Microsoft.SemanticKernel.Services; internal static class AIServiceProviderExtensions { - /// - /// Get the service matching the given id or the default if an id is not provided or not found. - /// - /// The type of the service. - /// The service provider. - /// Optional identifier of the desired service. - /// The service matching the given id, the default service, or null. - public static T? GetNamedServiceOrDefault( - this IAIServiceProvider serviceProvider, - string? serviceId = null) where T : IAIService - { - if ((serviceId != null) - && serviceProvider.TryGetService(serviceId, out var namedService)) - { - return namedService; - } - - return serviceProvider.GetService(); - } - - /// /// Tries to get the service of the specified type and name, and returns a value indicating whether the operation succeeded. /// diff --git a/dotnet/src/SemanticKernel/KernelBuilder.cs b/dotnet/src/SemanticKernel/KernelBuilder.cs index 79b41b5c124f..dbb85c369fea 100644 --- a/dotnet/src/SemanticKernel/KernelBuilder.cs +++ b/dotnet/src/SemanticKernel/KernelBuilder.cs @@ -146,10 +146,7 @@ public KernelBuilder Configure(Action configure) /// Adds a instance to the services collection /// /// The instance. - /// Optional: set as the default AI service for type - public KernelBuilder WithDefaultAIService( - TService instance, - bool setAsDefault = false) where TService : IAIService + public KernelBuilder WithDefaultAIService(TService instance) where TService : IAIService { this._aiServices.SetService(instance); return this; @@ -174,10 +171,7 @@ public KernelBuilder WithAIService( /// Adds a factory method to the services collection /// /// The factory method that creates the AI service instances of type . - /// Optional: set as the default AI service for type - public KernelBuilder WithDefaultAIService( - Func factory, - bool setAsDefault = false) where TService : IAIService + public KernelBuilder WithDefaultAIService(Func factory) where TService : IAIService { this._aiServices.SetService(factory); return this; diff --git a/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs b/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs index 02c644d23c05..c1139deb4210 100644 --- a/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs +++ b/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs @@ -94,7 +94,7 @@ internal static KernelBuilder AddCompletionBackend(this KernelBuilder kernelBuil switch (config.AIService) { case AIServiceOptions.AIServiceType.AzureOpenAI: - kernelBuilder.AddAzureChatCompletionService( + kernelBuilder.WithAzureChatCompletionService( serviceId: config.Label, deploymentName: config.DeploymentOrModelId, endpoint: config.Endpoint, @@ -103,7 +103,7 @@ internal static KernelBuilder AddCompletionBackend(this KernelBuilder kernelBuil break; case AIServiceOptions.AIServiceType.OpenAI: - kernelBuilder.AddOpenAIChatCompletionService( + kernelBuilder.WithOpenAIChatCompletionService( serviceId: config.Label, modelId: config.DeploymentOrModelId, apiKey: config.Key, @@ -127,7 +127,7 @@ internal static KernelBuilder AddEmbeddingBackend(this KernelBuilder kernelBuild switch (config.AIService) { case AIServiceOptions.AIServiceType.AzureOpenAI: - kernelBuilder.AddAzureTextEmbeddingGenerationService( + kernelBuilder.WithAzureTextEmbeddingGenerationService( serviceId: config.Label, deploymentName: config.DeploymentOrModelId, endpoint: config.Endpoint, @@ -135,7 +135,7 @@ internal static KernelBuilder AddEmbeddingBackend(this KernelBuilder kernelBuild break; case AIServiceOptions.AIServiceType.OpenAI: - kernelBuilder.AddOpenAITextEmbeddingGenerationService( + kernelBuilder.WithOpenAITextEmbeddingGenerationService( serviceId: config.Label, modelId: config.DeploymentOrModelId, apiKey: config.Key); diff --git a/samples/dotnet/KernelBuilder/Program.cs b/samples/dotnet/KernelBuilder/Program.cs index 768e3f0b7a64..bd145adb5ac1 100644 --- a/samples/dotnet/KernelBuilder/Program.cs +++ b/samples/dotnet/KernelBuilder/Program.cs @@ -66,7 +66,7 @@ var kernel4 = Kernel.Builder .WithLogger(NullLogger.Instance) .WithMemory(memory) - .AddAzureTextCompletionService("deploymentName", "https://...", "apiKey") + .WithAzureTextCompletionService("deploymentName", "https://...", "apiKey") .Build(); // Example: how to use a custom memory storage and custom embedding generator @@ -79,15 +79,15 @@ var kernel6 = Kernel.Builder .WithLogger(NullLogger.Instance) .WithMemoryStorage(memoryStorage) // Custom memory storage - .AddAzureTextCompletionService("myName1", "completionDeploymentName", "https://...", "apiKey") // This will be used when using AI completions - .AddAzureTextEmbeddingGenerationService("myName2", "embeddingsDeploymentName", "https://...", "apiKey") // This will be used when indexing memory records + .WithAzureTextCompletionService("myName1", "completionDeploymentName", "https://...", "apiKey") // This will be used when using AI completions + .WithAzureTextEmbeddingGenerationService("myName2", "embeddingsDeploymentName", "https://...", "apiKey") // This will be used when indexing memory records .Build(); // ========================================================================================================== // The AI services are defined with the builder var kernel7 = Kernel.Builder - .AddAzureTextCompletionService("myName1", "completionDeploymentName", "https://...", "apiKey", true) + .WithAzureTextCompletionService("myName1", "completionDeploymentName", "https://...", "apiKey", true) .Build(); // ========================================================================================================== diff --git a/samples/dotnet/KernelHttpServer/SemanticKernelFactory.cs b/samples/dotnet/KernelHttpServer/SemanticKernelFactory.cs index c074dbfc025e..72af718609d7 100644 --- a/samples/dotnet/KernelHttpServer/SemanticKernelFactory.cs +++ b/samples/dotnet/KernelHttpServer/SemanticKernelFactory.cs @@ -46,13 +46,13 @@ private static KernelBuilder _ConfigureKernelBuilder(ApiKeyConfig config, Kernel switch (config.CompletionConfig.AIService) { case AIService.OpenAI: - builder.AddOpenAITextCompletionService( + builder.WithOpenAITextCompletionService( config.CompletionConfig.DeploymentOrModelId, config.CompletionConfig.Key, config.CompletionConfig.ServiceId); break; case AIService.AzureOpenAI: - builder.AddAzureTextCompletionService( + builder.WithAzureTextCompletionService( config.CompletionConfig.DeploymentOrModelId, config.CompletionConfig.Endpoint, config.CompletionConfig.Key, @@ -67,13 +67,13 @@ private static KernelBuilder _ConfigureKernelBuilder(ApiKeyConfig config, Kernel switch (config.EmbeddingConfig.AIService) { case AIService.OpenAI: - builder.AddOpenAITextEmbeddingGenerationService( + builder.WithOpenAITextEmbeddingGenerationService( config.EmbeddingConfig.DeploymentOrModelId, config.EmbeddingConfig.Key, serviceId: config.EmbeddingConfig.ServiceId); break; case AIService.AzureOpenAI: - builder.AddAzureTextEmbeddingGenerationService( + builder.WithAzureTextEmbeddingGenerationService( config.EmbeddingConfig.DeploymentOrModelId, config.EmbeddingConfig.Endpoint, config.EmbeddingConfig.Key, diff --git a/samples/dotnet/graph-api-skills/Program.cs b/samples/dotnet/graph-api-skills/Program.cs index 099185cacd8c..2a7baa126eda 100644 --- a/samples/dotnet/graph-api-skills/Program.cs +++ b/samples/dotnet/graph-api-skills/Program.cs @@ -113,7 +113,7 @@ public static async Task Main() AzureOpenAIConfiguration? azureOpenAIConfiguration = configuration.GetSection("AzureOpenAI").Get(); if (azureOpenAIConfiguration != null) { - builder.AddAzureTextCompletionService( + builder.WithAzureTextCompletionService( deploymentName: azureOpenAIConfiguration.DeploymentName, endpoint: azureOpenAIConfiguration.Endpoint, apiKey: azureOpenAIConfiguration.ApiKey, @@ -127,7 +127,7 @@ public static async Task Main() OpenAIConfiguration? openAIConfiguration = configuration.GetSection("OpenAI").Get(); if (openAIConfiguration != null) { - builder.AddOpenAITextCompletionService( + builder.WithOpenAITextCompletionService( modelId: openAIConfiguration.ModelId, apiKey: openAIConfiguration.ApiKey, serviceId: openAIConfiguration.ServiceId, diff --git a/samples/dotnet/kernel-syntax-examples/Example04_CombineLLMPromptsAndNativeCode.cs b/samples/dotnet/kernel-syntax-examples/Example04_CombineLLMPromptsAndNativeCode.cs index ed3aa422714f..fa6d81ac27b6 100644 --- a/samples/dotnet/kernel-syntax-examples/Example04_CombineLLMPromptsAndNativeCode.cs +++ b/samples/dotnet/kernel-syntax-examples/Example04_CombineLLMPromptsAndNativeCode.cs @@ -16,8 +16,8 @@ public static async Task RunAsync() IKernel kernel = new KernelBuilder() .WithLogger(ConsoleLogger.Log) - .AddOpenAITextCompletionService("text-davinci-002", Env.Var("OPENAI_API_KEY"), serviceId: "text-davinci-002") - .AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) + .WithOpenAITextCompletionService("text-davinci-002", Env.Var("OPENAI_API_KEY"), serviceId: "text-davinci-002") + .WithOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) .Build(); // Load native skill diff --git a/samples/dotnet/kernel-syntax-examples/Example05_InlineFunctionDefinition.cs b/samples/dotnet/kernel-syntax-examples/Example05_InlineFunctionDefinition.cs index 102756db2830..7f30437a83e3 100644 --- a/samples/dotnet/kernel-syntax-examples/Example05_InlineFunctionDefinition.cs +++ b/samples/dotnet/kernel-syntax-examples/Example05_InlineFunctionDefinition.cs @@ -20,7 +20,7 @@ public static async Task RunAsync() IKernel kernel = new KernelBuilder() .WithLogger(ConsoleLogger.Log) - .AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) + .WithOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) .Build(); // Function defined using few-shot design pattern diff --git a/samples/dotnet/kernel-syntax-examples/Example06_TemplateLanguage.cs b/samples/dotnet/kernel-syntax-examples/Example06_TemplateLanguage.cs index 94ff2d0cf903..c0138d03b3e2 100644 --- a/samples/dotnet/kernel-syntax-examples/Example06_TemplateLanguage.cs +++ b/samples/dotnet/kernel-syntax-examples/Example06_TemplateLanguage.cs @@ -20,7 +20,7 @@ public static async Task RunAsync() IKernel kernel = Kernel.Builder .WithLogger(ConsoleLogger.Log) - .AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) + .WithOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) .Build(); // Load native skill into the kernel skill collection, sharing its functions with prompt templates diff --git a/samples/dotnet/kernel-syntax-examples/Example07_BingAndGoogleSkills.cs b/samples/dotnet/kernel-syntax-examples/Example07_BingAndGoogleSkills.cs index 59b3866b03b7..ddf6abed71a1 100644 --- a/samples/dotnet/kernel-syntax-examples/Example07_BingAndGoogleSkills.cs +++ b/samples/dotnet/kernel-syntax-examples/Example07_BingAndGoogleSkills.cs @@ -22,7 +22,7 @@ public static async Task RunAsync() { IKernel kernel = new KernelBuilder() .WithLogger(ConsoleLogger.Log) - .AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) + .WithOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) .Build(); // Load Bing skill diff --git a/samples/dotnet/kernel-syntax-examples/Example08_RetryHandler.cs b/samples/dotnet/kernel-syntax-examples/Example08_RetryHandler.cs index 2664e24427af..7d76031b96e9 100644 --- a/samples/dotnet/kernel-syntax-examples/Example08_RetryHandler.cs +++ b/samples/dotnet/kernel-syntax-examples/Example08_RetryHandler.cs @@ -47,7 +47,7 @@ private static async Task RunRetryHandlerConfigAsync(HttpRetryConfig? config = n kernelBuilder = kernelBuilder.Configure(c => c.DefaultHttpRetryConfig.RetryableStatusCodes.Add(HttpStatusCode.Unauthorized)); // OpenAI settings - you can set the OPENAI_API_KEY to an invalid value to see the retry policy in play - kernelBuilder = kernelBuilder.AddOpenAITextCompletionService("text-davinci-003", "BAD_KEY"); + kernelBuilder = kernelBuilder.WithOpenAITextCompletionService("text-davinci-003", "BAD_KEY"); var kernel = kernelBuilder.Build(); @@ -59,7 +59,7 @@ private static IKernel InitializeKernel() var kernel = Kernel.Builder .WithLogger(InfoLogger.Log) // OpenAI settings - you can set the OPENAI_API_KEY to an invalid value to see the retry policy in play - .AddOpenAITextCompletionService("text-davinci-003", "BAD_KEY") + .WithOpenAITextCompletionService("text-davinci-003", "BAD_KEY") .Build(); return kernel; @@ -76,7 +76,7 @@ private static async Task RunRetryPolicyBuilderAsync(Type retryHandlerFactoryTyp var kernel = Kernel.Builder.WithLogger(InfoLogger.Log) .WithRetryHandlerFactory((Activator.CreateInstance(retryHandlerFactoryType) as IDelegatingHandlerFactory)!) // OpenAI settings - you can set the OPENAI_API_KEY to an invalid value to see the retry policy in play - .AddOpenAITextCompletionService("text-davinci-003", "BAD_KEY") + .WithOpenAITextCompletionService("text-davinci-003", "BAD_KEY") .Build(); await ImportAndExecuteSkillAsync(kernel); diff --git a/samples/dotnet/kernel-syntax-examples/Example09_FunctionTypes.cs b/samples/dotnet/kernel-syntax-examples/Example09_FunctionTypes.cs index 6d5214cecb14..7267faff3ed4 100644 --- a/samples/dotnet/kernel-syntax-examples/Example09_FunctionTypes.cs +++ b/samples/dotnet/kernel-syntax-examples/Example09_FunctionTypes.cs @@ -21,7 +21,7 @@ public static async Task RunAsync() var kernel = Kernel.Builder .WithLogger(ConsoleLogger.Log) - .AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) + .WithOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) .Build(); // Load native skill into the kernel skill collection, sharing its functions with prompt templates diff --git a/samples/dotnet/kernel-syntax-examples/Example10_DescribeAllSkillsAndFunctions.cs b/samples/dotnet/kernel-syntax-examples/Example10_DescribeAllSkillsAndFunctions.cs index 4288de0b716b..9ca8ccd4f61c 100644 --- a/samples/dotnet/kernel-syntax-examples/Example10_DescribeAllSkillsAndFunctions.cs +++ b/samples/dotnet/kernel-syntax-examples/Example10_DescribeAllSkillsAndFunctions.cs @@ -21,7 +21,7 @@ public static void Run() Console.WriteLine("======== Describe all skills and functions ========"); var kernel = Kernel.Builder - .AddOpenAITextCompletionService("text-davinci-003", "none") + .WithOpenAITextCompletionService("text-davinci-003", "none") .Build(); // Import a native skill diff --git a/samples/dotnet/kernel-syntax-examples/Example12_SequentialPlanner.cs b/samples/dotnet/kernel-syntax-examples/Example12_SequentialPlanner.cs index 77ea2c601513..3f8d4337a5fc 100644 --- a/samples/dotnet/kernel-syntax-examples/Example12_SequentialPlanner.cs +++ b/samples/dotnet/kernel-syntax-examples/Example12_SequentialPlanner.cs @@ -27,7 +27,7 @@ private static async Task PoetrySamplesAsync() Console.WriteLine("======== Sequential Planner - Create and Execute Poetry Plan ========"); var kernel = new KernelBuilder() .WithLogger(ConsoleLogger.Log) - .AddAzureTextCompletionService( + .WithAzureTextCompletionService( Env.Var("AZURE_OPENAI_SERVICE_ID"), Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), Env.Var("AZURE_OPENAI_ENDPOINT"), @@ -138,11 +138,11 @@ private static async Task MemorySampleAsync() var kernel = new KernelBuilder() .WithLogger(ConsoleLogger.Log) - .AddAzureTextCompletionService( + .WithAzureTextCompletionService( Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), Env.Var("AZURE_OPENAI_ENDPOINT"), Env.Var("AZURE_OPENAI_KEY")) - .AddAzureTextEmbeddingGenerationService( + .WithAzureTextEmbeddingGenerationService( Env.Var("AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME"), Env.Var("AZURE_OPENAI_EMBEDDINGS_ENDPOINT"), Env.Var("AZURE_OPENAI_EMBEDDINGS_KEY")) @@ -182,7 +182,7 @@ private static IKernel InitializeKernelAndPlanner(out SequentialPlanner planner, { var kernel = new KernelBuilder() .WithLogger(ConsoleLogger.Log) - .AddAzureTextCompletionService( + .WithAzureTextCompletionService( Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), Env.Var("AZURE_OPENAI_ENDPOINT"), Env.Var("AZURE_OPENAI_KEY")) diff --git a/samples/dotnet/kernel-syntax-examples/Example13_ConversationSummarySkill.cs b/samples/dotnet/kernel-syntax-examples/Example13_ConversationSummarySkill.cs index 948a4a0c14a7..25877dde3b9c 100644 --- a/samples/dotnet/kernel-syntax-examples/Example13_ConversationSummarySkill.cs +++ b/samples/dotnet/kernel-syntax-examples/Example13_ConversationSummarySkill.cs @@ -180,7 +180,7 @@ private static IKernel InitializeKernel() { IKernel kernel = Kernel.Builder .WithLogger(ConsoleLogger.Log) - .AddAzureTextCompletionService( + .WithAzureTextCompletionService( Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), Env.Var("AZURE_OPENAI_ENDPOINT"), Env.Var("AZURE_OPENAI_KEY")) diff --git a/samples/dotnet/kernel-syntax-examples/Example14_SemanticMemory.cs b/samples/dotnet/kernel-syntax-examples/Example14_SemanticMemory.cs index 90ae64419961..d51cf4c597bb 100644 --- a/samples/dotnet/kernel-syntax-examples/Example14_SemanticMemory.cs +++ b/samples/dotnet/kernel-syntax-examples/Example14_SemanticMemory.cs @@ -56,7 +56,7 @@ public static async Task RunAsync() var kernelWithCustomDb = Kernel.Builder .WithLogger(ConsoleLogger.Log) - .AddOpenAITextEmbeddingGenerationService("ada", "text-embedding-ada-002", Env.Var("OPENAI_API_KEY")) + .WithOpenAITextEmbeddingGenerationService("ada", "text-embedding-ada-002", Env.Var("OPENAI_API_KEY")) .WithMemoryStorage(new VolatileMemoryStore()) .Build(); diff --git a/samples/dotnet/kernel-syntax-examples/Example15_MemorySkill.cs b/samples/dotnet/kernel-syntax-examples/Example15_MemorySkill.cs index e68c7cdfde9c..477d30e30666 100644 --- a/samples/dotnet/kernel-syntax-examples/Example15_MemorySkill.cs +++ b/samples/dotnet/kernel-syntax-examples/Example15_MemorySkill.cs @@ -16,8 +16,8 @@ public static async Task RunAsync() { var kernel = Kernel.Builder .WithLogger(ConsoleLogger.Log) - .AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) - .AddOpenAITextEmbeddingGenerationService("text-embedding-ada-002", Env.Var("OPENAI_API_KEY")) + .WithOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) + .WithOpenAITextEmbeddingGenerationService("text-embedding-ada-002", Env.Var("OPENAI_API_KEY")) .WithMemoryStorage(new VolatileMemoryStore()) .Build(); diff --git a/samples/dotnet/kernel-syntax-examples/Example17_ChatGPT.cs b/samples/dotnet/kernel-syntax-examples/Example17_ChatGPT.cs index 00f87c0bdb67..63f38ec821b1 100644 --- a/samples/dotnet/kernel-syntax-examples/Example17_ChatGPT.cs +++ b/samples/dotnet/kernel-syntax-examples/Example17_ChatGPT.cs @@ -21,7 +21,7 @@ public static async Task RunAsync() IKernel kernel = new KernelBuilder() .WithLogger(ConsoleLogger.Log) - .AddOpenAIChatCompletionService("gpt-3.5-turbo", Env.Var("OPENAI_API_KEY")) + .WithOpenAIChatCompletionService("gpt-3.5-turbo", Env.Var("OPENAI_API_KEY")) .Build(); IChatCompletion chatGPT = kernel.GetService(); diff --git a/samples/dotnet/kernel-syntax-examples/Example18_DallE.cs b/samples/dotnet/kernel-syntax-examples/Example18_DallE.cs index 8c9a0f409644..79f034776d3d 100644 --- a/samples/dotnet/kernel-syntax-examples/Example18_DallE.cs +++ b/samples/dotnet/kernel-syntax-examples/Example18_DallE.cs @@ -22,9 +22,9 @@ public static async Task RunAsync() IKernel kernel = new KernelBuilder() .WithLogger(ConsoleLogger.Log) // Add your image generation service - .AddOpenAIImageGenerationService("dallE", Env.Var("OPENAI_API_KEY")) + .WithOpenAIImageGenerationService("dallE", Env.Var("OPENAI_API_KEY")) // Add your chat completion service - .AddOpenAIChatCompletionService("gpt-3.5-turbo", Env.Var("OPENAI_API_KEY")) + .WithOpenAIChatCompletionService("gpt-3.5-turbo", Env.Var("OPENAI_API_KEY")) .Build(); IImageGeneration dallE = kernel.GetService(); diff --git a/samples/dotnet/kernel-syntax-examples/Example19_Qdrant.cs b/samples/dotnet/kernel-syntax-examples/Example19_Qdrant.cs index 10d7fc89072e..58a7b349b5ee 100644 --- a/samples/dotnet/kernel-syntax-examples/Example19_Qdrant.cs +++ b/samples/dotnet/kernel-syntax-examples/Example19_Qdrant.cs @@ -19,8 +19,8 @@ public static async Task RunAsync() QdrantMemoryStore memoryStore = new QdrantMemoryStore(Env.Var("QDRANT_ENDPOINT"), qdrantPort, vectorSize: 1536, ConsoleLogger.Log); IKernel kernel = Kernel.Builder .WithLogger(ConsoleLogger.Log) - .AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) - .AddOpenAITextEmbeddingGenerationService("text-embedding-ada-002", Env.Var("OPENAI_API_KEY")) + .WithOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY")) + .WithOpenAITextEmbeddingGenerationService("text-embedding-ada-002", Env.Var("OPENAI_API_KEY")) .WithMemoryStorage(memoryStore) .Build(); diff --git a/samples/dotnet/kernel-syntax-examples/Example26_AADAuth.cs b/samples/dotnet/kernel-syntax-examples/Example26_AADAuth.cs index 78601698258e..c947c1f6dac1 100644 --- a/samples/dotnet/kernel-syntax-examples/Example26_AADAuth.cs +++ b/samples/dotnet/kernel-syntax-examples/Example26_AADAuth.cs @@ -43,7 +43,7 @@ public static async Task RunAsync() IKernel kernel = new KernelBuilder() .WithLogger(ConsoleLogger.Log) // Add Azure chat completion service using DefaultAzureCredential AAD auth - .AddAzureChatCompletionService( + .WithAzureChatCompletionService( "gpt-35-turbo", "https://....openai.azure.com/", new DefaultAzureCredential(authOptions)) diff --git a/samples/dotnet/kernel-syntax-examples/Example27_SemanticFunctionsUsingChatGPT.cs b/samples/dotnet/kernel-syntax-examples/Example27_SemanticFunctionsUsingChatGPT.cs index e5997f138843..7f4c34b01dbb 100644 --- a/samples/dotnet/kernel-syntax-examples/Example27_SemanticFunctionsUsingChatGPT.cs +++ b/samples/dotnet/kernel-syntax-examples/Example27_SemanticFunctionsUsingChatGPT.cs @@ -19,7 +19,7 @@ public static async Task RunAsync() IKernel kernel = new KernelBuilder() .WithLogger(ConsoleLogger.Log) // Note: we use Chat Completion and GPT 3.5 Turbo - .AddAzureChatCompletionService("gpt-35-turbo", "https://....openai.azure.com/", "...API KEY...") + .WithAzureChatCompletionService("gpt-35-turbo", "https://....openai.azure.com/", "...API KEY...") .Build(); var func = kernel.CreateSemanticFunction( diff --git a/samples/dotnet/kernel-syntax-examples/Example28_ActionPlanner.cs b/samples/dotnet/kernel-syntax-examples/Example28_ActionPlanner.cs index 8635015a7506..9a9fd7b1f2b2 100644 --- a/samples/dotnet/kernel-syntax-examples/Example28_ActionPlanner.cs +++ b/samples/dotnet/kernel-syntax-examples/Example28_ActionPlanner.cs @@ -15,7 +15,7 @@ public static async Task RunAsync() Console.WriteLine("======== Action Planner ========"); var kernel = new KernelBuilder() .WithLogger(ConsoleLogger.Log) - .AddOpenAITextCompletionService("text-davinci-002", Env.Var("OPENAI_API_KEY"))// Note: Action Planner works with old models like text-davinci-002 + .WithOpenAITextCompletionService("text-davinci-002", Env.Var("OPENAI_API_KEY"))// Note: Action Planner works with old models like text-davinci-002 .Build(); string folder = RepoFiles.SampleSkillsPath(); diff --git a/samples/dotnet/kernel-syntax-examples/Example30_ChatWithPrompts.cs b/samples/dotnet/kernel-syntax-examples/Example30_ChatWithPrompts.cs index c03907d8298a..fca2572e247e 100644 --- a/samples/dotnet/kernel-syntax-examples/Example30_ChatWithPrompts.cs +++ b/samples/dotnet/kernel-syntax-examples/Example30_ChatWithPrompts.cs @@ -65,7 +65,7 @@ public static async Task RunAsync() // Usual kernel initialization, with GPT 3.5 Turbo IKernel kernel = new KernelBuilder() .WithLogger(ConsoleLogger.Log) - .AddOpenAIChatCompletionService("gpt-3.5-turbo", Env.Var("OPENAI_API_KEY"), serviceId: "chat") + .WithOpenAIChatCompletionService("gpt-3.5-turbo", Env.Var("OPENAI_API_KEY"), serviceId: "chat") .Build(); // As an example, we import the time skill, which is used in system prompt to read the current date. diff --git a/samples/dotnet/kernel-syntax-examples/Example31_CustomPlanner.cs b/samples/dotnet/kernel-syntax-examples/Example31_CustomPlanner.cs index 6f871628bc8e..acc92564655b 100644 --- a/samples/dotnet/kernel-syntax-examples/Example31_CustomPlanner.cs +++ b/samples/dotnet/kernel-syntax-examples/Example31_CustomPlanner.cs @@ -120,11 +120,11 @@ private static IKernel InitializeKernel() { return new KernelBuilder() .WithLogger(ConsoleLogger.Log) - .AddAzureTextCompletionService( + .WithAzureTextCompletionService( Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), Env.Var("AZURE_OPENAI_ENDPOINT"), Env.Var("AZURE_OPENAI_KEY")) - .AddAzureTextEmbeddingGenerationService( + .WithAzureTextEmbeddingGenerationService( Env.Var("AZURE_OPENAI_EMBEDDINGS_DEPLOYMENT_NAME"), Env.Var("AZURE_OPENAI_EMBEDDINGS_ENDPOINT"), Env.Var("AZURE_OPENAI_EMBEDDINGS_KEY")) diff --git a/samples/dotnet/kernel-syntax-examples/Example32_StreamingCompletion.cs b/samples/dotnet/kernel-syntax-examples/Example32_StreamingCompletion.cs index 857a050e76a7..c6aaae7d6108 100644 --- a/samples/dotnet/kernel-syntax-examples/Example32_StreamingCompletion.cs +++ b/samples/dotnet/kernel-syntax-examples/Example32_StreamingCompletion.cs @@ -24,7 +24,7 @@ private static async Task AzureOpenAITextCompletionStreamAsync() IKernel kernel = new KernelBuilder() .WithLogger(ConsoleLogger.Log) - .AddAzureTextCompletionService( + .WithAzureTextCompletionService( Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), Env.Var("AZURE_OPENAI_ENDPOINT"), Env.Var("AZURE_OPENAI_KEY")) @@ -41,7 +41,7 @@ private static async Task OpenAITextCompletionStreamAsync() IKernel kernel = new KernelBuilder() .WithLogger(ConsoleLogger.Log) - .AddOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY"), serviceId: "text-davinci-003") + .WithOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY"), serviceId: "text-davinci-003") .Build(); ITextCompletion textCompletion = kernel.GetService(); From c8a9a706815f67969815d4657c3c46df72a8dcb8 Mon Sep 17 00:00:00 2001 From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> Date: Tue, 9 May 2023 17:04:11 +0100 Subject: [PATCH 04/15] Improvements + fixes (#880) ### Description This PR does following: 1. Fixes not compiling code that got broken after the merge with latest bits from the main branch. 2. Pasing the logger registered in kernel builder to all service registered by With* kernel extension methods. 3. Removes unnecessary logger parameter from signatures of all With* kernel extension methods. --- .../OpenAIKernelBuilderExtensions.cs | 60 +++++++------------ .../SemanticKernel.UnitTests/KernelTests.cs | 5 +- dotnet/src/SemanticKernel/KernelBuilder.cs | 18 +++--- .../Example16_CustomLLM.cs | 2 +- .../Example17_ChatGPT.cs | 14 ++--- .../Example20_HuggingFace.cs | 2 +- .../Example33_StreamingChat.cs | 19 +++--- .../Example34_CustomChatModel.cs | 19 +++--- 8 files changed, 60 insertions(+), 79 deletions(-) diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs index 614b1d119453..bbc4960b5e20 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs @@ -31,7 +31,6 @@ public static class OpenAIKernelBuilderExtensions /// A local identifier for the given AI service /// Whether the service should be the default for its type. /// Custom for HTTP requests. - /// Application logger /// Self instance public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder builder, string deploymentName, @@ -39,10 +38,9 @@ public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder bu string apiKey, string? serviceId = null, bool setAsDefault = false, - HttpClient? httpClient = null, - ILogger? logger = null) + HttpClient? httpClient = null) { - builder.WithAIService(serviceId, () => + builder.WithAIService(serviceId, (logger) => new AzureTextCompletion( deploymentName, endpoint, @@ -65,7 +63,6 @@ public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder bu /// A local identifier for the given AI service /// Whether the service should be the default for its type. /// Custom for HTTP requests. - /// Application logger /// Self instance public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder builder, string deploymentName, @@ -73,10 +70,9 @@ public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder bu TokenCredential credentials, string? serviceId = null, bool setAsDefault = false, - HttpClient? httpClient = null, - ILogger? logger = null) + HttpClient? httpClient = null) { - builder.WithAIService(serviceId, () => + builder.WithAIService(serviceId, (logger) => new AzureTextCompletion( deploymentName, endpoint, @@ -99,7 +95,6 @@ public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder bu /// A local identifier for the given AI service /// Whether the service should be the default for its type. /// Custom for HTTP requests. - /// Application logger /// Self instance public static KernelBuilder WithOpenAITextCompletionService(this KernelBuilder builder, string modelId, @@ -107,10 +102,9 @@ public static KernelBuilder WithOpenAITextCompletionService(this KernelBuilder b string? orgId = null, string? serviceId = null, bool setAsDefault = false, - HttpClient? httpClient = null, - ILogger? logger = null) + HttpClient? httpClient = null) { - builder.WithAIService(serviceId, () => + builder.WithAIService(serviceId, (logger) => new OpenAITextCompletion( modelId, apiKey, @@ -136,7 +130,6 @@ public static KernelBuilder WithOpenAITextCompletionService(this KernelBuilder b /// A local identifier for the given AI service /// Whether the service should be the default for its type. /// Custom for HTTP requests. - /// Application logger /// Self instance public static KernelBuilder WithAzureTextEmbeddingGenerationService(this KernelBuilder builder, string deploymentName, @@ -144,10 +137,9 @@ public static KernelBuilder WithAzureTextEmbeddingGenerationService(this KernelB string apiKey, string? serviceId = null, bool setAsDefault = false, - HttpClient? httpClient = null, - ILogger? logger = null) + HttpClient? httpClient = null) { - builder.WithAIService(serviceId, () => + builder.WithAIService(serviceId, (logger) => new AzureTextEmbeddingGeneration( deploymentName, endpoint, @@ -169,7 +161,6 @@ public static KernelBuilder WithAzureTextEmbeddingGenerationService(this KernelB /// Whether the service should be the default for its type. /// A local identifier for the given AI service /// Custom for HTTP requests. - /// Application logger /// Self instance public static KernelBuilder WithAzureTextEmbeddingGenerationService(this KernelBuilder builder, string deploymentName, @@ -177,10 +168,9 @@ public static KernelBuilder WithAzureTextEmbeddingGenerationService(this KernelB TokenCredential credential, string? serviceId = null, bool setAsDefault = false, - HttpClient? httpClient = null, - ILogger? logger = null) + HttpClient? httpClient = null) { - builder.WithAIService(serviceId, () => + builder.WithAIService(serviceId, (logger) => new AzureTextEmbeddingGeneration( deploymentName, endpoint, @@ -202,7 +192,6 @@ public static KernelBuilder WithAzureTextEmbeddingGenerationService(this KernelB /// Whether the service should be the default for its type. /// A local identifier for the given AI service /// Custom for HTTP requests. - /// Application logger /// Self instance public static KernelBuilder WithOpenAITextEmbeddingGenerationService(this KernelBuilder builder, string modelId, @@ -210,10 +199,9 @@ public static KernelBuilder WithOpenAITextEmbeddingGenerationService(this Kernel string? orgId = null, string? serviceId = null, bool setAsDefault = false, - HttpClient? httpClient = null, - ILogger? logger = null) + HttpClient? httpClient = null) { - builder.WithAIService(serviceId, () => + builder.WithAIService(serviceId, (logger) => new OpenAITextEmbeddingGeneration( modelId, apiKey, @@ -240,7 +228,6 @@ public static KernelBuilder WithOpenAITextEmbeddingGenerationService(this Kernel /// A local identifier for the given AI service /// Whether the service should be the default for its type. /// Custom for HTTP requests. - /// Application logger /// Self instance public static KernelBuilder WithAzureChatCompletionService(this KernelBuilder builder, string deploymentName, @@ -249,10 +236,9 @@ public static KernelBuilder WithAzureChatCompletionService(this KernelBuilder bu bool alsoAsTextCompletion = true, string? serviceId = null, bool setAsDefault = false, - HttpClient? httpClient = null, - ILogger? logger = null) + HttpClient? httpClient = null) { - AzureChatCompletion Factory() => new AzureChatCompletion( + AzureChatCompletion Factory(ILogger logger) => new AzureChatCompletion( deploymentName, endpoint, apiKey, @@ -282,7 +268,6 @@ public static KernelBuilder WithAzureChatCompletionService(this KernelBuilder bu /// A local identifier for the given AI service /// Whether the service should be the default for its type. /// Custom for HTTP requests. - /// Application logger /// Self instance public static KernelBuilder WithAzureChatCompletionService(this KernelBuilder builder, string deploymentName, @@ -291,10 +276,9 @@ public static KernelBuilder WithAzureChatCompletionService(this KernelBuilder bu bool alsoAsTextCompletion = true, string? serviceId = null, bool setAsDefault = false, - HttpClient? httpClient = null, - ILogger? logger = null) + HttpClient? httpClient = null) { - AzureChatCompletion Factory() => new AzureChatCompletion( + AzureChatCompletion Factory(ILogger logger) => new AzureChatCompletion( deploymentName, endpoint, credentials, @@ -324,7 +308,6 @@ public static KernelBuilder WithAzureChatCompletionService(this KernelBuilder bu /// A local identifier for the given AI service /// Whether the service should be the default for its type. /// Custom for HTTP requests. - /// Application logger /// Self instance public static KernelBuilder WithOpenAIChatCompletionService(this KernelBuilder builder, string modelId, @@ -333,10 +316,9 @@ public static KernelBuilder WithOpenAIChatCompletionService(this KernelBuilder b string? serviceId = null, bool alsoAsTextCompletion = true, bool setAsDefault = false, - HttpClient? httpClient = null, - ILogger? logger = null) + HttpClient? httpClient = null) { - OpenAIChatCompletion Factory() => new OpenAIChatCompletion( + OpenAIChatCompletion Factory(ILogger logger) => new OpenAIChatCompletion( modelId, apiKey, orgId, @@ -367,17 +349,15 @@ public static KernelBuilder WithOpenAIChatCompletionService(this KernelBuilder b /// A local identifier for the given AI service /// Whether the service should be the default for its type. /// Custom for HTTP requests. - /// Application logger /// Self instance public static KernelBuilder WithOpenAIImageGenerationService(this KernelBuilder builder, string apiKey, string? orgId = null, string? serviceId = null, bool setAsDefault = false, - HttpClient? httpClient = null, - ILogger? logger = null) + HttpClient? httpClient = null) { - builder.WithAIService(serviceId, () => + builder.WithAIService(serviceId, (logger) => new OpenAIImageGeneration( apiKey, orgId, diff --git a/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs b/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs index a831493f01f8..b9e57207e633 100644 --- a/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Orchestration; @@ -21,7 +22,7 @@ public class KernelTests public void ItProvidesAccessToFunctionsViaSkillCollection() { // Arrange - var factory = new Mock>(); + var factory = new Mock>(); var kernel = Kernel.Builder .WithDefaultAIService(factory.Object) .Build(); @@ -50,7 +51,7 @@ public void ItProvidesAccessToFunctionsViaSkillCollection() public async Task ItProvidesAccessToFunctionsViaSKContextAsync() { // Arrange - var factory = new Mock>(); + var factory = new Mock>(); var kernel = Kernel.Builder .WithAIService("x", factory.Object) .Build(); diff --git a/dotnet/src/SemanticKernel/KernelBuilder.cs b/dotnet/src/SemanticKernel/KernelBuilder.cs index dbb85c369fea..bc93be6cc6aa 100644 --- a/dotnet/src/SemanticKernel/KernelBuilder.cs +++ b/dotnet/src/SemanticKernel/KernelBuilder.cs @@ -21,7 +21,7 @@ public sealed class KernelBuilder { private KernelConfig _config = new(); private ISemanticTextMemory _memory = NullMemory.Instance; - private ILogger _log = NullLogger.Instance; + private ILogger _logger = NullLogger.Instance; private IMemoryStore? _memoryStorage = null; private IDelegatingHandlerFactory? _httpHandlerFactory = null; private readonly AIServiceCollection _aiServices = new(); @@ -38,12 +38,12 @@ public IKernel Build() } var instance = new Kernel( - new SkillCollection(this._log), + new SkillCollection(this._logger), this._aiServices.Build(), - new PromptTemplateEngine(this._log), + new PromptTemplateEngine(this._logger), this._memory, this._config, - this._log ?? NullLogger.Instance + this._logger ?? NullLogger.Instance ); // TODO: decouple this from 'UseMemory' kernel extension @@ -63,7 +63,7 @@ public IKernel Build() public KernelBuilder WithLogger(ILogger log) { Verify.NotNull(log); - this._log = log; + this._logger = log; return this; } @@ -171,9 +171,9 @@ public KernelBuilder WithAIService( /// Adds a factory method to the services collection /// /// The factory method that creates the AI service instances of type . - public KernelBuilder WithDefaultAIService(Func factory) where TService : IAIService + public KernelBuilder WithDefaultAIService(Func factory) where TService : IAIService { - this._aiServices.SetService(factory); + this._aiServices.SetService(() => factory(this._logger)); return this; } @@ -185,10 +185,10 @@ public KernelBuilder WithDefaultAIService(Func factory) wher /// Optional: set as the default AI service for type public KernelBuilder WithAIService( string? serviceId, - Func factory, + Func factory, bool setAsDefault = false) where TService : IAIService { - this._aiServices.SetService(serviceId, factory, setAsDefault); + this._aiServices.SetService(serviceId, () => factory(this._logger), setAsDefault); return this; } } diff --git a/samples/dotnet/kernel-syntax-examples/Example16_CustomLLM.cs b/samples/dotnet/kernel-syntax-examples/Example16_CustomLLM.cs index be86180126bb..adc988ba8b77 100644 --- a/samples/dotnet/kernel-syntax-examples/Example16_CustomLLM.cs +++ b/samples/dotnet/kernel-syntax-examples/Example16_CustomLLM.cs @@ -79,7 +79,7 @@ private static async Task CustomTextCompletionWithSKFunctionAsync() // Add your text completion service as a singleton instance .WithAIService("myService1", new MyTextCompletionService()) // Add your text completion service as a factory method - .WithAIService("myService2", () => new MyTextCompletionService()) + .WithAIService("myService2", (_) => new MyTextCompletionService()) .Build(); const string FunctionDefinition = "Does the text contain grammar errors (Y/N)? Text: {{$input}}"; diff --git a/samples/dotnet/kernel-syntax-examples/Example17_ChatGPT.cs b/samples/dotnet/kernel-syntax-examples/Example17_ChatGPT.cs index f908ebf3007b..978a97e4d892 100644 --- a/samples/dotnet/kernel-syntax-examples/Example17_ChatGPT.cs +++ b/samples/dotnet/kernel-syntax-examples/Example17_ChatGPT.cs @@ -67,13 +67,13 @@ private static async Task AzureOpenAIChatSampleAsync() { Console.WriteLine("======== Azure Open AI - ChatGPT ========"); - IKernel kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); - - // Add your chat completion service - kernel.Config.AddAzureChatCompletionService( - Env.Var("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"), - Env.Var("AZURE_OPENAI_ENDPOINT"), - Env.Var("AZURE_OPENAI_KEY")); + IKernel kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + .WithAzureChatCompletionService( + Env.Var("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"), + Env.Var("AZURE_OPENAI_ENDPOINT"), + Env.Var("AZURE_OPENAI_KEY")) // Add your chat completion service + .Build(); IChatCompletion chatGPT = kernel.GetService(); diff --git a/samples/dotnet/kernel-syntax-examples/Example20_HuggingFace.cs b/samples/dotnet/kernel-syntax-examples/Example20_HuggingFace.cs index 2128d8be65b5..5229f686f8ab 100644 --- a/samples/dotnet/kernel-syntax-examples/Example20_HuggingFace.cs +++ b/samples/dotnet/kernel-syntax-examples/Example20_HuggingFace.cs @@ -20,7 +20,7 @@ public static async Task RunAsync() IKernel kernel = new KernelBuilder() .WithLogger(ConsoleLogger.Log) // Add HuggingFace text completion service as a factory methods - .WithDefaultAIService(() => new HuggingFaceTextCompletion(Env.Var("HF_API_KEY"), "gpt2")) + .WithDefaultAIService((_) => new HuggingFaceTextCompletion(Env.Var("HF_API_KEY"), "gpt2")) .Build(); const string FunctionDefinition = "Question: {{$input}}; Answer:"; diff --git a/samples/dotnet/kernel-syntax-examples/Example33_StreamingChat.cs b/samples/dotnet/kernel-syntax-examples/Example33_StreamingChat.cs index 3cc6ca815413..04ae36c34071 100644 --- a/samples/dotnet/kernel-syntax-examples/Example33_StreamingChat.cs +++ b/samples/dotnet/kernel-syntax-examples/Example33_StreamingChat.cs @@ -24,10 +24,10 @@ private static async Task OpenAIChatStreamSampleAsync() { Console.WriteLine("======== Open AI - ChatGPT Streaming ========"); - IKernel kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); - - // Add your chat completion service - kernel.Config.AddOpenAIChatCompletionService("gpt-3.5-turbo", Env.Var("OPENAI_API_KEY")); + IKernel kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + .WithOpenAIChatCompletionService("gpt-3.5-turbo", Env.Var("OPENAI_API_KEY")) // Add your chat completion service + .Build(); IChatCompletion chatGPT = kernel.GetService(); @@ -38,13 +38,10 @@ private static async Task AzureOpenAIChatStreamSampleAsync() { Console.WriteLine("======== Azure Open AI - ChatGPT Streaming ========"); - IKernel kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); - - // Add your chat completion service - kernel.Config.AddAzureChatCompletionService( - Env.Var("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"), - Env.Var("AZURE_OPENAI_ENDPOINT"), - Env.Var("AZURE_OPENAI_KEY")); + IKernel kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + .WithAzureChatCompletionService(Env.Var("AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"), Env.Var("AZURE_OPENAI_ENDPOINT"), Env.Var("OPENAI_API_KEY")) // Add your chat completion service + .Build(); IChatCompletion chatGPT = kernel.GetService(); diff --git a/samples/dotnet/kernel-syntax-examples/Example34_CustomChatModel.cs b/samples/dotnet/kernel-syntax-examples/Example34_CustomChatModel.cs index 1d0d0f25a9a9..ab41bd8adb4b 100644 --- a/samples/dotnet/kernel-syntax-examples/Example34_CustomChatModel.cs +++ b/samples/dotnet/kernel-syntax-examples/Example34_CustomChatModel.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI.ChatCompletion; using RepoUtils; @@ -81,11 +82,12 @@ private static async Task CustomChatSampleAsync() { Console.WriteLine("======== Custom LLM - Chat Completion ========"); - IKernel kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); + IChatCompletion Factory(ILogger l) => new MyChatCompletionService(); - IChatCompletion Factory(IKernel k) => new MyChatCompletionService(); - // Add your chat completion service - kernel.Config.AddChatCompletionService(Factory); + IKernel kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + .WithDefaultAIService(Factory) + .Build(); IChatCompletion customChat = kernel.GetService(); @@ -113,11 +115,12 @@ private static async Task CustomChatStreamSampleAsync() { Console.WriteLine("======== Custom LLM - Chat Completion Streaming ========"); - IKernel kernel = new KernelBuilder().WithLogger(ConsoleLogger.Log).Build(); + IChatCompletion Factory(ILogger l) => new MyChatCompletionService(); - IChatCompletion Factory(IKernel k) => new MyChatCompletionService(); - // Add your chat completion service - kernel.Config.AddChatCompletionService(Factory); + IKernel kernel = new KernelBuilder() + .WithLogger(ConsoleLogger.Log) + .WithDefaultAIService(Factory) + .Build(); IChatCompletion customChat = kernel.GetService(); From 1a284550b8ba457060a5a9b70cc8e2935452b2a6 Mon Sep 17 00:00:00 2001 From: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com> Date: Sat, 13 May 2023 03:26:57 +0100 Subject: [PATCH 05/15] A few examples of using SK SDK in DI/IoC container (#964) This PR adds a few examples showing the way SK SDK can be used by applications that use DI/IoC containers for bootstrapping. --------- Co-authored-by: Shawn Callegari <36091529+shawncal@users.noreply.github.com> --- dotnet/src/SemanticKernel/KernelBuilder.cs | 17 ++- .../Services/AIServiceProvider.cs | 2 +- .../Services/NamedServiceProvider.cs | 4 +- .../webapi/SemanticKernelExtensions.cs | 71 +++++----- .../Example35_DIContainer.cs | 130 ++++++++++++++++++ .../KernelSyntaxExamples.csproj | 1 + .../dotnet/kernel-syntax-examples/Program.cs | 3 + 7 files changed, 189 insertions(+), 39 deletions(-) create mode 100644 samples/dotnet/kernel-syntax-examples/Example35_DIContainer.cs diff --git a/dotnet/src/SemanticKernel/KernelBuilder.cs b/dotnet/src/SemanticKernel/KernelBuilder.cs index bc93be6cc6aa..82de9f9e7778 100644 --- a/dotnet/src/SemanticKernel/KernelBuilder.cs +++ b/dotnet/src/SemanticKernel/KernelBuilder.cs @@ -24,6 +24,7 @@ public sealed class KernelBuilder private ILogger _logger = NullLogger.Instance; private IMemoryStore? _memoryStorage = null; private IDelegatingHandlerFactory? _httpHandlerFactory = null; + private IPromptTemplateEngine? _promptTemplateEngine; private readonly AIServiceCollection _aiServices = new(); /// @@ -40,10 +41,10 @@ public IKernel Build() var instance = new Kernel( new SkillCollection(this._logger), this._aiServices.Build(), - new PromptTemplateEngine(this._logger), + this._promptTemplateEngine ?? new PromptTemplateEngine(this._logger), this._memory, this._config, - this._logger ?? NullLogger.Instance + this._logger ); // TODO: decouple this from 'UseMemory' kernel extension @@ -91,6 +92,18 @@ public KernelBuilder WithMemoryStorage(IMemoryStore storage) return this; } + /// + /// Add prompt template engine to the kernel to be built. + /// + /// Prompt template engine to add. + /// Updated kernel builder including the prompt template engine. + public KernelBuilder WithPromptTemplateEngine(IPromptTemplateEngine promptTemplateEngine) + { + Verify.NotNull(promptTemplateEngine); + this._promptTemplateEngine = promptTemplateEngine; + return this; + } + /// /// Add memory storage and an embedding generator to the kernel to be built. /// diff --git a/dotnet/src/SemanticKernel/Services/AIServiceProvider.cs b/dotnet/src/SemanticKernel/Services/AIServiceProvider.cs index 13c98bd26e22..cf5d9f0df8f3 100644 --- a/dotnet/src/SemanticKernel/Services/AIServiceProvider.cs +++ b/dotnet/src/SemanticKernel/Services/AIServiceProvider.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace Microsoft.SemanticKernel.Services; -internal class AIServiceProvider : NamedServiceProvider, IAIServiceProvider +public class AIServiceProvider : NamedServiceProvider, IAIServiceProvider { public AIServiceProvider(Dictionary>> services, Dictionary defaultIds) : base(services, defaultIds) diff --git a/dotnet/src/SemanticKernel/Services/NamedServiceProvider.cs b/dotnet/src/SemanticKernel/Services/NamedServiceProvider.cs index 19373e13ab1e..6899bbf997b1 100644 --- a/dotnet/src/SemanticKernel/Services/NamedServiceProvider.cs +++ b/dotnet/src/SemanticKernel/Services/NamedServiceProvider.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; namespace Microsoft.SemanticKernel.Services; -internal class NamedServiceProvider : INamedServiceProvider +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 = new(); @@ -13,7 +13,7 @@ internal class NamedServiceProvider : INamedServiceProvider // A dictionary that maps a service type to the name of the default service private readonly Dictionary _defaultIds; - internal NamedServiceProvider( + public NamedServiceProvider( Dictionary>> services, Dictionary defaultIds) { diff --git a/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs b/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs index c1139deb4210..5c4deab44827 100644 --- a/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs +++ b/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs @@ -68,16 +68,27 @@ internal static IServiceCollection AddSemanticKernelServices(this IServiceCollec // Add the planner. services.AddScoped(serviceProvider => { - return new CopilotChatPlanner( - serviceProvider.GetRequiredService(), - serviceProvider.GetRequiredService>()); + // Create a kernel for the planner with the same contexts as the chat's kernel except with no skills and its own completion backend. + // This allows the planner to use only the skills that are available at call time. + IKernel chatKernel = serviceProvider.GetRequiredService(); + + IOptions plannerOptions = serviceProvider.GetRequiredService>(); + + IKernel plannerKernel = Kernel.Builder + .WithLogger(serviceProvider.GetRequiredService>()) + .WithCompletionBackend(plannerOptions.Value.AIService!) + .WithPromptTemplateEngine(chatKernel.PromptTemplateEngine) + .WithMemory(chatKernel.Memory) + .Build(); + + return new CopilotChatPlanner(plannerKernel, plannerOptions); }); // Add the Semantic Kernel services.AddScoped(serviceProvider => new KernelBuilder() - .AddCompletionBackend(serviceProvider.GetRequiredService>()) - .AddEmbeddingBackend(serviceProvider.GetRequiredService>()) + .WithCompletionBackend(serviceProvider.GetRequiredService>().Get(AIServiceOptions.CompletionPropertyName)) + .WithEmbeddingBackend(serviceProvider.GetRequiredService>().Get(AIServiceOptions.EmbeddingPropertyName)) .WithMemory(serviceProvider.GetRequiredService()) .Build()); @@ -85,64 +96,56 @@ internal static IServiceCollection AddSemanticKernelServices(this IServiceCollec } /// - /// Add the completion backend to the kernel builder. + /// Add the completion backend to the kernel config /// - internal static KernelBuilder AddCompletionBackend(this KernelBuilder kernelBuilder, IOptionsSnapshot aiServiceOptions) + private static KernelBuilder WithCompletionBackend(this KernelBuilder kernelBuilder, AIServiceOptions aiServiceOptions) { - AIServiceOptions config = aiServiceOptions.Get(AIServiceOptions.CompletionPropertyName); - - switch (config.AIService) + switch (aiServiceOptions.AIService) { case AIServiceOptions.AIServiceType.AzureOpenAI: kernelBuilder.WithAzureChatCompletionService( - serviceId: config.Label, - deploymentName: config.DeploymentOrModelId, - endpoint: config.Endpoint, - apiKey: config.Key, - alsoAsTextCompletion: true); + deploymentName: aiServiceOptions.DeploymentOrModelId, + endpoint: aiServiceOptions.Endpoint, + apiKey: aiServiceOptions.Key); break; case AIServiceOptions.AIServiceType.OpenAI: kernelBuilder.WithOpenAIChatCompletionService( - serviceId: config.Label, - modelId: config.DeploymentOrModelId, - apiKey: config.Key, - alsoAsTextCompletion: true); + modelId: aiServiceOptions.DeploymentOrModelId, + apiKey: aiServiceOptions.Key); break; default: - throw new ArgumentException($"Invalid {nameof(config.AIService)} value in '{AIServiceOptions.CompletionPropertyName}' settings."); + throw new ArgumentException($"Invalid {nameof(aiServiceOptions.AIService)} value in '{AIServiceOptions.CompletionPropertyName}' settings."); } return kernelBuilder; } /// - /// Add the embedding backend to the kernel builder. + /// Add the embedding backend to the kernel config /// - internal static KernelBuilder AddEmbeddingBackend(this KernelBuilder kernelBuilder, IOptionsSnapshot aiServiceOptions) + private static KernelBuilder WithEmbeddingBackend(this KernelBuilder kernelBuilder, AIServiceOptions aiServiceOptions) { - AIServiceOptions config = aiServiceOptions.Get(AIServiceOptions.EmbeddingPropertyName); - - switch (config.AIService) + switch (aiServiceOptions.AIService) { case AIServiceOptions.AIServiceType.AzureOpenAI: kernelBuilder.WithAzureTextEmbeddingGenerationService( - serviceId: config.Label, - deploymentName: config.DeploymentOrModelId, - endpoint: config.Endpoint, - apiKey: config.Key); + deploymentName: aiServiceOptions.DeploymentOrModelId, + endpoint: aiServiceOptions.Endpoint, + apiKey: aiServiceOptions.Key, + serviceId: aiServiceOptions.Label); break; case AIServiceOptions.AIServiceType.OpenAI: kernelBuilder.WithOpenAITextEmbeddingGenerationService( - serviceId: config.Label, - modelId: config.DeploymentOrModelId, - apiKey: config.Key); + modelId: aiServiceOptions.DeploymentOrModelId, + apiKey: aiServiceOptions.Key, + serviceId: aiServiceOptions.Label); break; default: - throw new ArgumentException($"Invalid {nameof(config.AIService)} value in '{AIServiceOptions.EmbeddingPropertyName}' settings."); + throw new ArgumentException($"Invalid {nameof(aiServiceOptions.AIService)} value in '{AIServiceOptions.EmbeddingPropertyName}' settings."); } return kernelBuilder; @@ -154,7 +157,7 @@ internal static KernelBuilder AddEmbeddingBackend(this KernelBuilder kernelBuild /// The service configuration /// Custom for HTTP requests. /// Application logger - internal static ITextEmbeddingGeneration ToTextEmbeddingsService(this AIServiceOptions serviceConfig, + private static ITextEmbeddingGeneration ToTextEmbeddingsService(this AIServiceOptions serviceConfig, HttpClient? httpClient = null, ILogger? logger = null) { diff --git a/samples/dotnet/kernel-syntax-examples/Example35_DIContainer.cs b/samples/dotnet/kernel-syntax-examples/Example35_DIContainer.cs new file mode 100644 index 000000000000..74ee311db550 --- /dev/null +++ b/samples/dotnet/kernel-syntax-examples/Example35_DIContainer.cs @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Threading.Tasks; +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.Memory; +using Microsoft.SemanticKernel.Services; +using Microsoft.SemanticKernel.SkillDefinition; +using Microsoft.SemanticKernel.TemplateEngine; +using RepoUtils; + +/** + * The following examples show how to use SK SDK in applications using DI/IoC containers. + */ +public static class Example35_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.Log); + + //Registering Kernel + collection.AddTransient((serviceProvider) => + { + return Kernel.Builder + .WithLogger(serviceProvider.GetRequiredService()) + .WithOpenAITextCompletionService("text-davinci-002", Env.Var("OPENAI_API_KEY")) + .Build(); + }); + + //Registering class that uses Kernel to execute a skill + 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("text-davinci-002", Env.Var("OPENAI_API_KEY"))); + + //Registering Kernel dependencies + var collection = new ServiceCollection(); + collection.AddTransient((_) => ConsoleLogger.Log); + collection.AddTransient(); + collection.AddTransient(); + collection.AddTransient(); + 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(); + + //Registering class that uses Kernel to execute a skill + 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?"); + } + + /// + /// Class that uses/references Kernel. + /// +#pragma warning disable CA1812 // Avoid uninstantiated internal classes + private sealed class KernelClient +#pragma warning restore CA1812 // Avoid uninstantiated internal classes + { + private readonly IKernel _kernel; + private readonly ILogger _logger; + + public KernelClient(IKernel kernel, ILogger logger) + { + this._kernel = kernel; + this._logger = logger; + } + + public async Task SummarizeAsync(string ask) + { + string folder = RepoFiles.SampleSkillsPath(); + + var sumSkill = this._kernel.ImportSemanticSkillFromDirectory(folder, "SummarizeSkill"); + + var result = await this._kernel.RunAsync(ask, sumSkill["Summarize"]); + + this._logger.LogWarning("Result - {0}", result); + } + } +} diff --git a/samples/dotnet/kernel-syntax-examples/KernelSyntaxExamples.csproj b/samples/dotnet/kernel-syntax-examples/KernelSyntaxExamples.csproj index 24ffa6daa124..5a0df2fb869d 100644 --- a/samples/dotnet/kernel-syntax-examples/KernelSyntaxExamples.csproj +++ b/samples/dotnet/kernel-syntax-examples/KernelSyntaxExamples.csproj @@ -20,6 +20,7 @@ + diff --git a/samples/dotnet/kernel-syntax-examples/Program.cs b/samples/dotnet/kernel-syntax-examples/Program.cs index a2e64eefdb30..17e6f6a960a7 100644 --- a/samples/dotnet/kernel-syntax-examples/Program.cs +++ b/samples/dotnet/kernel-syntax-examples/Program.cs @@ -109,5 +109,8 @@ public static async Task Main() await Example34_CustomChatModel.RunAsync(); Console.WriteLine("== DONE =="); + + await Example35_DIContainer.RunAsync(); + Console.WriteLine("== DONE =="); } } From ea7db57920dece3bdd0d1f23cc10e6f573c7fe1c Mon Sep 17 00:00:00 2001 From: SergeyMenshykh Date: Mon, 15 May 2023 18:03:08 +0100 Subject: [PATCH 06/15] Copilot chat changes are rolled back. --- .../webapi/SemanticKernelExtensions.cs | 35 +++++++++++-------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs b/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs index fd5bf875a706..157746ceb261 100644 --- a/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs +++ b/samples/apps/copilot-chat-app/webapi/SemanticKernelExtensions.cs @@ -1,6 +1,5 @@ // Copyright (c) Microsoft. All rights reserved. -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI.Embeddings; @@ -43,8 +42,6 @@ internal static IServiceCollection AddSemanticKernelServices(this IServiceCollec .WithLogger(sp.GetRequiredService>()) .WithMemory(sp.GetRequiredService()) .WithConfiguration(sp.GetRequiredService()) - .WithCompletionBackend(sp.GetRequiredService>().Get(AIServiceOptions.CompletionPropertyName)) - .WithEmbeddingBackend(sp.GetRequiredService>().Get(AIServiceOptions.EmbeddingPropertyName)) .Build(); sp.GetRequiredService()(sp, kernel); @@ -54,13 +51,21 @@ internal static IServiceCollection AddSemanticKernelServices(this IServiceCollec // Semantic memory services.AddSemanticTextMemory(); + // AI backends + services.AddScoped(serviceProvider => new KernelConfig() + .AddCompletionBackend(serviceProvider.GetRequiredService>() + .Get(AIServiceOptions.CompletionPropertyName)) + .AddEmbeddingBackend(serviceProvider.GetRequiredService>() + .Get(AIServiceOptions.EmbeddingPropertyName))); + // Planner (AI plugins) support IOptions? plannerOptions = services.BuildServiceProvider().GetService>(); if (plannerOptions != null && plannerOptions.Value.Enabled) { services.AddScoped(sp => new CopilotChatPlanner(Kernel.Builder .WithLogger(sp.GetRequiredService>()) - .WithCompletionBackend(sp.GetRequiredService>().Value.AIService!) // TODO verify planner has AI service configured + .WithConfiguration( + new KernelConfig().AddCompletionBackend(sp.GetRequiredService>().Value.AIService!)) // TODO verify planner has AI service configured .Build())); } @@ -164,39 +169,39 @@ private static void AddSemanticTextMemory(this IServiceCollection services) /// /// Add the completion backend to the kernel config /// - private static KernelBuilder WithCompletionBackend(this KernelBuilder kernelBuilder, AIServiceOptions aiServiceOptions) + private static KernelConfig AddCompletionBackend(this KernelConfig kernelConfig, AIServiceOptions aiServiceOptions) { switch (aiServiceOptions.AIService) { case AIServiceOptions.AIServiceType.AzureOpenAI: - kernelBuilder.WithAzureChatCompletionService( + kernelConfig.AddAzureChatCompletionService( deploymentName: aiServiceOptions.DeploymentOrModelId, endpoint: aiServiceOptions.Endpoint, apiKey: aiServiceOptions.Key); break; case AIServiceOptions.AIServiceType.OpenAI: - kernelBuilder.WithOpenAIChatCompletionService( - modelId: aiServiceOptions.DeploymentOrModelId, - apiKey: aiServiceOptions.Key); + kernelConfig.AddOpenAIChatCompletionService( + modelId: aiServiceOptions.DeploymentOrModelId, + apiKey: aiServiceOptions.Key); break; default: throw new ArgumentException($"Invalid {nameof(aiServiceOptions.AIService)} value in '{AIServiceOptions.CompletionPropertyName}' settings."); } - return kernelBuilder; + return kernelConfig; } /// /// Add the embedding backend to the kernel config /// - private static KernelBuilder WithEmbeddingBackend(this KernelBuilder kernelBuilder, AIServiceOptions aiServiceOptions) + private static KernelConfig AddEmbeddingBackend(this KernelConfig kernelConfig, AIServiceOptions aiServiceOptions) { switch (aiServiceOptions.AIService) { case AIServiceOptions.AIServiceType.AzureOpenAI: - kernelBuilder.WithAzureTextEmbeddingGenerationService( + kernelConfig.AddAzureTextEmbeddingGenerationService( deploymentName: aiServiceOptions.DeploymentOrModelId, endpoint: aiServiceOptions.Endpoint, apiKey: aiServiceOptions.Key, @@ -204,7 +209,7 @@ private static KernelBuilder WithEmbeddingBackend(this KernelBuilder kernelBuild break; case AIServiceOptions.AIServiceType.OpenAI: - kernelBuilder.WithOpenAITextEmbeddingGenerationService( + kernelConfig.AddOpenAITextEmbeddingGenerationService( modelId: aiServiceOptions.DeploymentOrModelId, apiKey: aiServiceOptions.Key, serviceId: aiServiceOptions.Label); @@ -214,7 +219,7 @@ private static KernelBuilder WithEmbeddingBackend(this KernelBuilder kernelBuild throw new ArgumentException($"Invalid {nameof(aiServiceOptions.AIService)} value in '{AIServiceOptions.EmbeddingPropertyName}' settings."); } - return kernelBuilder; + return kernelConfig; } /// @@ -223,7 +228,7 @@ private static KernelBuilder WithEmbeddingBackend(this KernelBuilder kernelBuild /// The service configuration /// Custom for HTTP requests. /// Application logger - private static ITextEmbeddingGeneration ToTextEmbeddingsService(this AIServiceOptions serviceConfig, + private static IEmbeddingGeneration ToTextEmbeddingsService(this AIServiceOptions serviceConfig, HttpClient? httpClient = null, ILogger? logger = null) { From ad8c89f1a1611ccc523ca6a26b115d3df33a4ca0 Mon Sep 17 00:00:00 2001 From: SergeyMenshykh Date: Mon, 15 May 2023 19:44:58 +0100 Subject: [PATCH 07/15] Rolling back the change if change helps to fix failing test --- .../src/SemanticKernel.UnitTests/CoreSkills/HttpSkillTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/src/SemanticKernel.UnitTests/CoreSkills/HttpSkillTests.cs b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/HttpSkillTests.cs index def6de00526c..39106935e76a 100644 --- a/dotnet/src/SemanticKernel.UnitTests/CoreSkills/HttpSkillTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/HttpSkillTests.cs @@ -37,7 +37,7 @@ public void ItCanBeInstantiated() public void ItCanBeImported() { // Arrange - var kernel = Kernel.Builder.Build(); + var kernel = KernelBuilder.Create(); using var skill = new HttpSkill(); // Act - Assert no exception occurs e.g. due to reflection From ab03c6b96c9ed0aec40db58aea6ac9381dee54eb Mon Sep 17 00:00:00 2001 From: SergeyMenshykh Date: Mon, 15 May 2023 20:57:51 +0100 Subject: [PATCH 08/15] Code doing unnecessary attempts to resolve the AI service during the function's disposal is fixed. --- .../src/SemanticKernel/SkillDefinition/SKFunction.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/dotnet/src/SemanticKernel/SkillDefinition/SKFunction.cs b/dotnet/src/SemanticKernel/SkillDefinition/SKFunction.cs index c9231a2e3188..c9d3c6b88f76 100644 --- a/dotnet/src/SemanticKernel/SkillDefinition/SKFunction.cs +++ b/dotnet/src/SemanticKernel/SkillDefinition/SKFunction.cs @@ -325,9 +325,15 @@ internal SKFunction( private void ReleaseUnmanagedResources() { - if (this._aiService?.Value is not IDisposable disposable) { return; } + if (this._aiService == null || !this._aiService.IsValueCreated) + { + return; + } - disposable.Dispose(); + if (this._aiService.Value is IDisposable disposable) + { + disposable.Dispose(); + } } /// From b37388c30540dca75321ca3ec500e38181aecbaa Mon Sep 17 00:00:00 2001 From: SergeyMenshykh Date: Tue, 16 May 2023 10:55:42 +0100 Subject: [PATCH 09/15] Addressing comments --- .../OpenAIKernelBuilderExtensions.cs | 68 +++++++++++-------- .../OpenAI/AIServicesOpenAIExtensionsTests.cs | 27 +++++++- .../SemanticKernel.UnitTests/KernelTests.cs | 2 +- dotnet/src/SemanticKernel/KernelBuilder.cs | 4 +- 4 files changed, 67 insertions(+), 34 deletions(-) diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs index bbc4960b5e20..d3bfba133c16 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs @@ -10,6 +10,7 @@ using Microsoft.SemanticKernel.Connectors.AI.OpenAI.ImageGeneration; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; +using Microsoft.SemanticKernel.Reliability; #pragma warning disable IDE0130 // ReSharper disable once CheckNamespace - Using NS of KernelConfig @@ -40,13 +41,13 @@ public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder bu bool setAsDefault = false, HttpClient? httpClient = null) { - builder.WithAIService(serviceId, (logger) => + builder.WithAIService(serviceId, (parameters) => new AzureTextCompletion( deploymentName, endpoint, apiKey, - httpClient, - logger), + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.Logger), setAsDefault); return builder; @@ -72,13 +73,13 @@ public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder bu bool setAsDefault = false, HttpClient? httpClient = null) { - builder.WithAIService(serviceId, (logger) => + builder.WithAIService(serviceId, (parameters) => new AzureTextCompletion( deploymentName, endpoint, credentials, - httpClient, - logger), + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.Logger), setAsDefault); return builder; @@ -104,13 +105,13 @@ public static KernelBuilder WithOpenAITextCompletionService(this KernelBuilder b bool setAsDefault = false, HttpClient? httpClient = null) { - builder.WithAIService(serviceId, (logger) => + builder.WithAIService(serviceId, (parameters) => new OpenAITextCompletion( modelId, apiKey, orgId, - httpClient, - logger), + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.Logger), setAsDefault); return builder; } @@ -139,13 +140,13 @@ public static KernelBuilder WithAzureTextEmbeddingGenerationService(this KernelB bool setAsDefault = false, HttpClient? httpClient = null) { - builder.WithAIService(serviceId, (logger) => + builder.WithAIService(serviceId, (parameters) => new AzureTextEmbeddingGeneration( deploymentName, endpoint, apiKey, - httpClient, - logger), + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.Logger), setAsDefault); return builder; } @@ -170,13 +171,13 @@ public static KernelBuilder WithAzureTextEmbeddingGenerationService(this KernelB bool setAsDefault = false, HttpClient? httpClient = null) { - builder.WithAIService(serviceId, (logger) => + builder.WithAIService(serviceId, (parameters) => new AzureTextEmbeddingGeneration( deploymentName, endpoint, credential, - httpClient, - logger), + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.Logger), setAsDefault); return builder; } @@ -201,13 +202,13 @@ public static KernelBuilder WithOpenAITextEmbeddingGenerationService(this Kernel bool setAsDefault = false, HttpClient? httpClient = null) { - builder.WithAIService(serviceId, (logger) => + builder.WithAIService(serviceId, (parameters) => new OpenAITextEmbeddingGeneration( modelId, apiKey, orgId, - httpClient, - logger), + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.Logger), setAsDefault); return builder; } @@ -238,12 +239,12 @@ public static KernelBuilder WithAzureChatCompletionService(this KernelBuilder bu bool setAsDefault = false, HttpClient? httpClient = null) { - AzureChatCompletion Factory(ILogger logger) => new AzureChatCompletion( + AzureChatCompletion Factory((ILogger Logger, KernelConfig Config) parameters) => new AzureChatCompletion( deploymentName, endpoint, apiKey, - httpClient, - logger); + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.Logger); builder.WithAIService(serviceId, Factory, setAsDefault); @@ -278,12 +279,12 @@ public static KernelBuilder WithAzureChatCompletionService(this KernelBuilder bu bool setAsDefault = false, HttpClient? httpClient = null) { - AzureChatCompletion Factory(ILogger logger) => new AzureChatCompletion( + AzureChatCompletion Factory((ILogger Logger, KernelConfig Config) parameters) => new AzureChatCompletion( deploymentName, endpoint, credentials, - httpClient, - logger); + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.Logger); builder.WithAIService(serviceId, Factory, setAsDefault); @@ -318,12 +319,12 @@ public static KernelBuilder WithOpenAIChatCompletionService(this KernelBuilder b bool setAsDefault = false, HttpClient? httpClient = null) { - OpenAIChatCompletion Factory(ILogger logger) => new OpenAIChatCompletion( + OpenAIChatCompletion Factory((ILogger Logger, KernelConfig Config) parameters) => new OpenAIChatCompletion( modelId, apiKey, orgId, - httpClient, - logger); + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.Logger); builder.WithAIService(serviceId, Factory, setAsDefault); @@ -336,6 +337,13 @@ public static KernelBuilder WithOpenAIChatCompletionService(this KernelBuilder b return builder; } + private static HttpClient CreateHttpClient(this IDelegatingHandlerFactory handlerFactory, ILogger? logger) + { + var retryHandler = handlerFactory.Create(logger); + retryHandler.InnerHandler = new HttpClientHandler { CheckCertificateRevocationList = true }; + return new HttpClient(retryHandler); + } + #endregion #region Images @@ -357,12 +365,12 @@ public static KernelBuilder WithOpenAIImageGenerationService(this KernelBuilder bool setAsDefault = false, HttpClient? httpClient = null) { - builder.WithAIService(serviceId, (logger) => + builder.WithAIService(serviceId, ((ILogger Logger, KernelConfig Config) parameters) => new OpenAIImageGeneration( apiKey, orgId, - httpClient, - logger), + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.Logger), setAsDefault); return builder; diff --git a/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs index 12fcdf952c14..36664cef1dd7 100644 --- a/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs +++ b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs @@ -3,6 +3,7 @@ using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI.Embeddings; using Microsoft.SemanticKernel.AI.TextCompletion; +using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion; using Xunit; namespace SemanticKernel.Connectors.UnitTests.OpenAI; @@ -50,11 +51,35 @@ public void ItCanOverwriteServices() // Act - Assert no exception occurs targetBuilder.WithAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); targetBuilder.WithAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); - targetBuilder.WithOpenAITextCompletionService("model", "key, serviceId: \"one\""); + + targetBuilder.WithOpenAITextCompletionService("model", "key", serviceId: "one"); targetBuilder.WithOpenAITextCompletionService("model", "key", serviceId: "one"); + targetBuilder.WithAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "one"); targetBuilder.WithAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "one"); + targetBuilder.WithOpenAITextEmbeddingGenerationService("model", "key", serviceId: "one"); targetBuilder.WithOpenAITextEmbeddingGenerationService("model", "key", serviceId: "one"); + + targetBuilder.WithAzureChatCompletionService("dep", "https://localhost", "key", serviceId: "one"); + targetBuilder.WithAzureChatCompletionService("dep", "https://localhost", "key", serviceId: "one"); + + targetBuilder.WithOpenAIChatCompletionService("model", "key", serviceId: "one"); + targetBuilder.WithOpenAIChatCompletionService("model", "key", serviceId: "one"); + + targetBuilder.WithOpenAIImageGenerationService("model", "key", serviceId: "one"); + targetBuilder.WithOpenAIImageGenerationService("model", "key", serviceId: "one"); + + targetBuilder.WithDefaultAIService(new OpenAITextCompletion("model", "key")); + targetBuilder.WithDefaultAIService(new OpenAITextCompletion("model", "key")); + + targetBuilder.WithDefaultAIService((_) => new OpenAITextCompletion("model", "key")); + targetBuilder.WithDefaultAIService((_) => new OpenAITextCompletion("model", "key")); + + targetBuilder.WithAIService("one", new OpenAITextCompletion("model", "key")); + targetBuilder.WithAIService("one", new OpenAITextCompletion("model", "key")); + + targetBuilder.WithAIService("one", (_) => new OpenAITextCompletion("model", "key")); + targetBuilder.WithAIService("one", (_) => new OpenAITextCompletion("model", "key")); } } diff --git a/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs b/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs index 71332f0ce9b5..033ea442bf8d 100644 --- a/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs @@ -51,7 +51,7 @@ public void ItProvidesAccessToFunctionsViaSkillCollection() public async Task ItProvidesAccessToFunctionsViaSKContextAsync() { // Arrange - var factory = new Mock>(); + var factory = new Mock>(); var kernel = Kernel.Builder .WithAIService("x", factory.Object) .Build(); diff --git a/dotnet/src/SemanticKernel/KernelBuilder.cs b/dotnet/src/SemanticKernel/KernelBuilder.cs index 89e5a1573be5..352b8a6f3296 100644 --- a/dotnet/src/SemanticKernel/KernelBuilder.cs +++ b/dotnet/src/SemanticKernel/KernelBuilder.cs @@ -208,10 +208,10 @@ public KernelBuilder WithDefaultAIService(Func fact /// Optional: set as the default AI service for type public KernelBuilder WithAIService( string? serviceId, - Func factory, + Func<(ILogger Logger, KernelConfig Config), TService> factory, bool setAsDefault = false) where TService : IAIService { - this._aiServices.SetService(serviceId, () => factory(this._logger), setAsDefault); + this._aiServices.SetService(serviceId, () => factory((this._logger, this._config)), setAsDefault); return this; } } From e4a5bf2eda1931dda2a3302716a01fa24b59420f Mon Sep 17 00:00:00 2001 From: SergeyMenshykh Date: Fri, 19 May 2023 01:14:25 +0100 Subject: [PATCH 10/15] rollback accidently committed file --- dotnet/Directory.Build.targets | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/dotnet/Directory.Build.targets b/dotnet/Directory.Build.targets index 6f85f5df3943..f4fc12c8472f 100644 --- a/dotnet/Directory.Build.targets +++ b/dotnet/Directory.Build.targets @@ -3,7 +3,6 @@ - - - - - - - <_Parameter1 Condition="'%(InternalsVisibleTo.PublicKey)' != ''">%(InternalsVisibleTo.Identity), PublicKey="%(InternalsVisibleTo.PublicKey) - <_Parameter1 Condition="'%(InternalsVisibleTo.PublicKey)' == ''">%(InternalsVisibleTo.Identity) - <_Parameter1_TypeName>System.String - - - \ No newline at end of file From 41e89ec9892377c692683989a16f5f9d69047cc4 Mon Sep 17 00:00:00 2001 From: SergeyMenshykh Date: Fri, 19 May 2023 01:18:53 +0100 Subject: [PATCH 11/15] remove unnessesary changes --- dotnet/SK-dotnet.sln | 2 -- 1 file changed, 2 deletions(-) diff --git a/dotnet/SK-dotnet.sln b/dotnet/SK-dotnet.sln index bf226ddef523..499ae1c4514e 100644 --- a/dotnet/SK-dotnet.sln +++ b/dotnet/SK-dotnet.sln @@ -264,8 +264,6 @@ Global {EC3BB6D1-2FB2-4702-84C6-F791DE533ED4}.Release|Any CPU.Build.0 = Release|Any CPU {4D226C2F-AE9F-4EFB-AF2D-45C8FE5CB34E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4D226C2F-AE9F-4EFB-AF2D-45C8FE5CB34E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4D226C2F-AE9F-4EFB-AF2D-45C8FE5CB34E}.Publish|Any CPU.ActiveCfg = Publish|Any CPU - {4D226C2F-AE9F-4EFB-AF2D-45C8FE5CB34E}.Publish|Any CPU.Build.0 = Publish|Any CPU {4D226C2F-AE9F-4EFB-AF2D-45C8FE5CB34E}.Release|Any CPU.ActiveCfg = Release|Any CPU {4D226C2F-AE9F-4EFB-AF2D-45C8FE5CB34E}.Release|Any CPU.Build.0 = Release|Any CPU {E52F805C-794A-4CA9-B684-DFF358B18820}.Debug|Any CPU.ActiveCfg = Debug|Any CPU From 9e9a6a1488bb22aa29d97824b7ab78580dda8d25 Mon Sep 17 00:00:00 2001 From: name Date: Fri, 19 May 2023 13:34:09 -0700 Subject: [PATCH 12/15] fixing tests --- .../Connectors/OpenAI/OpenAICompletionTests.cs | 16 ++++++++++++---- dotnet/src/SemanticKernel/Planning/Plan.cs | 1 + 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs b/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs index fb44827089ac..1d3daa0e8f83 100644 --- a/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs @@ -3,11 +3,13 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.Orchestration; +using Microsoft.SemanticKernel.Reliability; using Microsoft.SemanticKernel.SkillDefinition; using SemanticKernel.IntegrationTests.TestSettings; using Xunit; @@ -86,8 +88,10 @@ public async Task OpenAIChatAsTextTestAsync(string prompt, string expectedAnswer public async Task CanUseOpenAiChatForTextCompletionAsync() { // Note: we use OpenAi Chat Completion and GPT 3.5 Turbo - IKernel target = Kernel.Builder.WithLogger(this._logger).Build(); - this.ConfigureChatOpenAI(target); + KernelBuilder builder = Kernel.Builder.WithLogger(this._logger); + this.ConfigureChatOpenAI(builder); + + IKernel target = builder.Build(); var func = target.CreateSemanticFunction( "List the two planets after '{{$input}}', excluding moons, using bullet points."); @@ -169,17 +173,21 @@ public async Task AzureOpenAIHttpRetryPolicyTestAsync(string prompt, string expe // Arrange var retryConfig = new HttpRetryConfig(); retryConfig.RetryableStatusCodes.Add(HttpStatusCode.Unauthorized); - IKernel target = Kernel.Builder.WithLogger(this._testOutputHelper).Configure(c => c.SetDefaultHttpRetryConfig(retryConfig)).Build(); + KernelBuilder builder = Kernel.Builder + .WithLogger(this._testOutputHelper) + .Configure(c => c.SetDefaultHttpRetryConfig(retryConfig)); var azureOpenAIConfiguration = this._configuration.GetSection("AzureOpenAI").Get(); Assert.NotNull(azureOpenAIConfiguration); // Use an invalid API key to force a 401 Unauthorized response - target.Config.AddAzureTextCompletionService( + builder.WithAzureTextCompletionService( deploymentName: azureOpenAIConfiguration.DeploymentName, endpoint: azureOpenAIConfiguration.Endpoint, apiKey: "INVALID_KEY"); + IKernel target = builder.Build(); + IDictionary skill = TestHelpers.GetSkills(target, "SummarizeSkill"); // Act diff --git a/dotnet/src/SemanticKernel/Planning/Plan.cs b/dotnet/src/SemanticKernel/Planning/Plan.cs index be75527382bc..8792e51d4564 100644 --- a/dotnet/src/SemanticKernel/Planning/Plan.cs +++ b/dotnet/src/SemanticKernel/Planning/Plan.cs @@ -101,6 +101,7 @@ public Plan(string goal) public Plan(string goal, params ISKFunction[] steps) : this(goal) { this.AddSteps(steps); + } /// From fbc7821208a2085b29dee4fc23a04285f55d9069 Mon Sep 17 00:00:00 2001 From: name Date: Fri, 19 May 2023 14:05:43 -0700 Subject: [PATCH 13/15] integration test fixes --- .../Connectors/OpenAI/OpenAICompletionTests.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs b/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs index c61a85c048d3..1e2cd79e300f 100644 --- a/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs @@ -145,10 +145,15 @@ public async Task AzureOpenAITestAsync(bool useChatModel, string prompt, string public async Task OpenAIHttpRetryPolicyTestAsync(string prompt, string expectedOutput) { // Arrange + var retryConfig = new HttpRetryConfig(); + retryConfig.RetryableStatusCodes.Add(HttpStatusCode.Unauthorized); + OpenAIConfiguration? openAIConfiguration = this._configuration.GetSection("OpenAI").Get(); Assert.NotNull(openAIConfiguration); IKernel target = Kernel.Builder + .WithLogger(this._testOutputHelper) + .Configure(c => c.SetDefaultHttpRetryConfig(retryConfig)) .WithOpenAITextCompletionService( serviceId: openAIConfiguration.ServiceId, modelId: openAIConfiguration.ModelId, From 0f3dad6176ee7628d0fa8d6e9ec42dbcc8c0de98 Mon Sep 17 00:00:00 2001 From: name Date: Fri, 19 May 2023 23:44:51 -0700 Subject: [PATCH 14/15] format --- dotnet/src/SemanticKernel/Planning/Plan.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/dotnet/src/SemanticKernel/Planning/Plan.cs b/dotnet/src/SemanticKernel/Planning/Plan.cs index 21f38837a768..ad20847b4701 100644 --- a/dotnet/src/SemanticKernel/Planning/Plan.cs +++ b/dotnet/src/SemanticKernel/Planning/Plan.cs @@ -104,7 +104,6 @@ public Plan(string goal) public Plan(string goal, params ISKFunction[] steps) : this(goal) { this.AddSteps(steps); - } /// From 2aed64dda2c5a869c8dd7d5eb57b37a65421da2d Mon Sep 17 00:00:00 2001 From: SergeyMenshykh Date: Mon, 22 May 2023 12:15:14 +0100 Subject: [PATCH 15/15] Marking the old functionality for registering connectors as obsolete. --- .../KernelConfigOpenAIExtensions.cs | 414 ++++++++++++++++++ .../KernelConfigOpenAIExtensionsTests.cs | 130 ++++++ .../KernelConfig.cs | 236 ++++++++++ .../KernelConfigTests.cs | 24 + .../SemanticKernel.UnitTests/KernelTests.cs | 54 +++ dotnet/src/SemanticKernel/Kernel.cs | 67 ++- 6 files changed, 922 insertions(+), 3 deletions(-) create mode 100644 dotnet/src/Connectors/Connectors.AI.OpenAI/KernelConfigOpenAIExtensions.cs create mode 100644 dotnet/src/Connectors/Connectors.UnitTests/OpenAI/KernelConfigOpenAIExtensionsTests.cs diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/KernelConfigOpenAIExtensions.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/KernelConfigOpenAIExtensions.cs new file mode 100644 index 000000000000..9b2facd59075 --- /dev/null +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/KernelConfigOpenAIExtensions.cs @@ -0,0 +1,414 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using System.Net.Http; +using Azure.Core; +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.ChatCompletion; +using Microsoft.SemanticKernel.Connectors.AI.OpenAI.ImageGeneration; +using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion; +using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; +using Microsoft.SemanticKernel.Reliability; + +#pragma warning disable IDE0130 +// ReSharper disable once CheckNamespace - Using NS of KernelConfig +namespace Microsoft.SemanticKernel; +#pragma warning restore IDE0130 + +public static class KernelConfigOpenAIExtensions +{ + #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. + /// + /// The kernel config instance + /// 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 + /// Custom for HTTP requests. + /// Application logger + /// Self instance + [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions. Please use the corresponding extension method in the KernelBuilder class instead.")] + public static KernelConfig AddAzureTextCompletionService(this KernelConfig config, + string deploymentName, + string endpoint, + string apiKey, + string? serviceId = null, + HttpClient? httpClient = null, + ILogger? logger = null) + { + ITextCompletion Factory(IKernel kernel) => new AzureTextCompletion( + deploymentName, + endpoint, + apiKey, + httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), + logger ?? kernel.Log); + + config.AddTextCompletionService(Factory, serviceId); + + return config; + } + + /// + /// Adds an Azure OpenAI text completion service to the list. + /// See https://learn.microsoft.com/azure/cognitive-services/openai for service details. + /// + /// The kernel config instance + /// 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 + /// Custom for HTTP requests. + /// Application logger + /// Self instance + [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions. Please use the corresponding extension method in the KernelBuilder class instead.")] + public static KernelConfig AddAzureTextCompletionService(this KernelConfig config, + string deploymentName, + string endpoint, + TokenCredential credentials, + string? serviceId = null, + HttpClient? httpClient = null, + ILogger? logger = null) + { + ITextCompletion Factory(IKernel kernel) => new AzureTextCompletion( + deploymentName, + endpoint, + credentials, + httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), + logger ?? kernel.Log); + + config.AddTextCompletionService(Factory, serviceId); + + return config; + } + + /// + /// Adds the OpenAI text completion service to the list. + /// See https://platform.openai.com/docs for service details. + /// + /// The kernel config instance + /// 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 + /// Custom for HTTP requests. + /// Application logger + /// Self instance + [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions. Please use the corresponding extension method in the KernelBuilder class instead.")] + public static KernelConfig AddOpenAITextCompletionService(this KernelConfig config, + string modelId, + string apiKey, + string? orgId = null, + string? serviceId = null, + HttpClient? httpClient = null, + ILogger? logger = null) + { + ITextCompletion Factory(IKernel kernel) => new OpenAITextCompletion( + modelId, + apiKey, + orgId, + httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), + logger ?? kernel.Log); + + config.AddTextCompletionService(Factory, serviceId); + + return config; + } + + #endregion + + #region Text Embedding + + /// + /// Adds an Azure OpenAI text embeddings service to the list. + /// See https://learn.microsoft.com/azure/cognitive-services/openai for service details. + /// + /// The kernel config instance + /// 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 + /// Custom for HTTP requests. + /// Application logger + /// Self instance + [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions. Please use the corresponding extension method in the KernelBuilder class instead.")] + public static KernelConfig AddAzureTextEmbeddingGenerationService(this KernelConfig config, + string deploymentName, + string endpoint, + string apiKey, + string? serviceId = null, + HttpClient? httpClient = null, + ILogger? logger = null) + { + IEmbeddingGeneration Factory(IKernel kernel) => new AzureTextEmbeddingGeneration( + deploymentName, + endpoint, + apiKey, + httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), + logger ?? kernel.Log); + + config.AddTextEmbeddingGenerationService(Factory, serviceId); + + return config; + } + + /// + /// Adds an Azure OpenAI text embeddings service to the list. + /// See https://learn.microsoft.com/azure/cognitive-services/openai for service details. + /// + /// The kernel config instance + /// 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 + /// Custom for HTTP requests. + /// Application logger + /// Self instance + [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions. Please use the corresponding extension method in the KernelBuilder class instead.")] + public static KernelConfig AddAzureTextEmbeddingGenerationService(this KernelConfig config, + string deploymentName, + string endpoint, + TokenCredential credentials, + string? serviceId = null, + HttpClient? httpClient = null, + ILogger? logger = null) + { + IEmbeddingGeneration Factory(IKernel kernel) => new AzureTextEmbeddingGeneration( + deploymentName, + endpoint, + credentials, + httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), + logger ?? kernel.Log); + + config.AddTextEmbeddingGenerationService(Factory, serviceId); + + return config; + } + + /// + /// Adds the OpenAI text embeddings service to the list. + /// See https://platform.openai.com/docs for service details. + /// + /// The kernel config instance + /// 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 + /// Custom for HTTP requests. + /// Application logger + /// Self instance + [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions. Please use the corresponding extension method in the KernelBuilder class instead.")] + public static KernelConfig AddOpenAITextEmbeddingGenerationService(this KernelConfig config, + string modelId, + string apiKey, + string? orgId = null, + string? serviceId = null, + HttpClient? httpClient = null, + ILogger? logger = null) + { + IEmbeddingGeneration Factory(IKernel kernel) => new OpenAITextEmbeddingGeneration( + modelId, + apiKey, + orgId, + httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), + logger ?? kernel.Log); + + config.AddTextEmbeddingGenerationService(Factory, serviceId); + + return config; + } + + #endregion + + #region Chat Completion + + /// + /// Adds the Azure OpenAI ChatGPT completion service to the list. + /// See https://platform.openai.com/docs for service details. + /// + /// The kernel config instance + /// 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 + /// Custom for HTTP requests. + /// Application logger + /// Self instance + [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions. Please use the corresponding extension method in the KernelBuilder class instead.")] + public static KernelConfig AddAzureChatCompletionService(this KernelConfig config, + string deploymentName, + string endpoint, + string apiKey, + bool alsoAsTextCompletion = true, + string? serviceId = null, + HttpClient? httpClient = null, + ILogger? logger = null) + { + IChatCompletion Factory(IKernel kernel) => new AzureChatCompletion( + deploymentName, endpoint, apiKey, kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), kernel.Log); + + config.AddChatCompletionService(Factory, serviceId); + + // If the class implements the text completion interface, allow to use it also for semantic functions + if (alsoAsTextCompletion && typeof(ITextCompletion).IsAssignableFrom(typeof(AzureChatCompletion))) + { + ITextCompletion TextServiceFactory(IKernel kernel) => new AzureChatCompletion( + deploymentName, + endpoint, + apiKey, + httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), + logger ?? kernel.Log); + + config.AddTextCompletionService(TextServiceFactory, serviceId); + } + + return config; + } + + /// + /// Adds the Azure OpenAI ChatGPT completion service to the list. + /// See https://platform.openai.com/docs for service details. + /// + /// The kernel config instance + /// 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 + /// Custom for HTTP requests. + /// Application logger + /// Self instance + [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions. Please use the corresponding extension method in the KernelBuilder class instead.")] + public static KernelConfig AddAzureChatCompletionService(this KernelConfig config, + string deploymentName, + string endpoint, + TokenCredential credentials, + bool alsoAsTextCompletion = true, + string? serviceId = null, + HttpClient? httpClient = null, + ILogger? logger = null) + { + IChatCompletion Factory(IKernel kernel) => new AzureChatCompletion( + deploymentName, + endpoint, + credentials, + httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), + logger ?? kernel.Log); + + config.AddChatCompletionService(Factory, serviceId); + + // If the class implements the text completion interface, allow to use it also for semantic functions + if (alsoAsTextCompletion && typeof(ITextCompletion).IsAssignableFrom(typeof(AzureChatCompletion))) + { + ITextCompletion TextServiceFactory(IKernel kernel) => new AzureChatCompletion( + deploymentName, + endpoint, + credentials, + httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), + logger ?? kernel.Log); + + config.AddTextCompletionService(TextServiceFactory, serviceId); + } + + return config; + } + + /// + /// Adds the OpenAI ChatGPT completion service to the list. + /// See https://platform.openai.com/docs for service details. + /// + /// The kernel config instance + /// 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. + /// Whether to use the service also for text completion, if supported + /// A local identifier for the given AI service + /// Custom for HTTP requests. + /// Application logger + /// Self instance + [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions. Please use the corresponding extension method in the KernelBuilder class instead.")] + public static KernelConfig AddOpenAIChatCompletionService(this KernelConfig config, + string modelId, + string apiKey, + string? orgId = null, + bool alsoAsTextCompletion = true, + string? serviceId = null, + HttpClient? httpClient = null, + ILogger? logger = null) + { + IChatCompletion Factory(IKernel kernel) => new OpenAIChatCompletion( + modelId, + apiKey, + orgId, + httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), + logger ?? kernel.Log); + + config.AddChatCompletionService(Factory, serviceId); + + // If the class implements the text completion interface, allow to use it also for semantic functions + if (alsoAsTextCompletion && typeof(ITextCompletion).IsAssignableFrom(typeof(OpenAIChatCompletion))) + { + ITextCompletion TextServiceFactory(IKernel kernel) => new OpenAIChatCompletion( + modelId, + apiKey, + orgId, + httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), + logger ?? kernel.Log); + + config.AddTextCompletionService(TextServiceFactory, serviceId); + } + + return config; + } + + #endregion + + #region Images + + /// + /// Add the OpenAI DallE image generation service to the list + /// + /// The kernel config 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. + /// A local identifier for the given AI service + /// Custom for HTTP requests. + /// Application logger + /// Self instance + [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions. Please use the corresponding extension method in the KernelBuilder class instead.")] + public static KernelConfig AddOpenAIImageGenerationService(this KernelConfig config, + string apiKey, + string? orgId = null, + string? serviceId = null, + HttpClient? httpClient = null, + ILogger? logger = null) + { + IImageGeneration Factory(IKernel kernel) => new OpenAIImageGeneration( + apiKey, + orgId, + httpClient ?? kernel.Config.HttpHandlerFactory.CreateHttpClient(kernel.Log), + logger ?? kernel.Log); + + config.AddImageGenerationService(Factory, serviceId); + + return config; + } + + #endregion + + private static HttpClient CreateHttpClient(this IDelegatingHandlerFactory handlerFactory, + ILogger? logger) + { + var retryHandler = handlerFactory.Create(logger); + retryHandler.InnerHandler = new HttpClientHandler { CheckCertificateRevocationList = true }; + return new HttpClient(retryHandler); + } +} diff --git a/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/KernelConfigOpenAIExtensionsTests.cs b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/KernelConfigOpenAIExtensionsTests.cs new file mode 100644 index 000000000000..982c8fe0a3a4 --- /dev/null +++ b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/KernelConfigOpenAIExtensionsTests.cs @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.SemanticKernel; +using Xunit; + +namespace SemanticKernel.Connectors.UnitTests.OpenAI; + +/// +/// Unit tests of . +/// +[System.Obsolete("All the methods of this class are deprecated and it will be removed in one of the next SK SDK versions.")] +public class KernelConfigOpenAIExtensionsTests +{ + [Fact] + public void ItSucceedsWhenAddingDifferentServiceTypeWithSameId() + { + var target = new KernelConfig(); + target.AddAzureTextCompletionService("depl", "https://url", "key", serviceId: "azure"); + target.AddAzureTextEmbeddingGenerationService("depl2", "https://url", "key", serviceId: "azure"); + + Assert.True(target.TextCompletionServices.ContainsKey("azure")); + Assert.True(target.TextEmbeddingGenerationServices.ContainsKey("azure")); + } + + [Fact] + public void ItTellsIfAServiceIsAvailable() + { + // Arrange + var target = new KernelConfig(); + target.AddAzureTextCompletionService("deployment1", "https://url", "key", serviceId: "azure"); + target.AddOpenAITextCompletionService("model", "apikey", serviceId: "oai"); + target.AddAzureTextEmbeddingGenerationService("deployment2", "https://url2", "key", serviceId: "azure"); + target.AddOpenAITextEmbeddingGenerationService("model2", "apikey2", serviceId: "oai2"); + + // Assert + Assert.True(target.TextCompletionServices.ContainsKey("azure")); + Assert.True(target.TextCompletionServices.ContainsKey("oai")); + Assert.True(target.TextEmbeddingGenerationServices.ContainsKey("azure")); + Assert.True(target.TextEmbeddingGenerationServices.ContainsKey("oai2")); + + Assert.False(target.TextCompletionServices.ContainsKey("azure2")); + Assert.False(target.TextCompletionServices.ContainsKey("oai2")); + Assert.False(target.TextEmbeddingGenerationServices.ContainsKey("azure1")); + Assert.False(target.TextEmbeddingGenerationServices.ContainsKey("oai")); + } + + [Fact] + public void ItCanOverwriteServices() + { + // Arrange + var target = new KernelConfig(); + + // Act - Assert no exception occurs + target.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); + target.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); + target.AddOpenAITextCompletionService("model", "key", serviceId: "one"); + target.AddOpenAITextCompletionService("model", "key", serviceId: "one"); + target.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "one"); + target.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "one"); + target.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "one"); + target.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "one"); + } + + [Fact] + public void ItCanRemoveAllServices() + { + // Arrange + var target = new KernelConfig(); + target.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); + target.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "2"); + target.AddOpenAITextCompletionService("model", "key", serviceId: "3"); + target.AddOpenAITextCompletionService("model", "key", serviceId: "4"); + target.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "5"); + target.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "6"); + target.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "7"); + target.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "8"); + + // Act + target.RemoveAllTextCompletionServices(); + target.RemoveAllTextEmbeddingGenerationServices(); + + // Assert + Assert.Empty(target.TextEmbeddingGenerationServices); + Assert.Empty(target.TextCompletionServices); + } + + [Fact] + public void ItCanRemoveAllTextCompletionServices() + { + // Arrange + var target = new KernelConfig(); + target.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); + target.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "2"); + target.AddOpenAITextCompletionService("model", "key", serviceId: "3"); + target.AddOpenAITextCompletionService("model", "key", serviceId: "4"); + + target.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "5"); + target.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "6"); + target.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "7"); + target.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "8"); + + // Act + target.RemoveAllTextCompletionServices(); + + // Assert (+1 for the default) + Assert.Equal(4 + 1, target.TextEmbeddingGenerationServices.Count); + } + + [Fact] + public void ItCanRemoveAllTextEmbeddingGenerationServices() + { + // Arrange + var target = new KernelConfig(); + target.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "one"); + target.AddAzureTextCompletionService("dep", "https://localhost", "key", serviceId: "2"); + target.AddOpenAITextCompletionService("model", "key", serviceId: "3"); + target.AddOpenAITextCompletionService("model", "key", serviceId: "4"); + target.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "5"); + target.AddAzureTextEmbeddingGenerationService("dep", "https://localhost", "key", serviceId: "6"); + target.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "7"); + target.AddOpenAITextEmbeddingGenerationService("model", "key", serviceId: "8"); + + // Act + target.RemoveAllTextEmbeddingGenerationServices(); + + // Assert (+1 for the default) + Assert.Equal(4 + 1, target.TextCompletionServices.Count); + Assert.Empty(target.TextEmbeddingGenerationServices); + } +} diff --git a/dotnet/src/SemanticKernel.Abstractions/KernelConfig.cs b/dotnet/src/SemanticKernel.Abstractions/KernelConfig.cs index 1594fe926113..44ad00c1a89c 100644 --- a/dotnet/src/SemanticKernel.Abstractions/KernelConfig.cs +++ b/dotnet/src/SemanticKernel.Abstractions/KernelConfig.cs @@ -1,5 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. +using System; +using System.Collections.Generic; +using Microsoft.SemanticKernel.AI.ChatCompletion; +using Microsoft.SemanticKernel.AI.Embeddings; +using Microsoft.SemanticKernel.AI.ImageGeneration; +using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Reliability; namespace Microsoft.SemanticKernel; @@ -20,6 +26,157 @@ public sealed class KernelConfig /// public HttpRetryConfig DefaultHttpRetryConfig { get; private set; } = new(); + /// + /// Text completion service factories + /// + [Obsolete("This property is deprecated and will be removed in one of the next SK SDK versions.")] + public Dictionary> TextCompletionServices { get; } = new(); + + /// + /// Chat completion service factories + /// + [Obsolete("This property is deprecated and will be removed in one of the next SK SDK versions.")] + public Dictionary> ChatCompletionServices { get; } = new(); + + /// + /// Text embedding generation service factories + /// + [Obsolete("This property is deprecated and will be removed in one of the next SK SDK versions.")] + public Dictionary>> TextEmbeddingGenerationServices { get; } = new(); + + /// + /// Image generation service factories + /// + [Obsolete("This property is deprecated and will be removed in one of the next SK SDK versions.")] + public Dictionary> ImageGenerationServices { get; } = new(); + + /// + /// Default name used when binding services if the user doesn't provide a custom value + /// + internal string DefaultServiceId => "__SK_DEFAULT"; + + /// + /// Add to the list a service for text completion, e.g. Azure OpenAI Text Completion. + /// + /// Function used to instantiate the service object + /// Id used to identify the service + /// Current object instance + /// Failure if a service with the same id already exists + [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions. Please use one of the WithAIService extension methods in the KernelBuilder class instead.")] + public KernelConfig AddTextCompletionService( + Func serviceFactory, + string? serviceId = null) + { + if (serviceId != null && serviceId.Equals(this.DefaultServiceId, StringComparison.OrdinalIgnoreCase)) + { + throw new KernelException( + KernelException.ErrorCodes.InvalidServiceConfiguration, + $"The service id '{serviceId}' is reserved, please use a different name"); + } + + serviceId ??= this.DefaultServiceId; + + this.TextCompletionServices[serviceId] = serviceFactory; + if (this.TextCompletionServices.Count == 1) + { + this.TextCompletionServices[this.DefaultServiceId] = serviceFactory; + } + + return this; + } + + /// + /// Add to the list a service for chat completion, e.g. OpenAI ChatGPT. + /// + /// Function used to instantiate the service object + /// Id used to identify the service + /// Current object instance + /// Failure if a service with the same id already exists + [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions. Please use one of the WithAIService extension methods in the KernelBuilder class instead.")] + public KernelConfig AddChatCompletionService( + Func serviceFactory, + string? serviceId = null) + { + if (serviceId != null && serviceId.Equals(this.DefaultServiceId, StringComparison.OrdinalIgnoreCase)) + { + throw new KernelException( + KernelException.ErrorCodes.InvalidServiceConfiguration, + $"The service id '{serviceId}' is reserved, please use a different name"); + } + + serviceId ??= this.DefaultServiceId; + + this.ChatCompletionServices[serviceId] = serviceFactory; + if (this.ChatCompletionServices.Count == 1) + { + this.ChatCompletionServices[this.DefaultServiceId] = serviceFactory; + } + + return this; + } + + /// + /// Add to the list a service for text embedding generation, e.g. Azure OpenAI Text Embedding. + /// + /// Function used to instantiate the service object + /// Id used to identify the service + /// Current object instance + /// Failure if a service with the same id already exists + [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions. Please use one of the WithAIService extension methods in the KernelBuilder class instead.")] + public KernelConfig AddTextEmbeddingGenerationService( + Func> serviceFactory, + string? serviceId = null) + { + if (serviceId != null && serviceId.Equals(this.DefaultServiceId, StringComparison.OrdinalIgnoreCase)) + { + throw new KernelException( + KernelException.ErrorCodes.InvalidServiceConfiguration, + $"The service id '{serviceId}' is reserved, please use a different name"); + } + + serviceId ??= this.DefaultServiceId; + + this.TextEmbeddingGenerationServices[serviceId] = serviceFactory; + if (this.TextEmbeddingGenerationServices.Count == 1) + { + this.TextEmbeddingGenerationServices[this.DefaultServiceId] = serviceFactory; + } + + return this; + } + + /// + /// Add to the list a service for image generation, e.g. OpenAI DallE. + /// + /// Function used to instantiate the service object + /// Id used to identify the service + /// Current object instance + /// Failure if a service with the same id already exists + [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions. Please use one of the WithAIService extension methods in the KernelBuilder class instead.")] + public KernelConfig AddImageGenerationService( + Func serviceFactory, + string? serviceId = null) + { + if (serviceId != null && serviceId.Equals(this.DefaultServiceId, StringComparison.OrdinalIgnoreCase)) + { + throw new KernelException( + KernelException.ErrorCodes.InvalidServiceConfiguration, + $"The service id '{serviceId}' is reserved, please use a different name"); + } + + serviceId ??= this.DefaultServiceId; + + this.ImageGenerationServices[serviceId] = serviceFactory; + if (this.ImageGenerationServices.Count == 1) + { + this.ImageGenerationServices[this.DefaultServiceId] = serviceFactory; + } + + return this; + } + + #region Set + /// /// Set the http retry handler factory to use for the kernel. /// @@ -45,4 +202,83 @@ public KernelConfig SetDefaultHttpRetryConfig(HttpRetryConfig? httpRetryConfig) return this; } + + /// + /// Set the default completion service to use for the kernel. + /// + /// Identifier of completion service to use. + /// The updated kernel configuration. + /// Thrown if the requested service doesn't exist. + [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions. Please use one of the WithDefaultAIService extension methods in the KernelBuilder class instead.")] + public KernelConfig SetDefaultTextCompletionService(string serviceId) + { + if (!this.TextCompletionServices.ContainsKey(serviceId)) + { + throw new KernelException( + KernelException.ErrorCodes.ServiceNotFound, + $"A text completion service id '{serviceId}' doesn't exist"); + } + + this.TextCompletionServices[this.DefaultServiceId] = this.TextCompletionServices[serviceId]; + return this; + } + + /// + /// Set the default embedding service to use for the kernel. + /// + /// Identifier of text embedding service to use. + /// The updated kernel configuration. + /// Thrown if the requested service doesn't exist. + [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions. Please use one of the WithDefaultAIService extension methods in the KernelBuilder class instead.")] + public KernelConfig SetDefaultTextEmbeddingGenerationService(string serviceId) + { + if (!this.TextEmbeddingGenerationServices.ContainsKey(serviceId)) + { + throw new KernelException( + KernelException.ErrorCodes.ServiceNotFound, + $"A text embedding generation service id '{serviceId}' doesn't exist"); + } + + this.TextEmbeddingGenerationServices[this.DefaultServiceId] = this.TextEmbeddingGenerationServices[serviceId]; + return this; + } + + #endregion + + #region Remove + + /// + /// Remove all text completion services. + /// + /// The updated kernel configuration. + [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions.")] + public KernelConfig RemoveAllTextCompletionServices() + { + this.TextCompletionServices.Clear(); + return this; + } + + /// + /// Remove all chat completion services. + /// + /// The updated kernel configuration. + [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions.")] + public KernelConfig RemoveAllChatCompletionServices() + { + this.ChatCompletionServices.Clear(); + return this; + } + + /// + /// Remove all text embedding generation services. + /// + /// The updated kernel configuration. + [Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions.")] + public KernelConfig RemoveAllTextEmbeddingGenerationServices() + { + this.TextEmbeddingGenerationServices.Clear(); + return this; + } + + #endregion } diff --git a/dotnet/src/SemanticKernel.UnitTests/KernelConfigTests.cs b/dotnet/src/SemanticKernel.UnitTests/KernelConfigTests.cs index bafd04ca17bd..150791140641 100644 --- a/dotnet/src/SemanticKernel.UnitTests/KernelConfigTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/KernelConfigTests.cs @@ -74,4 +74,28 @@ public void HttpRetryHandlerFactoryIsSetToDefaultHttpRetryHandlerFactoryIfNotSet // Assert Assert.IsType(config.HttpHandlerFactory); } + + [Fact] + [System.Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions.")] + public void ItFailsWhenSetNonExistentTextCompletionService() + { + var target = new KernelConfig(); + var exception = Assert.Throws(() => + { + target.SetDefaultTextCompletionService("azure"); + }); + Assert.Equal(KernelException.ErrorCodes.ServiceNotFound, exception.ErrorCode); + } + + [Fact] + [System.Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions.")] + public void ItFailsWhenSetNonExistentEmbeddingService() + { + var target = new KernelConfig(); + var exception = Assert.Throws(() => + { + target.SetDefaultTextEmbeddingGenerationService("azure"); + }); + Assert.Equal(KernelException.ErrorCodes.ServiceNotFound, exception.ErrorCode); + } } diff --git a/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs b/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs index d8adcd184090..6b50359ff774 100644 --- a/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs @@ -18,6 +18,60 @@ namespace SemanticKernel.UnitTests; public class KernelTests { + [Fact] + [System.Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions.")] + public void ItProvidesAccessToFunctionsViaSkillCollectionObsolete() + { + // Arrange + var kernel = KernelBuilder.Create(); + var factory = new Mock>(); + kernel.Config.AddTextCompletionService(factory.Object); + + var nativeSkill = new MySkill(); + kernel.CreateSemanticFunction(promptTemplate: "Tell me a joke", functionName: "joker", skillName: "jk", description: "Nice fun"); + kernel.ImportSkill(nativeSkill, "mySk"); + + // Act + FunctionsView data = kernel.Skills.GetFunctionsView(); + + // Assert - 3 functions, var name is not case sensitive + Assert.True(data.IsSemantic("jk", "joker")); + Assert.True(data.IsSemantic("JK", "JOKER")); + Assert.False(data.IsNative("jk", "joker")); + Assert.False(data.IsNative("JK", "JOKER")); + Assert.True(data.IsNative("mySk", "sayhello")); + Assert.True(data.IsNative("MYSK", "SayHello")); + Assert.True(data.IsNative("mySk", "ReadSkillCollectionAsync")); + Assert.True(data.IsNative("MYSK", "readskillcollectionasync")); + Assert.Single(data.SemanticFunctions["Jk"]); + Assert.Equal(3, data.NativeFunctions["mySk"].Count); + } + + [Fact] + [System.Obsolete("This method is deprecated and will be removed in one of the next SK SDK versions.")] + public async Task ItProvidesAccessToFunctionsViaSKContextObsoleteAsync() + { + // Arrange + var kernel = KernelBuilder.Create(); + var factory = new Mock>(); + kernel.Config.AddTextCompletionService(factory.Object); + + var nativeSkill = new MySkill(); + kernel.CreateSemanticFunction("Tell me a joke", functionName: "joker", skillName: "jk", description: "Nice fun"); + var skill = kernel.ImportSkill(nativeSkill, "mySk"); + + // Act + SKContext result = await kernel.RunAsync(skill["ReadSkillCollectionAsync"]); + + // Assert - 3 functions, var name is not case sensitive + Assert.Equal("Nice fun", result["jk.joker"]); + Assert.Equal("Nice fun", result["JK.JOKER"]); + Assert.Equal("Just say hello", result["mySk.sayhello"]); + Assert.Equal("Just say hello", result["mySk.SayHello"]); + Assert.Equal("Export info.", result["mySk.ReadSkillCollectionAsync"]); + Assert.Equal("Export info.", result["mysk.readskillcollectionasync"]); + } + [Fact] public void ItProvidesAccessToFunctionsViaSkillCollection() { diff --git a/dotnet/src/SemanticKernel/Kernel.cs b/dotnet/src/SemanticKernel/Kernel.cs index c76618ef85e1..d07a8b55b60d 100644 --- a/dotnet/src/SemanticKernel/Kernel.cs +++ b/dotnet/src/SemanticKernel/Kernel.cs @@ -7,6 +7,9 @@ using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.AI; +using Microsoft.SemanticKernel.AI.ChatCompletion; +using Microsoft.SemanticKernel.AI.Embeddings; +using Microsoft.SemanticKernel.AI.ImageGeneration; using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Diagnostics; using Microsoft.SemanticKernel.Memory; @@ -225,9 +228,67 @@ public SKContext CreateNewContext(CancellationToken cancellationToken = default) /// public T GetService(string? name = null) where T : IAIService { - return this._aiServiceProvider.GetService(name) ?? - throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, - $"Service of type {typeof(T)} and name {name ?? ""} not registered."); + var service = this._aiServiceProvider.GetService(name); + if (service != null) + { + return service; + } + + if (typeof(T) == typeof(ITextCompletion)) + { + name ??= this.Config.DefaultServiceId; + +#pragma warning disable CS0618 // Type or member is obsolete + if (!this.Config.TextCompletionServices.TryGetValue(name, out Func factory)) + { + throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, $"'{name}' text completion service not available"); + } + + var serv = factory.Invoke(this); + return (T)serv; + } + + if (typeof(T) == typeof(IEmbeddingGeneration)) + { + name ??= this.Config.DefaultServiceId; + + if (!this.Config.TextEmbeddingGenerationServices.TryGetValue(name, out Func> factory)) + { + throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, $"'{name}' text embedding service not available"); + } + + var serv = factory.Invoke(this); + return (T)serv; + } + + if (typeof(T) == typeof(IChatCompletion)) + { + name ??= this.Config.DefaultServiceId; + + if (!this.Config.ChatCompletionServices.TryGetValue(name, out Func factory)) + { + throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, $"'{name}' chat completion service not available"); + } + + var serv = factory.Invoke(this); + return (T)serv; + } + + if (typeof(T) == typeof(IImageGeneration)) + { + name ??= this.Config.DefaultServiceId; + + if (!this.Config.ImageGenerationServices.TryGetValue(name, out Func factory)) + { + throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, $"'{name}' image generation service not available"); + } + + var serv = factory.Invoke(this); + return (T)serv; + } +#pragma warning restore CS0618 // Type or member is obsolete + + throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, $"Service of type {typeof(T)} and name {name ?? ""} not registered."); } ///