From 67a23c0f93cf39bcb2e98a7bedf97eeda04861a4 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft Date: Thu, 26 Oct 2023 09:02:35 +0100 Subject: [PATCH 01/18] Intial version of 0016-aiservice-metadata.md --- docs/decisions/0016-aiservice-metadata.md | 74 +++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 docs/decisions/0016-aiservice-metadata.md diff --git a/docs/decisions/0016-aiservice-metadata.md b/docs/decisions/0016-aiservice-metadata.md new file mode 100644 index 000000000000..45e3dfc6dfd5 --- /dev/null +++ b/docs/decisions/0016-aiservice-metadata.md @@ -0,0 +1,74 @@ +--- +# These are optional elements. Feel free to remove any of them. +status: {proposed} +date: {2023-10-25} +deciders: semenshi, markwallace, rbarreto, dmytrostruk +consulted: +informed: +--- +# Add AI Service Metadata + +## Context and Problem Statement + +{Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. + You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.} + +Current implementation of `IAIService` is empty. + +```csharp +public interface IAIService +{ +} +``` + +We can retrieve `IAIService` instances using `T IKernel.GetService(string? name = null) where T : IAIService;` i.e., by service type and name (aka service id). +The concrete instance of an `IAIService` can have different attributes depending on the service provider e.g. Azure OpenAI has a deployment name and OpenAI services have a model id. + +Consider the following code snippet: + +```csharp +IKernel kernel = new KernelBuilder() + .WithLoggerFactory(ConsoleLogger.LoggerFactory) + .WithAzureChatCompletionService( + deploymentName: chatDeploymentName, + endpoint: endpoint, + serviceId: "AzureOpenAIChat", + apiKey: apiKey) + .WithOpenAIChatCompletionService( + modelId: openAIModelId, + serviceId: "OpenAIChat", + apiKey: openAIApiKey) + .Build(); +``` + +For Azure OpenAI we create the service with a deployment name. This is an arbitrary name specified by the person who deployed the AI model e.g. it could be `eastus-gpt-4` or `foo-bar`. +For OpenAI we create the service with a model id. This must match one of the deployed OpenAI models. + +From the perspective of a prompt creator using OpenAI, they will typically tune their prompts based on the model. So when the prompt is executed we need to be able to retrieve the service using the model id. + +```csharp +var service = kernel.GetService("OpenAIChat"); +``` + +The code snippet above shows the API that `IKernel` supports to retrieve an `IAService` instance. + +The `IChatCompletion` is a generic interface so it doesn't contain any properties which provide information about a specific connector instance. + +## Decision Drivers + +* We need a mechanism to store generic metadata for an `IAIService` instance. + * It will be the responsibility of the concrete `IAIService` instance to store the metadata that is relevant e.g., model id for OpenAI and HuggingFace AI services. +* We need to be able to iterate over the available `IAIService` instances. +* … + +## Considered Options + +* {title of option 1} +* {title of option 2} +* {title of option 3} +* … + +## Decision Outcome + +Chosen option: "{title of option 1}", because +{justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | … | comes out best (see below)}. From af49eff765ea9e75640dcd67b1f08d306b7a5bc0 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft Date: Tue, 31 Oct 2023 14:32:08 +0000 Subject: [PATCH 02/18] Define new abstractions --- ...metadata.md => 0018-aiservice-metadata.md} | 24 +++++++------------ .../Services/IAIService.cs | 5 ++++ .../Services/INamedServiceProvider.cs | 9 +++++++ 3 files changed, 23 insertions(+), 15 deletions(-) rename docs/decisions/{0016-aiservice-metadata.md => 0018-aiservice-metadata.md} (69%) diff --git a/docs/decisions/0016-aiservice-metadata.md b/docs/decisions/0018-aiservice-metadata.md similarity index 69% rename from docs/decisions/0016-aiservice-metadata.md rename to docs/decisions/0018-aiservice-metadata.md index 45e3dfc6dfd5..4020e8c37bb6 100644 --- a/docs/decisions/0016-aiservice-metadata.md +++ b/docs/decisions/0018-aiservice-metadata.md @@ -10,8 +10,12 @@ informed: ## Context and Problem Statement -{Describe the context and problem statement, e.g., in free form using two to three sentences or in the form of an illustrative story. - You may want to articulate the problem in form of a question and add links to collaboration boards or issue management systems.} +Developers need to be able to know more information about the `IAIService` that will eb used to execute a semantic function or a plan. +Some examples of why they need this information: + +1. As an SK developer I want to write a `IAIServiceSelector` which allows me to select the OpenAI service to used based on the configured model id so that I cna select the optimum (could eb cheapest) model to use based on the prompt I am executing. +2. As an SK developer I want to write a pre-invocation hook which will compute the token size of a prompt before the prompt is sent to the LLM, so that I can determine the optimum `IAIService` to use. The library I am using to compute the token size of the prompt requires the model id. + Current implementation of `IAIService` is empty. @@ -39,34 +43,24 @@ IKernel kernel = new KernelBuilder() serviceId: "OpenAIChat", apiKey: openAIApiKey) .Build(); -``` - -For Azure OpenAI we create the service with a deployment name. This is an arbitrary name specified by the person who deployed the AI model e.g. it could be `eastus-gpt-4` or `foo-bar`. -For OpenAI we create the service with a model id. This must match one of the deployed OpenAI models. - -From the perspective of a prompt creator using OpenAI, they will typically tune their prompts based on the model. So when the prompt is executed we need to be able to retrieve the service using the model id. -```csharp var service = kernel.GetService("OpenAIChat"); ``` -The code snippet above shows the API that `IKernel` supports to retrieve an `IAService` instance. +For Azure OpenAI we create the service with a deployment name. This is an arbitrary name specified by the person who deployed the AI model e.g. it could be `eastus-gpt-4` or `foo-bar`. +For OpenAI we create the service with a model id. This must match one of the deployed OpenAI models. -The `IChatCompletion` is a generic interface so it doesn't contain any properties which provide information about a specific connector instance. +From the perspective of a prompt creator using OpenAI, they will typically tune their prompts based on the model. So when the prompt is executed we need to be able to retrieve the service using the model id. As shown in the code snippet above the `IKernel` only supports retrieving an `IAService` instance by id. Additionally the `IChatCompletion` is a generic interface so it doesn't contain any properties which provide information about a specific connector instance. ## Decision Drivers * We need a mechanism to store generic metadata for an `IAIService` instance. * It will be the responsibility of the concrete `IAIService` instance to store the metadata that is relevant e.g., model id for OpenAI and HuggingFace AI services. * We need to be able to iterate over the available `IAIService` instances. -* … ## Considered Options * {title of option 1} -* {title of option 2} -* {title of option 3} -* … ## Decision Outcome diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs b/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs index 54085c6f5b4b..f9ef99d2faf7 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Microsoft.SemanticKernel.Services; @@ -10,4 +11,8 @@ namespace Microsoft.SemanticKernel.Services; [SuppressMessage("Design", "CA1040:Avoid empty interfaces")] public interface IAIService { + /// + /// Gets the AI service metadata. + /// + IReadOnlyDictionary Metadata { get; } } diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/INamedServiceProvider.cs b/dotnet/src/SemanticKernel.Abstractions/Services/INamedServiceProvider.cs index 2cb9263f8b86..ed003cd2e360 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Services/INamedServiceProvider.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Services/INamedServiceProvider.cs @@ -1,5 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. +using System.Collections.Generic; + namespace Microsoft.SemanticKernel.Services; /// @@ -15,4 +17,11 @@ public interface INamedServiceProvider /// The name of the service, or null for the default service. /// The service instance, or null if not found. T? GetService(string? name = null) where T : TService; + + /// + /// Gets all services of the specified type, or an empty collection of none are found. + /// + /// The type of the service. + /// Collection of services of the specified type, or an empty collection of none are found + ICollection GetServices() where T : TService; } From fe15315ebbb983e31256c2d1a5842c10c2eaf5f7 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft Date: Tue, 31 Oct 2023 14:32:25 +0000 Subject: [PATCH 03/18] Define new abstractions --- docs/decisions/0018-aiservice-metadata.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/decisions/0018-aiservice-metadata.md b/docs/decisions/0018-aiservice-metadata.md index 4020e8c37bb6..fe4f7e177d15 100644 --- a/docs/decisions/0018-aiservice-metadata.md +++ b/docs/decisions/0018-aiservice-metadata.md @@ -60,7 +60,10 @@ From the perspective of a prompt creator using OpenAI, they will typically tune ## Considered Options -* {title of option 1} +* Option #1 + * Extend `IAIService` to include this property `IReadOnlyDictionary Metadata { get; }` which returns the metadata as a readonly dictionary. It will be the responsibility of each `IAIService` implementation to populate this with the appropriate metadata. + * Extend `INamedServiceProvider` to include this method `ICollection GetServices() where T : TService;` + * Extend `OpenAIKernelBuilderExtensions` so that `WithAzureXXX` methods will include a `modelId` property if a specific model can be targeted. ## Decision Outcome From 96651a3e8ccbd94db05a36a8d70ad148b2ac6c7f Mon Sep 17 00:00:00 2001 From: markwallace-microsoft Date: Thu, 2 Nov 2023 14:27:24 +0000 Subject: [PATCH 04/18] Update ADR and also start POC to test out how well this proposed change will work --- docs/decisions/0018-aiservice-metadata.md | 5 ++++ .../Example16_CustomLLM.cs | 2 ++ .../Example34_CustomChatModel.cs | 2 ++ .../Example62_CustomAIServiceSelector.cs | 14 +++++++++- .../samples/KernelSyntaxExamples/Program.cs | 2 +- .../HuggingFaceTextCompletion.cs | 4 +++ .../HuggingFaceTextEmbeddingGeneration.cs | 4 +++ .../ChatCompletion/AzureChatCompletion.cs | 9 ++++++ .../ChatCompletion/OpenAIChatCompletion.cs | 7 +++++ .../AzureChatCompletionWithData.cs | 5 ++++ .../AzureOpenAIImageGeneration.cs | 7 +++++ .../ImageGeneration/OpenAIImageGeneration.cs | 7 +++++ .../TextCompletion/AzureTextCompletion.cs | 5 ++++ .../TextCompletion/OpenAITextCompletion.cs | 5 ++++ .../AzureTextEmbeddingGeneration.cs | 6 ++++ .../OpenAITextEmbeddingGeneration.cs | 5 ++++ .../KernelSemanticFunctionExtensionsTests.cs | 2 ++ .../Services/NamedServiceProvider.cs | 28 +++++++++++++++++++ ...redIAIServiceConfigurationProviderTests.cs | 3 ++ .../Services/ServiceRegistryTests.cs | 2 ++ 20 files changed, 122 insertions(+), 2 deletions(-) diff --git a/docs/decisions/0018-aiservice-metadata.md b/docs/decisions/0018-aiservice-metadata.md index fe4f7e177d15..e1ae5b6aac61 100644 --- a/docs/decisions/0018-aiservice-metadata.md +++ b/docs/decisions/0018-aiservice-metadata.md @@ -65,6 +65,11 @@ From the perspective of a prompt creator using OpenAI, they will typically tune * Extend `INamedServiceProvider` to include this method `ICollection GetServices() where T : TService;` * Extend `OpenAIKernelBuilderExtensions` so that `WithAzureXXX` methods will include a `modelId` property if a specific model can be targeted. +These methods would be used as follows: + +``` csharp +``` + ## Decision Outcome Chosen option: "{title of option 1}", because diff --git a/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs b/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs index e5f6405f7824..902668f92b9c 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs @@ -29,6 +29,8 @@ */ public class MyTextCompletionService : ITextCompletion { + public IReadOnlyDictionary Metadata => new Dictionary(); + public Task> GetCompletionsAsync(string text, AIRequestSettings? requestSettings, CancellationToken cancellationToken = default) { return Task.FromResult>(new List diff --git a/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs b/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs index c9e0c399d2de..38997fc3dc06 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs @@ -20,6 +20,8 @@ */ public sealed class MyChatCompletionService : IChatCompletion { + public IReadOnlyDictionary Metadata => new Dictionary(); + public ChatHistory CreateNewChat(string? instructions = null) { var chatHistory = new MyChatHistory(); diff --git a/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs b/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs index bd339078859a..79df69059d22 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs @@ -6,6 +6,7 @@ using Microsoft.ML.Tokenizers; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI; +using Microsoft.SemanticKernel.AI.ChatCompletion; using Microsoft.SemanticKernel.Connectors.AI.OpenAI; using Microsoft.SemanticKernel.Diagnostics; using Microsoft.SemanticKernel.Services; @@ -20,7 +21,7 @@ public static class Example62_CustomAIServiceSelector /// public static async Task RunAsync() { - Console.WriteLine("======== Example61_CustomAIServiceSelector ========"); + Console.WriteLine("======== Example62_CustomAIServiceSelector ========"); string apiKey = TestConfiguration.AzureOpenAI.ApiKey; string chatDeploymentName = TestConfiguration.AzureOpenAI.ChatDeploymentName; @@ -89,6 +90,17 @@ public class MyAIServiceSelector : IAIServiceSelector public (T?, AIRequestSettings?) SelectAIService(string renderedPrompt, IAIServiceProvider serviceProvider, IReadOnlyList? modelSettings) where T : IAIService { + var chatServices = serviceProvider.GetServices(); + foreach (var service in chatServices) + { + Console.WriteLine($"Service: {service.Metadata}"); + } + var services = serviceProvider.GetServices(); + foreach (var service in services) + { + Console.WriteLine($"Service: {service.Metadata}"); + } + if (modelSettings is null || modelSettings.Count == 0) { var service = serviceProvider.GetService(null); diff --git a/dotnet/samples/KernelSyntaxExamples/Program.cs b/dotnet/samples/KernelSyntaxExamples/Program.cs index de2b07fdf982..745873c2a71c 100644 --- a/dotnet/samples/KernelSyntaxExamples/Program.cs +++ b/dotnet/samples/KernelSyntaxExamples/Program.cs @@ -21,7 +21,7 @@ public static async Task Main(string[] args) using CancellationTokenSource cancellationTokenSource = new(); CancellationToken cancelToken = cancellationTokenSource.ConsoleCancellationToken(); - string? defaultFilter = null; // Modify to filter examples + string? defaultFilter = "Example62"; // Modify to filter examples // Check if args[0] is provided string? filter = args.Length > 0 ? args[0] : defaultFilter; diff --git a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs index e57690f76f98..c93d09e081c8 100644 --- a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs @@ -25,6 +25,7 @@ public sealed class HuggingFaceTextCompletion : ITextCompletion private readonly string? _endpoint; private readonly HttpClient _httpClient; private readonly string? _apiKey; + private readonly Dictionary _metadata = new(); /// /// Initializes a new instance of the class. @@ -62,6 +63,9 @@ public HuggingFaceTextCompletion(string model, string? apiKey = null, HttpClient this._endpoint = endpoint; } + /// + public IReadOnlyDictionary Metadata => this._metadata; + /// [Obsolete("Streaming capability is not supported, use GetCompletionsAsync instead")] public IAsyncEnumerable GetStreamingCompletionsAsync( diff --git a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs index 103212317b7c..6ff24fa873d7 100644 --- a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs @@ -22,6 +22,7 @@ public sealed class HuggingFaceTextEmbeddingGeneration : ITextEmbeddingGeneratio private readonly string _model; private readonly string? _endpoint; private readonly HttpClient _httpClient; + private readonly Dictionary _metadata = new(); /// /// Initializes a new instance of the class. @@ -77,6 +78,9 @@ public HuggingFaceTextEmbeddingGeneration(string model, HttpClient httpClient, s } } + /// + public IReadOnlyDictionary Metadata => this._metadata; + /// public async Task>> GenerateEmbeddingsAsync(IList data, CancellationToken cancellationToken = default) { diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureChatCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureChatCompletion.cs index dc4d7ed845dd..b9468a4a4c76 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureChatCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureChatCompletion.cs @@ -20,6 +20,8 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ChatCompletion; /// public sealed class AzureChatCompletion : AzureOpenAIClientBase, IChatCompletion, ITextCompletion { + private readonly Dictionary _metadata = new(); + /// /// Create an instance of the Azure OpenAI chat completion connector with API key auth /// @@ -35,6 +37,7 @@ public AzureChatCompletion( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(modelId, endpoint, apiKey, httpClient, loggerFactory) { + this._metadata.Add("ModelId", modelId); } /// @@ -52,6 +55,7 @@ public AzureChatCompletion( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(modelId, endpoint, credentials, httpClient, loggerFactory) { + this._metadata.Add("ModelId", modelId); } /// @@ -65,8 +69,13 @@ public AzureChatCompletion( OpenAIClient openAIClient, ILoggerFactory? loggerFactory = null) : base(modelId, openAIClient, loggerFactory) { + this._metadata.Add("ModelId", modelId); } + /// + public IReadOnlyDictionary Metadata => this._metadata; + + /// public Task> GetChatCompletionsAsync( ChatHistory chat, diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs index a54acfd2fd89..5ba6f01d621a 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs @@ -19,6 +19,8 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ChatCompletion; /// public sealed class OpenAIChatCompletion : OpenAIClientBase, IChatCompletion, ITextCompletion { + private readonly Dictionary _metadata = new(); + /// /// Create an instance of the OpenAI chat completion connector /// @@ -34,6 +36,7 @@ public OpenAIChatCompletion( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(modelId, apiKey, organization, httpClient, loggerFactory) { + this._metadata.Add("ModelId", modelId); } /// @@ -47,8 +50,12 @@ public OpenAIChatCompletion( OpenAIClient openAIClient, ILoggerFactory? loggerFactory = null) : base(modelId, openAIClient, loggerFactory) { + this._metadata.Add("ModelId", modelId); } + /// + public IReadOnlyDictionary Metadata => this._metadata; + /// public Task> GetChatCompletionsAsync( ChatHistory chat, diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureChatCompletionWithData.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureChatCompletionWithData.cs index 6ad5b799016b..fbfa24f946e7 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureChatCompletionWithData.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureChatCompletionWithData.cs @@ -43,8 +43,12 @@ public AzureChatCompletionWithData( this._httpClient = httpClient ?? new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); this._logger = loggerFactory is not null ? loggerFactory.CreateLogger(this.GetType()) : NullLogger.Instance; + this._metadata.Add("ModelId", config.CompletionModelId); } + /// + public IReadOnlyDictionary Metadata => this._metadata; + /// public ChatHistory CreateNewChat(string? instructions = null) { @@ -121,6 +125,7 @@ public async IAsyncEnumerable GetStreamingCompletionsAsync private readonly HttpClient _httpClient; private readonly ILogger _logger; + private readonly Dictionary _metadata = new(); private void ValidateConfig(AzureChatCompletionWithDataConfig config) { diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs index b0f110c65714..40cb8d1ba2fb 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading; @@ -19,6 +20,8 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ImageGeneration; /// public class AzureOpenAIImageGeneration : OpenAIClientBase, IImageGeneration { + private readonly Dictionary _metadata = new(); + /// /// Generation Image Operation path /// @@ -98,6 +101,10 @@ public AzureOpenAIImageGeneration(string apiKey, HttpClient httpClient, string? this._apiVersion = apiVersion; } + /// + public IReadOnlyDictionary Metadata => this._metadata; + + /// public async Task GenerateImageAsync(string description, int width, int height, CancellationToken cancellationToken = default) { diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs index 7bd0c1bc2199..2e802d59d6af 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using System.Collections.Generic; using System.Diagnostics; using System.Net.Http; using System.Threading; @@ -17,6 +18,8 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ImageGeneration; /// public class OpenAIImageGeneration : OpenAIClientBase, IImageGeneration { + private readonly Dictionary _metadata = new(); + /// /// OpenAI REST API endpoint /// @@ -51,6 +54,10 @@ public OpenAIImageGeneration( this._organizationHeaderValue = organization; } + /// + public IReadOnlyDictionary Metadata => this._metadata; + + /// Adds headers to use for OpenAI HTTP requests. private protected override void AddRequestHeaders(HttpRequestMessage request) { diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs index 2a549835a04a..646f62b70110 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs @@ -19,6 +19,8 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion; /// public sealed class AzureTextCompletion : AzureOpenAIClientBase, ITextCompletion { + private readonly Dictionary _metadata = new(); + /// /// Creates a new AzureTextCompletion client instance using API Key auth /// @@ -66,6 +68,9 @@ public AzureTextCompletion( { } + /// + public IReadOnlyDictionary Metadata => this._metadata; + /// public IAsyncEnumerable GetStreamingCompletionsAsync( string text, diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs index 9394be351f4d..1177f4d3c0f4 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs @@ -17,6 +17,8 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion; /// public sealed class OpenAITextCompletion : OpenAIClientBase, ITextCompletion { + private readonly Dictionary _metadata = new(); + /// /// Create an instance of the OpenAI text completion connector /// @@ -35,6 +37,9 @@ public OpenAITextCompletion( { } + /// + public IReadOnlyDictionary Metadata => this._metadata; + /// public IAsyncEnumerable GetStreamingCompletionsAsync( string text, diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureTextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureTextEmbeddingGeneration.cs index ceed977b10a0..64c719731620 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureTextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureTextEmbeddingGeneration.cs @@ -17,6 +17,8 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; /// public sealed class AzureTextEmbeddingGeneration : AzureOpenAIClientBase, ITextEmbeddingGeneration { + private readonly Dictionary _metadata = new(); + /// /// Creates a new AzureTextCompletion client instance using API Key auth /// @@ -51,6 +53,10 @@ public AzureTextEmbeddingGeneration( { } + /// + public IReadOnlyDictionary Metadata => this._metadata; + + /// /// Generates an embedding from the given . /// diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs index 1d19b0b546d5..9ff5cf426f35 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs @@ -16,6 +16,8 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; /// public sealed class OpenAITextEmbeddingGeneration : OpenAIClientBase, ITextEmbeddingGeneration { + private readonly Dictionary _metadata = new(); + /// /// Create an instance of the OpenAI text embedding connector /// @@ -34,6 +36,9 @@ public OpenAITextEmbeddingGeneration( { } + /// + public IReadOnlyDictionary Metadata => this._metadata; + /// /// Generates an embedding from the given . /// diff --git a/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs b/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs index f118965bfa26..790f3ad97096 100644 --- a/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs +++ b/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs @@ -72,6 +72,8 @@ public void Dispose() private sealed class RedirectTextCompletion : ITextCompletion { + public IReadOnlyDictionary Metadata => new Dictionary(); + Task> ITextCompletion.GetCompletionsAsync(string text, AIRequestSettings? requestSettings, CancellationToken cancellationToken) { return Task.FromResult>(new List { new RedirectTextCompletionResult(text) }); diff --git a/dotnet/src/SemanticKernel.Core/Services/NamedServiceProvider.cs b/dotnet/src/SemanticKernel.Core/Services/NamedServiceProvider.cs index ba5c903e4cd2..8036fae2a785 100644 --- a/dotnet/src/SemanticKernel.Core/Services/NamedServiceProvider.cs +++ b/dotnet/src/SemanticKernel.Core/Services/NamedServiceProvider.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Linq; namespace Microsoft.SemanticKernel.Services; @@ -56,6 +57,33 @@ public NamedServiceProvider( return null; } + /// + public ICollection GetServices() where T : TService + { + if (typeof(T) == typeof(TService)) + { + return this.GetAllServices(); + } + + if (this._services.TryGetValue(typeof(T), out var namedServices)) + { + return namedServices.Values.Select(f => f.Invoke()).Cast().ToList(); + } + + return Array.Empty(); + } + + private HashSet GetAllServices() + { + HashSet services = new(); + foreach (var namedServices in this._services.Values) + { + services.UnionWith(namedServices.Values.Select(f => f.Invoke()).Cast()); + } + + return services; + } + private Func? GetServiceFactory(string? name = null) where T : TService { // Get the nested dictionary for the service type diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs index c230a440e367..664d27c7997d 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs @@ -224,10 +224,13 @@ public void ItGetsAIServiceConfigurationByOrder(string[] serviceIds, string expe #region private private sealed class AIService : IAIService { + public IReadOnlyDictionary Metadata => new Dictionary(); } private sealed class TextCompletion : ITextCompletion { + public IReadOnlyDictionary Metadata => new Dictionary(); + public Task> GetCompletionsAsync(string text, AIRequestSettings? requestSettings = null, CancellationToken cancellationToken = default) { throw new NotImplementedException(); diff --git a/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs b/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs index 346d402bbccc..fda451c2795b 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. +using System.Collections.Generic; using Microsoft.SemanticKernel.Services; using Xunit; @@ -215,5 +216,6 @@ public void ItReturnsFalseIfTryGetServiceWithInvalidName() // A test service implementation private sealed class TestService : IAIService { + public IReadOnlyDictionary Metadata => new Dictionary(); } } From 1e65680ef834e9c8367f11a6a43a2e79abf9d793 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft Date: Fri, 3 Nov 2023 10:30:34 +0000 Subject: [PATCH 05/18] Finalise the initial draft of the ADR and get the samples working using the service metadata --- docs/decisions/0018-aiservice-metadata.md | 20 ++++ .../Example42_KernelBuilder.cs | 6 +- .../Example52_ApimAuth.cs | 2 +- .../Example61_MultipleLLMs.cs | 42 +++++-- .../Example62_CustomAIServiceSelector.cs | 112 +++++++++--------- dotnet/samples/KernelSyntaxExamples/README.md | 2 + .../KernelSyntaxExamples/TestConfiguration.cs | 2 + .../AzureSdk/AzureOpenAIClientBase.cs | 26 ++-- .../AzureSdk/ClientBase.cs | 12 +- .../AzureSdk/OpenAIClientBase.cs | 6 +- .../ChatCompletion/AzureChatCompletion.cs | 40 +++++-- .../OpenAIKernelBuilderExtensions.cs | 9 +- .../Services/AIServiceSelectorBase.cs | 43 +++++++ .../OrderedIAIServiceSelector.cs | 25 +++- 14 files changed, 238 insertions(+), 109 deletions(-) create mode 100644 dotnet/src/SemanticKernel.Core/Services/AIServiceSelectorBase.cs rename dotnet/src/SemanticKernel.Core/{Functions => Services}/OrderedIAIServiceSelector.cs (72%) diff --git a/docs/decisions/0018-aiservice-metadata.md b/docs/decisions/0018-aiservice-metadata.md index e1ae5b6aac61..55f7af3e9d6f 100644 --- a/docs/decisions/0018-aiservice-metadata.md +++ b/docs/decisions/0018-aiservice-metadata.md @@ -67,7 +67,27 @@ From the perspective of a prompt creator using OpenAI, they will typically tune These methods would be used as follows: +As an SK developer I want to write a custom `IAIServiceSelector` which will select an AI service based on the model id because I want to restrict which LLM is used. +In the sample below the service selector implementation looks for the first service that is a GPT3 model. + ``` csharp +public class Gpt3xAIServiceSelector : IAIServiceSelector +{ + public (T?, AIRequestSettings?) SelectAIService(string renderedPrompt, IAIServiceProvider serviceProvider, IReadOnlyList? modelSettings) where T : IAIService + { + var services = serviceProvider.GetServices(); + foreach (var service in services) + { + if (service.Metadata.TryGetValue("ModelId", out var serviceModelId) && serviceModelId.StartsWith("gpt-3", StringComparison.OrdinalIgnoreCase)) + { + Console.WriteLine($"Selected model: {serviceModelId}"); + return (service, new OpenAIRequestSettings()); + } + } + + throw new SKException("Unable to find AI service for GPT 3.x."); + } +} ``` ## Decision Outcome diff --git a/dotnet/samples/KernelSyntaxExamples/Example42_KernelBuilder.cs b/dotnet/samples/KernelSyntaxExamples/Example42_KernelBuilder.cs index 6638b78f7001..af420fa336e3 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example42_KernelBuilder.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example42_KernelBuilder.cs @@ -90,11 +90,11 @@ public static Task RunAsync() using var httpClient = new HttpClient(httpHandler); var aiServices = new AIServiceCollection(); ITextCompletion Factory() => new AzureChatCompletion( - modelId: azureOpenAIChatCompletionDeployment, + deploymentName: azureOpenAIChatCompletionDeployment, endpoint: azureOpenAIEndpoint, apiKey: azureOpenAIKey, - httpClient, - loggerFactory); + httpClient: httpClient, + loggerFactory: loggerFactory); aiServices.SetService("foo", Factory); IAIServiceProvider aiServiceProvider = aiServices.Build(); diff --git a/dotnet/samples/KernelSyntaxExamples/Example52_ApimAuth.cs b/dotnet/samples/KernelSyntaxExamples/Example52_ApimAuth.cs index 2bc4ecfe8f69..468fa64493a4 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example52_ApimAuth.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example52_ApimAuth.cs @@ -62,7 +62,7 @@ public static async Task RunAsync() var kernel = new KernelBuilder() .WithLoggerFactory(loggerFactory) .WithAIService(TestConfiguration.AzureOpenAI.ChatDeploymentName, (loggerFactory) => - new AzureChatCompletion(TestConfiguration.AzureOpenAI.ChatDeploymentName, openAIClient, loggerFactory)) + new AzureChatCompletion(TestConfiguration.AzureOpenAI.ChatDeploymentName, openAIClient: openAIClient, loggerFactory: loggerFactory)) .Build(); // Load semantic plugin defined with prompt templates diff --git a/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs b/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs index 66de8ae93926..4d08b90d3331 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI; +using Microsoft.SemanticKernel.Connectors.AI.OpenAI; using RepoUtils; // ReSharper disable once InconsistentNaming @@ -16,11 +17,12 @@ public static async Task RunAsync() { Console.WriteLine("======== Example61_MultipleLLMs ========"); - string apiKey = TestConfiguration.AzureOpenAI.ApiKey; - string chatDeploymentName = TestConfiguration.AzureOpenAI.ChatDeploymentName; - string endpoint = TestConfiguration.AzureOpenAI.Endpoint; + string azureApiKey = TestConfiguration.AzureOpenAI.ApiKey; + string azureDeploymentName = TestConfiguration.AzureOpenAI.ChatDeploymentName; + string azureModelId = TestConfiguration.AzureOpenAI.ChatModelId; + string azureEndpoint = TestConfiguration.AzureOpenAI.Endpoint; - if (apiKey == null || chatDeploymentName == null || endpoint == null) + if (azureApiKey == null || azureDeploymentName == null || azureEndpoint == null) { Console.WriteLine("Azure endpoint, apiKey, or deploymentName not found. Skipping example."); return; @@ -38,23 +40,26 @@ public static async Task RunAsync() IKernel kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) .WithAzureChatCompletionService( - deploymentName: chatDeploymentName, - endpoint: endpoint, + deploymentName: azureDeploymentName, + endpoint: azureEndpoint, + modelId: azureModelId, serviceId: "AzureOpenAIChat", - apiKey: apiKey) + apiKey: azureApiKey) .WithOpenAIChatCompletionService( modelId: openAIModelId, serviceId: "OpenAIChat", apiKey: openAIApiKey) .Build(); - await RunSemanticFunctionAsync(kernel, "AzureOpenAIChat"); - await RunSemanticFunctionAsync(kernel, "OpenAIChat"); + await RunByServiceIdAsync(kernel, "AzureOpenAIChat"); + await RunByServiceIdAsync(kernel, "OpenAIChat"); + await RunByModelIdAsync(kernel, azureModelId); + await RunByModelIdAsync(kernel, openAIModelId); } - public static async Task RunSemanticFunctionAsync(IKernel kernel, string serviceId) + public static async Task RunByServiceIdAsync(IKernel kernel, string serviceId) { - Console.WriteLine($"======== {serviceId} ========"); + Console.WriteLine($"======== Service Id: {serviceId} ========"); var prompt = "Hello AI, what can you do for me?"; @@ -66,4 +71,19 @@ public static async Task RunSemanticFunctionAsync(IKernel kernel, string service }); Console.WriteLine(result.GetValue()); } + + public static async Task RunByModelIdAsync(IKernel kernel, string modelId) + { + Console.WriteLine($"======== Model Id: {modelId} ========"); + + var prompt = "Hello AI, what can you do for me?"; + + var result = await kernel.InvokeSemanticFunctionAsync( + prompt, + requestSettings: new OpenAIRequestSettings() + { + ModelId = modelId + }); + Console.WriteLine(result.GetValue()); + } } diff --git a/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs b/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs index 79df69059d22..c5c84afe4032 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs @@ -6,7 +6,6 @@ using Microsoft.ML.Tokenizers; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI; -using Microsoft.SemanticKernel.AI.ChatCompletion; using Microsoft.SemanticKernel.Connectors.AI.OpenAI; using Microsoft.SemanticKernel.Diagnostics; using Microsoft.SemanticKernel.Services; @@ -23,11 +22,12 @@ public static async Task RunAsync() { Console.WriteLine("======== Example62_CustomAIServiceSelector ========"); - string apiKey = TestConfiguration.AzureOpenAI.ApiKey; - string chatDeploymentName = TestConfiguration.AzureOpenAI.ChatDeploymentName; - string endpoint = TestConfiguration.AzureOpenAI.Endpoint; + string azureApiKey = TestConfiguration.AzureOpenAI.ApiKey; + string azureDeploymentName = TestConfiguration.AzureOpenAI.ChatDeploymentName; + string azureModelId = TestConfiguration.AzureOpenAI.ChatModelId; + string azureEndpoint = TestConfiguration.AzureOpenAI.Endpoint; - if (apiKey == null || chatDeploymentName == null || endpoint == null) + if (azureApiKey == null || azureDeploymentName == null || azureModelId == null || azureEndpoint == null) { Console.WriteLine("Azure endpoint, apiKey, or deploymentName not found. Skipping example."); return; @@ -42,35 +42,54 @@ public static async Task RunAsync() return; } - IKernel kernel = new KernelBuilder() + KernelBuilder kernelBuilder = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) .WithAzureChatCompletionService( - deploymentName: chatDeploymentName, - endpoint: endpoint, + deploymentName: azureDeploymentName, + endpoint: azureEndpoint, + modelId: azureModelId, serviceId: "AzureOpenAIChat", - apiKey: apiKey) + apiKey: azureApiKey) .WithOpenAIChatCompletionService( modelId: openAIModelId, serviceId: "OpenAIChat", - apiKey: openAIApiKey) - .WithAIServiceSelector(new MyAIServiceSelector()) - .Build(); + apiKey: openAIApiKey); + + await RunWithRequestSettingsAsync(kernelBuilder, "Hello AI, what can you do for me?"); + await RunWithRequestSettingsAsync(kernelBuilder, "Hello AI, provide an indepth description of what can you do for me as a bulleted list?"); + await RunWithGpt3xAsync(kernelBuilder, "Hello AI, what can you do for me?"); + } + + public static async Task RunWithRequestSettingsAsync(KernelBuilder kernelBuilder, string prompt) + { + Console.WriteLine($"======== With Request Settings: {prompt} ========"); + + var kernel = kernelBuilder.WithAIServiceSelector(new TokenCountAIServiceSelector()).Build(); var modelSettings = new List { new OpenAIRequestSettings() { ServiceId = "AzureOpenAIChat", MaxTokens = 400 }, new OpenAIRequestSettings() { ServiceId = "OpenAIChat", MaxTokens = 200 } }; + var promptTemplateConfig = new PromptTemplateConfig() { ModelSettings = modelSettings }; + var promptTemplate = new PromptTemplate(prompt, promptTemplateConfig, kernel); - await RunSemanticFunctionAsync(kernel, "Hello AI, what can you do for me?", modelSettings); - await RunSemanticFunctionAsync(kernel, "Hello AI, provide an indepth description of what can you do for me as a bulleted list?", modelSettings); + var skfunction = kernel.RegisterSemanticFunction( + "MyFunction", + promptTemplateConfig, + promptTemplate); + + var result = await kernel.RunAsync(skfunction); + Console.WriteLine(result.GetValue()); } - public static async Task RunSemanticFunctionAsync(IKernel kernel, string prompt, List modelSettings) + public static async Task RunWithGpt3xAsync(KernelBuilder kernelBuilder, string prompt) { - Console.WriteLine($"======== {prompt} ========"); + Console.WriteLine($"======== Without GPT3x: {prompt} ========"); - var promptTemplateConfig = new PromptTemplateConfig() { ModelSettings = modelSettings }; + var kernel = kernelBuilder.WithAIServiceSelector(new Gpt3xAIServiceSelector()).Build(); + + var promptTemplateConfig = new PromptTemplateConfig(); var promptTemplate = new PromptTemplate(prompt, promptTemplateConfig, kernel); var skfunction = kernel.RegisterSemanticFunction( @@ -83,40 +102,20 @@ public static async Task RunSemanticFunctionAsync(IKernel kernel, string prompt, } } -public class MyAIServiceSelector : IAIServiceSelector +public class TokenCountAIServiceSelector : IAIServiceSelector { private readonly int _defaultMaxTokens = 300; private readonly int _minResponseTokens = 150; public (T?, AIRequestSettings?) SelectAIService(string renderedPrompt, IAIServiceProvider serviceProvider, IReadOnlyList? modelSettings) where T : IAIService { - var chatServices = serviceProvider.GetServices(); - foreach (var service in chatServices) - { - Console.WriteLine($"Service: {service.Metadata}"); - } - var services = serviceProvider.GetServices(); - foreach (var service in services) - { - Console.WriteLine($"Service: {service.Metadata}"); - } - - if (modelSettings is null || modelSettings.Count == 0) - { - var service = serviceProvider.GetService(null); - if (service is not null) - { - return (service, null); - } - } - else + if (modelSettings is not null) { var tokens = this.CountTokens(renderedPrompt); - string? serviceId = null; int fewestTokens = 0; + string? serviceId = null; AIRequestSettings? requestSettings = null; - AIRequestSettings? defaultRequestSettings = null; foreach (var model in modelSettings) { if (!string.IsNullOrEmpty(model.ServiceId)) @@ -132,11 +131,6 @@ public class MyAIServiceSelector : IAIServiceSelector } } } - else - { - // First request settings with empty or null service id is the default - defaultRequestSettings ??= model; - } } Console.WriteLine($"Prompt tokens: {tokens}, Response tokens: {fewestTokens}"); @@ -149,20 +143,12 @@ public class MyAIServiceSelector : IAIServiceSelector return (service, requestSettings); } } - - if (defaultRequestSettings is not null) - { - var service = serviceProvider.GetService(null); - if (service is not null) - { - return (service, defaultRequestSettings); - } - } } throw new SKException("Unable to find AI service to handled request."); } + /// /// MicrosoftML token counter implementation. /// @@ -174,3 +160,21 @@ private int CountTokens(string input) return tokens.Count; } } + +public class Gpt3xAIServiceSelector : IAIServiceSelector +{ + public (T?, AIRequestSettings?) SelectAIService(string renderedPrompt, IAIServiceProvider serviceProvider, IReadOnlyList? modelSettings) where T : IAIService + { + var services = serviceProvider.GetServices(); + foreach (var service in services) + { + if (service.Metadata.TryGetValue("ModelId", out var serviceModelId) && serviceModelId.StartsWith("gpt-3", StringComparison.OrdinalIgnoreCase)) + { + Console.WriteLine($"Selected model: {serviceModelId}"); + return (service, new OpenAIRequestSettings()); + } + } + + throw new SKException("Unable to find AI service for GPT 3.x."); + } +} diff --git a/dotnet/samples/KernelSyntaxExamples/README.md b/dotnet/samples/KernelSyntaxExamples/README.md index 26e95a78a215..fcbc8ceaa810 100644 --- a/dotnet/samples/KernelSyntaxExamples/README.md +++ b/dotnet/samples/KernelSyntaxExamples/README.md @@ -56,7 +56,9 @@ dotnet user-secrets set "OpenAI:ApiKey" "..." dotnet user-secrets set "AzureOpenAI:ServiceId" "..." dotnet user-secrets set "AzureOpenAI:DeploymentName" "..." +dotnet user-secrets set "AzureOpenAI:ModelId" "..." dotnet user-secrets set "AzureOpenAI:ChatDeploymentName" "..." +dotnet user-secrets set "AzureOpenAI:ChatModelId" "..." dotnet user-secrets set "AzureOpenAI:Endpoint" "https://... .openai.azure.com/" dotnet user-secrets set "AzureOpenAI:ApiKey" "..." diff --git a/dotnet/samples/KernelSyntaxExamples/TestConfiguration.cs b/dotnet/samples/KernelSyntaxExamples/TestConfiguration.cs index d4f0c3b854e5..894c4bf7bff7 100644 --- a/dotnet/samples/KernelSyntaxExamples/TestConfiguration.cs +++ b/dotnet/samples/KernelSyntaxExamples/TestConfiguration.cs @@ -67,7 +67,9 @@ public class AzureOpenAIConfig { public string ServiceId { get; set; } public string DeploymentName { get; set; } + public string ModelId { get; set; } public string ChatDeploymentName { get; set; } + public string ChatModelId { get; set; } public string Endpoint { get; set; } public string ApiKey { get; set; } } diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs index 71f193e032c3..86f3e33b5eed 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs @@ -24,51 +24,51 @@ public abstract class AzureOpenAIClientBase : ClientBase /// /// Initializes a new instance of the class using API Key authentication. /// - /// Azure OpenAI model ID or deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// 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 /// Custom for HTTP requests. /// The to use for logging. If null, no logging will be performed. private protected AzureOpenAIClientBase( - string modelId, + string deploymentName, string endpoint, string apiKey, HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(loggerFactory) { - Verify.NotNullOrWhiteSpace(modelId); + Verify.NotNullOrWhiteSpace(deploymentName); Verify.NotNullOrWhiteSpace(endpoint); Verify.StartsWith(endpoint, "https://", "The Azure OpenAI endpoint must start with 'https://'"); Verify.NotNullOrWhiteSpace(apiKey); var options = GetClientOptions(httpClient); - this.ModelId = modelId; + this.DeploymentOrModelName = deploymentName; this.Client = new OpenAIClient(new Uri(endpoint), new AzureKeyCredential(apiKey), options); } /// /// Initializes a new instance of the class supporting AAD authentication. /// - /// Azure OpenAI model ID or deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// 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 credential, e.g. DefaultAzureCredential, ManagedIdentityCredential, EnvironmentCredential, etc. /// Custom for HTTP requests. /// The to use for logging. If null, no logging will be performed. private protected AzureOpenAIClientBase( - string modelId, + string deploymentName, string endpoint, TokenCredential credential, HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(loggerFactory) { - Verify.NotNullOrWhiteSpace(modelId); + Verify.NotNullOrWhiteSpace(deploymentName); Verify.NotNullOrWhiteSpace(endpoint); Verify.StartsWith(endpoint, "https://", "The Azure OpenAI endpoint must start with 'https://'"); var options = GetClientOptions(httpClient); - this.ModelId = modelId; + this.DeploymentOrModelName = deploymentName; this.Client = new OpenAIClient(new Uri(endpoint), credential, options); } @@ -77,18 +77,18 @@ private protected AzureOpenAIClientBase( /// Note: instances created this way might not have the default diagnostics settings, /// it's up to the caller to configure the client. /// - /// Azure OpenAI model ID or deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Custom . /// The to use for logging. If null, no logging will be performed. private protected AzureOpenAIClientBase( - string modelId, + string deploymentName, OpenAIClient openAIClient, ILoggerFactory? loggerFactory = null) : base(loggerFactory) { - Verify.NotNullOrWhiteSpace(modelId); + Verify.NotNullOrWhiteSpace(deploymentName); Verify.NotNull(openAIClient); - this.ModelId = modelId; + this.DeploymentOrModelName = deploymentName; this.Client = openAIClient; } @@ -123,6 +123,6 @@ private static OpenAIClientOptions GetClientOptions(HttpClient? httpClient) /// Caller member name. Populated automatically by runtime. private protected void LogActionDetails([CallerMemberName] string? callerMemberName = default) { - this.Logger.LogInformation("Action: {Action}. Azure OpenAI Deployment Name: {DeploymentName}.", callerMemberName, this.ModelId); + this.Logger.LogInformation("Action: {Action}. Azure OpenAI Deployment Name: {DeploymentName}.", callerMemberName, this.DeploymentOrModelName); } } diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/ClientBase.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/ClientBase.cs index c31ea4a316c0..5c79dd024f4d 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/ClientBase.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/ClientBase.cs @@ -38,7 +38,7 @@ private protected ClientBase(ILoggerFactory? loggerFactory = null) /// /// Model Id or Deployment Name /// - private protected string ModelId { get; set; } = string.Empty; + private protected string DeploymentOrModelName { get; set; } = string.Empty; /// /// OpenAI / Azure OpenAI Client @@ -97,7 +97,7 @@ private protected async Task> InternalGetTextResultsA var options = CreateCompletionsOptions(text, textRequestSettings); Response? response = await RunRequestAsync?>( - () => this.Client.GetCompletionsAsync(this.ModelId, options, cancellationToken)).ConfigureAwait(false); + () => this.Client.GetCompletionsAsync(this.DeploymentOrModelName, options, cancellationToken)).ConfigureAwait(false); if (response is null) { @@ -135,7 +135,7 @@ private protected async IAsyncEnumerable InternalGetTextStr var options = CreateCompletionsOptions(text, textRequestSettings); Response? response = await RunRequestAsync>( - () => this.Client.GetCompletionsStreamingAsync(this.ModelId, options, cancellationToken)).ConfigureAwait(false); + () => this.Client.GetCompletionsStreamingAsync(this.DeploymentOrModelName, options, cancellationToken)).ConfigureAwait(false); using StreamingCompletions streamingChatCompletions = response.Value; await foreach (StreamingChoice choice in streamingChatCompletions.GetChoicesStreaming(cancellationToken)) @@ -160,7 +160,7 @@ private protected async Task>> InternalGetEmbeddings var options = new EmbeddingsOptions(text); Response? response = await RunRequestAsync?>( - () => this.Client.GetEmbeddingsAsync(this.ModelId, options, cancellationToken)).ConfigureAwait(false); + () => this.Client.GetEmbeddingsAsync(this.DeploymentOrModelName, options, cancellationToken)).ConfigureAwait(false); if (response is null) { @@ -199,7 +199,7 @@ private protected async Task> InternalGetChatResultsA var chatOptions = CreateChatCompletionsOptions(chatRequestSettings, chat); Response? response = await RunRequestAsync?>( - () => this.Client.GetChatCompletionsAsync(this.ModelId, chatOptions, cancellationToken)).ConfigureAwait(false); + () => this.Client.GetChatCompletionsAsync(this.DeploymentOrModelName, chatOptions, cancellationToken)).ConfigureAwait(false); if (response is null) { @@ -239,7 +239,7 @@ private protected async IAsyncEnumerable InternalGetChatSt var options = CreateChatCompletionsOptions(chatRequestSettings, chat); Response? response = await RunRequestAsync>( - () => this.Client.GetChatCompletionsStreamingAsync(this.ModelId, options, cancellationToken)).ConfigureAwait(false); + () => this.Client.GetChatCompletionsStreamingAsync(this.DeploymentOrModelName, options, cancellationToken)).ConfigureAwait(false); if (response is null) { diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/OpenAIClientBase.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/OpenAIClientBase.cs index ccd33cb90a4e..d0fbe506f045 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/OpenAIClientBase.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/OpenAIClientBase.cs @@ -38,7 +38,7 @@ private protected OpenAIClientBase( Verify.NotNullOrWhiteSpace(modelId); Verify.NotNullOrWhiteSpace(apiKey); - this.ModelId = modelId; + this.DeploymentOrModelName = modelId; var options = GetClientOptions(httpClient); @@ -66,7 +66,7 @@ private protected OpenAIClientBase( Verify.NotNullOrWhiteSpace(modelId); Verify.NotNull(openAIClient); - this.ModelId = modelId; + this.DeploymentOrModelName = modelId; this.Client = openAIClient; } @@ -76,7 +76,7 @@ private protected OpenAIClientBase( /// Caller member name. Populated automatically by runtime. private protected void LogActionDetails([CallerMemberName] string? callerMemberName = default) { - this.Logger.LogInformation("Action: {Action}. OpenAI Model ID: {ModelId}.", callerMemberName, this.ModelId); + this.Logger.LogInformation("Action: {Action}. OpenAI Model ID: {ModelId}.", callerMemberName, this.DeploymentOrModelName); } /// diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureChatCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureChatCompletion.cs index b9468a4a4c76..c452c4e9615a 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureChatCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureChatCompletion.cs @@ -25,57 +25,71 @@ public sealed class AzureChatCompletion : AzureOpenAIClientBase, IChatCompletion /// /// Create an instance of the Azure OpenAI chat completion connector with API key auth /// - /// Azure OpenAI model ID or deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// 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 + /// Azure OpenAI model id, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Custom for HTTP requests. /// The to use for logging. If null, no logging will be performed. public AzureChatCompletion( - string modelId, + string deploymentName, string endpoint, string apiKey, + string? modelId = null, HttpClient? httpClient = null, - ILoggerFactory? loggerFactory = null) : base(modelId, endpoint, apiKey, httpClient, loggerFactory) + ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, apiKey, httpClient, loggerFactory) { - this._metadata.Add("ModelId", modelId); + if (!string.IsNullOrEmpty(modelId)) + { + this._metadata.Add("ModelId", modelId!); + } } /// /// Create an instance of the Azure OpenAI chat completion connector with AAD auth /// - /// Azure OpenAI model ID or deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// 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. + /// Azure OpenAI model id, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Custom for HTTP requests. /// The to use for logging. If null, no logging will be performed. public AzureChatCompletion( - string modelId, + string deploymentName, string endpoint, TokenCredential credentials, + string? modelId = null, HttpClient? httpClient = null, - ILoggerFactory? loggerFactory = null) : base(modelId, endpoint, credentials, httpClient, loggerFactory) + ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, credentials, httpClient, loggerFactory) { - this._metadata.Add("ModelId", modelId); + if (!string.IsNullOrEmpty(modelId)) + { + this._metadata.Add("ModelId", modelId!); + } } /// /// Creates a new AzureChatCompletion client instance using the specified OpenAIClient /// - /// Azure OpenAI model ID or deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Custom . + /// Azure OpenAI model id, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// The to use for logging. If null, no logging will be performed. public AzureChatCompletion( - string modelId, + string deploymentName, OpenAIClient openAIClient, - ILoggerFactory? loggerFactory = null) : base(modelId, openAIClient, loggerFactory) + string? modelId = null, + ILoggerFactory? loggerFactory = null) : base(deploymentName, openAIClient, loggerFactory) { - this._metadata.Add("ModelId", modelId); + if (!string.IsNullOrEmpty(modelId)) + { + this._metadata.Add("ModelId", modelId!); + } } /// public IReadOnlyDictionary Metadata => this._metadata; - /// public Task> GetChatCompletionsAsync( ChatHistory chat, diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs index 8f39afe34bd6..2b7396f92705 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs @@ -265,6 +265,7 @@ public static KernelBuilder WithAzureChatCompletionService(this KernelBuilder bu string apiKey, bool alsoAsTextCompletion = true, string? serviceId = null, + string? modelId = null, bool setAsDefault = false, HttpClient? httpClient = null) { @@ -272,7 +273,7 @@ AzureChatCompletion Factory(ILoggerFactory loggerFactory, IDelegatingHandlerFact { OpenAIClient client = CreateAzureOpenAIClient(loggerFactory, httpHandlerFactory, deploymentName, endpoint, new AzureKeyCredential(apiKey), httpClient); - return new(deploymentName, client, loggerFactory); + return new(deploymentName, client, modelId, loggerFactory); }; builder.WithAIService(serviceId, Factory, setAsDefault); @@ -305,6 +306,7 @@ public static KernelBuilder WithAzureChatCompletionService(this KernelBuilder bu TokenCredential credentials, bool alsoAsTextCompletion = true, string? serviceId = null, + string? modelId = null, bool setAsDefault = false, HttpClient? httpClient = null) { @@ -312,7 +314,7 @@ AzureChatCompletion Factory(ILoggerFactory loggerFactory, IDelegatingHandlerFact { OpenAIClient client = CreateAzureOpenAIClient(loggerFactory, httpHandlerFactory, deploymentName, endpoint, credentials, httpClient); - return new(deploymentName, client, loggerFactory); + return new(deploymentName, client, modelId, loggerFactory); }; builder.WithAIService(serviceId, Factory, setAsDefault); @@ -415,11 +417,12 @@ public static KernelBuilder WithAzureChatCompletionService(this KernelBuilder bu OpenAIClient openAIClient, bool alsoAsTextCompletion = true, string? serviceId = null, + string? modelId = null, bool setAsDefault = false) { AzureChatCompletion Factory(ILoggerFactory loggerFactory) { - return new(deploymentName, openAIClient, loggerFactory); + return new(deploymentName, openAIClient, modelId, loggerFactory); }; builder.WithAIService(serviceId, Factory, setAsDefault); diff --git a/dotnet/src/SemanticKernel.Core/Services/AIServiceSelectorBase.cs b/dotnet/src/SemanticKernel.Core/Services/AIServiceSelectorBase.cs new file mode 100644 index 000000000000..7d5749a1a6f1 --- /dev/null +++ b/dotnet/src/SemanticKernel.Core/Services/AIServiceSelectorBase.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All rights reserved. + +#pragma warning disable IDE0130 +// ReSharper disable once CheckNamespace - Using the main namespace +using System.Collections.Generic; +using Microsoft.SemanticKernel.AI; +using Microsoft.SemanticKernel.Diagnostics; +using Microsoft.SemanticKernel.Services; + +namespace Microsoft.SemanticKernel; +#pragma warning restore IDE0130 + +/// +/// Base classe for implementing . +/// +public abstract class AIServiceSelectorBase : IAIServiceSelector +{ + /// + public (T?, AIRequestSettings?) SelectAIService(string renderedPrompt, IAIServiceProvider serviceProvider, IReadOnlyList? modelSettings) where T : IAIService + { + var services = serviceProvider.GetServices(); + foreach (var service in services) + { + var result = this.SelectAIService(renderedPrompt, service, modelSettings); + if (result is not null) + { + return ((T?, AIRequestSettings?))result; + } + } + + throw new SKException($"Valid service of type {typeof(T)} not found."); + } + + /// + /// Return the AI service and requesting settings if the specified provider is the vaid choice. + /// + /// + /// + /// + /// + /// + protected abstract (T?, AIRequestSettings?)? SelectAIService(string renderedPrompt, T service, IReadOnlyList? modelSettings) where T : IAIService; +} diff --git a/dotnet/src/SemanticKernel.Core/Functions/OrderedIAIServiceSelector.cs b/dotnet/src/SemanticKernel.Core/Services/OrderedIAIServiceSelector.cs similarity index 72% rename from dotnet/src/SemanticKernel.Core/Functions/OrderedIAIServiceSelector.cs rename to dotnet/src/SemanticKernel.Core/Services/OrderedIAIServiceSelector.cs index 0ce1c06f67cb..896c0cbd5c01 100644 --- a/dotnet/src/SemanticKernel.Core/Functions/OrderedIAIServiceSelector.cs +++ b/dotnet/src/SemanticKernel.Core/Services/OrderedIAIServiceSelector.cs @@ -4,9 +4,8 @@ using System.Linq; using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.Diagnostics; -using Microsoft.SemanticKernel.Services; -namespace Microsoft.SemanticKernel.Functions; +namespace Microsoft.SemanticKernel.Services; /// /// Implementation of that selects the AI service based on the order of the model settings. @@ -38,6 +37,14 @@ internal class OrderedIAIServiceSelector : IAIServiceSelector return (service, model); } } + else if (!string.IsNullOrEmpty(model.ModelId)) + { + var service = this.GetServiceByModelId(serviceProvider, model.ModelId!); + if (service is not null) + { + return (service, model); + } + } else { // First request settings with empty or null service id is the default @@ -58,4 +65,18 @@ internal class OrderedIAIServiceSelector : IAIServiceSelector var names = string.Join("|", modelSettings.Select(model => model.ServiceId).ToArray()); throw new SKException($"Service of type {typeof(T)} and name {names ?? ""} not registered."); } + + private T? GetServiceByModelId(IAIServiceProvider serviceProvider, string modelId) where T : IAIService + { + var services = serviceProvider.GetServices(); + foreach (var service in services) + { + if (service.Metadata.TryGetValue("ModelId", out var serviceModelId) && serviceModelId == modelId) + { + return service; + } + } + + return default; + } } From f019bac0fc700ed896280679e7afb4498b5f4666 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft Date: Sun, 5 Nov 2023 10:30:12 +0000 Subject: [PATCH 06/18] Add IAIService.ModelId property --- docs/decisions/0018-aiservice-metadata.md | 10 ++++++---- .../KernelSyntaxExamples/Example16_CustomLLM.cs | 4 ++++ .../Example34_CustomChatModel.cs | 2 ++ .../Example62_CustomAIServiceSelector.cs | 4 ++-- .../TextCompletion/HuggingFaceTextCompletion.cs | 3 +++ .../HuggingFaceTextEmbeddingGeneration.cs | 3 +++ .../ChatCompletion/AzureChatCompletion.cs | 15 ++++++--------- .../ChatCompletion/OpenAIChatCompletion.cs | 7 +++++-- .../AzureChatCompletionWithData.cs | 5 ++++- .../ImageGeneration/AzureOpenAIImageGeneration.cs | 4 +++- .../ImageGeneration/OpenAIImageGeneration.cs | 4 +++- .../TextCompletion/AzureTextCompletion.cs | 3 +++ .../TextCompletion/OpenAITextCompletion.cs | 3 +++ .../TextEmbedding/AzureTextEmbeddingGeneration.cs | 4 +++- .../OpenAITextEmbeddingGeneration.cs | 3 +++ .../KernelSemanticFunctionExtensionsTests.cs | 2 ++ .../Services/IAIService.cs | 6 ++++++ .../Services/OrderedIAIServiceSelector.cs | 2 +- ...OrderedIAIServiceConfigurationProviderTests.cs | 5 ++++- .../Services/ServiceRegistryTests.cs | 2 ++ 20 files changed, 68 insertions(+), 23 deletions(-) diff --git a/docs/decisions/0018-aiservice-metadata.md b/docs/decisions/0018-aiservice-metadata.md index 55f7af3e9d6f..f5f8c28e43c2 100644 --- a/docs/decisions/0018-aiservice-metadata.md +++ b/docs/decisions/0018-aiservice-metadata.md @@ -10,7 +10,7 @@ informed: ## Context and Problem Statement -Developers need to be able to know more information about the `IAIService` that will eb used to execute a semantic function or a plan. +Developers need to be able to know more information about the `IAIService` that will be used to execute a semantic function or a plan. Some examples of why they need this information: 1. As an SK developer I want to write a `IAIServiceSelector` which allows me to select the OpenAI service to used based on the configured model id so that I cna select the optimum (could eb cheapest) model to use based on the prompt I am executing. @@ -61,7 +61,9 @@ From the perspective of a prompt creator using OpenAI, they will typically tune ## Considered Options * Option #1 - * Extend `IAIService` to include this property `IReadOnlyDictionary Metadata { get; }` which returns the metadata as a readonly dictionary. It will be the responsibility of each `IAIService` implementation to populate this with the appropriate metadata. + * Extend `IAIService` to include the following properties: + * `string? ModelId { get; }` which returns the model id. It will be the responsibility of each `IAIService` implementation to populate this with the appropriate value. + * `IReadOnlyDictionary Metadata { get; }` which returns the metadata as a readonly dictionary. It will be the responsibility of each `IAIService` implementation to populate this with the appropriate metadata. * Extend `INamedServiceProvider` to include this method `ICollection GetServices() where T : TService;` * Extend `OpenAIKernelBuilderExtensions` so that `WithAzureXXX` methods will include a `modelId` property if a specific model can be targeted. @@ -78,9 +80,9 @@ public class Gpt3xAIServiceSelector : IAIServiceSelector var services = serviceProvider.GetServices(); foreach (var service in services) { - if (service.Metadata.TryGetValue("ModelId", out var serviceModelId) && serviceModelId.StartsWith("gpt-3", StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(service.ModelId) && service.ModelId.StartsWith("gpt-3", StringComparison.OrdinalIgnoreCase)) { - Console.WriteLine($"Selected model: {serviceModelId}"); + Console.WriteLine($"Selected model: {service.ModelId}"); return (service, new OpenAIRequestSettings()); } } diff --git a/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs b/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs index 902668f92b9c..537de1c4fde9 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs @@ -29,10 +29,14 @@ */ public class MyTextCompletionService : ITextCompletion { + public string? ModelId { get; private set; } + public IReadOnlyDictionary Metadata => new Dictionary(); public Task> GetCompletionsAsync(string text, AIRequestSettings? requestSettings, CancellationToken cancellationToken = default) { + this.ModelId = requestSettings?.ModelId; + return Task.FromResult>(new List { new MyTextCompletionStreamingResult() diff --git a/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs b/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs index 38997fc3dc06..a2cc36ed00cb 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs @@ -20,6 +20,8 @@ */ public sealed class MyChatCompletionService : IChatCompletion { + public string? ModelId { get; private set; } + public IReadOnlyDictionary Metadata => new Dictionary(); public ChatHistory CreateNewChat(string? instructions = null) diff --git a/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs b/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs index c5c84afe4032..f5992f3bcfae 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs @@ -168,9 +168,9 @@ public class Gpt3xAIServiceSelector : IAIServiceSelector var services = serviceProvider.GetServices(); foreach (var service in services) { - if (service.Metadata.TryGetValue("ModelId", out var serviceModelId) && serviceModelId.StartsWith("gpt-3", StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(service.ModelId) && service.ModelId.StartsWith("gpt-3", StringComparison.OrdinalIgnoreCase)) { - Console.WriteLine($"Selected model: {serviceModelId}"); + Console.WriteLine($"Selected model: {service.ModelId}"); return (service, new OpenAIRequestSettings()); } } diff --git a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs index c93d09e081c8..c15d4969eed7 100644 --- a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs @@ -63,6 +63,9 @@ public HuggingFaceTextCompletion(string model, string? apiKey = null, HttpClient this._endpoint = endpoint; } + /// + public string? ModelId { get; private set; } + /// public IReadOnlyDictionary Metadata => this._metadata; diff --git a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs index 6ff24fa873d7..ac0002e67b46 100644 --- a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs @@ -78,6 +78,9 @@ public HuggingFaceTextEmbeddingGeneration(string model, HttpClient httpClient, s } } + /// + public string? ModelId { get; private set; } + /// public IReadOnlyDictionary Metadata => this._metadata; diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureChatCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureChatCompletion.cs index c452c4e9615a..8323799a5f00 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureChatCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureChatCompletion.cs @@ -41,7 +41,7 @@ public AzureChatCompletion( { if (!string.IsNullOrEmpty(modelId)) { - this._metadata.Add("ModelId", modelId!); + this.ModelId = modelId; } } @@ -62,10 +62,7 @@ public AzureChatCompletion( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, credentials, httpClient, loggerFactory) { - if (!string.IsNullOrEmpty(modelId)) - { - this._metadata.Add("ModelId", modelId!); - } + this.ModelId = modelId; } /// @@ -81,12 +78,12 @@ public AzureChatCompletion( string? modelId = null, ILoggerFactory? loggerFactory = null) : base(deploymentName, openAIClient, loggerFactory) { - if (!string.IsNullOrEmpty(modelId)) - { - this._metadata.Add("ModelId", modelId!); - } + this.ModelId = modelId; } + /// + public string? ModelId { get; private set; } + /// public IReadOnlyDictionary Metadata => this._metadata; diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs index 5ba6f01d621a..fa9b6e4b3a1b 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs @@ -36,7 +36,7 @@ public OpenAIChatCompletion( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(modelId, apiKey, organization, httpClient, loggerFactory) { - this._metadata.Add("ModelId", modelId); + this.ModelId = modelId; } /// @@ -50,9 +50,12 @@ public OpenAIChatCompletion( OpenAIClient openAIClient, ILoggerFactory? loggerFactory = null) : base(modelId, openAIClient, loggerFactory) { - this._metadata.Add("ModelId", modelId); + this.ModelId = modelId; } + /// + public string? ModelId { get; private set; } + /// public IReadOnlyDictionary Metadata => this._metadata; diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureChatCompletionWithData.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureChatCompletionWithData.cs index fbfa24f946e7..e67c6998f462 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureChatCompletionWithData.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureChatCompletionWithData.cs @@ -43,9 +43,12 @@ public AzureChatCompletionWithData( this._httpClient = httpClient ?? new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); this._logger = loggerFactory is not null ? loggerFactory.CreateLogger(this.GetType()) : NullLogger.Instance; - this._metadata.Add("ModelId", config.CompletionModelId); + this.ModelId = config.CompletionModelId; } + /// + public string? ModelId { get; private set; } + /// public IReadOnlyDictionary Metadata => this._metadata; diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs index 40cb8d1ba2fb..4a09df49836c 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs @@ -102,8 +102,10 @@ public AzureOpenAIImageGeneration(string apiKey, HttpClient httpClient, string? } /// - public IReadOnlyDictionary Metadata => this._metadata; + public string? ModelId { get; private set; } + /// + public IReadOnlyDictionary Metadata => this._metadata; /// public async Task GenerateImageAsync(string description, int width, int height, CancellationToken cancellationToken = default) diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs index 2e802d59d6af..fba153fbd364 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs @@ -55,8 +55,10 @@ public OpenAIImageGeneration( } /// - public IReadOnlyDictionary Metadata => this._metadata; + public string? ModelId { get; private set; } + /// + public IReadOnlyDictionary Metadata => this._metadata; /// Adds headers to use for OpenAI HTTP requests. private protected override void AddRequestHeaders(HttpRequestMessage request) diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs index 646f62b70110..b18420f713e0 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs @@ -68,6 +68,9 @@ public AzureTextCompletion( { } + /// + public string? ModelId { get; private set; } + /// public IReadOnlyDictionary Metadata => this._metadata; diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs index 1177f4d3c0f4..5c27cf306a65 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs @@ -37,6 +37,9 @@ public OpenAITextCompletion( { } + /// + public string? ModelId { get; private set; } + /// public IReadOnlyDictionary Metadata => this._metadata; diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureTextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureTextEmbeddingGeneration.cs index 64c719731620..20e756f91a04 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureTextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureTextEmbeddingGeneration.cs @@ -54,8 +54,10 @@ public AzureTextEmbeddingGeneration( } /// - public IReadOnlyDictionary Metadata => this._metadata; + public string? ModelId { get; private set; } + /// + public IReadOnlyDictionary Metadata => this._metadata; /// /// Generates an embedding from the given . diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs index 9ff5cf426f35..93c68f3d3ed2 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs @@ -36,6 +36,9 @@ public OpenAITextEmbeddingGeneration( { } + /// + public string? ModelId { get; private set; } + /// public IReadOnlyDictionary Metadata => this._metadata; diff --git a/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs b/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs index 790f3ad97096..4f24747557dc 100644 --- a/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs +++ b/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs @@ -72,6 +72,8 @@ public void Dispose() private sealed class RedirectTextCompletion : ITextCompletion { + public string? ModelId => null; + public IReadOnlyDictionary Metadata => new Dictionary(); Task> ITextCompletion.GetCompletionsAsync(string text, AIRequestSettings? requestSettings, CancellationToken cancellationToken) diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs b/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs index f9ef99d2faf7..4f56717336de 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs @@ -11,6 +11,12 @@ namespace Microsoft.SemanticKernel.Services; [SuppressMessage("Design", "CA1040:Avoid empty interfaces")] public interface IAIService { + /// + /// Model identifier. + /// This identifies the AI model these settings are configured for e.g., gpt-4, gpt-3.5-turbo + /// + string? ModelId { get; } + /// /// Gets the AI service metadata. /// diff --git a/dotnet/src/SemanticKernel.Core/Services/OrderedIAIServiceSelector.cs b/dotnet/src/SemanticKernel.Core/Services/OrderedIAIServiceSelector.cs index 896c0cbd5c01..c16080eaa594 100644 --- a/dotnet/src/SemanticKernel.Core/Services/OrderedIAIServiceSelector.cs +++ b/dotnet/src/SemanticKernel.Core/Services/OrderedIAIServiceSelector.cs @@ -71,7 +71,7 @@ internal class OrderedIAIServiceSelector : IAIServiceSelector var services = serviceProvider.GetServices(); foreach (var service in services) { - if (service.Metadata.TryGetValue("ModelId", out var serviceModelId) && serviceModelId == modelId) + if (!string.IsNullOrEmpty(service.ModelId) && service.ModelId == modelId) { return service; } diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs index 664d27c7997d..9abd266228f5 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs @@ -7,7 +7,6 @@ using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Diagnostics; -using Microsoft.SemanticKernel.Functions; using Microsoft.SemanticKernel.Services; using Xunit; @@ -225,12 +224,16 @@ public void ItGetsAIServiceConfigurationByOrder(string[] serviceIds, string expe private sealed class AIService : IAIService { public IReadOnlyDictionary Metadata => new Dictionary(); + + public string? ModelId { get; } } private sealed class TextCompletion : ITextCompletion { public IReadOnlyDictionary Metadata => new Dictionary(); + public string? ModelId { get; } + public Task> GetCompletionsAsync(string text, AIRequestSettings? requestSettings = null, CancellationToken cancellationToken = default) { throw new NotImplementedException(); diff --git a/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs b/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs index fda451c2795b..4f6167fe9fd9 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs @@ -216,6 +216,8 @@ public void ItReturnsFalseIfTryGetServiceWithInvalidName() // A test service implementation private sealed class TestService : IAIService { + public string? ModelId { get; } + public IReadOnlyDictionary Metadata => new Dictionary(); } } From 0cadf9a47418c0b895b9f35b2bdac06fbe262860 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft Date: Tue, 7 Nov 2023 19:01:05 +0000 Subject: [PATCH 07/18] Update all IAIService instances --- docs/decisions/0018-aiservice-metadata.md | 4 +-- .../Example12_SequentialPlanner.cs | 2 +- .../Example16_CustomLLM.cs | 2 +- .../Example34_CustomChatModel.cs | 2 +- .../Example42_KernelBuilder.cs | 2 +- .../HuggingFaceTextCompletion.cs | 21 ++++++++---- .../HuggingFaceTextEmbeddingGeneration.cs | 30 +++++++++++----- .../AzureSdk/AzureOpenAIClientBase.cs | 6 ++++ .../AzureSdk/OpenAIClientBase.cs | 11 ++++++ .../AzureOpenAIChatCompletion.cs | 12 +++---- .../ChatCompletion/OpenAIChatCompletion.cs | 9 +++-- .../AzureOpenAIChatCompletionWithData.cs | 4 +-- .../CustomClient/OpenAIClientBase.cs | 16 +++++++++ .../AzureOpenAIImageGeneration.cs | 7 ++-- .../ImageGeneration/OpenAIImageGeneration.cs | 9 +++-- .../OpenAIKernelBuilderExtensions.cs | 17 ++++++++-- .../OpenAIMemoryBuilderExtensions.cs | 6 ++++ .../TextCompletion/AzureTextCompletion.cs | 34 +++++++++++++------ .../TextCompletion/OpenAITextCompletion.cs | 10 ++++-- .../AzureOpenAITextEmbeddingGeneration.cs | 24 ++++++++----- .../OpenAITextEmbeddingGeneration.cs | 5 +-- .../KustoMemoryRecord.cs | 4 +-- .../PostgresMemoryEntry.cs | 2 +- .../KernelSemanticFunctionExtensionsTests.cs | 2 +- .../Events/FunctionInvokedEventArgs.cs | 2 +- .../Memory/MemoryRecord.cs | 4 +-- .../Orchestration/FunctionResult.cs | 2 +- .../Services/IAIService.cs | 4 +-- ...redIAIServiceConfigurationProviderTests.cs | 4 +-- .../Services/ServiceRegistryTests.cs | 2 +- 30 files changed, 187 insertions(+), 72 deletions(-) diff --git a/docs/decisions/0018-aiservice-metadata.md b/docs/decisions/0018-aiservice-metadata.md index f5f8c28e43c2..5012c2205641 100644 --- a/docs/decisions/0018-aiservice-metadata.md +++ b/docs/decisions/0018-aiservice-metadata.md @@ -2,7 +2,7 @@ # These are optional elements. Feel free to remove any of them. status: {proposed} date: {2023-10-25} -deciders: semenshi, markwallace, rbarreto, dmytrostruk +deciders: SergeyMenshykh, markwallace, rbarreto, dmytrostruk consulted: informed: --- @@ -63,7 +63,7 @@ From the perspective of a prompt creator using OpenAI, they will typically tune * Option #1 * Extend `IAIService` to include the following properties: * `string? ModelId { get; }` which returns the model id. It will be the responsibility of each `IAIService` implementation to populate this with the appropriate value. - * `IReadOnlyDictionary Metadata { get; }` which returns the metadata as a readonly dictionary. It will be the responsibility of each `IAIService` implementation to populate this with the appropriate metadata. + * `IReadOnlyDictionary Attributes { get; }` which returns the attributes as a readonly dictionary. It will be the responsibility of each `IAIService` implementation to populate this with the appropriate metadata. * Extend `INamedServiceProvider` to include this method `ICollection GetServices() where T : TService;` * Extend `OpenAIKernelBuilderExtensions` so that `WithAzureXXX` methods will include a `modelId` property if a specific model can be targeted. diff --git a/dotnet/samples/KernelSyntaxExamples/Example12_SequentialPlanner.cs b/dotnet/samples/KernelSyntaxExamples/Example12_SequentialPlanner.cs index d72b9e5f3487..db4bcdd4157c 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example12_SequentialPlanner.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example12_SequentialPlanner.cs @@ -300,7 +300,7 @@ private static SemanticTextMemory InitializeMemory() var memoryStorage = new VolatileMemoryStore(); var textEmbeddingGenerator = new AzureOpenAITextEmbeddingGeneration( - modelId: TestConfiguration.AzureOpenAIEmbeddings.DeploymentName, + deploymentName: TestConfiguration.AzureOpenAIEmbeddings.DeploymentName, endpoint: TestConfiguration.AzureOpenAIEmbeddings.Endpoint, apiKey: TestConfiguration.AzureOpenAIEmbeddings.ApiKey); diff --git a/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs b/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs index 537de1c4fde9..e0077418362b 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs @@ -31,7 +31,7 @@ public class MyTextCompletionService : ITextCompletion { public string? ModelId { get; private set; } - public IReadOnlyDictionary Metadata => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); public Task> GetCompletionsAsync(string text, AIRequestSettings? requestSettings, CancellationToken cancellationToken = default) { diff --git a/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs b/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs index a2cc36ed00cb..752385346771 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs @@ -22,7 +22,7 @@ public sealed class MyChatCompletionService : IChatCompletion { public string? ModelId { get; private set; } - public IReadOnlyDictionary Metadata => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); public ChatHistory CreateNewChat(string? instructions = null) { diff --git a/dotnet/samples/KernelSyntaxExamples/Example42_KernelBuilder.cs b/dotnet/samples/KernelSyntaxExamples/Example42_KernelBuilder.cs index 2058bc38e1ad..f7cc55a3ccb1 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example42_KernelBuilder.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example42_KernelBuilder.cs @@ -73,7 +73,7 @@ public static Task RunAsync() var loggerFactory = NullLoggerFactory.Instance; var memoryStorage = new VolatileMemoryStore(); var textEmbeddingGenerator = new AzureOpenAITextEmbeddingGeneration( - modelId: azureOpenAIEmbeddingDeployment, + deploymentName: azureOpenAIEmbeddingDeployment, endpoint: azureOpenAIEndpoint, apiKey: azureOpenAIKey, loggerFactory: loggerFactory); diff --git a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs index c15d4969eed7..38b84e029af6 100644 --- a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs @@ -9,6 +9,7 @@ using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Diagnostics; +using Microsoft.SemanticKernel.Services; namespace Microsoft.SemanticKernel.Connectors.AI.HuggingFace.TextCompletion; @@ -19,13 +20,17 @@ namespace Microsoft.SemanticKernel.Connectors.AI.HuggingFace.TextCompletion; public sealed class HuggingFaceTextCompletion : ITextCompletion #pragma warning restore CA1001 // Types that own disposable fields should be disposable. No need to dispose the Http client here. It can either be an internal client using NonDisposableHttpClientHandler or an external client managed by the calling code, which should handle its disposal. { + /// + /// Attribute name used to store the endpoint in the dictionary. + /// + public const string EndpointAttribute = "Endpoint"; + private const string HuggingFaceApiEndpoint = "https://api-inference.huggingface.co/models"; - private readonly string _model; private readonly string? _endpoint; private readonly HttpClient _httpClient; private readonly string? _apiKey; - private readonly Dictionary _metadata = new(); + private readonly Dictionary _attributes = new(); /// /// Initializes a new instance of the class. @@ -39,7 +44,9 @@ public HuggingFaceTextCompletion(Uri endpoint, string model) Verify.NotNullOrWhiteSpace(model); this._endpoint = endpoint.AbsoluteUri; - this._model = model; + this._attributes.Add(EndpointAttribute, this._endpoint); + + this.ModelId = model; this._httpClient = new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); } @@ -57,17 +64,19 @@ public HuggingFaceTextCompletion(string model, string? apiKey = null, HttpClient { Verify.NotNullOrWhiteSpace(model); - this._model = model; this._apiKey = apiKey; this._httpClient = httpClient ?? new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); this._endpoint = endpoint; + this._attributes.Add(EndpointAttribute, this._endpoint ?? HuggingFaceApiEndpoint); + + this.ModelId = model; } /// public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Metadata => this._metadata; + public IReadOnlyDictionary Attributes => this._attributes; /// [Obsolete("Streaming capability is not supported, use GetCompletionsAsync instead")] @@ -141,7 +150,7 @@ private Uri GetRequestUri() baseUrl = this._httpClient.BaseAddress!.AbsoluteUri; } - return new Uri($"{baseUrl!.TrimEnd('/')}/{this._model}"); + return new Uri($"{baseUrl!.TrimEnd('/')}/{this.ModelId}"); } #endregion diff --git a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs index ac0002e67b46..67b06217bcd4 100644 --- a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.SemanticKernel.AI.Embeddings; using Microsoft.SemanticKernel.Diagnostics; +using Microsoft.SemanticKernel.Services; namespace Microsoft.SemanticKernel.Connectors.AI.HuggingFace.TextEmbedding; @@ -19,10 +20,14 @@ namespace Microsoft.SemanticKernel.Connectors.AI.HuggingFace.TextEmbedding; public sealed class HuggingFaceTextEmbeddingGeneration : ITextEmbeddingGeneration #pragma warning restore CA1001 // Types that own disposable fields should be disposable. No need to dispose the Http client here. It can either be an internal client using NonDisposableHttpClientHandler or an external client managed by the calling code, which should handle its disposal. { - private readonly string _model; + /// + /// Attribute name used to store the endpoint in the dictionary. + /// + public const string EndpointAttribute = "Endpoint"; + private readonly string? _endpoint; private readonly HttpClient _httpClient; - private readonly Dictionary _metadata = new(); + private readonly Dictionary _attributes = new(); /// /// Initializes a new instance of the class. @@ -35,8 +40,10 @@ public HuggingFaceTextEmbeddingGeneration(Uri endpoint, string model) Verify.NotNull(endpoint); Verify.NotNullOrWhiteSpace(model); + this.ModelId = model; + this._endpoint = endpoint.AbsoluteUri; - this._model = model; + this._attributes.Add(EndpointAttribute, this._endpoint); this._httpClient = new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); } @@ -51,9 +58,10 @@ public HuggingFaceTextEmbeddingGeneration(string model, string endpoint) Verify.NotNullOrWhiteSpace(model); Verify.NotNullOrWhiteSpace(endpoint); - this._model = model; - this._endpoint = endpoint; + this.ModelId = model; + this._endpoint = endpoint; + this._attributes.Add(EndpointAttribute, this._endpoint); this._httpClient = new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); } @@ -68,10 +76,16 @@ public HuggingFaceTextEmbeddingGeneration(string model, HttpClient httpClient, s Verify.NotNullOrWhiteSpace(model); Verify.NotNull(httpClient); - this._model = model; + this.ModelId = model; + this._endpoint = endpoint; this._httpClient = httpClient; + if (!string.IsNullOrEmpty(endpoint)) + { + this._attributes.Add(EndpointAttribute, endpoint!); + } + if (httpClient.BaseAddress == null && string.IsNullOrEmpty(endpoint)) { throw new SKException("The HttpClient BaseAddress and endpoint are both null or empty. Please ensure at least one is provided."); @@ -82,7 +96,7 @@ public HuggingFaceTextEmbeddingGeneration(string model, HttpClient httpClient, s public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Metadata => this._metadata; + public IReadOnlyDictionary Attributes => this._attributes; /// public async Task>> GenerateEmbeddingsAsync(IList data, CancellationToken cancellationToken = default) @@ -140,7 +154,7 @@ private Uri GetRequestUri() throw new SKException("No endpoint or HTTP client base address has been provided"); } - return new Uri($"{baseUrl!.TrimEnd('/')}/{this._model}"); + return new Uri($"{baseUrl!.TrimEnd('/')}/{this.ModelId}"); } #endregion diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs index 86f3e33b5eed..7242e51c2399 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs @@ -9,6 +9,7 @@ using Azure.Core.Pipeline; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.Diagnostics; +using Microsoft.SemanticKernel.Services; namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.AzureSdk; /// @@ -16,6 +17,11 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.AzureSdk; /// public abstract class AzureOpenAIClientBase : ClientBase { + /// + /// Attribute name used to store the deployment name in the dictionary. + /// + public const string DeploymentNameAttribute = "DeploymentName"; + /// /// OpenAI / Azure OpenAI Client /// diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/OpenAIClientBase.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/OpenAIClientBase.cs index d0fbe506f045..8e64ff2fc4c5 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/OpenAIClientBase.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/OpenAIClientBase.cs @@ -7,6 +7,7 @@ using Azure.Core.Pipeline; using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.Diagnostics; +using Microsoft.SemanticKernel.Services; namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.AzureSdk; @@ -15,6 +16,16 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.AzureSdk; /// public abstract class OpenAIClientBase : ClientBase { + /// + /// Attribute name used to store the orhanization in the dictionary. + /// + public const string OrganizationAttribute = "Organization"; + + /// + /// Attribute name used to store the endpoint in the dictionary. + /// + public const string EndpointAttribute = "Endpoint"; + /// /// OpenAI / Azure OpenAI Client /// diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureOpenAIChatCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureOpenAIChatCompletion.cs index a1775a12d4ec..2bed2d12a70b 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureOpenAIChatCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureOpenAIChatCompletion.cs @@ -20,7 +20,7 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ChatCompletion; /// public sealed class AzureOpenAIChatCompletion : AzureOpenAIClientBase, IChatCompletion, ITextCompletion { - private readonly Dictionary _metadata = new(); + private readonly Dictionary _attributes = new(); /// /// Create an instance of the connector with API key auth. @@ -39,10 +39,8 @@ public AzureOpenAIChatCompletion( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, apiKey, httpClient, loggerFactory) { - if (!string.IsNullOrEmpty(modelId)) - { - this.ModelId = modelId; - } + this.ModelId = modelId; + this._attributes.Add(DeploymentNameAttribute, deploymentName); } /// @@ -63,6 +61,7 @@ public AzureOpenAIChatCompletion( ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, credentials, httpClient, loggerFactory) { this.ModelId = modelId; + this._attributes.Add(DeploymentNameAttribute, deploymentName); } /// @@ -79,13 +78,14 @@ public AzureOpenAIChatCompletion( ILoggerFactory? loggerFactory = null) : base(deploymentName, openAIClient, loggerFactory) { this.ModelId = modelId; + this._attributes.Add(DeploymentNameAttribute, deploymentName); } /// public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Metadata => this._metadata; + public IReadOnlyDictionary Attributes => this._attributes; /// public Task> GetChatCompletionsAsync( diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs index fa9b6e4b3a1b..1302a819d947 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs @@ -19,7 +19,7 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ChatCompletion; /// public sealed class OpenAIChatCompletion : OpenAIClientBase, IChatCompletion, ITextCompletion { - private readonly Dictionary _metadata = new(); + private readonly Dictionary _attributes = new(); /// /// Create an instance of the OpenAI chat completion connector @@ -37,6 +37,11 @@ public OpenAIChatCompletion( ILoggerFactory? loggerFactory = null) : base(modelId, apiKey, organization, httpClient, loggerFactory) { this.ModelId = modelId; + + if (!string.IsNullOrWhiteSpace(organization)) + { + this._attributes.Add(OrganizationAttribute, organization!); + } } /// @@ -57,7 +62,7 @@ public OpenAIChatCompletion( public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Metadata => this._metadata; + public IReadOnlyDictionary Attributes => this._attributes; /// public Task> GetChatCompletionsAsync( diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureOpenAIChatCompletionWithData.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureOpenAIChatCompletionWithData.cs index fd0ce2ef0849..85891d170491 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureOpenAIChatCompletionWithData.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureOpenAIChatCompletionWithData.cs @@ -50,7 +50,7 @@ public AzureOpenAIChatCompletionWithData( public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Metadata => this._metadata; + public IReadOnlyDictionary Attributes => this._attributes; /// public ChatHistory CreateNewChat(string? instructions = null) @@ -128,7 +128,7 @@ public async IAsyncEnumerable GetStreamingCompletionsAsync private readonly HttpClient _httpClient; private readonly ILogger _logger; - private readonly Dictionary _metadata = new(); + private readonly Dictionary _attributes = new(); private void ValidateConfig(AzureOpenAIChatCompletionWithDataConfig config) { diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/CustomClient/OpenAIClientBase.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/CustomClient/OpenAIClientBase.cs index 1a098ceac4e3..ed541b13eeb6 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/CustomClient/OpenAIClientBase.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/CustomClient/OpenAIClientBase.cs @@ -12,6 +12,7 @@ using Microsoft.SemanticKernel.Connectors.AI.OpenAI.ImageGeneration; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; using Microsoft.SemanticKernel.Diagnostics; +using Microsoft.SemanticKernel.Services; using Microsoft.SemanticKernel.Text; namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.CustomClient; @@ -19,6 +20,21 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.CustomClient; /// Base type for OpenAI clients. public abstract class OpenAIClientBase { + /// + /// Attribute name used to store the orhanization in the dictionary. + /// + public const string OrganizationAttribute = "Organization"; + + /// + /// Attribute name used to store the endpoint in the dictionary. + /// + public const string EndpointAttribute = "Endpoint"; + + /// + /// Attribute name used to store the API version in the dictionary. + /// + public const string ApiVersionAttribute = "ApiVersion"; + /// /// Initializes a new instance of the class. /// diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs index 888a20d88cef..9af319856d45 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs @@ -20,7 +20,7 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ImageGeneration; /// public class AzureOpenAIImageGeneration : OpenAIClientBase, IImageGeneration { - private readonly Dictionary _metadata = new(); + private readonly Dictionary _attributes = new(); /// /// Generation Image Operation path @@ -71,6 +71,7 @@ public AzureOpenAIImageGeneration(string endpoint, string apiKey, HttpClient? ht this._apiKey = apiKey; this._maxRetryCount = maxRetryCount; this._apiVersion = apiVersion; + this._attributes.Add(EndpointAttribute, endpoint); } /// @@ -99,13 +100,15 @@ public AzureOpenAIImageGeneration(string apiKey, HttpClient httpClient, string? this._apiKey = apiKey; this._maxRetryCount = maxRetryCount; this._apiVersion = apiVersion; + this._attributes.Add(EndpointAttribute, endpoint); + this._attributes.Add(ApiVersionAttribute, apiVersion); } /// public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Metadata => this._metadata; + public IReadOnlyDictionary Attributes => this._attributes; /// public async Task GenerateImageAsync(string description, int width, int height, CancellationToken cancellationToken = default) diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs index fba153fbd364..be2dac461ecb 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs @@ -18,7 +18,7 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ImageGeneration; /// public class OpenAIImageGeneration : OpenAIClientBase, IImageGeneration { - private readonly Dictionary _metadata = new(); + private readonly Dictionary _attributes = new(); /// /// OpenAI REST API endpoint @@ -52,13 +52,18 @@ public OpenAIImageGeneration( Verify.NotNullOrWhiteSpace(apiKey); this._authorizationHeaderValue = $"Bearer {apiKey}"; this._organizationHeaderValue = organization; + + if (!string.IsNullOrEmpty(organization)) + { + this._attributes.Add(OrganizationAttribute, organization!); + } } /// public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Metadata => this._metadata; + public IReadOnlyDictionary Attributes => this._attributes; /// Adds headers to use for OpenAI HTTP requests. private protected override void AddRequestHeaders(HttpRequestMessage request) diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs index 244ef9a74cda..b47bbacde451 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIKernelBuilderExtensions.cs @@ -39,6 +39,7 @@ public static class OpenAIKernelBuilderExtensions /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Azure OpenAI API key, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// A local identifier for the given AI service + /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Whether the service should be the default for its type. /// Custom for HTTP requests. /// Self instance @@ -47,13 +48,14 @@ public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder bu string endpoint, string apiKey, string? serviceId = null, + string? modelId = null, bool setAsDefault = false, HttpClient? httpClient = null) { builder.WithAIService(serviceId, (loggerFactory, httpHandlerFactory) => { var client = CreateAzureOpenAIClient(loggerFactory, httpHandlerFactory, deploymentName, endpoint, new AzureKeyCredential(apiKey), httpClient); - return new AzureTextCompletion(deploymentName, client, loggerFactory); + return new AzureTextCompletion(deploymentName, client, modelId, loggerFactory); }, setAsDefault); return builder; @@ -68,6 +70,7 @@ public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder bu /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Token credentials, e.g. DefaultAzureCredential, ManagedIdentityCredential, EnvironmentCredential, etc. /// A local identifier for the given AI service + /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Whether the service should be the default for its type. /// Custom for HTTP requests. /// Self instance @@ -76,13 +79,14 @@ public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder bu string endpoint, TokenCredential credentials, string? serviceId = null, + string? modelId = null, bool setAsDefault = false, HttpClient? httpClient = null) { builder.WithAIService(serviceId, (loggerFactory, httpHandlerFactory) => { var client = CreateAzureOpenAIClient(loggerFactory, httpHandlerFactory, deploymentName, endpoint, credentials, httpClient); - return new AzureTextCompletion(deploymentName, client, loggerFactory); + return new AzureTextCompletion(deploymentName, client, modelId, loggerFactory); }, setAsDefault); return builder; @@ -96,18 +100,21 @@ public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder bu /// Azure OpenAI deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Custom . /// A local identifier for the given AI service + /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Whether the service should be the default for its type. /// Self instance public static KernelBuilder WithAzureTextCompletionService(this KernelBuilder builder, string deploymentName, OpenAIClient openAIClient, string? serviceId = null, + string? modelId = null, bool setAsDefault = false) { builder.WithAIService(serviceId, (loggerFactory) => new AzureTextCompletion( deploymentName, openAIClient, + modelId, loggerFactory), setAsDefault); @@ -158,6 +165,7 @@ public static KernelBuilder WithOpenAITextCompletionService(this KernelBuilder b /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Azure OpenAI API key, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// A local identifier for the given AI service + /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Whether the service should be the default for its type. /// Custom for HTTP requests. /// Self instance @@ -166,6 +174,7 @@ public static KernelBuilder WithAzureOpenAITextEmbeddingGenerationService(this K string endpoint, string apiKey, string? serviceId = null, + string? modelId = null, bool setAsDefault = false, HttpClient? httpClient = null) { @@ -174,6 +183,7 @@ public static KernelBuilder WithAzureOpenAITextEmbeddingGenerationService(this K deploymentName, endpoint, apiKey, + modelId, HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), loggerFactory), setAsDefault); @@ -189,6 +199,7 @@ public static KernelBuilder WithAzureOpenAITextEmbeddingGenerationService(this K /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Token credentials, e.g. DefaultAzureCredential, ManagedIdentityCredential, EnvironmentCredential, etc. /// A local identifier for the given AI service + /// Model identifier, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Whether the service should be the default for its type. /// Custom for HTTP requests. /// Self instance @@ -197,6 +208,7 @@ public static KernelBuilder WithAzureOpenAITextEmbeddingGenerationService(this K string endpoint, TokenCredential credential, string? serviceId = null, + string? modelId = null, bool setAsDefault = false, HttpClient? httpClient = null) { @@ -205,6 +217,7 @@ public static KernelBuilder WithAzureOpenAITextEmbeddingGenerationService(this K deploymentName, endpoint, credential, + modelId, HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), loggerFactory), setAsDefault); diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIMemoryBuilderExtensions.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIMemoryBuilderExtensions.cs index f993831b8e35..6fe4e3ed8ee8 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIMemoryBuilderExtensions.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/OpenAIMemoryBuilderExtensions.cs @@ -21,6 +21,7 @@ public static class OpenAIMemoryBuilderExtensions /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Azure OpenAI API key, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// A local identifier for the given AI service + /// Model identifier /// Whether the service should be the default for its type. /// Custom for HTTP requests. /// Self instance @@ -30,6 +31,7 @@ public static MemoryBuilder WithAzureOpenAITextEmbeddingGenerationService( string endpoint, string apiKey, string? serviceId = null, + string? modelId = null, bool setAsDefault = false, HttpClient? httpClient = null) { @@ -38,6 +40,7 @@ public static MemoryBuilder WithAzureOpenAITextEmbeddingGenerationService( deploymentName, endpoint, apiKey, + modelId, HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), loggerFactory)); @@ -53,6 +56,7 @@ public static MemoryBuilder WithAzureOpenAITextEmbeddingGenerationService( /// Azure OpenAI deployment URL, see https://learn.microsoft.com/azure/cognitive-services/openai/quickstart /// Token credentials, e.g. DefaultAzureCredential, ManagedIdentityCredential, EnvironmentCredential, etc. /// A local identifier for the given AI service + /// Model identifier /// Whether the service should be the default for its type. /// Custom for HTTP requests. /// Self instance @@ -62,6 +66,7 @@ public static MemoryBuilder WithAzureOpenAITextEmbeddingGenerationService( string endpoint, TokenCredential credential, string? serviceId = null, + string? modelId = null, bool setAsDefault = false, HttpClient? httpClient = null) { @@ -70,6 +75,7 @@ public static MemoryBuilder WithAzureOpenAITextEmbeddingGenerationService( deploymentName, endpoint, credential, + modelId, HttpClientProvider.GetHttpClient(httpHandlerFactory, httpClient, loggerFactory), loggerFactory)); diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs index b18420f713e0..580235f843dd 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs @@ -19,60 +19,72 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion; /// public sealed class AzureTextCompletion : AzureOpenAIClientBase, ITextCompletion { - private readonly Dictionary _metadata = new(); + private readonly Dictionary _attributes = new(); /// /// Creates a new AzureTextCompletion client instance using API Key auth /// - /// Azure OpenAI model ID or deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// 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 + /// Azure OpenAI model id, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Custom for HTTP requests. /// The to use for logging. If null, no logging will be performed. public AzureTextCompletion( - string modelId, + string deploymentName, string endpoint, string apiKey, + string? modelId = null, HttpClient? httpClient = null, - ILoggerFactory? loggerFactory = null) : base(modelId, endpoint, apiKey, httpClient, loggerFactory) + ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, apiKey, httpClient, loggerFactory) { + this.ModelId = modelId; + this._attributes.Add(DeploymentNameAttribute, deploymentName); } /// /// Creates a new AzureTextCompletion client instance supporting AAD auth /// - /// Azure OpenAI model ID or deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// 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. + /// Azure OpenAI model id, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Custom for HTTP requests. /// The to use for logging. If null, no logging will be performed. public AzureTextCompletion( - string modelId, + string deploymentName, string endpoint, TokenCredential credential, + string? modelId = null, HttpClient? httpClient = null, - ILoggerFactory? loggerFactory = null) : base(modelId, endpoint, credential, httpClient, loggerFactory) + ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, credential, httpClient, loggerFactory) { + this.ModelId = modelId; + this._attributes.Add(DeploymentNameAttribute, deploymentName); } /// /// Creates a new AzureTextCompletion client instance using the specified OpenAIClient /// - /// Azure OpenAI model ID or deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// Azure OpenAI model ID or deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Custom . + /// Azure OpenAI model id, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// The to use for logging. If null, no logging will be performed. public AzureTextCompletion( - string modelId, + string deploymentName, OpenAIClient openAIClient, - ILoggerFactory? loggerFactory = null) : base(modelId, openAIClient, loggerFactory) + string? modelId = null, + ILoggerFactory? loggerFactory = null) : base(deploymentName, openAIClient, loggerFactory) { + this.ModelId = modelId; + this._attributes.Add(DeploymentNameAttribute, deploymentName); } /// public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Metadata => this._metadata; + public IReadOnlyDictionary Attributes => this._attributes; /// public IAsyncEnumerable GetStreamingCompletionsAsync( diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs index 5c27cf306a65..3081f8d254fa 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs @@ -17,7 +17,7 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion; /// public sealed class OpenAITextCompletion : OpenAIClientBase, ITextCompletion { - private readonly Dictionary _metadata = new(); + private readonly Dictionary _attributes = new(); /// /// Create an instance of the OpenAI text completion connector @@ -35,13 +35,19 @@ public OpenAITextCompletion( ILoggerFactory? loggerFactory = null ) : base(modelId, apiKey, organization, httpClient, loggerFactory) { + this.ModelId = modelId; + + if (!string.IsNullOrWhiteSpace(organization)) + { + this._attributes.Add(OrganizationAttribute, organization!); + } } /// public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Metadata => this._metadata; + public IReadOnlyDictionary Attributes => this._attributes; /// public IAsyncEnumerable GetStreamingCompletionsAsync( diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs index 925b5aff1ef8..c8e4630c4834 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs @@ -17,47 +17,55 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; /// public sealed class AzureOpenAITextEmbeddingGeneration : AzureOpenAIClientBase, ITextEmbeddingGeneration { - private readonly Dictionary _metadata = new(); + private readonly Dictionary _attributes = new(); /// /// Creates a new client instance using API Key auth. /// - /// Azure OpenAI model ID or deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// 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 + /// Azure OpenAI model id, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Custom for HTTP requests. /// The to use for logging. If null, no logging will be performed. public AzureOpenAITextEmbeddingGeneration( - string modelId, + string deploymentName, string endpoint, string apiKey, + string? modelId = null, HttpClient? httpClient = null, - ILoggerFactory? loggerFactory = null) : base(modelId, endpoint, apiKey, httpClient, loggerFactory) + ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, apiKey, httpClient, loggerFactory) { + this.ModelId = modelId; + this._attributes.Add(DeploymentNameAttribute, deploymentName); } /// /// Creates a new client instance supporting AAD auth. /// - /// Azure OpenAI model ID or deployment name, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource + /// 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. + /// Azure OpenAI model id, see https://learn.microsoft.com/azure/cognitive-services/openai/how-to/create-resource /// Custom for HTTP requests. /// The to use for logging. If null, no logging will be performed. public AzureOpenAITextEmbeddingGeneration( - string modelId, + string deploymentName, string endpoint, TokenCredential credential, + string? modelId = null, HttpClient? httpClient = null, - ILoggerFactory? loggerFactory = null) : base(modelId, endpoint, credential, httpClient, loggerFactory) + ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, credential, httpClient, loggerFactory) { + this.ModelId = modelId; + this._attributes.Add(DeploymentNameAttribute, deploymentName); } /// public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Metadata => this._metadata; + public IReadOnlyDictionary Attributes => this._attributes; /// /// Generates an embedding from the given . diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs index 93c68f3d3ed2..96c6b598be02 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs @@ -16,7 +16,7 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; /// public sealed class OpenAITextEmbeddingGeneration : OpenAIClientBase, ITextEmbeddingGeneration { - private readonly Dictionary _metadata = new(); + private readonly Dictionary _attributes = new(); /// /// Create an instance of the OpenAI text embedding connector @@ -34,13 +34,14 @@ public OpenAITextEmbeddingGeneration( ILoggerFactory? loggerFactory = null ) : base(modelId, apiKey, organization, httpClient, loggerFactory) { + this.ModelId = modelId; } /// public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Metadata => this._metadata; + public IReadOnlyDictionary Attributes => this._attributes; /// /// Generates an embedding from the given . diff --git a/dotnet/src/Connectors/Connectors.Memory.Kusto/KustoMemoryRecord.cs b/dotnet/src/Connectors/Connectors.Memory.Kusto/KustoMemoryRecord.cs index a7e8783f06f1..d0f423a51ce3 100644 --- a/dotnet/src/Connectors/Connectors.Memory.Kusto/KustoMemoryRecord.cs +++ b/dotnet/src/Connectors/Connectors.Memory.Kusto/KustoMemoryRecord.cs @@ -19,7 +19,7 @@ public sealed class KustoMemoryRecord public string Key { get; set; } /// - /// Metadata associated with memory entity. + /// Attributes associated with memory entity. /// public MemoryRecordMetadata Metadata { get; set; } @@ -44,7 +44,7 @@ public KustoMemoryRecord(MemoryRecord record) : this(record.Key, record.Metadata /// Initializes a new instance of the class. /// /// Entity key. - /// Metadata associated with memory entity. + /// Attributes associated with memory entity. /// Source content embedding. /// Optional timestamp. public KustoMemoryRecord(string key, MemoryRecordMetadata metadata, ReadOnlyMemory embedding, DateTimeOffset? timestamp = null) diff --git a/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresMemoryEntry.cs b/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresMemoryEntry.cs index a7429b44c157..5b019bc3edfd 100644 --- a/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresMemoryEntry.cs +++ b/dotnet/src/Connectors/Connectors.Memory.Postgres/PostgresMemoryEntry.cs @@ -16,7 +16,7 @@ public record struct PostgresMemoryEntry public string Key { get; set; } /// - /// Metadata as a string. + /// Attributes as a string. /// public string MetadataString { get; set; } diff --git a/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs b/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs index 43825320c637..50c4b2568fdc 100644 --- a/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs +++ b/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs @@ -71,7 +71,7 @@ private sealed class RedirectTextCompletion : ITextCompletion { public string? ModelId => null; - public IReadOnlyDictionary Metadata => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); Task> ITextCompletion.GetCompletionsAsync(string text, AIRequestSettings? requestSettings, CancellationToken cancellationToken) { diff --git a/dotnet/src/SemanticKernel.Abstractions/Events/FunctionInvokedEventArgs.cs b/dotnet/src/SemanticKernel.Abstractions/Events/FunctionInvokedEventArgs.cs index b36674e28a15..49d7d041514d 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Events/FunctionInvokedEventArgs.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Events/FunctionInvokedEventArgs.cs @@ -18,7 +18,7 @@ public class FunctionInvokedEventArgs : SKCancelEventArgs public bool IsRepeatRequested => this._repeatRequested; /// - /// Metadata for storing additional information about function execution result. + /// Attributes for storing additional information about function execution result. /// public Dictionary Metadata => this._metadata ??= new(); diff --git a/dotnet/src/SemanticKernel.Abstractions/Memory/MemoryRecord.cs b/dotnet/src/SemanticKernel.Abstractions/Memory/MemoryRecord.cs index d87c7a876ed3..970a1cdd279e 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Memory/MemoryRecord.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Memory/MemoryRecord.cs @@ -21,7 +21,7 @@ public class MemoryRecord : DataEntryBase public ReadOnlyMemory Embedding { get; } /// - /// Metadata associated with a Semantic Kernel memory. + /// Attributes associated with a Semantic Kernel memory. /// [JsonPropertyName("metadata")] public MemoryRecordMetadata Metadata { get; } @@ -138,7 +138,7 @@ public static MemoryRecord FromJsonMetadata( /// /// Create a memory record from a memory record's metadata. /// - /// Metadata associated with a memory. + /// Attributes associated with a memory. /// Optional embedding associated with a memory record. /// Optional existing database key. /// optional timestamp. diff --git a/dotnet/src/SemanticKernel.Abstractions/Orchestration/FunctionResult.cs b/dotnet/src/SemanticKernel.Abstractions/Orchestration/FunctionResult.cs index eaac4d7ae3e1..d27c700afe02 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Orchestration/FunctionResult.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Orchestration/FunctionResult.cs @@ -23,7 +23,7 @@ public sealed class FunctionResult public string PluginName { get; internal set; } /// - /// Metadata for storing additional information about function execution result. + /// Attributes for storing additional information about function execution result. /// public Dictionary Metadata { diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs b/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs index 4f56717336de..5e71cac1d84a 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs @@ -18,7 +18,7 @@ public interface IAIService string? ModelId { get; } /// - /// Gets the AI service metadata. + /// Gets the AI service attributes. /// - IReadOnlyDictionary Metadata { get; } + IReadOnlyDictionary Attributes { get; } } diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs index 9abd266228f5..8d8cf7cfdc05 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs @@ -223,14 +223,14 @@ public void ItGetsAIServiceConfigurationByOrder(string[] serviceIds, string expe #region private private sealed class AIService : IAIService { - public IReadOnlyDictionary Metadata => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); public string? ModelId { get; } } private sealed class TextCompletion : ITextCompletion { - public IReadOnlyDictionary Metadata => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); public string? ModelId { get; } diff --git a/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs b/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs index 4f6167fe9fd9..394b977d42ff 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs @@ -218,6 +218,6 @@ private sealed class TestService : IAIService { public string? ModelId { get; } - public IReadOnlyDictionary Metadata => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); } } From f841486e99270881ac7aca7e4092d7d32b2712ec Mon Sep 17 00:00:00 2001 From: markwallace-microsoft Date: Tue, 7 Nov 2023 19:54:50 +0000 Subject: [PATCH 08/18] Switch service attribute values to object --- dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs | 2 +- .../samples/KernelSyntaxExamples/Example34_CustomChatModel.cs | 2 +- .../TextCompletion/HuggingFaceTextCompletion.cs | 4 ++-- .../TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs | 4 ++-- .../ChatCompletion/AzureOpenAIChatCompletion.cs | 4 ++-- .../ChatCompletion/OpenAIChatCompletion.cs | 4 ++-- .../AzureOpenAIChatCompletionWithData.cs | 4 ++-- .../ImageGeneration/AzureOpenAIImageGeneration.cs | 4 ++-- .../ImageGeneration/OpenAIImageGeneration.cs | 4 ++-- .../TextCompletion/AzureTextCompletion.cs | 4 ++-- .../TextCompletion/OpenAITextCompletion.cs | 4 ++-- .../TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs | 4 ++-- .../TextEmbedding/OpenAITextEmbeddingGeneration.cs | 4 ++-- .../Extensions/KernelSemanticFunctionExtensionsTests.cs | 2 +- dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs | 2 +- .../Functions/OrderedIAIServiceConfigurationProviderTests.cs | 4 ++-- .../SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs | 2 +- 17 files changed, 29 insertions(+), 29 deletions(-) diff --git a/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs b/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs index e0077418362b..ff49cde89830 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs @@ -31,7 +31,7 @@ public class MyTextCompletionService : ITextCompletion { public string? ModelId { get; private set; } - public IReadOnlyDictionary Attributes => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); public Task> GetCompletionsAsync(string text, AIRequestSettings? requestSettings, CancellationToken cancellationToken = default) { diff --git a/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs b/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs index 752385346771..e4df5077fe29 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs @@ -22,7 +22,7 @@ public sealed class MyChatCompletionService : IChatCompletion { public string? ModelId { get; private set; } - public IReadOnlyDictionary Attributes => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); public ChatHistory CreateNewChat(string? instructions = null) { diff --git a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs index 38b84e029af6..832d1719d384 100644 --- a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs @@ -30,7 +30,7 @@ public sealed class HuggingFaceTextCompletion : ITextCompletion private readonly string? _endpoint; private readonly HttpClient _httpClient; private readonly string? _apiKey; - private readonly Dictionary _attributes = new(); + private readonly Dictionary _attributes = new(); /// /// Initializes a new instance of the class. @@ -76,7 +76,7 @@ public HuggingFaceTextCompletion(string model, string? apiKey = null, HttpClient public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this._attributes; /// [Obsolete("Streaming capability is not supported, use GetCompletionsAsync instead")] diff --git a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs index 67b06217bcd4..688cbb454cb1 100644 --- a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs @@ -27,7 +27,7 @@ public sealed class HuggingFaceTextEmbeddingGeneration : ITextEmbeddingGeneratio private readonly string? _endpoint; private readonly HttpClient _httpClient; - private readonly Dictionary _attributes = new(); + private readonly Dictionary _attributes = new(); /// /// Initializes a new instance of the class. @@ -96,7 +96,7 @@ public HuggingFaceTextEmbeddingGeneration(string model, HttpClient httpClient, s public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this._attributes; /// public async Task>> GenerateEmbeddingsAsync(IList data, CancellationToken cancellationToken = default) diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureOpenAIChatCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureOpenAIChatCompletion.cs index 2bed2d12a70b..a268b7dbbd28 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureOpenAIChatCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureOpenAIChatCompletion.cs @@ -20,7 +20,7 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ChatCompletion; /// public sealed class AzureOpenAIChatCompletion : AzureOpenAIClientBase, IChatCompletion, ITextCompletion { - private readonly Dictionary _attributes = new(); + private readonly Dictionary _attributes = new(); /// /// Create an instance of the connector with API key auth. @@ -85,7 +85,7 @@ public AzureOpenAIChatCompletion( public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this._attributes; /// public Task> GetChatCompletionsAsync( diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs index 1302a819d947..5d7bfcef386d 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs @@ -19,7 +19,7 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ChatCompletion; /// public sealed class OpenAIChatCompletion : OpenAIClientBase, IChatCompletion, ITextCompletion { - private readonly Dictionary _attributes = new(); + private readonly Dictionary _attributes = new(); /// /// Create an instance of the OpenAI chat completion connector @@ -62,7 +62,7 @@ public OpenAIChatCompletion( public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this._attributes; /// public Task> GetChatCompletionsAsync( diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureOpenAIChatCompletionWithData.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureOpenAIChatCompletionWithData.cs index 85891d170491..fb1b9bfbf5da 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureOpenAIChatCompletionWithData.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureOpenAIChatCompletionWithData.cs @@ -50,7 +50,7 @@ public AzureOpenAIChatCompletionWithData( public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this._attributes; /// public ChatHistory CreateNewChat(string? instructions = null) @@ -128,7 +128,7 @@ public async IAsyncEnumerable GetStreamingCompletionsAsync private readonly HttpClient _httpClient; private readonly ILogger _logger; - private readonly Dictionary _attributes = new(); + private readonly Dictionary _attributes = new(); private void ValidateConfig(AzureOpenAIChatCompletionWithDataConfig config) { diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs index 9af319856d45..1da70ca60894 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs @@ -20,7 +20,7 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ImageGeneration; /// public class AzureOpenAIImageGeneration : OpenAIClientBase, IImageGeneration { - private readonly Dictionary _attributes = new(); + private readonly Dictionary _attributes = new(); /// /// Generation Image Operation path @@ -108,7 +108,7 @@ public AzureOpenAIImageGeneration(string apiKey, HttpClient httpClient, string? public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this._attributes; /// public async Task GenerateImageAsync(string description, int width, int height, CancellationToken cancellationToken = default) diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs index be2dac461ecb..560f71c85d7a 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs @@ -18,7 +18,7 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ImageGeneration; /// public class OpenAIImageGeneration : OpenAIClientBase, IImageGeneration { - private readonly Dictionary _attributes = new(); + private readonly Dictionary _attributes = new(); /// /// OpenAI REST API endpoint @@ -63,7 +63,7 @@ public OpenAIImageGeneration( public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this._attributes; /// Adds headers to use for OpenAI HTTP requests. private protected override void AddRequestHeaders(HttpRequestMessage request) diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs index 580235f843dd..903a656fe645 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs @@ -19,7 +19,7 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion; /// public sealed class AzureTextCompletion : AzureOpenAIClientBase, ITextCompletion { - private readonly Dictionary _attributes = new(); + private readonly Dictionary _attributes = new(); /// /// Creates a new AzureTextCompletion client instance using API Key auth @@ -84,7 +84,7 @@ public AzureTextCompletion( public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this._attributes; /// public IAsyncEnumerable GetStreamingCompletionsAsync( diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs index 3081f8d254fa..a9f5b70cef16 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs @@ -17,7 +17,7 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion; /// public sealed class OpenAITextCompletion : OpenAIClientBase, ITextCompletion { - private readonly Dictionary _attributes = new(); + private readonly Dictionary _attributes = new(); /// /// Create an instance of the OpenAI text completion connector @@ -47,7 +47,7 @@ public OpenAITextCompletion( public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this._attributes; /// public IAsyncEnumerable GetStreamingCompletionsAsync( diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs index c8e4630c4834..712d62ee5e70 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs @@ -17,7 +17,7 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; /// public sealed class AzureOpenAITextEmbeddingGeneration : AzureOpenAIClientBase, ITextEmbeddingGeneration { - private readonly Dictionary _attributes = new(); + private readonly Dictionary _attributes = new(); /// /// Creates a new client instance using API Key auth. @@ -65,7 +65,7 @@ public AzureOpenAITextEmbeddingGeneration( public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this._attributes; /// /// Generates an embedding from the given . diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs index 96c6b598be02..2cf7ae50668a 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs @@ -16,7 +16,7 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; /// public sealed class OpenAITextEmbeddingGeneration : OpenAIClientBase, ITextEmbeddingGeneration { - private readonly Dictionary _attributes = new(); + private readonly Dictionary _attributes = new(); /// /// Create an instance of the OpenAI text embedding connector @@ -41,7 +41,7 @@ public OpenAITextEmbeddingGeneration( public string? ModelId { get; private set; } /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this._attributes; /// /// Generates an embedding from the given . diff --git a/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs b/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs index 50c4b2568fdc..7fbd29a98fc5 100644 --- a/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs +++ b/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs @@ -71,7 +71,7 @@ private sealed class RedirectTextCompletion : ITextCompletion { public string? ModelId => null; - public IReadOnlyDictionary Attributes => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); Task> ITextCompletion.GetCompletionsAsync(string text, AIRequestSettings? requestSettings, CancellationToken cancellationToken) { diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs b/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs index 5e71cac1d84a..739d75ce8754 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs @@ -20,5 +20,5 @@ public interface IAIService /// /// Gets the AI service attributes. /// - IReadOnlyDictionary Attributes { get; } + IReadOnlyDictionary Attributes { get; } } diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs index 8d8cf7cfdc05..b103b3e3af74 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs @@ -223,14 +223,14 @@ public void ItGetsAIServiceConfigurationByOrder(string[] serviceIds, string expe #region private private sealed class AIService : IAIService { - public IReadOnlyDictionary Attributes => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); public string? ModelId { get; } } private sealed class TextCompletion : ITextCompletion { - public IReadOnlyDictionary Attributes => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); public string? ModelId { get; } diff --git a/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs b/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs index 394b977d42ff..738b814b55c2 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs @@ -218,6 +218,6 @@ private sealed class TestService : IAIService { public string? ModelId { get; } - public IReadOnlyDictionary Attributes => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); } } From 68b4d5469da83e401db0774b3c3e5a9ab1301cdc Mon Sep 17 00:00:00 2001 From: markwallace-microsoft Date: Tue, 7 Nov 2023 20:53:30 +0000 Subject: [PATCH 09/18] Fix issues associated with merge from main --- dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs | 2 +- .../KernelSyntaxExamples/Example34_CustomChatModel.cs | 2 +- .../Extensions/KernelSemanticFunctionExtensionsTests.cs | 2 +- .../Events/FunctionInvokedEventArgs.cs | 5 ----- .../src/SemanticKernel.Abstractions/Memory/MemoryRecord.cs | 4 ++-- .../Orchestration/FunctionResult.cs | 2 +- 6 files changed, 6 insertions(+), 11 deletions(-) diff --git a/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs b/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs index ff49cde89830..8ba4f5863d79 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs @@ -31,7 +31,7 @@ public class MyTextCompletionService : ITextCompletion { public string? ModelId { get; private set; } - public IReadOnlyDictionary Attributes => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); public Task> GetCompletionsAsync(string text, AIRequestSettings? requestSettings, CancellationToken cancellationToken = default) { diff --git a/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs b/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs index e4df5077fe29..f693e4a614bc 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs @@ -22,7 +22,7 @@ public sealed class MyChatCompletionService : IChatCompletion { public string? ModelId { get; private set; } - public IReadOnlyDictionary Attributes => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); public ChatHistory CreateNewChat(string? instructions = null) { diff --git a/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs b/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs index 7fbd29a98fc5..36a91ee23515 100644 --- a/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs +++ b/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs @@ -71,7 +71,7 @@ private sealed class RedirectTextCompletion : ITextCompletion { public string? ModelId => null; - public IReadOnlyDictionary Attributes => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); Task> ITextCompletion.GetCompletionsAsync(string text, AIRequestSettings? requestSettings, CancellationToken cancellationToken) { diff --git a/dotnet/src/SemanticKernel.Abstractions/Events/FunctionInvokedEventArgs.cs b/dotnet/src/SemanticKernel.Abstractions/Events/FunctionInvokedEventArgs.cs index a00a84017d86..a8bc1dbf1524 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Events/FunctionInvokedEventArgs.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Events/FunctionInvokedEventArgs.cs @@ -14,11 +14,6 @@ public class FunctionInvokedEventArgs : SKCancelEventArgs /// public bool IsRepeatRequested => this._repeatRequested; - /// - /// Attributes for storing additional information about function execution result. - /// - public Dictionary Metadata => this._metadata ??= new(); - /// /// Initializes a new instance of the class. /// diff --git a/dotnet/src/SemanticKernel.Abstractions/Memory/MemoryRecord.cs b/dotnet/src/SemanticKernel.Abstractions/Memory/MemoryRecord.cs index 970a1cdd279e..d87c7a876ed3 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Memory/MemoryRecord.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Memory/MemoryRecord.cs @@ -21,7 +21,7 @@ public class MemoryRecord : DataEntryBase public ReadOnlyMemory Embedding { get; } /// - /// Attributes associated with a Semantic Kernel memory. + /// Metadata associated with a Semantic Kernel memory. /// [JsonPropertyName("metadata")] public MemoryRecordMetadata Metadata { get; } @@ -138,7 +138,7 @@ public static MemoryRecord FromJsonMetadata( /// /// Create a memory record from a memory record's metadata. /// - /// Attributes associated with a memory. + /// Metadata associated with a memory. /// Optional embedding associated with a memory record. /// Optional existing database key. /// optional timestamp. diff --git a/dotnet/src/SemanticKernel.Abstractions/Orchestration/FunctionResult.cs b/dotnet/src/SemanticKernel.Abstractions/Orchestration/FunctionResult.cs index d27c700afe02..eaac4d7ae3e1 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Orchestration/FunctionResult.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Orchestration/FunctionResult.cs @@ -23,7 +23,7 @@ public sealed class FunctionResult public string PluginName { get; internal set; } /// - /// Attributes for storing additional information about function execution result. + /// Metadata for storing additional information about function execution result. /// public Dictionary Metadata { From 59775443da83b6da337e2b4da96665a55b8deebe Mon Sep 17 00:00:00 2001 From: markwallace-microsoft Date: Tue, 7 Nov 2023 20:58:44 +0000 Subject: [PATCH 10/18] Fix typo and formatting issues --- dotnet/src/SemanticKernel.Core/Kernel.cs | 1 - .../src/SemanticKernel.Core/Services/AIServiceSelectorBase.cs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/dotnet/src/SemanticKernel.Core/Kernel.cs b/dotnet/src/SemanticKernel.Core/Kernel.cs index fa9dec0266d6..fb2e617c5177 100644 --- a/dotnet/src/SemanticKernel.Core/Kernel.cs +++ b/dotnet/src/SemanticKernel.Core/Kernel.cs @@ -12,7 +12,6 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.SemanticKernel.Diagnostics; using Microsoft.SemanticKernel.Events; -using Microsoft.SemanticKernel.Functions; using Microsoft.SemanticKernel.Http; using Microsoft.SemanticKernel.Memory; using Microsoft.SemanticKernel.Orchestration; diff --git a/dotnet/src/SemanticKernel.Core/Services/AIServiceSelectorBase.cs b/dotnet/src/SemanticKernel.Core/Services/AIServiceSelectorBase.cs index 7d5749a1a6f1..109f5bf1d452 100644 --- a/dotnet/src/SemanticKernel.Core/Services/AIServiceSelectorBase.cs +++ b/dotnet/src/SemanticKernel.Core/Services/AIServiceSelectorBase.cs @@ -11,7 +11,7 @@ namespace Microsoft.SemanticKernel; #pragma warning restore IDE0130 /// -/// Base classe for implementing . +/// Base class for implementing . /// public abstract class AIServiceSelectorBase : IAIServiceSelector { @@ -32,7 +32,7 @@ public abstract class AIServiceSelectorBase : IAIServiceSelector } /// - /// Return the AI service and requesting settings if the specified provider is the vaid choice. + /// Return the AI service and requesting settings if the specified provider is the valid choice. /// /// /// From 3f2e78dc74517975e4f7a3de0529bcab91a92098 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft Date: Tue, 7 Nov 2023 21:20:32 +0000 Subject: [PATCH 11/18] Remove suppress message --- dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs b/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs index 739d75ce8754..7b9a6801e678 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs @@ -1,14 +1,12 @@ // Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; namespace Microsoft.SemanticKernel.Services; /// /// Represents an empty interface for AI services. /// -[SuppressMessage("Design", "CA1040:Avoid empty interfaces")] public interface IAIService { /// From 6ce99ca2e571f58621c17c67718da36648c47e25 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft Date: Tue, 7 Nov 2023 21:22:24 +0000 Subject: [PATCH 12/18] Fix typo --- docs/decisions/0018-aiservice-metadata.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/decisions/0018-aiservice-metadata.md b/docs/decisions/0018-aiservice-metadata.md index 5012c2205641..0ab0aaf18d7d 100644 --- a/docs/decisions/0018-aiservice-metadata.md +++ b/docs/decisions/0018-aiservice-metadata.md @@ -13,7 +13,7 @@ informed: Developers need to be able to know more information about the `IAIService` that will be used to execute a semantic function or a plan. Some examples of why they need this information: -1. As an SK developer I want to write a `IAIServiceSelector` which allows me to select the OpenAI service to used based on the configured model id so that I cna select the optimum (could eb cheapest) model to use based on the prompt I am executing. +1. As an SK developer I want to write a `IAIServiceSelector` which allows me to select the OpenAI service to used based on the configured model id so that I can select the optimum (could eb cheapest) model to use based on the prompt I am executing. 2. As an SK developer I want to write a pre-invocation hook which will compute the token size of a prompt before the prompt is sent to the LLM, so that I can determine the optimum `IAIService` to use. The library I am using to compute the token size of the prompt requires the model id. From c9078d43c0c169e691c4cb5c9707d63e8fba4019 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft Date: Fri, 10 Nov 2023 13:32:23 +0000 Subject: [PATCH 13/18] Work in progress --- ...metadata.md => 0021-aiservice-metadata.md} | 69 +++++++++++++++++-- .../HuggingFaceTextCompletion.cs | 27 +++----- .../HuggingFaceTextEmbeddingGeneration.cs | 38 ++++------ .../AzureSdk/AzureOpenAIClientBase.cs | 4 +- .../AzureSdk/ClientBase.cs | 13 ++++ .../AzureSdk/OpenAIClientBase.cs | 7 +- .../AzureOpenAIChatCompletion.cs | 16 ++--- .../ChatCompletion/OpenAIChatCompletion.cs | 18 ++--- .../AzureOpenAIChatCompletionWithData.cs | 11 ++- .../CustomClient/OpenAIClientBase.cs | 16 ----- .../AzureOpenAIImageGeneration.cs | 14 ++-- .../ImageGeneration/OpenAIImageGeneration.cs | 7 +- .../TextCompletion/AzureTextCompletion.cs | 16 ++--- .../TextCompletion/OpenAITextCompletion.cs | 12 ++-- .../AzureOpenAITextEmbeddingGeneration.cs | 13 +--- .../OpenAITextEmbeddingGeneration.cs | 10 +-- .../Services/IAIService.cs | 8 +-- .../Services/IAIServiceExtensions.cs | 65 +++++++++++++++++ .../Services/OrderedIAIServiceSelector.cs | 3 +- 19 files changed, 204 insertions(+), 163 deletions(-) rename docs/decisions/{0018-aiservice-metadata.md => 0021-aiservice-metadata.md} (59%) create mode 100644 dotnet/src/SemanticKernel.Abstractions/Services/IAIServiceExtensions.cs diff --git a/docs/decisions/0018-aiservice-metadata.md b/docs/decisions/0021-aiservice-metadata.md similarity index 59% rename from docs/decisions/0018-aiservice-metadata.md rename to docs/decisions/0021-aiservice-metadata.md index 0ab0aaf18d7d..70822b1e82c4 100644 --- a/docs/decisions/0018-aiservice-metadata.md +++ b/docs/decisions/0021-aiservice-metadata.md @@ -1,7 +1,7 @@ --- # These are optional elements. Feel free to remove any of them. status: {proposed} -date: {2023-10-25} +date: {2023-11-10} deciders: SergeyMenshykh, markwallace, rbarreto, dmytrostruk consulted: informed: @@ -16,7 +16,6 @@ Some examples of why they need this information: 1. As an SK developer I want to write a `IAIServiceSelector` which allows me to select the OpenAI service to used based on the configured model id so that I can select the optimum (could eb cheapest) model to use based on the prompt I am executing. 2. As an SK developer I want to write a pre-invocation hook which will compute the token size of a prompt before the prompt is sent to the LLM, so that I can determine the optimum `IAIService` to use. The library I am using to compute the token size of the prompt requires the model id. - Current implementation of `IAIService` is empty. ```csharp @@ -66,12 +65,28 @@ From the perspective of a prompt creator using OpenAI, they will typically tune * `IReadOnlyDictionary Attributes { get; }` which returns the attributes as a readonly dictionary. It will be the responsibility of each `IAIService` implementation to populate this with the appropriate metadata. * Extend `INamedServiceProvider` to include this method `ICollection GetServices() where T : TService;` * Extend `OpenAIKernelBuilderExtensions` so that `WithAzureXXX` methods will include a `modelId` property if a specific model can be targeted. +* Option #2 + * Extend `IAIService` to include the following method: + * `T? GetAttributes() where T : AIServiceAttributes;` which returns an instance of `AIServiceAttributes`. It will be the responsibility of each `IAIService` implementation to define it's own service attributes class and populate this with the appropriate values. + * Extend `INamedServiceProvider` to include this method `ICollection GetServices() where T : TService;` + * Extend `OpenAIKernelBuilderExtensions` so that `WithAzureXXX` methods will include a `modelId` property if a specific model can be targeted. +* Option #3 +* Option #2 + * Extend `IAIService` to include the following properties: + * `public IReadOnlyDictionary Attributes => this.InternalAttributes;` which returns a read only dictionary. It will be the responsibility of each `IAIService` implementation to define it's own service attributes class and populate this with the appropriate values. + * `ModelId` + * `Endpoint` + * `ApiVersion` + * Extend `INamedServiceProvider` to include this method `ICollection GetServices() where T : TService;` + * Extend `OpenAIKernelBuilderExtensions` so that `WithAzureXXX` methods will include a `modelId` property if a specific model can be targeted. -These methods would be used as follows: +These options would be used as follows: As an SK developer I want to write a custom `IAIServiceSelector` which will select an AI service based on the model id because I want to restrict which LLM is used. In the sample below the service selector implementation looks for the first service that is a GPT3 model. +### Option 1 + ``` csharp public class Gpt3xAIServiceSelector : IAIServiceSelector { @@ -92,7 +107,51 @@ public class Gpt3xAIServiceSelector : IAIServiceSelector } ``` +## Option 2 + +``` csharp +public class Gpt3xAIServiceSelector : IAIServiceSelector +{ + public (T?, AIRequestSettings?) SelectAIService(string renderedPrompt, IAIServiceProvider serviceProvider, IReadOnlyList? modelSettings) where T : IAIService + { + var services = serviceProvider.GetServices(); + foreach (var service in services) + { + var serviceModelId = service.GetAttributes()?.ModelId; + if (!string.IsNullOrEmpty(serviceModelId) && serviceModelId.StartsWith("gpt-3", StringComparison.OrdinalIgnoreCase)) + { + Console.WriteLine($"Selected model: {serviceModelId}"); + return (service, new OpenAIRequestSettings()); + } + } + + throw new SKException("Unable to find AI service for GPT 3.x."); + } +} +``` + +## Option 3 + +```csharp +public (T?, AIRequestSettings?) SelectAIService(string renderedPrompt, IAIServiceProvider serviceProvider, IReadOnlyList? modelSettings) where T : IAIService +{ + var services = serviceProvider.GetServices(); + foreach (var service in services) + { + var serviceModelId = service.GetModelId(); + var serviceOrganization = service.GetAttribute(OpenAIServiceAttributes.OrganizationKey); + var serviceDeploymentName = service.GetAttribute(AzureOpenAIServiceAttributes.DeploymentNameKey); + if (!string.IsNullOrEmpty(serviceModelId) && serviceModelId.StartsWith("gpt-3", StringComparison.OrdinalIgnoreCase)) + { + Console.WriteLine($"Selected model: {serviceModelId}"); + return (service, new OpenAIRequestSettings()); + } + } + + throw new SKException("Unable to find AI service for GPT 3.x."); +} +``` + ## Decision Outcome -Chosen option: "{title of option 1}", because -{justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force {force} | … | comes out best (see below)}. +Chosen option: Option 1, because it's a simple implementation and allows easy iteration over all possible attributes. diff --git a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs index 832d1719d384..168e4e0d8bb4 100644 --- a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextCompletion/HuggingFaceTextCompletion.cs @@ -20,17 +20,13 @@ namespace Microsoft.SemanticKernel.Connectors.AI.HuggingFace.TextCompletion; public sealed class HuggingFaceTextCompletion : ITextCompletion #pragma warning restore CA1001 // Types that own disposable fields should be disposable. No need to dispose the Http client here. It can either be an internal client using NonDisposableHttpClientHandler or an external client managed by the calling code, which should handle its disposal. { - /// - /// Attribute name used to store the endpoint in the dictionary. - /// - public const string EndpointAttribute = "Endpoint"; - private const string HuggingFaceApiEndpoint = "https://api-inference.huggingface.co/models"; + private readonly string _model; private readonly string? _endpoint; private readonly HttpClient _httpClient; private readonly string? _apiKey; - private readonly Dictionary _attributes = new(); + private readonly Dictionary _attributes = new(); /// /// Initializes a new instance of the class. @@ -43,10 +39,10 @@ public HuggingFaceTextCompletion(Uri endpoint, string model) Verify.NotNull(endpoint); Verify.NotNullOrWhiteSpace(model); + this._model = model; this._endpoint = endpoint.AbsoluteUri; - this._attributes.Add(EndpointAttribute, this._endpoint); - - this.ModelId = model; + this._attributes.Add(IAIServiceExtensions.ModelIdKey, this._model); + this._attributes.Add(IAIServiceExtensions.EndpointKey, this._endpoint); this._httpClient = new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); } @@ -64,19 +60,16 @@ public HuggingFaceTextCompletion(string model, string? apiKey = null, HttpClient { Verify.NotNullOrWhiteSpace(model); + this._model = model; this._apiKey = apiKey; this._httpClient = httpClient ?? new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); this._endpoint = endpoint; - this._attributes.Add(EndpointAttribute, this._endpoint ?? HuggingFaceApiEndpoint); - - this.ModelId = model; + this._attributes.Add(IAIServiceExtensions.ModelIdKey, this._model); + this._attributes.Add(IAIServiceExtensions.EndpointKey, this._endpoint ?? HuggingFaceApiEndpoint); } /// - public string? ModelId { get; private set; } - - /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this._attributes; /// [Obsolete("Streaming capability is not supported, use GetCompletionsAsync instead")] @@ -150,7 +143,7 @@ private Uri GetRequestUri() baseUrl = this._httpClient.BaseAddress!.AbsoluteUri; } - return new Uri($"{baseUrl!.TrimEnd('/')}/{this.ModelId}"); + return new Uri($"{baseUrl!.TrimEnd('/')}/{this._model}"); } #endregion diff --git a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs index 688cbb454cb1..1d6c19907558 100644 --- a/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.HuggingFace/TextEmbedding/HuggingFaceTextEmbeddingGeneration.cs @@ -20,14 +20,10 @@ namespace Microsoft.SemanticKernel.Connectors.AI.HuggingFace.TextEmbedding; public sealed class HuggingFaceTextEmbeddingGeneration : ITextEmbeddingGeneration #pragma warning restore CA1001 // Types that own disposable fields should be disposable. No need to dispose the Http client here. It can either be an internal client using NonDisposableHttpClientHandler or an external client managed by the calling code, which should handle its disposal. { - /// - /// Attribute name used to store the endpoint in the dictionary. - /// - public const string EndpointAttribute = "Endpoint"; - + private readonly string _model; private readonly string? _endpoint; private readonly HttpClient _httpClient; - private readonly Dictionary _attributes = new(); + private readonly Dictionary _attributes = new(); /// /// Initializes a new instance of the class. @@ -40,11 +36,10 @@ public HuggingFaceTextEmbeddingGeneration(Uri endpoint, string model) Verify.NotNull(endpoint); Verify.NotNullOrWhiteSpace(model); - this.ModelId = model; - + this._model = model; this._endpoint = endpoint.AbsoluteUri; - this._attributes.Add(EndpointAttribute, this._endpoint); - + this._attributes.Add(IAIServiceExtensions.ModelIdKey, this._model); + this._attributes.Add(IAIServiceExtensions.EndpointKey, this._endpoint); this._httpClient = new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); } @@ -58,10 +53,10 @@ public HuggingFaceTextEmbeddingGeneration(string model, string endpoint) Verify.NotNullOrWhiteSpace(model); Verify.NotNullOrWhiteSpace(endpoint); - this.ModelId = model; - + this._model = model; this._endpoint = endpoint; - this._attributes.Add(EndpointAttribute, this._endpoint); + this._attributes.Add(IAIServiceExtensions.ModelIdKey, this._model); + this._attributes.Add(IAIServiceExtensions.EndpointKey, this._endpoint); this._httpClient = new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); } @@ -76,15 +71,11 @@ public HuggingFaceTextEmbeddingGeneration(string model, HttpClient httpClient, s Verify.NotNullOrWhiteSpace(model); Verify.NotNull(httpClient); - this.ModelId = model; - + this._model = model; this._endpoint = endpoint; this._httpClient = httpClient; - - if (!string.IsNullOrEmpty(endpoint)) - { - this._attributes.Add(EndpointAttribute, endpoint!); - } + this._attributes.Add(IAIServiceExtensions.ModelIdKey, this._model); + this._attributes.Add(IAIServiceExtensions.EndpointKey, this._endpoint ?? this._httpClient.BaseAddress.ToString()); if (httpClient.BaseAddress == null && string.IsNullOrEmpty(endpoint)) { @@ -93,10 +84,7 @@ public HuggingFaceTextEmbeddingGeneration(string model, HttpClient httpClient, s } /// - public string? ModelId { get; private set; } - - /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this._attributes; /// public async Task>> GenerateEmbeddingsAsync(IList data, CancellationToken cancellationToken = default) @@ -154,7 +142,7 @@ private Uri GetRequestUri() throw new SKException("No endpoint or HTTP client base address has been provided"); } - return new Uri($"{baseUrl!.TrimEnd('/')}/{this.ModelId}"); + return new Uri($"{baseUrl!.TrimEnd('/')}/{this._model}"); } #endregion diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs index 7242e51c2399..4ddf336ed2bc 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs @@ -18,9 +18,9 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.AzureSdk; public abstract class AzureOpenAIClientBase : ClientBase { /// - /// Attribute name used to store the deployment name in the dictionary. + /// Key used to store the deployment name in the dictionary. /// - public const string DeploymentNameAttribute = "DeploymentName"; + public const string DeploymentNameKey = "DeploymentName"; /// /// OpenAI / Azure OpenAI Client diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/ClientBase.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/ClientBase.cs index 6219f4356af5..35dae4906177 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/ClientBase.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/ClientBase.cs @@ -53,6 +53,11 @@ private protected ClientBase(ILoggerFactory? loggerFactory = null) /// private protected ILogger Logger { get; set; } + /// + /// Storage for AI service attributes. + /// + private protected Dictionary InternalAttributes = new(); + /// /// Instance of for metrics. /// @@ -302,6 +307,14 @@ private protected async IAsyncEnumerable InternalGetChatSt } } + private protected void AddAttribute(string key, string? value) + { + if (!string.IsNullOrEmpty(value)) + { + this.InternalAttributes.Add(key, value!); + } + } + private static OpenAIChatHistory PrepareChatHistory(string text, AIRequestSettings? requestSettings, out OpenAIRequestSettings settings) { settings = OpenAIRequestSettings.FromRequestSettings(requestSettings); diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/OpenAIClientBase.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/OpenAIClientBase.cs index 8e64ff2fc4c5..ebb3764da94a 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/OpenAIClientBase.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/OpenAIClientBase.cs @@ -19,12 +19,7 @@ public abstract class OpenAIClientBase : ClientBase /// /// Attribute name used to store the orhanization in the dictionary. /// - public const string OrganizationAttribute = "Organization"; - - /// - /// Attribute name used to store the endpoint in the dictionary. - /// - public const string EndpointAttribute = "Endpoint"; + public const string OrganizationKey = "Organization"; /// /// OpenAI / Azure OpenAI Client diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureOpenAIChatCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureOpenAIChatCompletion.cs index a268b7dbbd28..9179658dbe70 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureOpenAIChatCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureOpenAIChatCompletion.cs @@ -20,8 +20,6 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ChatCompletion; /// public sealed class AzureOpenAIChatCompletion : AzureOpenAIClientBase, IChatCompletion, ITextCompletion { - private readonly Dictionary _attributes = new(); - /// /// Create an instance of the connector with API key auth. /// @@ -39,8 +37,7 @@ public AzureOpenAIChatCompletion( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, apiKey, httpClient, loggerFactory) { - this.ModelId = modelId; - this._attributes.Add(DeploymentNameAttribute, deploymentName); + this.StoreAttributes(deploymentName, modelId); } /// @@ -60,8 +57,7 @@ public AzureOpenAIChatCompletion( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, credentials, httpClient, loggerFactory) { - this.ModelId = modelId; - this._attributes.Add(DeploymentNameAttribute, deploymentName); + this.StoreAttributes(deploymentName, modelId); } /// @@ -77,15 +73,11 @@ public AzureOpenAIChatCompletion( string? modelId = null, ILoggerFactory? loggerFactory = null) : base(deploymentName, openAIClient, loggerFactory) { - this.ModelId = modelId; - this._attributes.Add(DeploymentNameAttribute, deploymentName); + this.StoreAttributes(deploymentName, modelId); } /// - public string? ModelId { get; private set; } - - /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this.InternalAttributes; /// public Task> GetChatCompletionsAsync( diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs index 5d7bfcef386d..ce84c8f4b22f 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/OpenAIChatCompletion.cs @@ -10,6 +10,7 @@ using Microsoft.SemanticKernel.AI.ChatCompletion; using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.AzureSdk; +using Microsoft.SemanticKernel.Services; namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ChatCompletion; @@ -19,8 +20,6 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ChatCompletion; /// public sealed class OpenAIChatCompletion : OpenAIClientBase, IChatCompletion, ITextCompletion { - private readonly Dictionary _attributes = new(); - /// /// Create an instance of the OpenAI chat completion connector /// @@ -36,12 +35,8 @@ public OpenAIChatCompletion( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(modelId, apiKey, organization, httpClient, loggerFactory) { - this.ModelId = modelId; - - if (!string.IsNullOrWhiteSpace(organization)) - { - this._attributes.Add(OrganizationAttribute, organization!); - } + this.AddAttribute(IAIServiceExtensions.ModelIdKey, modelId); + this.AddAttribute(OrganizationKey, organization!); } /// @@ -55,14 +50,11 @@ public OpenAIChatCompletion( OpenAIClient openAIClient, ILoggerFactory? loggerFactory = null) : base(modelId, openAIClient, loggerFactory) { - this.ModelId = modelId; + this.AddAttribute(IAIServiceExtensions.ModelIdKey, modelId); } /// - public string? ModelId { get; private set; } - - /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this.InternalAttributes; /// public Task> GetChatCompletionsAsync( diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureOpenAIChatCompletionWithData.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureOpenAIChatCompletionWithData.cs index fb1b9bfbf5da..83d38df59595 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureOpenAIChatCompletionWithData.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletionWithData/AzureOpenAIChatCompletionWithData.cs @@ -16,6 +16,7 @@ using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.ChatCompletion; using Microsoft.SemanticKernel.Diagnostics; +using Microsoft.SemanticKernel.Services; using Microsoft.SemanticKernel.Text; namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ChatCompletionWithData; @@ -43,14 +44,11 @@ public AzureOpenAIChatCompletionWithData( this._httpClient = httpClient ?? new HttpClient(NonDisposableHttpClientHandler.Instance, disposeHandler: false); this._logger = loggerFactory is not null ? loggerFactory.CreateLogger(this.GetType()) : NullLogger.Instance; - this.ModelId = config.CompletionModelId; + this._attributes.Add(IAIServiceExtensions.ModelIdKey, config.CompletionModelId); } /// - public string? ModelId { get; private set; } - - /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this._attributes; /// public ChatHistory CreateNewChat(string? instructions = null) @@ -128,8 +126,7 @@ public async IAsyncEnumerable GetStreamingCompletionsAsync private readonly HttpClient _httpClient; private readonly ILogger _logger; - private readonly Dictionary _attributes = new(); - + private readonly Dictionary _attributes = new(); private void ValidateConfig(AzureOpenAIChatCompletionWithDataConfig config) { Verify.NotNull(config); diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/CustomClient/OpenAIClientBase.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/CustomClient/OpenAIClientBase.cs index ed541b13eeb6..1a098ceac4e3 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/CustomClient/OpenAIClientBase.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/CustomClient/OpenAIClientBase.cs @@ -12,7 +12,6 @@ using Microsoft.SemanticKernel.Connectors.AI.OpenAI.ImageGeneration; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; using Microsoft.SemanticKernel.Diagnostics; -using Microsoft.SemanticKernel.Services; using Microsoft.SemanticKernel.Text; namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.CustomClient; @@ -20,21 +19,6 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.CustomClient; /// Base type for OpenAI clients. public abstract class OpenAIClientBase { - /// - /// Attribute name used to store the orhanization in the dictionary. - /// - public const string OrganizationAttribute = "Organization"; - - /// - /// Attribute name used to store the endpoint in the dictionary. - /// - public const string EndpointAttribute = "Endpoint"; - - /// - /// Attribute name used to store the API version in the dictionary. - /// - public const string ApiVersionAttribute = "ApiVersion"; - /// /// Initializes a new instance of the class. /// diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs index 1da70ca60894..ae41d4163385 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs @@ -10,6 +10,7 @@ using Microsoft.SemanticKernel.AI.ImageGeneration; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.CustomClient; using Microsoft.SemanticKernel.Diagnostics; +using Microsoft.SemanticKernel.Services; using Microsoft.SemanticKernel.Text; namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ImageGeneration; @@ -20,8 +21,6 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ImageGeneration; /// public class AzureOpenAIImageGeneration : OpenAIClientBase, IImageGeneration { - private readonly Dictionary _attributes = new(); - /// /// Generation Image Operation path /// @@ -71,7 +70,7 @@ public AzureOpenAIImageGeneration(string endpoint, string apiKey, HttpClient? ht this._apiKey = apiKey; this._maxRetryCount = maxRetryCount; this._apiVersion = apiVersion; - this._attributes.Add(EndpointAttribute, endpoint); + this.AddAttribute(IAIServiceExtensions.EndpointKey, endpoint); } /// @@ -100,15 +99,12 @@ public AzureOpenAIImageGeneration(string apiKey, HttpClient httpClient, string? this._apiKey = apiKey; this._maxRetryCount = maxRetryCount; this._apiVersion = apiVersion; - this._attributes.Add(EndpointAttribute, endpoint); - this._attributes.Add(ApiVersionAttribute, apiVersion); + this._attributes.Add(IAIServiceExtensions.EndpointKey, endpoint); + this._attributes.Add(IAIServiceExtensions.ApiVersionKey, apiVersion); } /// - public string? ModelId { get; private set; } - - /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this.InternalAttributes; /// public async Task GenerateImageAsync(string description, int width, int height, CancellationToken cancellationToken = default) diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs index 560f71c85d7a..c555635c2742 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs @@ -18,8 +18,6 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ImageGeneration; /// public class OpenAIImageGeneration : OpenAIClientBase, IImageGeneration { - private readonly Dictionary _attributes = new(); - /// /// OpenAI REST API endpoint /// @@ -60,10 +58,7 @@ public OpenAIImageGeneration( } /// - public string? ModelId { get; private set; } - - /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this._attributes; /// Adds headers to use for OpenAI HTTP requests. private protected override void AddRequestHeaders(HttpRequestMessage request) diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs index 903a656fe645..36658f976e67 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs @@ -19,8 +19,6 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion; /// public sealed class AzureTextCompletion : AzureOpenAIClientBase, ITextCompletion { - private readonly Dictionary _attributes = new(); - /// /// Creates a new AzureTextCompletion client instance using API Key auth /// @@ -38,8 +36,7 @@ public AzureTextCompletion( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, apiKey, httpClient, loggerFactory) { - this.ModelId = modelId; - this._attributes.Add(DeploymentNameAttribute, deploymentName); + this.StoreAttributes(deploymentName, modelId); } /// @@ -59,8 +56,7 @@ public AzureTextCompletion( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, credential, httpClient, loggerFactory) { - this.ModelId = modelId; - this._attributes.Add(DeploymentNameAttribute, deploymentName); + this.StoreAttributes(deploymentName, modelId); } /// @@ -76,15 +72,11 @@ public AzureTextCompletion( string? modelId = null, ILoggerFactory? loggerFactory = null) : base(deploymentName, openAIClient, loggerFactory) { - this.ModelId = modelId; - this._attributes.Add(DeploymentNameAttribute, deploymentName); + this.StoreAttributes(deploymentName, modelId); } /// - public string? ModelId { get; private set; } - - /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this.InternalAttributes; /// public IAsyncEnumerable GetStreamingCompletionsAsync( diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs index a9f5b70cef16..ebb7ee701312 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs @@ -8,6 +8,7 @@ using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.AzureSdk; +using Microsoft.SemanticKernel.Services; namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion; @@ -17,8 +18,6 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion; /// public sealed class OpenAITextCompletion : OpenAIClientBase, ITextCompletion { - private readonly Dictionary _attributes = new(); - /// /// Create an instance of the OpenAI text completion connector /// @@ -35,19 +34,16 @@ public OpenAITextCompletion( ILoggerFactory? loggerFactory = null ) : base(modelId, apiKey, organization, httpClient, loggerFactory) { - this.ModelId = modelId; + this._attributes.Add(IAIServiceExtensions.ModelIdKey, modelId); if (!string.IsNullOrWhiteSpace(organization)) { - this._attributes.Add(OrganizationAttribute, organization!); + this._attributes.Add(OrganizationKey, organization!); } } /// - public string? ModelId { get; private set; } - - /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this._attributes; /// public IAsyncEnumerable GetStreamingCompletionsAsync( diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs index 712d62ee5e70..d09f29c1cab9 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs @@ -17,8 +17,6 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; /// public sealed class AzureOpenAITextEmbeddingGeneration : AzureOpenAIClientBase, ITextEmbeddingGeneration { - private readonly Dictionary _attributes = new(); - /// /// Creates a new client instance using API Key auth. /// @@ -36,8 +34,7 @@ public AzureOpenAITextEmbeddingGeneration( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, apiKey, httpClient, loggerFactory) { - this.ModelId = modelId; - this._attributes.Add(DeploymentNameAttribute, deploymentName); + this.StoreAttributes(deploymentName, modelId); } /// @@ -57,15 +54,11 @@ public AzureOpenAITextEmbeddingGeneration( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, credential, httpClient, loggerFactory) { - this.ModelId = modelId; - this._attributes.Add(DeploymentNameAttribute, deploymentName); + this.StoreAttributes(deploymentName, modelId); } /// - public string? ModelId { get; private set; } - - /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this.InternalAttributes; /// /// Generates an embedding from the given . diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs index 2cf7ae50668a..7b60f7d81214 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.AI.Embeddings; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.AzureSdk; +using Microsoft.SemanticKernel.Services; namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; @@ -16,8 +17,6 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; /// public sealed class OpenAITextEmbeddingGeneration : OpenAIClientBase, ITextEmbeddingGeneration { - private readonly Dictionary _attributes = new(); - /// /// Create an instance of the OpenAI text embedding connector /// @@ -34,14 +33,11 @@ public OpenAITextEmbeddingGeneration( ILoggerFactory? loggerFactory = null ) : base(modelId, apiKey, organization, httpClient, loggerFactory) { - this.ModelId = modelId; + this._attributes.Add(IAIServiceExtensions.ModelIdKey, modelId); } /// - public string? ModelId { get; private set; } - - /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this._attributes; /// /// Generates an embedding from the given . diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs b/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs index 7b9a6801e678..05e9619f909d 100644 --- a/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs +++ b/dotnet/src/SemanticKernel.Abstractions/Services/IAIService.cs @@ -9,14 +9,8 @@ namespace Microsoft.SemanticKernel.Services; /// public interface IAIService { - /// - /// Model identifier. - /// This identifies the AI model these settings are configured for e.g., gpt-4, gpt-3.5-turbo - /// - string? ModelId { get; } - /// /// Gets the AI service attributes. /// - IReadOnlyDictionary Attributes { get; } + IReadOnlyDictionary Attributes { get; } } diff --git a/dotnet/src/SemanticKernel.Abstractions/Services/IAIServiceExtensions.cs b/dotnet/src/SemanticKernel.Abstractions/Services/IAIServiceExtensions.cs new file mode 100644 index 000000000000..26ab85185afa --- /dev/null +++ b/dotnet/src/SemanticKernel.Abstractions/Services/IAIServiceExtensions.cs @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.SemanticKernel.Services; + +/// +/// Extension methods for . +/// +public static class IAIServiceExtensions +{ + /// + /// Key used to store the model identifier in the dictionary. + /// + public const string ModelIdKey = "ModelId"; + + /// + /// Key used to store the endpoint key in the dictionary. + /// + public const string EndpointKey = "Endpoint"; + + /// + /// Key used to store the API version in the dictionary. + /// + public const string ApiVersionKey = "ApiVersion"; + + /// + /// Gets the model identifier. + /// + /// + /// + public static string? GetModelId(this IAIService service) + { + return service.GetAttribute(ModelIdKey); + } + + /// + /// Gets the endpoint. + /// + /// + /// + public static string? GetEndpoint(this IAIService service) + { + return service.GetAttribute(EndpointKey); + } + + /// + /// Gets the API version. + /// + /// + /// + public static string? GetApiVersion(this IAIService service) + { + return service.GetAttribute(ApiVersionKey); + } + + /// + /// Gets the specified attribute. + /// + /// + /// + /// + public static string? GetAttribute(this IAIService service, string key) + { + return service.Attributes?.TryGetValue(key, out var value) == true ? value as string : null; + } +} diff --git a/dotnet/src/SemanticKernel.Core/Services/OrderedIAIServiceSelector.cs b/dotnet/src/SemanticKernel.Core/Services/OrderedIAIServiceSelector.cs index c16080eaa594..ca77f4f99586 100644 --- a/dotnet/src/SemanticKernel.Core/Services/OrderedIAIServiceSelector.cs +++ b/dotnet/src/SemanticKernel.Core/Services/OrderedIAIServiceSelector.cs @@ -71,7 +71,8 @@ internal class OrderedIAIServiceSelector : IAIServiceSelector var services = serviceProvider.GetServices(); foreach (var service in services) { - if (!string.IsNullOrEmpty(service.ModelId) && service.ModelId == modelId) + string? serviceModelId = service.GetModelId(); + if (!string.IsNullOrEmpty(serviceModelId) && serviceModelId == modelId) { return service; } From a210aeb96789fc775a9c7e2523c34d8a1e6c1ace Mon Sep 17 00:00:00 2001 From: markwallace-microsoft Date: Fri, 10 Nov 2023 14:20:02 +0000 Subject: [PATCH 14/18] Get the dictionary based service attributes working --- .../Example16_CustomLLM.cs | 2 +- .../Example34_CustomChatModel.cs | 2 +- .../Example61_MultipleLLMs.cs | 8 +++---- .../Example62_CustomAIServiceSelector.cs | 5 ++-- .../samples/KernelSyntaxExamples/Program.cs | 2 +- .../AzureSdk/AzureOpenAIClientBase.cs | 2 ++ .../AzureOpenAIChatCompletion.cs | 7 +++--- .../CustomClient/OpenAIClientBase.cs | 24 +++++++++++++++++++ .../AzureOpenAIImageGeneration.cs | 4 ++-- .../ImageGeneration/OpenAIImageGeneration.cs | 7 ++---- .../TextCompletion/AzureTextCompletion.cs | 7 +++--- .../TextCompletion/OpenAITextCompletion.cs | 10 +++----- .../AzureOpenAITextEmbeddingGeneration.cs | 5 ++-- .../OpenAITextEmbeddingGeneration.cs | 4 ++-- .../KernelSemanticFunctionExtensionsTests.cs | 2 +- ...redIAIServiceConfigurationProviderTests.cs | 4 ++-- .../Services/ServiceRegistryTests.cs | 2 +- 17 files changed, 60 insertions(+), 37 deletions(-) diff --git a/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs b/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs index 8ba4f5863d79..e0077418362b 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example16_CustomLLM.cs @@ -31,7 +31,7 @@ public class MyTextCompletionService : ITextCompletion { public string? ModelId { get; private set; } - public IReadOnlyDictionary Attributes => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); public Task> GetCompletionsAsync(string text, AIRequestSettings? requestSettings, CancellationToken cancellationToken = default) { diff --git a/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs b/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs index f693e4a614bc..752385346771 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example34_CustomChatModel.cs @@ -22,7 +22,7 @@ public sealed class MyChatCompletionService : IChatCompletion { public string? ModelId { get; private set; } - public IReadOnlyDictionary Attributes => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); public ChatHistory CreateNewChat(string? instructions = null) { diff --git a/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs b/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs index fbb8913a0a47..7aa53216d39f 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs @@ -4,7 +4,6 @@ using System.Threading.Tasks; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI; -using Microsoft.SemanticKernel.Connectors.AI.OpenAI; using RepoUtils; // ReSharper disable once InconsistentNaming @@ -43,6 +42,7 @@ public static async Task RunAsync() deploymentName: azureDeploymentName, endpoint: azureEndpoint, serviceId: "AzureOpenAIChat", + modelId: azureModelId, apiKey: azureApiKey) .WithOpenAIChatCompletionService( modelId: openAIModelId, @@ -50,8 +50,8 @@ public static async Task RunAsync() apiKey: openAIApiKey) .Build(); - await RunByServiceIdAsync(kernel, "AzureOpenAIChat"); - await RunByServiceIdAsync(kernel, "OpenAIChat"); + //await RunByServiceIdAsync(kernel, "AzureOpenAIChat"); + //await RunByServiceIdAsync(kernel, "OpenAIChat"); await RunByModelIdAsync(kernel, azureModelId); await RunByModelIdAsync(kernel, openAIModelId); } @@ -79,7 +79,7 @@ public static async Task RunByModelIdAsync(IKernel kernel, string modelId) var result = await kernel.InvokeSemanticFunctionAsync( prompt, - requestSettings: new OpenAIRequestSettings() + requestSettings: new AIRequestSettings() { ModelId = modelId }); diff --git a/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs b/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs index 9efd88f3e7bb..982830af35fd 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs @@ -164,9 +164,10 @@ public class Gpt3xAIServiceSelector : IAIServiceSelector var services = serviceProvider.GetServices(); foreach (var service in services) { - if (!string.IsNullOrEmpty(service.ModelId) && service.ModelId.StartsWith("gpt-3", StringComparison.OrdinalIgnoreCase)) + var serviceModelId = service.GetModelId(); + if (!string.IsNullOrEmpty(serviceModelId) && serviceModelId.StartsWith("gpt-3", StringComparison.OrdinalIgnoreCase)) { - Console.WriteLine($"Selected model: {service.ModelId}"); + Console.WriteLine($"Selected model: {serviceModelId}"); return (service, new OpenAIRequestSettings()); } } diff --git a/dotnet/samples/KernelSyntaxExamples/Program.cs b/dotnet/samples/KernelSyntaxExamples/Program.cs index 745873c2a71c..0ec6eedf4af5 100644 --- a/dotnet/samples/KernelSyntaxExamples/Program.cs +++ b/dotnet/samples/KernelSyntaxExamples/Program.cs @@ -21,7 +21,7 @@ public static async Task Main(string[] args) using CancellationTokenSource cancellationTokenSource = new(); CancellationToken cancelToken = cancellationTokenSource.ConsoleCancellationToken(); - string? defaultFilter = "Example62"; // Modify to filter examples + string? defaultFilter = "Example61"; // Modify to filter examples // Check if args[0] is provided string? filter = args.Length > 0 ? args[0] : defaultFilter; diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs index 4ddf336ed2bc..c7d9cc9dabd8 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/AzureSdk/AzureOpenAIClientBase.cs @@ -96,6 +96,8 @@ private protected AzureOpenAIClientBase( this.DeploymentOrModelName = deploymentName; this.Client = openAIClient; + + this.AddAttribute(DeploymentNameKey, deploymentName); } /// diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureOpenAIChatCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureOpenAIChatCompletion.cs index 9179658dbe70..c40a039c965c 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureOpenAIChatCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ChatCompletion/AzureOpenAIChatCompletion.cs @@ -11,6 +11,7 @@ using Microsoft.SemanticKernel.AI.ChatCompletion; using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.AzureSdk; +using Microsoft.SemanticKernel.Services; namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.ChatCompletion; @@ -37,7 +38,7 @@ public AzureOpenAIChatCompletion( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, apiKey, httpClient, loggerFactory) { - this.StoreAttributes(deploymentName, modelId); + this.AddAttribute(IAIServiceExtensions.ModelIdKey, modelId); } /// @@ -57,7 +58,7 @@ public AzureOpenAIChatCompletion( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, credentials, httpClient, loggerFactory) { - this.StoreAttributes(deploymentName, modelId); + this.AddAttribute(IAIServiceExtensions.ModelIdKey, modelId); } /// @@ -73,7 +74,7 @@ public AzureOpenAIChatCompletion( string? modelId = null, ILoggerFactory? loggerFactory = null) : base(deploymentName, openAIClient, loggerFactory) { - this.StoreAttributes(deploymentName, modelId); + this.AddAttribute(IAIServiceExtensions.ModelIdKey, modelId); } /// diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/CustomClient/OpenAIClientBase.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/CustomClient/OpenAIClientBase.cs index 1a098ceac4e3..fb280ae4ae00 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/CustomClient/OpenAIClientBase.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/CustomClient/OpenAIClientBase.cs @@ -12,6 +12,7 @@ using Microsoft.SemanticKernel.Connectors.AI.OpenAI.ImageGeneration; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; using Microsoft.SemanticKernel.Diagnostics; +using Microsoft.SemanticKernel.Services; using Microsoft.SemanticKernel.Text; namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.CustomClient; @@ -19,6 +20,11 @@ namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.CustomClient; /// Base type for OpenAI clients. public abstract class OpenAIClientBase { + /// + /// Key used to store the organizarion in the dictionary. + /// + public const string OrganizationKey = "Organization"; + /// /// Initializes a new instance of the class. /// @@ -30,6 +36,11 @@ private protected OpenAIClientBase(HttpClient? httpClient, ILoggerFactory? logge this._logger = loggerFactory is not null ? loggerFactory.CreateLogger(this.GetType()) : NullLogger.Instance; } + /// + /// Storage for AI service attributes. + /// + private protected Dictionary InternalAttributes = new(); + /// Adds headers to use for OpenAI HTTP requests. private protected virtual void AddRequestHeaders(HttpRequestMessage request) { @@ -75,6 +86,19 @@ private protected async Task> ExecuteImageGenerationRequestAsync( return result.Images.Select(extractResponseFunc).ToList(); } + /// + /// Add attribute to the internal attribute dictionary if the value is not null or empty. + /// + /// Attribute key + /// Attribute value + private protected void AddAttribute(string key, string? value) + { + if (!string.IsNullOrEmpty(value)) + { + this.InternalAttributes.Add(key, value!); + } + } + #region private ================================================================================ /// diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs index ae41d4163385..5b25f44ac881 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/AzureOpenAIImageGeneration.cs @@ -99,8 +99,8 @@ public AzureOpenAIImageGeneration(string apiKey, HttpClient httpClient, string? this._apiKey = apiKey; this._maxRetryCount = maxRetryCount; this._apiVersion = apiVersion; - this._attributes.Add(IAIServiceExtensions.EndpointKey, endpoint); - this._attributes.Add(IAIServiceExtensions.ApiVersionKey, apiVersion); + this.AddAttribute(IAIServiceExtensions.EndpointKey, endpoint); + this.AddAttribute(IAIServiceExtensions.ApiVersionKey, apiVersion); } /// diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs index c555635c2742..359ef19b4cb4 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/ImageGeneration/OpenAIImageGeneration.cs @@ -51,14 +51,11 @@ public OpenAIImageGeneration( this._authorizationHeaderValue = $"Bearer {apiKey}"; this._organizationHeaderValue = organization; - if (!string.IsNullOrEmpty(organization)) - { - this._attributes.Add(OrganizationAttribute, organization!); - } + this.AddAttribute(OrganizationKey, organization!); } /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this.InternalAttributes; /// Adds headers to use for OpenAI HTTP requests. private protected override void AddRequestHeaders(HttpRequestMessage request) diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs index 36658f976e67..99e1faded63d 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/AzureTextCompletion.cs @@ -10,6 +10,7 @@ using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.AI.TextCompletion; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.AzureSdk; +using Microsoft.SemanticKernel.Services; namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextCompletion; @@ -36,7 +37,7 @@ public AzureTextCompletion( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, apiKey, httpClient, loggerFactory) { - this.StoreAttributes(deploymentName, modelId); + this.AddAttribute(IAIServiceExtensions.ModelIdKey, modelId); } /// @@ -56,7 +57,7 @@ public AzureTextCompletion( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, credential, httpClient, loggerFactory) { - this.StoreAttributes(deploymentName, modelId); + this.AddAttribute(IAIServiceExtensions.ModelIdKey, modelId); } /// @@ -72,7 +73,7 @@ public AzureTextCompletion( string? modelId = null, ILoggerFactory? loggerFactory = null) : base(deploymentName, openAIClient, loggerFactory) { - this.StoreAttributes(deploymentName, modelId); + this.AddAttribute(IAIServiceExtensions.ModelIdKey, modelId); } /// diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs index ebb7ee701312..a49f3128b958 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextCompletion/OpenAITextCompletion.cs @@ -34,16 +34,12 @@ public OpenAITextCompletion( ILoggerFactory? loggerFactory = null ) : base(modelId, apiKey, organization, httpClient, loggerFactory) { - this._attributes.Add(IAIServiceExtensions.ModelIdKey, modelId); - - if (!string.IsNullOrWhiteSpace(organization)) - { - this._attributes.Add(OrganizationKey, organization!); - } + this.AddAttribute(IAIServiceExtensions.ModelIdKey, modelId); + this.AddAttribute(OrganizationKey, organization!); } /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this.InternalAttributes; /// public IAsyncEnumerable GetStreamingCompletionsAsync( diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs index d09f29c1cab9..cb759e88c589 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/AzureOpenAITextEmbeddingGeneration.cs @@ -9,6 +9,7 @@ using Microsoft.Extensions.Logging; using Microsoft.SemanticKernel.AI.Embeddings; using Microsoft.SemanticKernel.Connectors.AI.OpenAI.AzureSdk; +using Microsoft.SemanticKernel.Services; namespace Microsoft.SemanticKernel.Connectors.AI.OpenAI.TextEmbedding; @@ -34,7 +35,7 @@ public AzureOpenAITextEmbeddingGeneration( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, apiKey, httpClient, loggerFactory) { - this.StoreAttributes(deploymentName, modelId); + this.AddAttribute(IAIServiceExtensions.ModelIdKey, modelId); } /// @@ -54,7 +55,7 @@ public AzureOpenAITextEmbeddingGeneration( HttpClient? httpClient = null, ILoggerFactory? loggerFactory = null) : base(deploymentName, endpoint, credential, httpClient, loggerFactory) { - this.StoreAttributes(deploymentName, modelId); + this.AddAttribute(IAIServiceExtensions.ModelIdKey, modelId); } /// diff --git a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs index 7b60f7d81214..af409f0bd4f5 100644 --- a/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs +++ b/dotnet/src/Connectors/Connectors.AI.OpenAI/TextEmbedding/OpenAITextEmbeddingGeneration.cs @@ -33,11 +33,11 @@ public OpenAITextEmbeddingGeneration( ILoggerFactory? loggerFactory = null ) : base(modelId, apiKey, organization, httpClient, loggerFactory) { - this._attributes.Add(IAIServiceExtensions.ModelIdKey, modelId); + this.AddAttribute(IAIServiceExtensions.ModelIdKey, modelId); } /// - public IReadOnlyDictionary Attributes => this._attributes; + public IReadOnlyDictionary Attributes => this.InternalAttributes; /// /// Generates an embedding from the given . diff --git a/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs b/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs index 36a91ee23515..50c4b2568fdc 100644 --- a/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs +++ b/dotnet/src/IntegrationTests/Extensions/KernelSemanticFunctionExtensionsTests.cs @@ -71,7 +71,7 @@ private sealed class RedirectTextCompletion : ITextCompletion { public string? ModelId => null; - public IReadOnlyDictionary Attributes => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); Task> ITextCompletion.GetCompletionsAsync(string text, AIRequestSettings? requestSettings, CancellationToken cancellationToken) { diff --git a/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs b/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs index b103b3e3af74..8d8cf7cfdc05 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Functions/OrderedIAIServiceConfigurationProviderTests.cs @@ -223,14 +223,14 @@ public void ItGetsAIServiceConfigurationByOrder(string[] serviceIds, string expe #region private private sealed class AIService : IAIService { - public IReadOnlyDictionary Attributes => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); public string? ModelId { get; } } private sealed class TextCompletion : ITextCompletion { - public IReadOnlyDictionary Attributes => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); public string? ModelId { get; } diff --git a/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs b/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs index 738b814b55c2..394b977d42ff 100644 --- a/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs +++ b/dotnet/src/SemanticKernel.UnitTests/Services/ServiceRegistryTests.cs @@ -218,6 +218,6 @@ private sealed class TestService : IAIService { public string? ModelId { get; } - public IReadOnlyDictionary Attributes => new Dictionary(); + public IReadOnlyDictionary Attributes => new Dictionary(); } } From b0b24b962d1367df36438b03ca50d08a7bcfe3eb Mon Sep 17 00:00:00 2001 From: markwallace-microsoft Date: Fri, 10 Nov 2023 15:05:18 +0000 Subject: [PATCH 15/18] Fix up samples --- .../Example61_MultipleLLMs.cs | 4 +- .../Example62_CustomAIServiceSelector.cs | 86 ++++--------------- .../Services/AIServiceSelectorBase.cs | 16 ++-- 3 files changed, 28 insertions(+), 78 deletions(-) diff --git a/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs b/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs index 7aa53216d39f..afcc8905fe13 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs @@ -50,8 +50,8 @@ public static async Task RunAsync() apiKey: openAIApiKey) .Build(); - //await RunByServiceIdAsync(kernel, "AzureOpenAIChat"); - //await RunByServiceIdAsync(kernel, "OpenAIChat"); + await RunByServiceIdAsync(kernel, "AzureOpenAIChat"); + await RunByServiceIdAsync(kernel, "OpenAIChat"); await RunByModelIdAsync(kernel, azureModelId); await RunByModelIdAsync(kernel, openAIModelId); } diff --git a/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs b/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs index c130936a8a4e..3e08fe655e9e 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All rights reserved. using System; -using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI; @@ -9,7 +8,6 @@ using Microsoft.SemanticKernel.Diagnostics; using Microsoft.SemanticKernel.Orchestration; using Microsoft.SemanticKernel.Services; -using Microsoft.SemanticKernel.TemplateEngine; using RepoUtils; // ReSharper disable once InconsistentNaming @@ -42,94 +40,46 @@ public static async Task RunAsync() return; } - KernelBuilder kernelBuilder = new KernelBuilder() + var kernel = new KernelBuilder() .WithLoggerFactory(ConsoleLogger.LoggerFactory) .WithAzureOpenAIChatCompletionService( deploymentName: azureDeploymentName, endpoint: azureEndpoint, serviceId: "AzureOpenAIChat", + modelId: azureModelId, apiKey: azureApiKey) .WithOpenAIChatCompletionService( modelId: openAIModelId, serviceId: "OpenAIChat", apiKey: openAIApiKey) - .WithAIServiceSelector(new ByModelIdAIServiceSelector(openAIModelId)) + // Use the custom AI service selector to select the GPT 3.x model + .WithAIServiceSelector(new Gpt3xAIServiceSelector()) .Build(); - var modelSettings = new List - { - new OpenAIRequestSettings() { ServiceId = "AzureOpenAIChat", ModelId = "" }, - new OpenAIRequestSettings() { ServiceId = "OpenAIChat", ModelId = openAIModelId } - }; - var promptTemplateConfig = new PromptTemplateConfig() { ModelSettings = modelSettings }; - - await RunSemanticFunctionAsync(kernel, "Hello AI, what can you do for me?", modelSettings); - } - - public static async Task RunWithGpt3xAsync(KernelBuilder kernelBuilder, string prompt) - { - Console.WriteLine($"======== Without GPT3x: {prompt} ========"); - - var kernel = kernelBuilder.WithAIServiceSelector(new Gpt3xAIServiceSelector()).Build(); - - var promptTemplateConfig = new PromptTemplateConfig(); - - var skfunction = kernel.RegisterSemanticFunction( - "MyFunction", - prompt, - promptTemplateConfig); - - var result = await kernel.RunAsync(skfunction); + var prompt = "Hello AI, what can you do for me?"; + var result = await kernel.InvokeSemanticFunctionAsync(prompt); Console.WriteLine(result.GetValue()); } -} -public class ByModelIdAIServiceSelector : IAIServiceSelector -{ - private readonly string _openAIModelId; - - public ByModelIdAIServiceSelector(string openAIModelId) - { - this._openAIModelId = openAIModelId; - } - - public (T?, AIRequestSettings?) SelectAIService(SKContext context, ISKFunction skfunction) where T : IAIService + /// + /// Custom AI service selector that selects the GPT 3.x model + /// + private class Gpt3xAIServiceSelector : IAIServiceSelector { - foreach (var model in skfunction.ModelSettings) + public (T?, AIRequestSettings?) SelectAIService(SKContext context, ISKFunction skfunction) where T : IAIService { - if (model is OpenAIRequestSettings openAIModel) + var services = context.ServiceProvider.GetServices(); + foreach (var service in services) { - if (openAIModel.ModelId == this._openAIModelId) + var serviceModelId = service.GetModelId(); + if (!string.IsNullOrEmpty(serviceModelId) && serviceModelId.StartsWith("gpt-3", StringComparison.OrdinalIgnoreCase)) { - var service = context.ServiceProvider.GetService(openAIModel.ServiceId); - if (service is not null) - { - Console.WriteLine($"======== Selected service: {openAIModel.ServiceId} {openAIModel.ModelId} ========"); - return (service, model); - } + Console.WriteLine($"Selected model: {serviceModelId}"); + return (service, new OpenAIRequestSettings()); } } - } - - throw new SKException("Unable to find AI service to handled request."); - } -} -public class Gpt3xAIServiceSelector : IAIServiceSelector -{ - public (T?, AIRequestSettings?) SelectAIService(string renderedPrompt, IAIServiceProvider serviceProvider, IReadOnlyList? modelSettings) where T : IAIService - { - var services = serviceProvider.GetServices(); - foreach (var service in services) - { - var serviceModelId = service.GetModelId(); - if (!string.IsNullOrEmpty(serviceModelId) && serviceModelId.StartsWith("gpt-3", StringComparison.OrdinalIgnoreCase)) - { - Console.WriteLine($"Selected model: {serviceModelId}"); - return (service, new OpenAIRequestSettings()); - } + throw new SKException("Unable to find AI service for GPT 3.x."); } - - throw new SKException("Unable to find AI service for GPT 3.x."); } } diff --git a/dotnet/src/SemanticKernel.Core/Services/AIServiceSelectorBase.cs b/dotnet/src/SemanticKernel.Core/Services/AIServiceSelectorBase.cs index 109f5bf1d452..e4eb0540856e 100644 --- a/dotnet/src/SemanticKernel.Core/Services/AIServiceSelectorBase.cs +++ b/dotnet/src/SemanticKernel.Core/Services/AIServiceSelectorBase.cs @@ -2,9 +2,9 @@ #pragma warning disable IDE0130 // ReSharper disable once CheckNamespace - Using the main namespace -using System.Collections.Generic; using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.Diagnostics; +using Microsoft.SemanticKernel.Orchestration; using Microsoft.SemanticKernel.Services; namespace Microsoft.SemanticKernel; @@ -16,12 +16,12 @@ namespace Microsoft.SemanticKernel; public abstract class AIServiceSelectorBase : IAIServiceSelector { /// - public (T?, AIRequestSettings?) SelectAIService(string renderedPrompt, IAIServiceProvider serviceProvider, IReadOnlyList? modelSettings) where T : IAIService + public (T?, AIRequestSettings?) SelectAIService(SKContext context, ISKFunction skfunction) where T : IAIService { - var services = serviceProvider.GetServices(); + var services = context.ServiceProvider.GetServices(); foreach (var service in services) { - var result = this.SelectAIService(renderedPrompt, service, modelSettings); + var result = this.SelectAIService(context, skfunction, service); if (result is not null) { return ((T?, AIRequestSettings?))result; @@ -35,9 +35,9 @@ public abstract class AIServiceSelectorBase : IAIServiceSelector /// Return the AI service and requesting settings if the specified provider is the valid choice. /// /// - /// - /// - /// + /// + /// + /// Instance of /// - protected abstract (T?, AIRequestSettings?)? SelectAIService(string renderedPrompt, T service, IReadOnlyList? modelSettings) where T : IAIService; + protected abstract (T?, AIRequestSettings?)? SelectAIService(SKContext context, ISKFunction skfunction, T service) where T : IAIService; } From 6d3a84e617bec465bcc3b3ca6078d022a15b93b8 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft Date: Fri, 10 Nov 2023 15:46:16 +0000 Subject: [PATCH 16/18] Clean up samples --- .../Example61_MultipleLLMs.cs | 27 +++++++++++++++++-- .../Example62_CustomAIServiceSelector.cs | 6 +++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs b/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs index afcc8905fe13..9700b775fffa 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example61_MultipleLLMs.cs @@ -1,9 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.SemanticKernel; using Microsoft.SemanticKernel.AI; +using Microsoft.SemanticKernel.TemplateEngine; using RepoUtils; // ReSharper disable once InconsistentNaming @@ -51,9 +53,8 @@ public static async Task RunAsync() .Build(); await RunByServiceIdAsync(kernel, "AzureOpenAIChat"); - await RunByServiceIdAsync(kernel, "OpenAIChat"); - await RunByModelIdAsync(kernel, azureModelId); await RunByModelIdAsync(kernel, openAIModelId); + await RunByFirstModelIdAsync(kernel, "gpt-4-1106-preview", azureModelId, openAIModelId); } public static async Task RunByServiceIdAsync(IKernel kernel, string serviceId) @@ -85,4 +86,26 @@ public static async Task RunByModelIdAsync(IKernel kernel, string modelId) }); Console.WriteLine(result.GetValue()); } + + public static async Task RunByFirstModelIdAsync(IKernel kernel, params string[] modelIds) + { + Console.WriteLine($"======== Model Ids: {string.Join(", ", modelIds)} ========"); + + var prompt = "Hello AI, what can you do for me?"; + + var modelSettings = new List(); + foreach (var modelId in modelIds) + { + modelSettings.Add(new AIRequestSettings() { ModelId = modelId }); + } + var promptTemplateConfig = new PromptTemplateConfig() { ModelSettings = modelSettings }; + + var skfunction = kernel.RegisterSemanticFunction( + "HelloAI", + prompt, + promptTemplateConfig); + + var result = await kernel.RunAsync(skfunction); + Console.WriteLine(result.GetValue()); + } } diff --git a/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs b/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs index 3e08fe655e9e..bde303c45186 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs @@ -47,7 +47,8 @@ public static async Task RunAsync() endpoint: azureEndpoint, serviceId: "AzureOpenAIChat", modelId: azureModelId, - apiKey: azureApiKey) + apiKey: azureApiKey, + setAsDefault: true) .WithOpenAIChatCompletionService( modelId: openAIModelId, serviceId: "OpenAIChat", @@ -72,9 +73,10 @@ private class Gpt3xAIServiceSelector : IAIServiceSelector foreach (var service in services) { var serviceModelId = service.GetModelId(); + var endpoint = service.GetEndpoint(); if (!string.IsNullOrEmpty(serviceModelId) && serviceModelId.StartsWith("gpt-3", StringComparison.OrdinalIgnoreCase)) { - Console.WriteLine($"Selected model: {serviceModelId}"); + Console.WriteLine($"Selected model: {serviceModelId} {endpoint}"); return (service, new OpenAIRequestSettings()); } } From 60a33df75e99d5a0f2dee3d46ad50f749e8600a4 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft Date: Fri, 10 Nov 2023 15:52:24 +0000 Subject: [PATCH 17/18] dotnet format --- .../KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs | 2 +- .../SemanticKernel.Core/Services/OrderedIAIServiceSelector.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs b/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs index bde303c45186..074a85659019 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs @@ -65,7 +65,7 @@ public static async Task RunAsync() /// /// Custom AI service selector that selects the GPT 3.x model /// - private class Gpt3xAIServiceSelector : IAIServiceSelector + private sealed class Gpt3xAIServiceSelector : IAIServiceSelector { public (T?, AIRequestSettings?) SelectAIService(SKContext context, ISKFunction skfunction) where T : IAIService { diff --git a/dotnet/src/SemanticKernel.Core/Services/OrderedIAIServiceSelector.cs b/dotnet/src/SemanticKernel.Core/Services/OrderedIAIServiceSelector.cs index 66bb6be9ac97..3eaf239bd1c3 100644 --- a/dotnet/src/SemanticKernel.Core/Services/OrderedIAIServiceSelector.cs +++ b/dotnet/src/SemanticKernel.Core/Services/OrderedIAIServiceSelector.cs @@ -4,7 +4,6 @@ using Microsoft.SemanticKernel.AI; using Microsoft.SemanticKernel.Diagnostics; using Microsoft.SemanticKernel.Orchestration; -using Microsoft.SemanticKernel.Services; namespace Microsoft.SemanticKernel.Services; From 6d084c541f7c26fe75a1c4c9a31fd948e0e8ae38 Mon Sep 17 00:00:00 2001 From: markwallace-microsoft Date: Fri, 10 Nov 2023 18:07:41 +0000 Subject: [PATCH 18/18] Update samples --- .../KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs | 1 + dotnet/samples/KernelSyntaxExamples/Program.cs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs b/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs index 074a85659019..ade2db537a07 100644 --- a/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs +++ b/dotnet/samples/KernelSyntaxExamples/Example62_CustomAIServiceSelector.cs @@ -72,6 +72,7 @@ private sealed class Gpt3xAIServiceSelector : IAIServiceSelector var services = context.ServiceProvider.GetServices(); foreach (var service in services) { + // Find the first service that has a model id that starts with "gpt-3" var serviceModelId = service.GetModelId(); var endpoint = service.GetEndpoint(); if (!string.IsNullOrEmpty(serviceModelId) && serviceModelId.StartsWith("gpt-3", StringComparison.OrdinalIgnoreCase)) diff --git a/dotnet/samples/KernelSyntaxExamples/Program.cs b/dotnet/samples/KernelSyntaxExamples/Program.cs index 0ec6eedf4af5..de2b07fdf982 100644 --- a/dotnet/samples/KernelSyntaxExamples/Program.cs +++ b/dotnet/samples/KernelSyntaxExamples/Program.cs @@ -21,7 +21,7 @@ public static async Task Main(string[] args) using CancellationTokenSource cancellationTokenSource = new(); CancellationToken cancelToken = cancellationTokenSource.ConsoleCancellationToken(); - string? defaultFilter = "Example61"; // Modify to filter examples + string? defaultFilter = null; // Modify to filter examples // Check if args[0] is provided string? filter = args.Length > 0 ? args[0] : defaultFilter;