From 76b617bf9b2eac0f17e92f90c747e757a61da94c Mon Sep 17 00:00:00 2001 From: Ahmad Adel Date: Fri, 15 Mar 2024 07:38:12 +0200 Subject: [PATCH 1/4] Refactor ChatController for Asynchronous Plugin Registration This commit introduces asynchronous registration of plugins in the ChatController. The changes improve the efficiency of the chat function by allowing multiple plugins to be registered concurrently. This enhancement is expected to improve response times and overall performance of the chat functionality. --- webapi/Controllers/ChatController.cs | 141 ++++++++++++++++----------- 1 file changed, 84 insertions(+), 57 deletions(-) diff --git a/webapi/Controllers/ChatController.cs b/webapi/Controllers/ChatController.cs index 0d7116d2c..0e7f4db5d 100644 --- a/webapi/Controllers/ChatController.cs +++ b/webapi/Controllers/ChatController.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; @@ -52,6 +52,7 @@ public class ChatController : ControllerBase, IDisposable private const string ChatPluginName = nameof(ChatPlugin); private const string ChatFunctionName = "Chat"; private const string GeneratingResponseClientCall = "ReceiveBotResponseStatus"; + private readonly string _pluginDirectoryPath; public ChatController( ILogger logger, @@ -66,6 +67,7 @@ public ChatController( this._disposables = new List(); this._serviceOptions = serviceOptions.Value; this._plugins = plugins; + this._pluginDirectoryPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "Plugins", "OpenApi"); } /// @@ -194,87 +196,112 @@ private async Task RegisterFunctionsAsync(Kernel kernel, Dictionary(); + // GitHub if (authHeaders.TryGetValue("GITHUB", out string? GithubAuthHeader)) { - this._logger.LogInformation("Enabling GitHub plugin."); - BearerAuthenticationProvider authenticationProvider = new(() => Task.FromResult(GithubAuthHeader)); - await kernel.ImportPluginFromOpenApiAsync( - pluginName: "GitHubPlugin", - filePath: Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "Plugins", "OpenApi/GitHubPlugin/openapi.json"), - new OpenApiFunctionExecutionParameters - { - AuthCallback = authenticationProvider.AuthenticateRequestAsync, - }); + tasks.Add(this.RegisterGithubPlugin(kernel, GithubAuthHeader)); } // Jira if (authHeaders.TryGetValue("JIRA", out string? JiraAuthHeader)) { - this._logger.LogInformation("Registering Jira plugin"); - var authenticationProvider = new BasicAuthenticationProvider(() => { return Task.FromResult(JiraAuthHeader); }); - var hasServerUrlOverride = variables.TryGetValue("jira-server-url", out object? serverUrlOverride); - - await kernel.ImportPluginFromOpenApiAsync( - pluginName: "JiraPlugin", - filePath: Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "Plugins", "OpenApi/JiraPlugin/openapi.json"), - new OpenApiFunctionExecutionParameters - { - AuthCallback = authenticationProvider.AuthenticateRequestAsync, - ServerUrlOverride = hasServerUrlOverride ? new Uri(serverUrlOverride!.ToString()!) : null, - }); + tasks.Add(this.RegisterJiraPlugin(kernel, JiraAuthHeader, variables)); } // Microsoft Graph if (authHeaders.TryGetValue("GRAPH", out string? GraphAuthHeader)) { - this._logger.LogInformation("Enabling Microsoft Graph plugin(s)."); - BearerAuthenticationProvider authenticationProvider = new(() => Task.FromResult(GraphAuthHeader)); - GraphServiceClient graphServiceClient = this.CreateGraphServiceClient(authenticationProvider.GraphClientAuthenticateRequestAsync); - - kernel.ImportPluginFromObject(new TaskListPlugin(new MicrosoftToDoConnector(graphServiceClient)), "todo"); - kernel.ImportPluginFromObject(new CalendarPlugin(new OutlookCalendarConnector(graphServiceClient)), "calendar"); - kernel.ImportPluginFromObject(new EmailPlugin(new OutlookMailConnector(graphServiceClient)), "email"); + tasks.Add(this.RegisterMicrosoftGraphPlugins(kernel, GraphAuthHeader)); } if (variables.TryGetValue("customPlugins", out object? customPluginsString)) { - CustomPlugin[]? customPlugins = JsonSerializer.Deserialize(customPluginsString!.ToString()!); + tasks.Add(this.RegisterCustomPlugins(kernel, customPluginsString, authHeaders)); + } + + await Task.WhenAll(tasks); + } + + private async Task RegisterGithubPlugin(Kernel kernel, string GithubAuthHeader) + { + this._logger.LogInformation("Enabling GitHub plugin."); + BearerAuthenticationProvider authenticationProvider = new(() => Task.FromResult(GithubAuthHeader)); + await kernel.ImportPluginFromOpenApiAsync( + pluginName: "GitHubPlugin", + filePath: Path.Combine(this._pluginDirectoryPath, "GitHubPlugin/openapi.json"), + new OpenApiFunctionExecutionParameters + { + AuthCallback = authenticationProvider.AuthenticateRequestAsync, + }); + } + + private async Task RegisterJiraPlugin(Kernel kernel, string JiraAuthHeader, KernelArguments variables) + { + this._logger.LogInformation("Registering Jira plugin"); + var authenticationProvider = new BasicAuthenticationProvider(() => { return Task.FromResult(JiraAuthHeader); }); + var hasServerUrlOverride = variables.TryGetValue("jira-server-url", out object? serverUrlOverride); + + await kernel.ImportPluginFromOpenApiAsync( + pluginName: "JiraPlugin", + filePath: Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "Plugins", "OpenApi/JiraPlugin/openapi.json"), + new OpenApiFunctionExecutionParameters + { + AuthCallback = authenticationProvider.AuthenticateRequestAsync, + ServerUrlOverride = hasServerUrlOverride ? new Uri(serverUrlOverride!.ToString()!) : null, + }); + } - if (customPlugins != null) + private Task RegisterMicrosoftGraphPlugins(Kernel kernel, string GraphAuthHeader) + { + this._logger.LogInformation("Enabling Microsoft Graph plugin(s)."); + BearerAuthenticationProvider authenticationProvider = new(() => Task.FromResult(GraphAuthHeader)); + GraphServiceClient graphServiceClient = this.CreateGraphServiceClient(authenticationProvider.GraphClientAuthenticateRequestAsync); + + kernel.ImportPluginFromObject(new TaskListPlugin(new MicrosoftToDoConnector(graphServiceClient)), "todo"); + kernel.ImportPluginFromObject(new CalendarPlugin(new OutlookCalendarConnector(graphServiceClient)), "calendar"); + kernel.ImportPluginFromObject(new EmailPlugin(new OutlookMailConnector(graphServiceClient)), "email"); + return Task.CompletedTask; + } + + private async Task RegisterCustomPlugins(Kernel kernel, object? customPluginsString, Dictionary authHeaders) + { + CustomPlugin[]? customPlugins = JsonSerializer.Deserialize(customPluginsString!.ToString()!); + + if (customPlugins != null) + { + foreach (CustomPlugin plugin in customPlugins) { - foreach (CustomPlugin plugin in customPlugins) + if (authHeaders.TryGetValue(plugin.AuthHeaderTag.ToUpperInvariant(), out string? PluginAuthValue)) { - if (authHeaders.TryGetValue(plugin.AuthHeaderTag.ToUpperInvariant(), out string? PluginAuthValue)) + // Register the ChatGPT plugin with the kernel. + this._logger.LogInformation("Enabling {0} plugin.", plugin.NameForHuman); + + // TODO: [Issue #44] Support other forms of auth. Currently, we only support user PAT or no auth. + var requiresAuth = !plugin.AuthType.Equals("none", StringComparison.OrdinalIgnoreCase); + Task authCallback(HttpRequestMessage request, string _, OpenAIAuthenticationConfig __, CancellationToken ___ = default) { - // Register the ChatGPT plugin with the kernel. - this._logger.LogInformation("Enabling {0} plugin.", plugin.NameForHuman); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", PluginAuthValue); - // TODO: [Issue #44] Support other forms of auth. Currently, we only support user PAT or no auth. - var requiresAuth = !plugin.AuthType.Equals("none", StringComparison.OrdinalIgnoreCase); - Task authCallback(HttpRequestMessage request, string _, OpenAIAuthenticationConfig __, CancellationToken ___ = default) - { - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", PluginAuthValue); - - return Task.CompletedTask; - } - - await kernel.ImportPluginFromOpenAIAsync( - $"{plugin.NameForModel}Plugin", - PluginUtils.GetPluginManifestUri(plugin.ManifestDomain), - new OpenAIFunctionExecutionParameters - { - HttpClient = this._httpClientFactory.CreateClient(), - IgnoreNonCompliantErrors = true, - AuthCallback = requiresAuth ? authCallback : null - }); + return Task.CompletedTask; } + + await kernel.ImportPluginFromOpenAIAsync( + $"{plugin.NameForModel}Plugin", + PluginUtils.GetPluginManifestUri(plugin.ManifestDomain), + new OpenAIFunctionExecutionParameters + { + HttpClient = this._httpClientFactory.CreateClient(), + IgnoreNonCompliantErrors = true, + AuthCallback = requiresAuth ? authCallback : null + }); } } - else - { - this._logger.LogDebug("Failed to deserialize custom plugin details: {0}", customPluginsString); - } + } + else + { + this._logger.LogDebug("Failed to deserialize custom plugin details: {0}", customPluginsString); } } From 7fc79169042a4f1443539dd62966ce921bb51a42 Mon Sep 17 00:00:00 2001 From: Ahmed Adel Date: Fri, 22 Mar 2024 12:53:04 +0200 Subject: [PATCH 2/4] Implement GetPluginFullPath instead of _pluginDirectoryPath --- webapi/Controllers/ChatController.cs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/webapi/Controllers/ChatController.cs b/webapi/Controllers/ChatController.cs index 0e7f4db5d..55b85cb9c 100644 --- a/webapi/Controllers/ChatController.cs +++ b/webapi/Controllers/ChatController.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; @@ -52,7 +52,6 @@ public class ChatController : ControllerBase, IDisposable private const string ChatPluginName = nameof(ChatPlugin); private const string ChatFunctionName = "Chat"; private const string GeneratingResponseClientCall = "ReceiveBotResponseStatus"; - private readonly string _pluginDirectoryPath; public ChatController( ILogger logger, @@ -67,7 +66,6 @@ public ChatController( this._disposables = new List(); this._serviceOptions = serviceOptions.Value; this._plugins = plugins; - this._pluginDirectoryPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "Plugins", "OpenApi"); } /// @@ -230,7 +228,7 @@ private async Task RegisterGithubPlugin(Kernel kernel, string GithubAuthHeader) BearerAuthenticationProvider authenticationProvider = new(() => Task.FromResult(GithubAuthHeader)); await kernel.ImportPluginFromOpenApiAsync( pluginName: "GitHubPlugin", - filePath: Path.Combine(this._pluginDirectoryPath, "GitHubPlugin/openapi.json"), + filePath: GetPluginFullPath("GitHubPlugin/openapi.json"), new OpenApiFunctionExecutionParameters { AuthCallback = authenticationProvider.AuthenticateRequestAsync, @@ -245,12 +243,12 @@ private async Task RegisterJiraPlugin(Kernel kernel, string JiraAuthHeader, Kern await kernel.ImportPluginFromOpenApiAsync( pluginName: "JiraPlugin", - filePath: Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "Plugins", "OpenApi/JiraPlugin/openapi.json"), + filePath: GetPluginFullPath("OpenApi/JiraPlugin/openapi.json"), new OpenApiFunctionExecutionParameters { AuthCallback = authenticationProvider.AuthenticateRequestAsync, ServerUrlOverride = hasServerUrlOverride ? new Uri(serverUrlOverride!.ToString()!) : null, - }); + }); ; ; } private Task RegisterMicrosoftGraphPlugins(Kernel kernel, string GraphAuthHeader) @@ -381,6 +379,11 @@ private static KernelArguments GetContextVariables(Ask ask, IAuthInfo authInfo, return contextVariables; } + private static string GetPluginFullPath(string pluginPath) + { + return Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "Plugins", "OpenApi"); + } + /// /// Dispose of the object. /// From f2736cb992906b4eb0368cb1e45226bd0f1c0c3a Mon Sep 17 00:00:00 2001 From: Ahmed Adel Date: Fri, 22 Mar 2024 13:30:32 +0200 Subject: [PATCH 3/4] make RegisterCustomPlugins retuen IEnumerable --- webapi/Controllers/ChatController.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/webapi/Controllers/ChatController.cs b/webapi/Controllers/ChatController.cs index 55b85cb9c..452c69e7f 100644 --- a/webapi/Controllers/ChatController.cs +++ b/webapi/Controllers/ChatController.cs @@ -216,7 +216,7 @@ private async Task RegisterFunctionsAsync(Kernel kernel, Dictionary authHeaders) + private IEnumerable RegisterCustomPlugins(Kernel kernel, object? customPluginsString, Dictionary authHeaders) { CustomPlugin[]? customPlugins = JsonSerializer.Deserialize(customPluginsString!.ToString()!); @@ -285,7 +285,7 @@ Task authCallback(HttpRequestMessage request, string _, OpenAIAuthenticationConf return Task.CompletedTask; } - await kernel.ImportPluginFromOpenAIAsync( + yield return kernel.ImportPluginFromOpenAIAsync( $"{plugin.NameForModel}Plugin", PluginUtils.GetPluginManifestUri(plugin.ManifestDomain), new OpenAIFunctionExecutionParameters From 2f7415cc77631a3a4d79cd3dd8277f6d7800f248 Mon Sep 17 00:00:00 2001 From: Ahmed Adel Date: Fri, 22 Mar 2024 13:33:55 +0200 Subject: [PATCH 4/4] use pluginPath parameter --- webapi/Controllers/ChatController.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapi/Controllers/ChatController.cs b/webapi/Controllers/ChatController.cs index 452c69e7f..56fb28216 100644 --- a/webapi/Controllers/ChatController.cs +++ b/webapi/Controllers/ChatController.cs @@ -381,7 +381,7 @@ private static KernelArguments GetContextVariables(Ask ask, IAuthInfo authInfo, private static string GetPluginFullPath(string pluginPath) { - return Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "Plugins", "OpenApi"); + return Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!, "Plugins", pluginPath); } ///