diff --git a/dotnet/Directory.Build.targets b/dotnet/Directory.Build.targets index 266991482b..f4fc12c847 100644 --- a/dotnet/Directory.Build.targets +++ b/dotnet/Directory.Build.targets @@ -3,7 +3,6 @@ - /// 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/Connectors/Connectors.AI.OpenAI/Connectors.AI.OpenAI.csproj b/dotnet/src/Connectors/Connectors.AI.OpenAI/Connectors.AI.OpenAI.csproj index 616cda3962..5478eaac19 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 @@ - + @@ -20,7 +20,7 @@ - + diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/KernelConfigOpenAIExtensions.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/KernelConfigOpenAIExtensions.cs index 3e8b327bea..9b2facd590 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/KernelConfigOpenAIExtensions.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/KernelConfigOpenAIExtensions.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +using System; using System.Net.Http; using Azure.Core; using Microsoft.Extensions.Logging; @@ -34,6 +35,7 @@ public static class KernelConfigOpenAIExtensions /// 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, @@ -66,6 +68,7 @@ public static class KernelConfigOpenAIExtensions /// 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, @@ -98,6 +101,7 @@ public static class KernelConfigOpenAIExtensions /// 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, @@ -134,6 +138,7 @@ public static class KernelConfigOpenAIExtensions /// 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, @@ -166,6 +171,7 @@ public static class KernelConfigOpenAIExtensions /// 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, @@ -198,6 +204,7 @@ public static class KernelConfigOpenAIExtensions /// 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, @@ -235,6 +242,7 @@ public static class KernelConfigOpenAIExtensions /// 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, @@ -278,6 +286,7 @@ public static class KernelConfigOpenAIExtensions /// 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, @@ -325,6 +334,7 @@ public static class KernelConfigOpenAIExtensions /// 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, @@ -373,6 +383,7 @@ public static class KernelConfigOpenAIExtensions /// 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, diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs new file mode 100644 index 0000000000..c3259d7f88 --- /dev/null +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs @@ -0,0 +1,380 @@ +// Copyright (c) Microsoft. All rights reserved. + +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.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 OpenAIKernelBuilderExtensions +{ + #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 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. + /// Self instance + public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder builder, + string deploymentName, + string endpoint, + string apiKey, + string? serviceId = null, + bool setAsDefault = false, + HttpClient? httpClient = null) + { + builder.WithAIService(serviceId, (parameters) => + new AzureTextCompletion( + deploymentName, + endpoint, + apiKey, + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.Logger), + setAsDefault); + + 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 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. + /// Self instance + public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder builder, + string deploymentName, + string endpoint, + TokenCredential credentials, + string? serviceId = null, + bool setAsDefault = false, + HttpClient? httpClient = null) + { + builder.WithAIService(serviceId, (parameters) => + new AzureTextCompletion( + deploymentName, + endpoint, + credentials, + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.Logger), + setAsDefault); + + return builder; + } + + /// + /// Adds the OpenAI text completion service to the list. + /// See https://platform.openai.com/docs for service details. + /// + /// 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. + /// Self instance + public static KernelBuilder WithOpenAITextCompletionService(this KernelBuilder builder, + string modelId, + string apiKey, + string? orgId = null, + string? serviceId = null, + bool setAsDefault = false, + HttpClient? httpClient = null) + { + builder.WithAIService(serviceId, (parameters) => + new OpenAITextCompletion( + modelId, + apiKey, + orgId, + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.Logger), + setAsDefault); + return builder; + } + + #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 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. + /// Self instance + public static KernelBuilder WithAzureTextEmbeddingGenerationService(this KernelBuilder builder, + string deploymentName, + string endpoint, + string apiKey, + string? serviceId = null, + bool setAsDefault = false, + HttpClient? httpClient = null) + { + builder.WithAIService(serviceId, (parameters) => + new AzureTextEmbeddingGeneration( + deploymentName, + endpoint, + apiKey, + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.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 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. + /// Self instance + public static KernelBuilder WithAzureTextEmbeddingGenerationService(this KernelBuilder builder, + string deploymentName, + string endpoint, + TokenCredential credential, + string? serviceId = null, + bool setAsDefault = false, + HttpClient? httpClient = null) + { + builder.WithAIService(serviceId, (parameters) => + new AzureTextEmbeddingGeneration( + deploymentName, + endpoint, + credential, + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.Logger), + setAsDefault); + return builder; + } + + /// + /// Adds the OpenAI text embeddings service to the list. + /// See https://platform.openai.com/docs for service details. + /// + /// 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. + /// Self instance + public static KernelBuilder WithOpenAITextEmbeddingGenerationService(this KernelBuilder builder, + string modelId, + string apiKey, + string? orgId = null, + string? serviceId = null, + bool setAsDefault = false, + HttpClient? httpClient = null) + { + builder.WithAIService(serviceId, (parameters) => + new OpenAITextEmbeddingGeneration( + modelId, + apiKey, + orgId, + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.Logger), + setAsDefault); + return builder; + } + + #endregion + + #region Chat Completion + + /// + /// Adds the Azure OpenAI ChatGPT completion service to the list. + /// See https://platform.openai.com/docs for service details. + /// + /// 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. + /// Self instance + public static KernelBuilder WithAzureChatCompletionService(this KernelBuilder builder, + string deploymentName, + string endpoint, + string apiKey, + bool alsoAsTextCompletion = true, + string? serviceId = null, + bool setAsDefault = false, + HttpClient? httpClient = null) + { + AzureChatCompletion Factory((ILogger Logger, KernelConfig Config) parameters) => new( + deploymentName, + endpoint, + apiKey, + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.Logger); + + 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))) + { + builder.WithAIService(serviceId, Factory, setAsDefault); + } + + return builder; + } + + /// + /// Adds the Azure OpenAI ChatGPT completion service to the list. + /// See https://platform.openai.com/docs for service details. + /// + /// 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. + /// Self instance + public static KernelBuilder WithAzureChatCompletionService(this KernelBuilder builder, + string deploymentName, + string endpoint, + TokenCredential credentials, + bool alsoAsTextCompletion = true, + string? serviceId = null, + bool setAsDefault = false, + HttpClient? httpClient = null) + { + AzureChatCompletion Factory((ILogger Logger, KernelConfig Config) parameters) => new( + deploymentName, + endpoint, + credentials, + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.Logger); + + 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))) + { + builder.WithAIService(serviceId, Factory, setAsDefault); + } + + return builder; + } + + /// + /// Adds the OpenAI ChatGPT completion service to the list. + /// See https://platform.openai.com/docs for service details. + /// + /// 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 to use the service also for text completion, if supported + /// Whether the service should be the default for its type. + /// Custom for HTTP requests. + /// Self instance + public static KernelBuilder WithOpenAIChatCompletionService(this KernelBuilder builder, + string modelId, + string apiKey, + string? orgId = null, + string? serviceId = null, + bool alsoAsTextCompletion = true, + bool setAsDefault = false, + HttpClient? httpClient = null) + { + OpenAIChatCompletion Factory((ILogger Logger, KernelConfig Config) parameters) => new( + modelId, + apiKey, + orgId, + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.Logger); + + 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))) + { + builder.WithAIService(serviceId, Factory, setAsDefault); + } + + 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 + + /// + /// Add the OpenAI DallE image generation service to the list + /// + /// The instance + /// OpenAI API key, see https://platform.openai.com/account/api-keys + /// OpenAI organization id. This is usually optional unless your account belongs to multiple organizations. + /// A local identifier for the given AI service + /// Whether the service should be the default for its type. + /// Custom for HTTP requests. + /// Self instance + public static KernelBuilder WithOpenAIImageGenerationService(this KernelBuilder builder, + string apiKey, + string? orgId = null, + string? serviceId = null, + bool setAsDefault = false, + HttpClient? httpClient = null) + { + builder.WithAIService(serviceId, ((ILogger Logger, KernelConfig Config) parameters) => + new OpenAIImageGeneration( + apiKey, + orgId, + httpClient ?? parameters.Config.HttpHandlerFactory.CreateHttpClient(parameters.Logger), + parameters.Logger), + setAsDefault); + + return builder; + } + + #endregion +} diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureTextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureTextEmbeddingGeneration.cs index 985d278e05..bb1c5bed47 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 53a36e4e06..b93fdeff67 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.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs new file mode 100644 index 0000000000..36664cef1d --- /dev/null +++ b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/AIServicesOpenAIExtensionsTests.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft. All rights reserved. + +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; + +/// +/// Unit tests of . +/// +public class AIServicesOpenAIExtensionsTests +{ + [Fact] + public void ItSucceedsWhenAddingDifferentServiceTypeWithSameId() + { + KernelBuilder targetBuilder = Kernel.Builder; + targetBuilder.WithAzureTextCompletionService("depl", "https://url", "key", "azure"); + targetBuilder.WithAzureTextEmbeddingGenerationService("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.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(); + 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.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"); + + 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/Connectors/Connectors.UnitTests/OpenAI/KernelConfigOpenAIExtensionsTests.cs b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/KernelConfigOpenAIExtensionsTests.cs index 1d0c27a64a..982c8fe0a3 100644 --- a/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/KernelConfigOpenAIExtensionsTests.cs +++ b/dotnet/src/Connectors/Connectors.UnitTests/OpenAI/KernelConfigOpenAIExtensionsTests.cs @@ -8,6 +8,7 @@ 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] diff --git a/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs b/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs index c9e0475c7a..1e2cd79e30 100644 --- a/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs +++ b/dotnet/src/IntegrationTests/Connectors/OpenAI/OpenAICompletionTests.cs @@ -36,9 +36,6 @@ public sealed class OpenAICompletionTests : IDisposable .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.")] @@ -46,9 +43,17 @@ public sealed class OpenAICompletionTests : IDisposable 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) + .WithOpenAITextCompletionService( + serviceId: openAIConfiguration.ServiceId, + modelId: openAIConfiguration.ModelId, + apiKey: openAIConfiguration.ApiKey, + setAsDefault: true) + .Build(); IDictionary skill = TestHelpers.GetSkills(target, "ChatSkill"); @@ -64,9 +69,11 @@ public sealed class OpenAICompletionTests : IDisposable public async Task OpenAIChatAsTextTestAsync(string prompt, string expectedAnswerContains) { // Arrange - IKernel target = Kernel.Builder.WithLogger(this._logger).Build(); + KernelBuilder builder = Kernel.Builder.WithLogger(this._logger); - this.ConfigureChatOpenAI(target); + this.ConfigureChatOpenAI(builder); + + IKernel target = builder.Build(); IDictionary skill = TestHelpers.GetSkills(target, "ChatSkill"); @@ -81,8 +88,10 @@ public sealed class OpenAICompletionTests : IDisposable 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."); @@ -101,17 +110,23 @@ public sealed class OpenAICompletionTests : IDisposable public async Task AzureOpenAITestAsync(bool useChatModel, string prompt, string expectedAnswerContains) { // Arrange - IKernel target = Kernel.Builder.WithLogger(this._logger).Build(); + + var azureOpenAIConfiguration = this._configuration.GetSection("AzureOpenAI").Get(); + Assert.NotNull(azureOpenAIConfiguration); + + var builder = Kernel.Builder.WithLogger(this._logger); if (useChatModel) { - this.ConfigureAzureOpenAIChatAsText(target); + this.ConfigureAzureOpenAIChatAsText(builder); } else { - this.ConfigureAzureOpenAI(target); + this.ConfigureAzureOpenAI(builder); } + IKernel target = builder.Build(); + IDictionary skill = TestHelpers.GetSkills(target, "ChatSkill"); // Act @@ -132,15 +147,18 @@ public sealed class OpenAICompletionTests : IDisposable // 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 + .WithLogger(this._testOutputHelper) + .Configure(c => c.SetDefaultHttpRetryConfig(retryConfig)) + .WithOpenAITextCompletionService( + 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"); @@ -160,17 +178,21 @@ public sealed class OpenAICompletionTests : IDisposable // 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 @@ -184,15 +206,16 @@ public sealed class OpenAICompletionTests : IDisposable 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 + .WithOpenAITextCompletionService( + modelId: openAIConfiguration.ModelId, + apiKey: "INVALID_KEY", + serviceId: openAIConfiguration.ServiceId) + .Build(); IDictionary skill = TestHelpers.GetSkills(target, "SummarizeSkill"); @@ -210,16 +233,17 @@ public sealed class OpenAICompletionTests : IDisposable 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) + .WithAzureTextCompletionService( + deploymentName: azureOpenAIConfiguration.DeploymentName, + endpoint: azureOpenAIConfiguration.Endpoint, + apiKey: "INVALID_KEY", + serviceId: azureOpenAIConfiguration.ServiceId) + .Build(); IDictionary skill = TestHelpers.GetSkills(target, "SummarizeSkill"); @@ -236,17 +260,18 @@ public sealed class OpenAICompletionTests : IDisposable [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) + .WithAzureTextCompletionService( + deploymentName: azureOpenAIConfiguration.DeploymentName, + endpoint: azureOpenAIConfiguration.Endpoint, + apiKey: azureOpenAIConfiguration.ApiKey, + serviceId: azureOpenAIConfiguration.ServiceId) + .Build(); IDictionary skill = TestHelpers.GetSkills(target, "SummarizeSkill"); @@ -316,7 +341,7 @@ public sealed class OpenAICompletionTests : IDisposable } } - private void ConfigureOpenAI(IKernel kernel) + private void ConfigureOpenAI(KernelBuilder kernelBuilder) { var openAIConfiguration = this._configuration.GetSection("OpenAI").Get(); @@ -325,15 +350,14 @@ public sealed class OpenAICompletionTests : IDisposable Assert.NotNull(openAIConfiguration.ApiKey); Assert.NotNull(openAIConfiguration.ServiceId); - kernel.Config.AddOpenAITextCompletionService( + kernelBuilder.WithOpenAITextCompletionService( modelId: openAIConfiguration.ModelId, apiKey: openAIConfiguration.ApiKey, - serviceId: openAIConfiguration.ServiceId); - - kernel.Config.SetDefaultTextCompletionService(openAIConfiguration.ServiceId); + serviceId: openAIConfiguration.ServiceId, + setAsDefault: true); } - private void ConfigureChatOpenAI(IKernel kernel) + private void ConfigureChatOpenAI(KernelBuilder kernelBuilder) { var openAIConfiguration = this._configuration.GetSection("OpenAI").Get(); @@ -342,15 +366,14 @@ public sealed class OpenAICompletionTests : IDisposable Assert.NotNull(openAIConfiguration.ApiKey); Assert.NotNull(openAIConfiguration.ServiceId); - kernel.Config.AddOpenAIChatCompletionService( + kernelBuilder.WithOpenAIChatCompletionService( modelId: openAIConfiguration.ChatModelId, apiKey: openAIConfiguration.ApiKey, - serviceId: openAIConfiguration.ServiceId); - - kernel.Config.SetDefaultTextCompletionService(openAIConfiguration.ServiceId); + serviceId: openAIConfiguration.ServiceId, + setAsDefault: true); } - private void ConfigureAzureOpenAI(IKernel kernel) + private void ConfigureAzureOpenAI(KernelBuilder kernelBuilder) { var azureOpenAIConfiguration = this._configuration.GetSection("AzureOpenAI").Get(); @@ -360,16 +383,15 @@ public sealed class OpenAICompletionTests : IDisposable Assert.NotNull(azureOpenAIConfiguration.ApiKey); Assert.NotNull(azureOpenAIConfiguration.ServiceId); - kernel.Config.AddAzureTextCompletionService( + kernelBuilder.WithAzureTextCompletionService( deploymentName: azureOpenAIConfiguration.DeploymentName, endpoint: azureOpenAIConfiguration.Endpoint, apiKey: azureOpenAIConfiguration.ApiKey, - serviceId: azureOpenAIConfiguration.ServiceId); - - kernel.Config.SetDefaultTextCompletionService(azureOpenAIConfiguration.ServiceId); + serviceId: azureOpenAIConfiguration.ServiceId, + setAsDefault: true); } - private void ConfigureAzureOpenAIChatAsText(IKernel kernel) + private void ConfigureAzureOpenAIChatAsText(KernelBuilder kernelBuilder) { var azureOpenAIConfiguration = this._configuration.GetSection("AzureOpenAI").Get(); @@ -379,13 +401,11 @@ public sealed class OpenAICompletionTests : IDisposable Assert.NotNull(azureOpenAIConfiguration.Endpoint); Assert.NotNull(azureOpenAIConfiguration.ServiceId); - kernel.Config.AddAzureChatCompletionService( + kernelBuilder.WithAzureChatCompletionService( deploymentName: azureOpenAIConfiguration.ChatDeploymentName, 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 63f71a72af..dc326b7b06 100644 --- a/dotnet/src/IntegrationTests/Planning/PlanTests.cs +++ b/dotnet/src/IntegrationTests/Planning/PlanTests.cs @@ -478,37 +478,31 @@ public sealed class PlanTests : IDisposable AzureOpenAIConfiguration? azureOpenAIEmbeddingsConfiguration = this._configuration.GetSection("AzureOpenAIEmbeddings").Get(); Assert.NotNull(azureOpenAIEmbeddingsConfiguration); - var builder = Kernel.Builder - .WithLogger(this._logger) - .Configure(config => - { - if (useChatModel) - { - config.AddAzureChatCompletionService( + var builder = Kernel.Builder.WithLogger(this._logger); + + if (useChatModel) + { + builder.WithAzureChatCompletionService( deploymentName: azureOpenAIConfiguration.ChatDeploymentName!, endpoint: azureOpenAIConfiguration.Endpoint, apiKey: azureOpenAIConfiguration.ApiKey); - } - else - { - config.AddAzureTextCompletionService( + } + else + { + builder.WithAzureTextCompletionService( deploymentName: azureOpenAIConfiguration.DeploymentName, endpoint: azureOpenAIConfiguration.Endpoint, apiKey: azureOpenAIConfiguration.ApiKey); - } - - if (useEmbeddings) - { - config.AddAzureTextEmbeddingGenerationService( - deploymentName: azureOpenAIEmbeddingsConfiguration.DeploymentName, - endpoint: azureOpenAIEmbeddingsConfiguration.Endpoint, - apiKey: azureOpenAIEmbeddingsConfiguration.ApiKey); - } - }); + } if (useEmbeddings) { - builder = builder.WithMemoryStorage(new VolatileMemoryStore()); + builder + .WithAzureTextEmbeddingGenerationService( + 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 5546a3007b..fa78fd901f 100644 --- a/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlanParserTests.cs +++ b/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlanParserTests.cs @@ -32,13 +32,12 @@ public class SequentialPlanParserTests Assert.NotNull(azureOpenAIConfiguration); IKernel kernel = Kernel.Builder - .Configure(config => - { - config.AddAzureTextCompletionService( - deploymentName: azureOpenAIConfiguration.DeploymentName, - endpoint: azureOpenAIConfiguration.Endpoint, - apiKey: azureOpenAIConfiguration.ApiKey); - }) + .WithAzureTextCompletionService( + 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 8c4a077812..afd4db0f7e 100644 --- a/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlannerTests.cs +++ b/dotnet/src/IntegrationTests/Planning/SequentialPlanner/SequentialPlannerTests.cs @@ -109,37 +109,30 @@ public sealed class SequentialPlannerTests : IDisposable AzureOpenAIConfiguration? azureOpenAIEmbeddingsConfiguration = this._configuration.GetSection("AzureOpenAIEmbeddings").Get(); Assert.NotNull(azureOpenAIEmbeddingsConfiguration); - var builder = Kernel.Builder - .WithLogger(this._logger) - .Configure(config => - { - if (useChatModel) - { - config.AddAzureChatCompletionService( - deploymentName: azureOpenAIConfiguration.ChatDeploymentName!, - endpoint: azureOpenAIConfiguration.Endpoint, - apiKey: azureOpenAIConfiguration.ApiKey); - } - else - { - config.AddAzureTextCompletionService( - deploymentName: azureOpenAIConfiguration.DeploymentName, - endpoint: azureOpenAIConfiguration.Endpoint, - apiKey: azureOpenAIConfiguration.ApiKey); - } + var builder = Kernel.Builder.WithLogger(this._logger); - if (useEmbeddings) - { - config.AddAzureTextEmbeddingGenerationService( - deploymentName: azureOpenAIEmbeddingsConfiguration.DeploymentName, - endpoint: azureOpenAIEmbeddingsConfiguration.Endpoint, - apiKey: azureOpenAIEmbeddingsConfiguration.ApiKey); - } - }); + if (useChatModel) + { + builder.WithAzureChatCompletionService( + deploymentName: azureOpenAIConfiguration.ChatDeploymentName!, + endpoint: azureOpenAIConfiguration.Endpoint, + apiKey: azureOpenAIConfiguration.ApiKey); + } + else + { + builder.WithAzureTextCompletionService( + deploymentName: azureOpenAIConfiguration.DeploymentName, + endpoint: azureOpenAIConfiguration.Endpoint, + apiKey: azureOpenAIConfiguration.ApiKey); + } if (useEmbeddings) { - builder = builder.WithMemoryStorage(new VolatileMemoryStore()); + builder.WithAzureTextEmbeddingGenerationService( + deploymentName: azureOpenAIEmbeddingsConfiguration.DeploymentName, + endpoint: azureOpenAIEmbeddingsConfiguration.Endpoint, + apiKey: azureOpenAIEmbeddingsConfiguration.ApiKey) + .WithMemoryStorage(new VolatileMemoryStore()); } var kernel = builder.Build(); 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 0000000000..f6edaf5a2e --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/ChatCompletionServiceExtensions.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.SemanticKernel.AI.ChatCompletion; +using Microsoft.SemanticKernel.Services; + +// Use base namespace for better discoverability and to avoid conflicts with other extensions. +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace Microsoft.SemanticKernel; +#pragma warning restore IDE0130 // Namespace does not match folder structure + +public static class ChatCompletionServiceExtensions +{ + /// + /// Get the matching the given , or + /// the default if is not provided or not found. + /// + /// The service provider. + /// Optional identifier of the desired service. + /// The completion service id matching the given id or the default. + /// Thrown when no suitable service is found. + public static IChatCompletion GetChatCompletionService( + this IAIServiceProvider services, + string? serviceId = null) => services.GetService(serviceId) + ?? throw new KernelException(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 608dd85c32..9138488f3d 100644 --- a/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/IChatCompletion.cs +++ b/dotnet/src/SemanticKernel.Abstractions/AI/ChatCompletion/IChatCompletion.cs @@ -3,10 +3,11 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Microsoft.SemanticKernel.Services; namespace Microsoft.SemanticKernel.AI.ChatCompletion; -public interface IChatCompletion +public interface IChatCompletion : IAIService { /// /// Create a new empty chat instance 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 0000000000..a15e767890 --- /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 c3128b921a..33d3e1ec7f 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 0000000000..372a2a2b51 --- /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 0000000000..613161f680 --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/AI/Embeddings/TextEmbeddingServiceExtensions.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.SemanticKernel.AI.Embeddings; +using Microsoft.SemanticKernel.Services; + +// Use base namespace for better discoverability and to avoid conflicts with other extensions. +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace Microsoft.SemanticKernel; +#pragma warning restore IDE0130 // Namespace does not match folder structure + +public static class TextEmbeddingServiceExtensions +{ + /// + /// Get the matching the given , or the default + /// if the is not provided or not found. + /// + /// The service provider. + /// Optional identifier of the desired service. + /// The embedding service matching the given id or the default service. + /// Thrown when no suitable service is found. + public static ITextEmbeddingGeneration GetTextEmbeddingService( + this IAIServiceProvider services, + string? serviceId = null) + => services.GetService(serviceId) + ?? throw new KernelException(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 4336506c43..ded72f7855 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 0000000000..17541b3e3a --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/AI/ImageGeneration/ImageGenerationServiceExtensions.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.SemanticKernel.AI.ImageGeneration; +using Microsoft.SemanticKernel.Services; + +// Use base namespace for better discoverability and to avoid conflicts with other extensions. +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace Microsoft.SemanticKernel; +#pragma warning restore IDE0130 // Namespace does not match folder structure + +public static class ImageGenerationServiceExtensions +{ + /// + /// Get the matching the given , or the default + /// if the is not provided or not found. + /// + /// The service provider. + /// Optional identifier of the desired service. + /// The id matching the given id or the default. + /// Thrown when no suitable service is found. + public static IImageGeneration GetImageGenerationService( + this IAIServiceProvider services, + string? serviceId = null) => services.GetService(serviceId) + ?? throw new KernelException(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 eb584a6b76..a84df6e838 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 { /// /// Get completion results 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 0000000000..2d592c5f6c --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/AI/TextCompletion/TextCompletionServiceExtensions.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All rights reserved. + +using Microsoft.SemanticKernel.AI.TextCompletion; +using Microsoft.SemanticKernel.Services; + +// Use base namespace for better discoverability and to avoid conflicts with other extensions. +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace Microsoft.SemanticKernel; +#pragma warning restore IDE0130 // Namespace does not match folder structure + +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/IKernel.cs b/dotnet/src/SemanticKernel.Abstractions/IKernel.cs index 1391dd5c13..7ed41af984 100644 --- a/dotnet/src/SemanticKernel.Abstractions/IKernel.cs +++ b/dotnet/src/SemanticKernel.Abstractions/IKernel.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.Memory; using Microsoft.SemanticKernel.Orchestration; using Microsoft.SemanticKernel.SemanticFunctions; +using Microsoft.SemanticKernel.Services; using Microsoft.SemanticKernel.SkillDefinition; using Microsoft.SemanticKernel.TemplateEngine; @@ -171,5 +172,5 @@ public interface IKernel /// 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 4ca40d688c..44ad00c1a8 100644 --- a/dotnet/src/SemanticKernel.Abstractions/KernelConfig.cs +++ b/dotnet/src/SemanticKernel.Abstractions/KernelConfig.cs @@ -29,21 +29,25 @@ public sealed class KernelConfig /// /// 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(); /// @@ -58,6 +62,7 @@ public sealed class KernelConfig /// 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) @@ -87,6 +92,7 @@ public sealed class KernelConfig /// 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) @@ -116,6 +122,7 @@ public sealed class KernelConfig /// 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) @@ -145,6 +152,7 @@ public sealed class KernelConfig /// 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) @@ -201,6 +209,7 @@ public sealed class KernelConfig /// 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)) @@ -220,6 +229,7 @@ public sealed class KernelConfig /// 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)) @@ -241,6 +251,7 @@ public sealed class KernelConfig /// 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(); @@ -251,6 +262,7 @@ public sealed class KernelConfig /// 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(); @@ -261,6 +273,7 @@ public sealed class KernelConfig /// 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(); diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs b/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs new file mode 100644 index 0000000000..3d2ab690bf --- /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 0000000000..5f4ff9a507 --- /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 0000000000..ca0ec11eb2 --- /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 0000000000..05c0ec77f3 --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/Services/ServiceExtensions.cs @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.SemanticKernel.Services; +internal static class AIServiceProviderExtensions +{ + /// + /// Tries to get the service of the specified type and name, and returns a value indicating whether the operation succeeded. + /// + /// The type of the service. + /// The service provider. + /// The output parameter to receive the service instance, or null if not found. + /// True if the service was found, false otherwise. + public static bool TryGetService(this IAIServiceProvider serviceProvider, + [NotNullWhen(true)] out T? service) where T : IAIService + { + service = serviceProvider.GetService(); + return service != null; + } + + /// + /// Tries to get the service of the specified type and name, and returns a value indicating whether the operation succeeded. + /// + /// The type of the service. + /// The service provider. + /// The name of the service, or null for the default service. + /// The output parameter to receive the service instance, or null if not found. + /// True if the service was found, false otherwise. + public static bool TryGetService(this IAIServiceProvider serviceProvider, + string? name, [NotNullWhen(true)] out T? service) where T : IAIService + { + service = serviceProvider.GetService(name); + return service != null; + } +} diff --git a/dotnet/src/SemanticKernel.UnitTests/AI/ChatCompletion/ChatCompletionServiceExtensionTests.cs b/dotnet/src/SemanticKernel.UnitTests/AI/ChatCompletion/ChatCompletionServiceExtensionTests.cs new file mode 100644 index 0000000000..86788477d4 --- /dev/null +++ b/dotnet/src/SemanticKernel.UnitTests/AI/ChatCompletion/ChatCompletionServiceExtensionTests.cs @@ -0,0 +1,147 @@ +// 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 0000000000..0638ce0085 --- /dev/null +++ b/dotnet/src/SemanticKernel.UnitTests/AI/Embeddings/TextEmbeddingServiceExtensionTests.cs @@ -0,0 +1,147 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System; +using Microsoft.SemanticKernel; +using Microsoft.SemanticKernel.AI.Embeddings; +using Microsoft.SemanticKernel.Services; +using Moq; +using Xunit; + +namespace SemanticKernel.UnitTests.AI.Embeddings; + +/// +/// Unit tests of . +/// +public class TextEmbeddingServiceExtensionsTests +{ + [Fact] + public void ItCanAddTextEmbeddingServiceInstance() + { + // Arrange + var services = new AIServiceCollection(); + var serviceId = "test"; + var instance = Mock.Of(); + + // Act + services.SetService(serviceId, instance); + var provider = services.Build(); + + // Assert + Assert.True(provider.TryGetService(serviceId, out var instanceRetrieved)); + Assert.Same(instance, instanceRetrieved); + } + + [Fact] + public void ItCanAddTextEmbeddingServiceFactory() + { + // Arrange + var services = new AIServiceCollection(); + var serviceId = "test"; + var instance = Mock.Of(); + var factory = new Func(() => instance); + + // Act + services.SetService(serviceId, factory); + var provider = services.Build(); + + // Assert + Assert.True(provider.TryGetService(serviceId, out _)); + } + + [Fact] + public void ItCanSetDefaultTextEmbeddingService() + { + // Arrange + var services = new AIServiceCollection(); + var serviceId1 = "test1"; + var serviceId2 = "test2"; + var instance1 = Mock.Of(); + var instance2 = Mock.Of(); + services.SetService(serviceId1, instance1); + + // Act + services.SetService(serviceId2, instance2, setAsDefault: true); + var provider = services.Build(); + + // Assert + Assert.True(provider.TryGetService(out var instanceRetrieved)); + Assert.Same(instance2, instanceRetrieved); + } + + [Fact] + public void ItReturnsFalseIfNoDefaultTextEmbeddingServiceIsSet() + { + // Arrange + var services = new AIServiceCollection(); + var provider = services.Build(); + + // Assert + Assert.False(provider.TryGetService(out var instanceRetrieved)); + } + + [Fact] + public void ItReturnsTrueIfHasTextEmbeddingServiceWithValidId() + { + // Arrange + var services = new AIServiceCollection(); + var serviceId = "test"; + var instance = Mock.Of(); + services.SetService(serviceId, instance); + var provider = services.Build(); + + // Act + var result = provider.HasTextEmbeddingService(serviceId); + + // Assert + Assert.True(result); + } + + [Fact] + public void ItReturnsFalseIfHasTextEmbeddingServiceWithInvalidId() + { + // Arrange + var services = new AIServiceCollection(); + var serviceId1 = "test1"; + var serviceId2 = "test2"; + var instance = Mock.Of(); + services.SetService(serviceId1, instance); + var provider = services.Build(); + + // Act + var result = provider.HasTextEmbeddingService(serviceId2); + + // Assert + Assert.False(result); + } + + [Fact] + public void ItReturnsTrueIfHasTextEmbeddingServiceWithNullIdAndDefaultIsSet() + { + // Arrange + var services = new AIServiceCollection(); + var serviceId = "test"; + var instance = Mock.Of(); + services.SetService(serviceId, instance, setAsDefault: true); + var provider = services.Build(); + + // Act + var result = provider.HasTextEmbeddingService(); + + // Assert + Assert.True(result); + } + + [Fact] + public void ItReturnsFalseIfHasTextEmbeddingServiceWithNullIdAndNoDefaultExists() + { + // Arrange + var services = new AIServiceCollection(); + var provider = services.Build(); + + // Act + var result = provider.HasTextEmbeddingService(); + + // Assert + Assert.False(result); + } +} diff --git a/dotnet/src/SemanticKernel.UnitTests/AI/ImageGeneration/ImageCompletionServiceExtensionTests.cs b/dotnet/src/SemanticKernel.UnitTests/AI/ImageGeneration/ImageCompletionServiceExtensionTests.cs new file mode 100644 index 0000000000..165f5e636d --- /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 0000000000..689b88d115 --- /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 f617856a59..0dfe8b2d53 100644 --- a/dotnet/src/SemanticKernel.UnitTests/CoreSkills/FileIOSkillTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/FileIOSkillTests.cs @@ -27,7 +27,7 @@ public class FileIOSkillTests 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/MathSkillTests.cs b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/MathSkillTests.cs index 6f4239bcd3..1c3cc2f405 100644 --- a/dotnet/src/SemanticKernel.UnitTests/CoreSkills/MathSkillTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/MathSkillTests.cs @@ -26,7 +26,7 @@ public class MathSkillTests 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 300339d292..138e17982b 100644 --- a/dotnet/src/SemanticKernel.UnitTests/CoreSkills/TextSkillTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/TextSkillTests.cs @@ -24,7 +24,7 @@ public class TextSkillTests 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 b6a22b2aed..9f63f9cdfa 100644 --- a/dotnet/src/SemanticKernel.UnitTests/CoreSkills/TimeSkillTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/TimeSkillTests.cs @@ -20,7 +20,7 @@ public class TimeSkillTests 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 b9c669d34d..1911a96ecd 100644 --- a/dotnet/src/SemanticKernel.UnitTests/CoreSkills/WaitSkillTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/CoreSkills/WaitSkillTests.cs @@ -23,7 +23,7 @@ public class WaitSkillTests 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 50ecb9ce1a..1507911406 100644 --- a/dotnet/src/SemanticKernel.UnitTests/KernelConfigTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/KernelConfigTests.cs @@ -76,6 +76,7 @@ public class KernelConfigTests } [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(); @@ -87,6 +88,7 @@ public class KernelConfigTests } [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(); diff --git a/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs b/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs index df8d929562..6b50359ff7 100644 --- a/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/KernelTests.cs @@ -4,6 +4,7 @@ using System; 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; @@ -18,7 +19,8 @@ namespace SemanticKernel.UnitTests; public class KernelTests { [Fact] - public void ItProvidesAccessToFunctionsViaSkillCollection() + [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(); @@ -46,7 +48,8 @@ public class KernelTests } [Fact] - public async Task ItProvidesAccessToFunctionsViaSKContextAsync() + [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(); @@ -69,11 +72,65 @@ public class KernelTests Assert.Equal("Export info.", result["mysk.readskillcollectionasync"]); } + [Fact] + public void ItProvidesAccessToFunctionsViaSkillCollection() + { + // Arrange + 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"); + 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] + public async Task ItProvidesAccessToFunctionsViaSKContextAsync() + { + // Arrange + 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"); + 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 async Task RunAsyncDoesNotRunWhenCancelledAsync() { // Arrange - var kernel = KernelBuilder.Create(); + var kernel = Kernel.Builder.Build(); var nativeSkill = new MySkill(); var skill = kernel.ImportSkill(nativeSkill, "mySk"); @@ -93,7 +150,7 @@ public class KernelTests public async Task RunAsyncRunsWhenNotCancelledAsync() { // Arrange - var kernel = KernelBuilder.Create(); + var kernel = Kernel.Builder.Build(); var nativeSkill = new MySkill(); kernel.ImportSkill(nativeSkill, "mySk"); @@ -112,7 +169,7 @@ public class KernelTests 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 +182,7 @@ public class KernelTests public void ItAllowsToImportSkillsInTheGlobalNamespace() { // Arrange - var kernel = KernelBuilder.Create(); + var kernel = Kernel.Builder.Build(); // Act IDictionary skill = kernel.ImportSkill(new MySkill()); @@ -140,7 +197,7 @@ public class KernelTests public void ItAllowsToImportTheSameSkillMultipleTimes() { // Arrange - var kernel = KernelBuilder.Create(); + var kernel = Kernel.Builder.Build(); // Act - Assert no exception occurs kernel.ImportSkill(new MySkill()); @@ -148,16 +205,6 @@ public class KernelTests 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 6931774463..3e9cae6a87 100644 --- a/dotnet/src/SemanticKernel.UnitTests/SemanticKernel.UnitTests.csproj +++ b/dotnet/src/SemanticKernel.UnitTests/SemanticKernel.UnitTests.csproj @@ -27,6 +27,7 @@ + diff --git a/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs b/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs new file mode 100644 index 0000000000..346d402bbc --- /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/Kernel.cs b/dotnet/src/SemanticKernel/Kernel.cs index 07f1d5636e..d07a8b55b6 100644 --- a/dotnet/src/SemanticKernel/Kernel.cs +++ b/dotnet/src/SemanticKernel/Kernel.cs @@ -15,6 +15,7 @@ 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 +59,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, @@ -73,6 +76,8 @@ public sealed class Kernel : IKernel, IDisposable this.Config = config; this.PromptTemplateEngine = promptTemplateEngine; this._memory = memory; + this._aiServiceProvider = aiServiceProvider; + this._promptTemplateEngine = promptTemplateEngine; this._skillCollection = skillCollection; } @@ -221,22 +226,26 @@ public sealed class Kernel : IKernel, IDisposable } /// - 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 + 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 service = factory.Invoke(this); - return (T)service; + var serv = factory.Invoke(this); + return (T)serv; } if (typeof(T) == typeof(IEmbeddingGeneration)) @@ -248,8 +257,8 @@ public sealed class Kernel : IKernel, IDisposable throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, $"'{name}' text embedding service not available"); } - var service = factory.Invoke(this); - return (T)service; + var serv = factory.Invoke(this); + return (T)serv; } if (typeof(T) == typeof(IChatCompletion)) @@ -261,8 +270,8 @@ public sealed class Kernel : IKernel, IDisposable throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, $"'{name}' chat completion service not available"); } - var service = factory.Invoke(this); - return (T)service; + var serv = factory.Invoke(this); + return (T)serv; } if (typeof(T) == typeof(IImageGeneration)) @@ -274,11 +283,12 @@ public sealed class Kernel : IKernel, IDisposable throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, $"'{name}' image generation service not available"); } - var service = factory.Invoke(this); - return (T)service; + var serv = factory.Invoke(this); + return (T)serv; } +#pragma warning restore CS0618 // Type or member is obsolete - throw new NotSupportedException("The kernel service collection doesn't support the type " + typeof(T).FullName); + throw new KernelException(KernelException.ErrorCodes.ServiceNotFound, $"Service of type {typeof(T)} and name {name ?? ""} not registered."); } /// @@ -297,6 +307,8 @@ public sealed class Kernel : IKernel, IDisposable 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 3e32ea0462..352b8a6f32 100644 --- a/dotnet/src/SemanticKernel/KernelBuilder.cs +++ b/dotnet/src/SemanticKernel/KernelBuilder.cs @@ -7,6 +7,7 @@ using Microsoft.SemanticKernel.AI.Embeddings; using Microsoft.SemanticKernel.Diagnostics; using Microsoft.SemanticKernel.Memory; using Microsoft.SemanticKernel.Reliability; +using Microsoft.SemanticKernel.Services; using Microsoft.SemanticKernel.SkillDefinition; using Microsoft.SemanticKernel.TemplateEngine; @@ -20,9 +21,11 @@ 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 IPromptTemplateEngine? _promptTemplateEngine; + private readonly AIServiceCollection _aiServices = new(); /// /// Create a new kernel instance @@ -46,11 +49,12 @@ public sealed class KernelBuilder } var instance = new Kernel( - new SkillCollection(this._log), - new PromptTemplateEngine(this._log), + new SkillCollection(this._logger), + this._aiServices.Build(), + this._promptTemplateEngine ?? new PromptTemplateEngine(this._logger), this._memory, this._config, - this._log + this._logger ); // TODO: decouple this from 'UseMemory' kernel extension @@ -70,7 +74,7 @@ public sealed class KernelBuilder public KernelBuilder WithLogger(ILogger log) { Verify.NotNull(log); - this._log = log; + this._logger = log; return this; } @@ -98,6 +102,18 @@ public sealed class KernelBuilder 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. /// @@ -105,7 +121,7 @@ public sealed class KernelBuilder /// 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 +164,54 @@ public sealed class KernelBuilder configure.Invoke(this._config); return this; } + + /// + /// Adds a instance to the services collection + /// + /// The instance. + public KernelBuilder WithDefaultAIService(TService instance) where TService : IAIService + { + this._aiServices.SetService(instance); + return this; + } + + /// + /// Adds a 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 . + public KernelBuilder WithDefaultAIService(Func factory) where TService : IAIService + { + this._aiServices.SetService(() => factory(this._logger)); + 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<(ILogger Logger, KernelConfig Config), TService> factory, + bool setAsDefault = false) where TService : IAIService + { + this._aiServices.SetService(serviceId, () => factory((this._logger, this._config)), setAsDefault); + return this; + } } diff --git a/dotnet/src/SemanticKernel/Memory/MemoryConfiguration.cs b/dotnet/src/SemanticKernel/Memory/MemoryConfiguration.cs index f3247c5a98..caa1776709 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 class MemoryConfiguration /// 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 7535c285be..57b94fc826 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 0000000000..dc61b4bda1 --- /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 new AIServiceProvider(servicesClone, defaultsClone); + } + + private bool HasDefault() where T : IAIService + => this._defaultIds.TryGetValue(typeof(T), out var defaultName) + && !string.IsNullOrEmpty(defaultName); +} diff --git a/dotnet/src/SemanticKernel/Services/AIServiceProvider.cs b/dotnet/src/SemanticKernel/Services/AIServiceProvider.cs new file mode 100644 index 0000000000..cf5d9f0df8 --- /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; +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 new file mode 100644 index 0000000000..6899bbf997 --- /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; +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(); + private readonly Dictionary>> _services; + + // A dictionary that maps a service type to the name of the default service + private readonly Dictionary _defaultIds; + + public NamedServiceProvider( + Dictionary>> services, + Dictionary defaultIds) + { + this._services = services; + this._defaultIds = defaultIds; + } + + /// + public T? GetService(string? name = null) where T : TService + { + // Return the service, casting or invoking the factory if needed + var factory = this.GetServiceFactory(name); + if (factory is Func) + { + return factory.Invoke(); + } + + return default; + } + + /// + private string? GetDefaultServiceName() where T : TService + { + // Returns the name of the default service for the given type, or null if none + var type = typeof(T); + if (this._defaultIds.TryGetValue(type, out var name)) + { + return name; + } + + return null; + } + + 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 2fce212eda..1d883de09c 100644 --- a/dotnet/src/SemanticKernel/SkillDefinition/SKFunction.cs +++ b/dotnet/src/SemanticKernel/SkillDefinition/SKFunction.cs @@ -229,7 +229,7 @@ public sealed class SKFunction : ISKFunction, IDisposable { Verify.NotNull(serviceFactory); this.VerifyIsSemantic(); - this._aiService = serviceFactory.Invoke(); + this._aiService = new Lazy(serviceFactory); return this; } @@ -279,7 +279,7 @@ public sealed class SKFunction : ISKFunction, IDisposable 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 @@ -345,9 +345,15 @@ public sealed class SKFunction : ISKFunction, IDisposable private void ReleaseUnmanagedResources() { - if (this._aiService is not IDisposable disposable) { return; } + if (this._aiService == null || !this._aiService.IsValueCreated) + { + return; + } - disposable.Dispose(); + if (this._aiService.Value is IDisposable disposable) + { + disposable.Dispose(); + } } /// @@ -374,7 +380,7 @@ public sealed class SKFunction : ISKFunction, IDisposable 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.UnitTests/Web/SearchUrlSkillTests.cs b/dotnet/src/Skills/Skills.UnitTests/Web/SearchUrlSkillTests.cs index 041137150b..0536c2ca67 100644 --- a/dotnet/src/Skills/Skills.UnitTests/Web/SearchUrlSkillTests.cs +++ b/dotnet/src/Skills/Skills.UnitTests/Web/SearchUrlSkillTests.cs @@ -23,7 +23,7 @@ public class SearchUrlSkillTests 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/dotnet/KernelBuilder/Program.cs b/samples/dotnet/KernelBuilder/Program.cs index 918df062f1..c1c055812e 100644 --- a/samples/dotnet/KernelBuilder/Program.cs +++ b/samples/dotnet/KernelBuilder/Program.cs @@ -10,8 +10,11 @@ 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(); +#pragma warning disable CA1852 // Seal internal types +IKernel kernel1 = Kernel.Builder.Build(); +#pragma warning restore CA1852 // Seal internal types IKernel kernel2 = Kernel.Builder.Build(); @@ -49,13 +52,16 @@ var memory = new SemanticTextMemory(memoryStorage, textEmbeddingGenerator); var skills = new SkillCollection(); var templateEngine = new PromptTemplateEngine(logger); var config = new KernelConfig(); + var httpHandler = new DefaultHttpRetryHandler(new HttpRetryConfig(), logger); var httpClient = new HttpClient(httpHandler); -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 @@ -65,10 +71,7 @@ var kernel3 = new Kernel(skills, templateEngine, memory, config, logger); var kernel4 = Kernel.Builder .WithLogger(NullLogger.Instance) .WithMemory(memory) - .Configure(c => - { - c.AddAzureTextCompletionService("deploymentName", "https://...", "apiKey"); - }) + .WithAzureTextCompletionService("deploymentName", "https://...", "apiKey") .Build(); // Example: how to use a custom memory storage and custom embedding generator @@ -81,36 +84,17 @@ var kernel5 = Kernel.Builder 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"); - }) + .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 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"); - }) + .WithAzureTextCompletionService("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 d474dba386..72af718609 100644 --- a/samples/dotnet/KernelHttpServer/SemanticKernelFactory.cs +++ b/samples/dotnet/KernelHttpServer/SemanticKernelFactory.cs @@ -43,53 +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.WithOpenAITextCompletionService( + config.CompletionConfig.DeploymentOrModelId, + config.CompletionConfig.Key, + config.CompletionConfig.ServiceId); + break; + case AIService.AzureOpenAI: + builder.WithAzureTextCompletionService( + 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.AddOpenAIChatCompletionService( - modelId: config.CompletionConfig.DeploymentOrModelId, - apiKey: config.CompletionConfig.Key, - serviceId: config.CompletionConfig.ServiceId, - alsoAsTextCompletion: true); + builder.WithOpenAITextEmbeddingGenerationService( + config.EmbeddingConfig.DeploymentOrModelId, + config.EmbeddingConfig.Key, + serviceId: config.EmbeddingConfig.ServiceId); break; case AIService.AzureOpenAI: - c.AddAzureChatCompletionService( - deploymentName: config.CompletionConfig.DeploymentOrModelId, - endpoint: config.CompletionConfig.Endpoint, - apiKey: config.CompletionConfig.Key, - serviceId: config.CompletionConfig.ServiceId, - alsoAsTextCompletion: true); + builder.WithAzureTextEmbeddingGenerationService( + 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 5c57363b5b..8d1ddebe03 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 6bf8a04436..943cb4294e 100644 --- a/samples/dotnet/graph-api-skills/Program.cs +++ b/samples/dotnet/graph-api-skills/Program.cs @@ -65,6 +65,18 @@ public sealed class Program 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. @@ -95,22 +107,20 @@ public sealed class Program 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.WithAzureTextCompletionService( deploymentName: azureOpenAIConfiguration.DeploymentName, endpoint: azureOpenAIConfiguration.Endpoint, apiKey: azureOpenAIConfiguration.ApiKey, - serviceId: azureOpenAIConfiguration.ServiceId); + serviceId: azureOpenAIConfiguration.ServiceId, + setAsDefault: azureOpenAIConfiguration.ServiceId == defaultCompletionServiceId); } } @@ -119,25 +129,19 @@ public sealed class Program OpenAIConfiguration? openAIConfiguration = configuration.GetSection("OpenAI").Get(); if (openAIConfiguration != null) { - sk.Config.AddOpenAITextCompletionService( + builder.WithOpenAITextCompletionService( 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."); - } + IKernel sk = builder.Build(); - sk.Config.SetDefaultTextCompletionService(defaultCompletionServiceId); - - 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 = RepoUtils.RepoFiles.SampleSkillsPath(); diff --git a/samples/dotnet/kernel-syntax-examples/Example04_CombineLLMPromptsAndNativeCode.cs b/samples/dotnet/kernel-syntax-examples/Example04_CombineLLMPromptsAndNativeCode.cs index 2766458f13..fa6d81ac27 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 class Example04_CombineLLMPromptsAndNativeCode { 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) + .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 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 458a5987f6..7f30437a83 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 class Example05_InlineFunctionDefinition * 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) + .WithOpenAITextCompletionService("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 c6d050e2bd..c0138d03b3 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 class Example06_TemplateLanguage { 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) + .WithOpenAITextCompletionService("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 e7c1a00f83..b96ae2f6c6 100644 --- a/samples/dotnet/kernel-syntax-examples/Example07_BingAndGoogleSkills.cs +++ b/samples/dotnet/kernel-syntax-examples/Example07_BingAndGoogleSkills.cs @@ -21,9 +21,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) + .WithOpenAITextCompletionService("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 f95991077e..7d76031b96 100644 --- a/samples/dotnet/kernel-syntax-examples/Example08_RetryHandler.cs +++ b/samples/dotnet/kernel-syntax-examples/Example08_RetryHandler.cs @@ -47,7 +47,7 @@ public static class Example08_RetryHandler 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.WithOpenAITextCompletionService("text-davinci-003", "BAD_KEY"); var kernel = kernelBuilder.Build(); @@ -56,9 +56,11 @@ public static class Example08_RetryHandler 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 + .WithOpenAITextCompletionService("text-davinci-003", "BAD_KEY") + .Build(); return kernel; } @@ -71,13 +73,11 @@ public static class Example08_RetryHandler 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 + .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 c42a5b3131..7fc8086aaf 100644 --- a/samples/dotnet/kernel-syntax-examples/Example09_FunctionTypes.cs +++ b/samples/dotnet/kernel-syntax-examples/Example09_FunctionTypes.cs @@ -18,8 +18,10 @@ public static class Example09_FunctionTypes var fakeContext = new SKContext(logger: 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) + .WithOpenAITextCompletionService("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 b0680a583b..9ca8ccd4f6 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 class Example10_DescribeAllSkillsAndFunctions { Console.WriteLine("======== Describe all skills and functions ========"); - var kernel = KernelBuilder.Create(); - kernel.Config.AddOpenAITextCompletionService("text-davinci-003", "none"); + var kernel = Kernel.Builder + .WithOpenAITextCompletionService("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 49243c7ad5..3f8d4337a5 100644 --- a/samples/dotnet/kernel-syntax-examples/Example12_SequentialPlanner.cs +++ b/samples/dotnet/kernel-syntax-examples/Example12_SequentialPlanner.cs @@ -25,11 +25,14 @@ internal static class Example12_SequentialPlanner 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) + .WithAzureTextCompletionService( + 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 @@ internal static class Example12_SequentialPlanner var kernel = new KernelBuilder() .WithLogger(ConsoleLogger.Log) - .Configure( - config => - { - config.AddAzureTextCompletionService( + .WithAzureTextCompletionService( Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), Env.Var("AZURE_OPENAI_ENDPOINT"), - Env.Var("AZURE_OPENAI_KEY")); - - config.AddAzureTextEmbeddingGenerationService( + Env.Var("AZURE_OPENAI_KEY")) + .WithAzureTextEmbeddingGenerationService( 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 @@ internal static class Example12_SequentialPlanner 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) + .WithAzureTextCompletionService( + 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 f6078236fe..135386ee61 100644 --- a/samples/dotnet/kernel-syntax-examples/Example13_ConversationSummarySkill.cs +++ b/samples/dotnet/kernel-syntax-examples/Example13_ConversationSummarySkill.cs @@ -178,11 +178,13 @@ Jane: Goodbye! 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) + .WithAzureTextCompletionService( + 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 00f034396f..d51cf4c597 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 class Example14_SemanticMemory var kernelWithCustomDb = Kernel.Builder .WithLogger(ConsoleLogger.Log) - .Configure(c => c.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 0e305b4000..d972be6bfb 100644 --- a/samples/dotnet/kernel-syntax-examples/Example15_MemorySkill.cs +++ b/samples/dotnet/kernel-syntax-examples/Example15_MemorySkill.cs @@ -17,11 +17,8 @@ public static class Example15_MemorySkill { 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")); - }) + .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/Example16_CustomLLM.cs b/samples/dotnet/kernel-syntax-examples/Example16_CustomLLM.cs index 33a70b5608..553138deea 100644 --- a/samples/dotnet/kernel-syntax-examples/Example16_CustomLLM.cs +++ b/samples/dotnet/kernel-syntax-examples/Example16_CustomLLM.cs @@ -85,12 +85,13 @@ public static class Example16_CustomLLM { 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 e511677e48..978a97e4d8 100644 --- a/samples/dotnet/kernel-syntax-examples/Example17_ChatGPT.cs +++ b/samples/dotnet/kernel-syntax-examples/Example17_ChatGPT.cs @@ -52,10 +52,11 @@ public static class Example17_ChatGPT { Console.WriteLine("======== Open AI - 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) + .WithOpenAIChatCompletionService("gpt-3.5-turbo", Env.Var("OPENAI_API_KEY")) + .Build(); IChatCompletion chatGPT = kernel.GetService(); @@ -66,13 +67,13 @@ public static class Example17_ChatGPT { 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/Example18_DallE.cs b/samples/dotnet/kernel-syntax-examples/Example18_DallE.cs index 62b69deb16..79f034776d 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 class Example18_DallE { 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 + .WithOpenAIImageGenerationService("dallE", Env.Var("OPENAI_API_KEY")) + // Add your chat completion service + .WithOpenAIChatCompletionService("gpt-3.5-turbo", Env.Var("OPENAI_API_KEY")) + .Build(); IImageGeneration dallE = kernel.GetService(); @@ -41,9 +44,6 @@ public static class Example18_DallE 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 62c528e139..b9af42703b 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 class Example19_Qdrant QdrantMemoryStore memoryStore = new(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")); - }) + .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/Example20_HuggingFace.cs b/samples/dotnet/kernel-syntax-examples/Example20_HuggingFace.cs index 6b4156322e..8e0643421a 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 class Example20_HuggingFace { 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 9b6b7486a3..c947c1f6da 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 class Example26_AADAuth { 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 class Example26_AADAuth 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 + .WithAzureChatCompletionService( + "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 89d401c28e..7f4c34b01d 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 class Example27_SemanticFunctionsUsingChatGPT { 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 + .WithAzureChatCompletionService("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 7a79567172..9a9fd7b1f2 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) + .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(); 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 6d935322ed..fca2572e24 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 class Example30_ChatWithPrompts 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) + .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. // 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 5ae1e2a90b..7d9ce0408a 100644 --- a/samples/dotnet/kernel-syntax-examples/Example31_CustomPlanner.cs +++ b/samples/dotnet/kernel-syntax-examples/Example31_CustomPlanner.cs @@ -120,19 +120,14 @@ internal static class Example31_CustomPlanner { 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")); - }) + .WithAzureTextCompletionService( + Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), + Env.Var("AZURE_OPENAI_ENDPOINT"), + Env.Var("AZURE_OPENAI_KEY")) + .WithAzureTextEmbeddingGenerationService( + 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 88dc5b35c7..c6aaae7d61 100644 --- a/samples/dotnet/kernel-syntax-examples/Example32_StreamingCompletion.cs +++ b/samples/dotnet/kernel-syntax-examples/Example32_StreamingCompletion.cs @@ -22,11 +22,13 @@ public static class Example32_StreamingCompletion { 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) + .WithAzureTextCompletionService( + 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 @@ public static class Example32_StreamingCompletion { 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) + .WithOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY"), serviceId: "text-davinci-003") + .Build(); ITextCompletion textCompletion = kernel.GetService(); diff --git a/samples/dotnet/kernel-syntax-examples/Example33_StreamingChat.cs b/samples/dotnet/kernel-syntax-examples/Example33_StreamingChat.cs index b93ece5288..b23171d20c 100644 --- a/samples/dotnet/kernel-syntax-examples/Example33_StreamingChat.cs +++ b/samples/dotnet/kernel-syntax-examples/Example33_StreamingChat.cs @@ -24,10 +24,10 @@ public static class Example33_StreamingChat { 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 @@ public static class Example33_StreamingChat { 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 1d0d0f25a9..ab41bd8adb 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.Linq; 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 @@ public static class Example34_CustomChatModel { 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 @@ public static class Example34_CustomChatModel { 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(); diff --git a/samples/dotnet/kernel-syntax-examples/Example36_MultiCompletion.cs b/samples/dotnet/kernel-syntax-examples/Example36_MultiCompletion.cs index 51fdf84c2f..56a8a5df32 100644 --- a/samples/dotnet/kernel-syntax-examples/Example36_MultiCompletion.cs +++ b/samples/dotnet/kernel-syntax-examples/Example36_MultiCompletion.cs @@ -22,11 +22,12 @@ public static class Example36_MultiCompletion { Console.WriteLine("======== Azure OpenAI - Multiple Text Completion ========"); - 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() + .WithAzureTextCompletionService( + Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), + Env.Var("AZURE_OPENAI_ENDPOINT"), + Env.Var("AZURE_OPENAI_KEY")) + .WithLogger(ConsoleLogger.Log).Build(); ITextCompletion textCompletion = kernel.GetService(); @@ -37,8 +38,9 @@ public static class Example36_MultiCompletion { Console.WriteLine("======== Open AI - Multiple Text Completion ========"); - 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() + .WithOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY"), serviceId: "text-davinci-003") + .WithLogger(ConsoleLogger.Log).Build(); ITextCompletion textCompletion = kernel.GetService(); diff --git a/samples/dotnet/kernel-syntax-examples/Example37_MultiStreamingCompletion.cs b/samples/dotnet/kernel-syntax-examples/Example37_MultiStreamingCompletion.cs index 200da0a675..8a8697fff9 100644 --- a/samples/dotnet/kernel-syntax-examples/Example37_MultiStreamingCompletion.cs +++ b/samples/dotnet/kernel-syntax-examples/Example37_MultiStreamingCompletion.cs @@ -25,11 +25,12 @@ public static class Example37_MultiStreamingCompletion { Console.WriteLine("======== Azure OpenAI - Multiple 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() + .WithAzureTextCompletionService( + Env.Var("AZURE_OPENAI_DEPLOYMENT_NAME"), + Env.Var("AZURE_OPENAI_ENDPOINT"), + Env.Var("AZURE_OPENAI_KEY")) + .WithLogger(ConsoleLogger.Log).Build(); ITextCompletion textCompletion = kernel.GetService(); @@ -40,8 +41,9 @@ public static class Example37_MultiStreamingCompletion { Console.WriteLine("======== Open AI - Multiple 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() + .WithOpenAITextCompletionService("text-davinci-003", Env.Var("OPENAI_API_KEY"), serviceId: "text-davinci-003") + .WithLogger(ConsoleLogger.Log).Build(); ITextCompletion textCompletion = kernel.GetService(); diff --git a/samples/dotnet/kernel-syntax-examples/Example38_Pinecone.cs b/samples/dotnet/kernel-syntax-examples/Example38_Pinecone.cs index 22eaae78c7..9c048fe8d6 100644 --- a/samples/dotnet/kernel-syntax-examples/Example38_Pinecone.cs +++ b/samples/dotnet/kernel-syntax-examples/Example38_Pinecone.cs @@ -29,11 +29,8 @@ public static class Example38_Pinecone 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")); - }) + .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/Example39_Postgres.cs b/samples/dotnet/kernel-syntax-examples/Example39_Postgres.cs index fecbec4244..f1ea702cff 100644 --- a/samples/dotnet/kernel-syntax-examples/Example39_Postgres.cs +++ b/samples/dotnet/kernel-syntax-examples/Example39_Postgres.cs @@ -18,11 +18,8 @@ public static class Example39_Postgres using PostgresMemoryStore memoryStore = await PostgresMemoryStore.ConnectAsync(connectionString, vectorSize: 1536); 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")); - }) + .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/Example40_DIContainer.cs b/samples/dotnet/kernel-syntax-examples/Example40_DIContainer.cs new file mode 100644 index 0000000000..a7de83da84 --- /dev/null +++ b/samples/dotnet/kernel-syntax-examples/Example40_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 Example40_DIContainer +{ + public static async Task RunAsync() + { + await UseKernelInDIPowerAppAsync(); + + await UseKernelInDIPowerApp_AdvancedScenarioAsync(); + } + + /// + /// This example shows how to register a Kernel in a DI container using KernelBuilder instead of + /// registering its dependencies. + /// + private static async Task UseKernelInDIPowerAppAsync() + { + //Bootstrapping code that initializes the modules, components, and classes that applications use. + //For regular .NET applications, the bootstrapping code usually resides either in the Main method or very close to it. + //In ASP.NET Core applications, the bootstrapping code is typically located in the ConfigureServices method of the Startup class. + + //Registering Kernel dependencies + var collection = new ServiceCollection(); + collection.AddTransient((_) => ConsoleLogger.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 b03a8ad554..a30f33d8a3 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 0e3763ed41..ff3ce1bf99 100644 --- a/samples/dotnet/kernel-syntax-examples/Program.cs +++ b/samples/dotnet/kernel-syntax-examples/Program.cs @@ -124,5 +124,8 @@ public static class Program await Example39_Postgres.RunAsync(); Console.WriteLine("== DONE =="); + + await Example40_DIContainer.RunAsync(); + Console.WriteLine("== DONE =="); } }