diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentTool.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentTool.cs index 875062f8..0ffe91f6 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentTool.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Enums/AgentTool.cs @@ -4,5 +4,4 @@ public class AgentTool { public const string FileAnalyzer = "file-analyzer"; public const string ImageGenerator = "image-generator"; - public const string HttpHandler = "http-handler"; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentToolHook.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentToolHook.cs new file mode 100644 index 00000000..d7bb7b88 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentToolHook.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Abstraction.Agents; + +public interface IAgentToolHook +{ + void AddTools(List tools); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Files/IBotSharpFileService.cs b/src/Infrastructure/BotSharp.Abstraction/Files/IBotSharpFileService.cs index 76570c0e..ea703bd0 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Files/IBotSharpFileService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Files/IBotSharpFileService.cs @@ -29,4 +29,6 @@ public interface IBotSharpFileService /// /// (string, byte[]) GetFileInfoFromData(string data); + + string GetFileContentType(string filePath); } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs index 69e67c30..12b0512b 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs @@ -57,11 +57,13 @@ public partial class AgentService : IAgentService public IEnumerable GetAgentTools() { - var tools = typeof(AgentTool).GetFields(BindingFlags.Public | BindingFlags.Static) - .Where(f => f.IsLiteral && f.FieldType == typeof(string)) - .Select(x => x.GetRawConstantValue()?.ToString()) - .ToList(); + var tools = new List(); - return tools; + var hooks = _services.GetServices(); + foreach (var hook in hooks) + { + hook.AddTools(tools); + } + return tools.Where(x => !string.IsNullOrWhiteSpace(x)).Distinct().OrderBy(x => x).ToList(); } } diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index e502e7f3..e72ff4a1 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -49,7 +49,8 @@ - + + @@ -159,7 +160,10 @@ PreserveNewest - + + PreserveNewest + + PreserveNewest diff --git a/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.cs b/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.cs index f06bc0a3..94c9c8e1 100644 --- a/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.cs +++ b/src/Infrastructure/BotSharp.Core/Files/BotSharpFileService.cs @@ -66,8 +66,7 @@ public partial class BotSharpFileService : IBotSharpFileService return (contentType, Convert.FromBase64String(base64Str)); } - #region Private methods - private string GetFileContentType(string filePath) + public string GetFileContentType(string filePath) { string contentType; var provider = new FileExtensionContentTypeProvider(); @@ -79,6 +78,7 @@ public partial class BotSharpFileService : IBotSharpFileService return contentType; } + #region Private methods private bool ExistDirectory(string? dir) { return !string.IsNullOrEmpty(dir) && Directory.Exists(dir); diff --git a/src/Infrastructure/BotSharp.Core/Files/FilePlugin.cs b/src/Infrastructure/BotSharp.Core/Files/FilePlugin.cs index 9c657f7c..e549011b 100644 --- a/src/Infrastructure/BotSharp.Core/Files/FilePlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Files/FilePlugin.cs @@ -17,5 +17,6 @@ public class FilePlugin : IBotSharpPlugin services.AddScoped(); services.AddScoped(); + services.AddScoped(); } } diff --git a/src/Infrastructure/BotSharp.Core/Files/Hooks/AttachmentProcessingHook.cs b/src/Infrastructure/BotSharp.Core/Files/Hooks/AttachmentProcessingHook.cs index e114ceb6..8fc1c11d 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Hooks/AttachmentProcessingHook.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Hooks/AttachmentProcessingHook.cs @@ -19,8 +19,8 @@ public class AttachmentProcessingHook : AgentHookBase if (isConvMode && isEnabled) { - var (prompt, loadAttachmentFn) = GetLoadAttachmentFn(); - if (loadAttachmentFn != null) + var (prompt, fn) = GetPromptAndFunction(); + if (fn != null) { if (!string.IsNullOrWhiteSpace(prompt)) { @@ -29,11 +29,11 @@ public class AttachmentProcessingHook : AgentHookBase if (agent.Functions == null) { - agent.Functions = new List { loadAttachmentFn }; + agent.Functions = new List { fn }; } else { - agent.Functions.Add(loadAttachmentFn); + agent.Functions.Add(fn); } } } @@ -41,13 +41,13 @@ public class AttachmentProcessingHook : AgentHookBase base.OnAgentLoaded(agent); } - private (string, FunctionDef?) GetLoadAttachmentFn() + private (string, FunctionDef?) GetPromptAndFunction() { - var fnName = "load_attachment"; + var fn = "load_attachment"; var db = _services.GetRequiredService(); var agent = db.GetAgent(TOOL_ASSISTANT); - var prompt = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo($"{fnName}_prompt"))?.Content ?? string.Empty; - var loadAttachmentFn = agent?.Functions?.FirstOrDefault(x => x.Name.IsEqualTo(fnName)); + var prompt = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo($"{fn}.fn"))?.Content ?? string.Empty; + var loadAttachmentFn = agent?.Functions?.FirstOrDefault(x => x.Name.IsEqualTo(fn)); return (prompt, loadAttachmentFn); } } diff --git a/src/Infrastructure/BotSharp.Core/Files/Hooks/FileAnalyzerToolHook.cs b/src/Infrastructure/BotSharp.Core/Files/Hooks/FileAnalyzerToolHook.cs new file mode 100644 index 00000000..c97c7dea --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Files/Hooks/FileAnalyzerToolHook.cs @@ -0,0 +1,10 @@ + +namespace BotSharp.Core.Files.Hooks; + +public class FileAnalyzerToolHook : IAgentToolHook +{ + public void AddTools(List tools) + { + tools.Add(AgentTool.FileAnalyzer); + } +} diff --git a/src/Infrastructure/BotSharp.Core/data/agents/00000000-0000-0000-0000-000000000000/functions.json b/src/Infrastructure/BotSharp.Core/data/agents/00000000-0000-0000-0000-000000000000/functions.json index 75b0b53e..116398b3 100644 --- a/src/Infrastructure/BotSharp.Core/data/agents/00000000-0000-0000-0000-000000000000/functions.json +++ b/src/Infrastructure/BotSharp.Core/data/agents/00000000-0000-0000-0000-000000000000/functions.json @@ -16,5 +16,28 @@ }, "required": [ "user_request", "file_types" ] } + }, + + { + "name": "handle_http_request", + "description": "If the user requests to send an http request, you need to capture the http method and request content, and then call this function to send the http request.", + "parameters": { + "type": "object", + "properties": { + "request_url": { + "type": "string", + "description": "The http url that is requested. It can be an absolute url that starts with 'http' or 'https', or a relative url that starts with '/'" + }, + "http_method": { + "type": "string", + "description": "The http method that is requested, e.g., GET, POST, PUT, and DELETE." + }, + "request_content": { + "type": "string", + "description": "The http request content. It must be in json format.." + } + }, + "required": [ "request_url", "http_method" ] + } } ] \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/data/agents/00000000-0000-0000-0000-000000000000/templates/handle_http_request.fn.liquid b/src/Infrastructure/BotSharp.Core/data/agents/00000000-0000-0000-0000-000000000000/templates/handle_http_request.fn.liquid new file mode 100644 index 00000000..7640d81e --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/data/agents/00000000-0000-0000-0000-000000000000/templates/handle_http_request.fn.liquid @@ -0,0 +1 @@ +Please call handle_http_request if user wants to send an http request. \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/data/agents/00000000-0000-0000-0000-000000000000/templates/load_attachment_prompt.liquid b/src/Infrastructure/BotSharp.Core/data/agents/00000000-0000-0000-0000-000000000000/templates/load_attachment.fn.liquid similarity index 100% rename from src/Infrastructure/BotSharp.Core/data/agents/00000000-0000-0000-0000-000000000000/templates/load_attachment_prompt.liquid rename to src/Infrastructure/BotSharp.Core/data/agents/00000000-0000-0000-0000-000000000000/templates/load_attachment.fn.liquid diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs index 700d4015..d9e46f1e 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs @@ -3,6 +3,7 @@ using BotSharp.Abstraction.Instructs; using BotSharp.Abstraction.Instructs.Models; using BotSharp.Core.Infrastructures; using BotSharp.OpenAPI.ViewModels.Instructs; +using NetTopologySuite.IO; namespace BotSharp.OpenAPI.Controllers; diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/BotSharp.Plugin.AzureOpenAI.csproj b/src/Plugins/BotSharp.Plugin.AzureOpenAI/BotSharp.Plugin.AzureOpenAI.csproj index 6c76a9fb..8452774b 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/BotSharp.Plugin.AzureOpenAI.csproj +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/BotSharp.Plugin.AzureOpenAI.csproj @@ -1,4 +1,4 @@ - + netstandard2.1 @@ -11,7 +11,7 @@ - + diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs index 50fd33ce..d526f9f9 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs @@ -1,21 +1,4 @@ -using Azure.AI.OpenAI; -using BotSharp.Abstraction.Agents; -using BotSharp.Abstraction.Agents.Enums; -using BotSharp.Abstraction.Agents.Models; -using BotSharp.Abstraction.Conversations; -using BotSharp.Abstraction.Conversations.Models; -using BotSharp.Abstraction.Files; -using BotSharp.Abstraction.Loggers; -using BotSharp.Abstraction.MLTasks; -using BotSharp.Abstraction.Utilities; -using BotSharp.Plugin.AzureOpenAI.Settings; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; +using OpenAI.Chat; namespace BotSharp.Plugin.AzureOpenAI.Providers; @@ -49,22 +32,23 @@ public class ChatCompletionProvider : IChatCompletion } var client = ProviderHelper.GetClient(Provider, _model, _services); - var (prompt, chatCompletionsOptions) = PrepareOptions(agent, conversations); - chatCompletionsOptions.DeploymentName = _model; - var response = client.GetChatCompletions(chatCompletionsOptions); - var choice = response.Value.Choices[0]; - var message = choice.Message; + var chatClient = client.GetChatClient(_model); + var (prompt, messages, options) = PrepareOptions(agent, conversations); + + var response = chatClient.CompleteChat(messages, options); + var value = response.Value; + var reason = value.FinishReason; + var content = value.Content; RoleDialogModel responseMessage; - - if (choice.FinishReason == CompletionsFinishReason.FunctionCall) + if (reason == ChatFinishReason.FunctionCall) { - responseMessage = new RoleDialogModel(AgentRole.Function, message.Content) + responseMessage = new RoleDialogModel(AgentRole.Function, content[0].Text) { CurrentAgentId = agent.Id, MessageId = conversations.Last().MessageId, - FunctionName = message.FunctionCall.Name, - FunctionArgs = message.FunctionCall.Arguments + FunctionName = value.FunctionCall.FunctionName, + FunctionArgs = value.FunctionCall.FunctionArguments }; // Somethings LLM will generate a function name with agent name. @@ -73,28 +57,20 @@ public class ChatCompletionProvider : IChatCompletion responseMessage.FunctionName = responseMessage.FunctionName.Split('.').Last(); } } - else if (choice.FinishReason == CompletionsFinishReason.ToolCalls) + else if (reason == ChatFinishReason.ToolCalls) { - // Add the assistant message with tool calls to the conversation history - // ChatRequestAssistantMessage toolCallHistoryMessage = new(message); - // chatCompletionsOptions.Messages.Add(toolCallHistoryMessage); - - // Add a new tool message for each tool call that is resolved - var toolCall = message.ToolCalls.First() as ChatCompletionsFunctionToolCall; - // var toolCallResponseMessage = GetToolCallResponseMessage(toolCall); - // Now make a new request with all the messages thus far, including the original - - responseMessage = new RoleDialogModel(AgentRole.Function, message.Content) + var toolCall = value.ToolCalls.FirstOrDefault(); + responseMessage = new RoleDialogModel(AgentRole.Function, content[0].Text) { CurrentAgentId = agent.Id, MessageId = conversations.Last().MessageId, - FunctionName = toolCall.Name, - FunctionArgs = toolCall.Arguments + FunctionName = toolCall?.FunctionName, + FunctionArgs = toolCall?.FunctionArguments }; } else { - responseMessage = new RoleDialogModel(AgentRole.Assistant, message.Content) + responseMessage = new RoleDialogModel(AgentRole.Assistant, content[0].Text) { CurrentAgentId = agent.Id, MessageId = conversations.Last().MessageId @@ -109,8 +85,8 @@ public class ChatCompletionProvider : IChatCompletion Prompt = prompt, Provider = Provider, Model = _model, - PromptCount = response.Value.Usage.PromptTokens, - CompletionCount = response.Value.Usage.CompletionTokens + PromptCount = response.Value.Usage.InputTokens, + CompletionCount = response.Value.Usage.OutputTokens }); } @@ -131,14 +107,15 @@ public class ChatCompletionProvider : IChatCompletion } var client = ProviderHelper.GetClient(Provider, _model, _services); - var (prompt, chatCompletionsOptions) = PrepareOptions(agent, conversations); + var chatClient = client.GetChatClient(_model); + var (prompt, messages, options) = PrepareOptions(agent, conversations); - chatCompletionsOptions.DeploymentName = _model; - var response = await client.GetChatCompletionsAsync(chatCompletionsOptions); - var choice = response.Value.Choices[0]; - var message = choice.Message; + var response = await chatClient.CompleteChatAsync(messages, options); + var value = response.Value; + var reason = value.FinishReason; + var content = value.Content; - var msg = new RoleDialogModel(AgentRole.Assistant, message.Content) + var msg = new RoleDialogModel(AgentRole.Assistant, content[0].Text) { CurrentAgentId = agent.Id }; @@ -151,20 +128,20 @@ public class ChatCompletionProvider : IChatCompletion Prompt = prompt, Provider = Provider, Model = _model, - PromptCount = response.Value.Usage.PromptTokens, - CompletionCount = response.Value.Usage.CompletionTokens + PromptCount = response.Value.Usage.InputTokens, + CompletionCount = response.Value.Usage.OutputTokens }); } - if (choice.FinishReason == CompletionsFinishReason.FunctionCall) + if (reason == ChatFinishReason.FunctionCall) { - _logger.LogInformation($"[{agent.Name}]: {message.FunctionCall.Name}({message.FunctionCall.Arguments})"); + _logger.LogInformation($"[{agent.Name}]: {value.FunctionCall.FunctionName}({value.FunctionCall.FunctionArguments})"); - var funcContextIn = new RoleDialogModel(AgentRole.Function, message.Content) + var funcContextIn = new RoleDialogModel(AgentRole.Function, content[0].Text) { CurrentAgentId = agent.Id, - FunctionName = message.FunctionCall.Name, - FunctionArgs = message.FunctionCall.Arguments + FunctionName = value.FunctionCall.FunctionName, + FunctionArgs = value.FunctionCall.FunctionArguments }; // Somethings LLM will generate a function name with agent name. @@ -188,37 +165,33 @@ public class ChatCompletionProvider : IChatCompletion public async Task GetChatCompletionsStreamingAsync(Agent agent, List conversations, Func onMessageReceived) { var client = ProviderHelper.GetClient(Provider, _model, _services); - var (prompt, chatCompletionsOptions) = PrepareOptions(agent, conversations); - chatCompletionsOptions.DeploymentName = _model; - var response = await client.GetChatCompletionsStreamingAsync(chatCompletionsOptions); + var chatClient = client.GetChatClient(_model); + var (prompt, messages, options) = PrepareOptions(agent, conversations); + + var response = chatClient.CompleteChatStreamingAsync(messages, options); - string output = ""; await foreach (var choice in response) { - if (choice.FinishReason == CompletionsFinishReason.FunctionCall) + if (choice.FinishReason == ChatFinishReason.FunctionCall) { - Console.Write(choice.FunctionArgumentsUpdate); + Console.Write(choice.FunctionCallUpdate?.FunctionArgumentsUpdate); - await onMessageReceived(new RoleDialogModel(ChatRole.Assistant.ToString(), choice.FunctionArgumentsUpdate)); + await onMessageReceived(new RoleDialogModel(AgentRole.Assistant, choice.FunctionCallUpdate?.FunctionArgumentsUpdate)); continue; } - if (choice.ContentUpdate == null) - continue; - Console.Write(choice.ContentUpdate); + if (choice.ContentUpdate.IsNullOrEmpty()) continue; - _logger.LogInformation(choice.ContentUpdate); + _logger.LogInformation(choice.ContentUpdate[0].Text); - await onMessageReceived(new RoleDialogModel(choice.Role.ToString(), choice.ContentUpdate)); - - output = ""; + await onMessageReceived(new RoleDialogModel(choice.Role.ToString(), choice.ContentUpdate[0].Text)); } return true; } - protected (string, ChatCompletionsOptions) PrepareOptions(Agent agent, List conversations) + protected (string, IEnumerable, ChatCompletionOptions) PrepareOptions(Agent agent, List conversations) { var agentService = _services.GetRequiredService(); var fileService = _services.GetRequiredService(); @@ -227,75 +200,66 @@ public class ChatCompletionProvider : IChatCompletion var settings = settingsService.GetSetting(Provider, _model); var allowMultiModal = settings != null && settings.MultiModal; - var chatCompletionsOptions = new ChatCompletionsOptions(); - + var messages = new List(); + + var temperature = float.Parse(state.GetState("temperature", "0.0")); + var maxTokens = int.Parse(state.GetState("max_tokens", "1024")); + var options = new ChatCompletionOptions() + { + Temperature = temperature, + MaxTokens = maxTokens + }; + + foreach (var function in agent.Functions) + { + if (!agentService.RenderFunction(agent, function)) continue; + + var property = agentService.RenderFunctionProperty(agent, function); + + options.Tools.Add(ChatTool.CreateFunctionTool( + functionName: function.Name, + functionDescription: function.Description, + functionParameters: BinaryData.FromObjectAsJson(property))); + } + if (!string.IsNullOrEmpty(agent.Instruction)) { var instruction = agentService.RenderedInstruction(agent); - chatCompletionsOptions.Messages.Add(new ChatRequestSystemMessage(instruction)); + messages.Add(new SystemChatMessage(instruction)); } if (!string.IsNullOrEmpty(agent.Knowledges)) { - chatCompletionsOptions.Messages.Add(new ChatRequestSystemMessage(agent.Knowledges)); + messages.Add(new SystemChatMessage(agent.Knowledges)); } var samples = ProviderHelper.GetChatSamples(agent.Samples); - foreach (var message in samples) + foreach (var sample in samples) { - chatCompletionsOptions.Messages.Add(message.Role == AgentRole.User ? - new ChatRequestUserMessage(message.Content) : - new ChatRequestAssistantMessage(message.Content)); - } - - foreach (var function in agent.Functions) - { - if (agentService.RenderFunction(agent, function)) - { - var property = agentService.RenderFunctionProperty(agent, function); - - // legacy function call - /*chatCompletionsOptions.Functions.Add(new FunctionDefinition - { - Name = function.Name, - Description = function.Description, - Parameters = BinaryData.FromObjectAsJson(property) - });*/ - - // new chat tool - chatCompletionsOptions.Tools.Add(new ChatCompletionsFunctionToolDefinition - { - Name = function.Name, - Description = function.Description, - Parameters = BinaryData.FromObjectAsJson(property) - }); - } + messages.Add(sample.Role == AgentRole.User ? new UserChatMessage(sample.Content) : new AssistantChatMessage(sample.Content)); } foreach (var message in conversations) { - if (message.Role == ChatRole.Function) + if (message.Role == AgentRole.Function) { - chatCompletionsOptions.Messages.Add(new ChatRequestAssistantMessage(string.Empty) + messages.Add(new AssistantChatMessage(string.Empty) { - FunctionCall = new FunctionCall(message.FunctionName, message.FunctionArgs ?? String.Empty), + FunctionCall = new ChatFunctionCall(message.FunctionName, message.FunctionArgs ?? string.Empty) }); - chatCompletionsOptions.Messages.Add(new ChatRequestFunctionMessage(message.FunctionName, message.Content)); - // chatCompletionsOptions.Messages.Add(new ChatRequestToolMessage(message.Content, message.ToolCallId)); + messages.Add(new FunctionChatMessage(message.FunctionName, message.Content)); } - else if (message.Role == ChatRole.User) + else if (message.Role == AgentRole.User) { var text = !string.IsNullOrWhiteSpace(message.Payload) ? message.Payload : message.Content; + var chat = new UserChatMessage(text) + { + ParticipantName = message.FunctionName + }; - ChatRequestUserMessage userMessage = null; if (allowMultiModal) { - var chatItems = new List() - { - new ChatMessageTextContentItem(text) - }; - if (!message.Files.IsNullOrEmpty()) { foreach (var file in message.Files) @@ -303,127 +267,97 @@ public class ChatCompletionProvider : IChatCompletion if (!string.IsNullOrEmpty(file.FileUrl)) { var uri = new Uri(file.FileUrl); - chatItems.Add(new ChatMessageImageContentItem(uri, ChatMessageImageDetailLevel.Low)); + var contentPart = ChatMessageContentPart.CreateImageMessageContentPart(uri, ImageChatMessageContentPartDetail.Low); + chat = new UserChatMessage(contentPart, text); } else if (!string.IsNullOrEmpty(file.FileData)) { var (contentType, bytes) = fileService.GetFileInfoFromData(file.FileData); - using var stream = new MemoryStream(bytes, 0, bytes.Length); - chatItems.Add(new ChatMessageImageContentItem(stream, contentType, ChatMessageImageDetailLevel.Low)); + var contentPart = ChatMessageContentPart.CreateImageMessageContentPart(BinaryData.FromBytes(bytes), contentType, ImageChatMessageContentPartDetail.Low); + chat = new UserChatMessage(contentPart, text); } else if (!string.IsNullOrEmpty(file.FileStorageUrl)) { + var contentType = fileService.GetFileContentType(file.FileStorageUrl); using var stream = File.OpenRead(file.FileStorageUrl); - chatItems.Add(new ChatMessageImageContentItem(stream, file.ContentType, ChatMessageImageDetailLevel.Low)); + var contentPart = ChatMessageContentPart.CreateImageMessageContentPart(BinaryData.FromStream(stream), contentType, ImageChatMessageContentPartDetail.Low); + chat = new UserChatMessage(contentPart, text); } } } - - //if (!string.IsNullOrEmpty(message.ImageUrl)) - //{ - // var uri = new Uri(message.ImageUrl); - // userMessage.MultimodalContentItems.Add( - // new ChatMessageImageContentItem(uri, ChatMessageImageDetailLevel.Low)); - //} - - userMessage = new ChatRequestUserMessage(chatItems) - { - // To display Planner name in log - Name = message.FunctionName, - }; } - else - { - userMessage = new ChatRequestUserMessage(text) - { - // To display Planner name in log - Name = message.FunctionName, - }; - } - - chatCompletionsOptions.Messages.Add(userMessage); + messages.Add(chat); } - else if (message.Role == ChatRole.Assistant) + else if (message.Role == AgentRole.Assistant) { - chatCompletionsOptions.Messages.Add(new ChatRequestAssistantMessage(message.Content)); + messages.Add(new AssistantChatMessage(message.Content)); } } - // https://community.openai.com/t/cheat-sheet-mastering-temperature-and-top-p-in-chatgpt-api-a-few-tips-and-tricks-on-controlling-the-creativity-deterministic-output-of-prompt-responses/172683 - //var state = _services.GetRequiredService(); - var temperature = float.Parse(state.GetState("temperature", "0.0")); - var samplingFactor = float.Parse(state.GetState("sampling_factor", "0.0")); - chatCompletionsOptions.Temperature = temperature; - chatCompletionsOptions.NucleusSamplingFactor = samplingFactor; - chatCompletionsOptions.MaxTokens = int.Parse(state.GetState("max_tokens", "1024")); - // chatCompletionsOptions.FrequencyPenalty = 0; - // chatCompletionsOptions.PresencePenalty = 0; - - var prompt = GetPrompt(chatCompletionsOptions); - - return (prompt, chatCompletionsOptions); + var prompt = GetPrompt(messages, options); + return (prompt, messages, options); } - private string GetPrompt(ChatCompletionsOptions chatCompletionsOptions) + + private string GetPrompt(IEnumerable messages, ChatCompletionOptions options) { var prompt = string.Empty; - if (chatCompletionsOptions.Messages.Count > 0) + if (!messages.IsNullOrEmpty()) { // System instruction - var verbose = string.Join("\r\n", chatCompletionsOptions.Messages - .Where(x => x.Role == AgentRole.System) - .Select(x => x as ChatRequestSystemMessage).Select(x => + var verbose = string.Join("\r\n", messages + .Select(x => x as SystemChatMessage) + .Where(x => x != null) + .Select(x => { - if (!string.IsNullOrEmpty(x.Name)) + if (!string.IsNullOrEmpty(x.ParticipantName)) { // To display Agent name in log - return $"[{x.Name}]: {x.Content}"; + return $"[{x.ParticipantName}]: {x.Content[0].Text}"; } - return $"{x.Role}: {x.Content}"; + return $"{AgentRole.System}: {x.Content[0].Text}"; })); prompt += $"{verbose}\r\n"; prompt += "\r\n[CONVERSATION]"; - verbose = string.Join("\r\n", chatCompletionsOptions.Messages - .Where(x => x.Role != AgentRole.System).Select(x => + verbose = string.Join("\r\n", messages + .Where(x => (x as SystemChatMessage) == null) + .Select(x => { - if (x.Role == ChatRole.Function) + var fnMessage = x as FunctionChatMessage; + if (fnMessage != null) { - var m = x as ChatRequestFunctionMessage; - return $"{m.Role}: {m.Content}"; + return $"{AgentRole.Function}: {fnMessage.Content[0].Text}"; } - else if (x.Role == ChatRole.User) + + var userMessage = x as UserChatMessage; + if (userMessage != null) { - var m = x as ChatRequestUserMessage; - var content = m.Content ?? string.Join(", ", m.MultimodalContentItems - .Where(m => m is ChatMessageTextContentItem) - .Select(m => (m as ChatMessageTextContentItem)?.Text)); - return !string.IsNullOrEmpty(m.Name) && m.Name != "route_to_agent" ? - $"{m.Name}: {content}" : - $"{m.Role}: {content}"; + var content = x.Content[0].Text; + return !string.IsNullOrEmpty(userMessage.ParticipantName) && userMessage.ParticipantName != "route_to_agent" ? + $"{userMessage.ParticipantName}: {content}" : + $"{AgentRole.User}: {content}"; } - else if (x.Role == ChatRole.Assistant) + + var assistMessage = x as AssistantChatMessage; + if (assistMessage != null) { - var m = x as ChatRequestAssistantMessage; - return m.FunctionCall != null ? - $"{m.Role}: Call function {m.FunctionCall.Name}({m.FunctionCall.Arguments})" : - $"{m.Role}: {m.Content}"; - } - else - { - throw new NotImplementedException("Not found role"); + return assistMessage.FunctionCall != null ? + $"{AgentRole.Assistant}: Call function {assistMessage.FunctionCall.FunctionName}({assistMessage.FunctionCall.FunctionArguments})" : + $"{AgentRole.Assistant}: {assistMessage.Content[0].Text}"; } + + return string.Empty; })); prompt += $"\r\n{verbose}\r\n"; } - if (chatCompletionsOptions.Tools.Count > 0) + if (!options.Tools.IsNullOrEmpty()) { - var functions = string.Join("\r\n", chatCompletionsOptions.Tools.Select(x => + var functions = string.Join("\r\n", options.Tools.Select(fn => { - var fn = x as ChatCompletionsFunctionToolDefinition; - return $"\r\n{fn.Name}: {fn.Description}\r\n{fn.Parameters}"; + return $"\r\n{fn.FunctionName}: {fn.FunctionDescription}\r\n{fn.FunctionParameters}"; })); prompt += $"\r\n[FUNCTIONS]{functions}\r\n"; } @@ -435,15 +369,4 @@ public class ChatCompletionProvider : IChatCompletion { _model = model; } - - ChatRequestToolMessage GetToolCallResponseMessage(ChatCompletionsToolCall toolCall) - { - var functionToolCall = toolCall as ChatCompletionsFunctionToolCall; - // Validate and process the JSON arguments for the function call - string unvalidatedArguments = functionToolCall.Arguments; - var functionResultData = (object)null; // GetYourFunctionResultData(unvalidatedArguments); - // Here, replacing with an example as if returned from "GetYourFunctionResultData" - functionResultData = "31 celsius"; - return new ChatRequestToolMessage(functionResultData.ToString(), toolCall.Id); - } } diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ImageGenerationProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ImageGenerationProvider.cs index c0211f11..94fe9743 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ImageGenerationProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ImageGenerationProvider.cs @@ -1,17 +1,4 @@ -using Azure.AI.OpenAI; -using BotSharp.Abstraction.Agents.Enums; -using BotSharp.Abstraction.Agents.Models; -using BotSharp.Abstraction.Conversations; -using BotSharp.Abstraction.Conversations.Models; -using BotSharp.Abstraction.Loggers; -using BotSharp.Abstraction.MLTasks; -using BotSharp.Plugin.AzureOpenAI.Settings; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; +using OpenAI.Images; namespace BotSharp.Plugin.AzureOpenAI.Providers; @@ -47,21 +34,32 @@ public class ImageGenerationProvider : IImageGeneration } var client = ProviderHelper.GetClient(Provider, _model, _services); - var options = PrepareOptions(conversations); - var response = await client.GetImageGenerationsAsync(options); - var image = response.Value.Data.First(); + var (prompt, options) = PrepareOptions(conversations); + var imageClient = client.GetImageClient(_model); + + ImageGenerationOptions myoptions = new() + { + Quality = GeneratedImageQuality.High, + Size = GeneratedImageSize.W1792xH1024, + Style = GeneratedImageStyle.Vivid, + ResponseFormat = GeneratedImageFormat.Bytes + }; + + var response = imageClient.GenerateImage(prompt, myoptions); + var imageUri = response.Value.ImageUri; + var revisedPrompt = response.Value.RevisedPrompt; var content = string.Empty; - if (!string.IsNullOrEmpty(image.RevisedPrompt)) + if (!string.IsNullOrEmpty(revisedPrompt)) { - content = image.RevisedPrompt; + content = revisedPrompt; } var responseMessage = new RoleDialogModel(AgentRole.Assistant, content) { CurrentAgentId = agent.Id, MessageId = conversations.LastOrDefault()?.MessageId ?? string.Empty, - Data = image.Url.AbsoluteUri ?? image.Base64Data + Data = imageUri.AbsoluteUri }; // After @@ -69,10 +67,10 @@ public class ImageGenerationProvider : IImageGeneration { await hook.AfterGenerated(responseMessage, new TokenStatsModel { - Prompt = options.Prompt, + Prompt = prompt, Provider = Provider, Model = _model, - PromptCount = options.Prompt.Split(' ', StringSplitOptions.RemoveEmptyEntries).Count(), + PromptCount = prompt.Split(' ', StringSplitOptions.RemoveEmptyEntries).Count(), CompletionCount = content.Split(' ', StringSplitOptions.RemoveEmptyEntries).Count() }); } @@ -80,25 +78,99 @@ public class ImageGenerationProvider : IImageGeneration return responseMessage; } - private ImageGenerationOptions PrepareOptions(List conversations) + private (string, ImageGenerationOptions) PrepareOptions(List conversations) { - var state = _services.GetRequiredService(); + var prompt = conversations.LastOrDefault()?.Payload ?? conversations.LastOrDefault()?.Content ?? string.Empty; - var sizeValue = !string.IsNullOrEmpty(state.GetState("image_size")) ? state.GetState("image_size") : "1024x1024"; - var qualityValue = !string.IsNullOrEmpty(state.GetState("image_quality")) ? state.GetState("image_quality") : "standard"; + var state = _services.GetRequiredService(); + var size = state.GetState("image_size"); + var quality = state.GetState("image_quality"); + var style = state.GetState("image_style"); var options = new ImageGenerationOptions { - DeploymentName = _model, - Prompt = conversations.LastOrDefault()?.Payload ?? conversations.LastOrDefault()?.Content ?? string.Empty, - Size = new ImageSize(sizeValue), - Quality = new ImageGenerationQuality(qualityValue) + //Size = GetImageSize(size), + //Quality = GetImageQuality(quality), + //Style = GetImageStyle(style), + //ResponseFormat = GeneratedImageFormat.Uri }; - return options; + return (prompt, options); } public void SetModelName(string model) { _model = model; } + + private GeneratedImageSize GetImageSize(string size) + { + var value = !string.IsNullOrEmpty(size) ? size : "1024x1024"; + + GeneratedImageSize retSize; + switch (value) + { + case "256x256": + retSize = GeneratedImageSize.W256xH256; + break; + case "512x512": + retSize = GeneratedImageSize.W512xH512; + break; + case "1024x1024": + retSize = GeneratedImageSize.W1024xH1024; + break; + case "1024x1792": + retSize = GeneratedImageSize.W1024xH1792; + break; + case "1792x1024": + retSize = GeneratedImageSize.W1792xH1024; + break; + default: + retSize = GeneratedImageSize.W1024xH1024; + break; + } + + return retSize; + } + + private GeneratedImageQuality GetImageQuality(string quality) + { + var value = !string.IsNullOrEmpty(quality) ? quality : "standard"; + + GeneratedImageQuality retQuality; + switch (value) + { + case "standard": + retQuality = GeneratedImageQuality.Standard; + break; + case "hd": + retQuality = GeneratedImageQuality.High; + break; + default: + retQuality = GeneratedImageQuality.Standard; + break; + } + + return retQuality; + } + + private GeneratedImageStyle GetImageStyle(string style) + { + var value = !string.IsNullOrEmpty(style) ? style : "natural"; + + GeneratedImageStyle retStyle; + switch (value) + { + case "standard": + retStyle = GeneratedImageStyle.Natural; + break; + case "vivid": + retStyle = GeneratedImageStyle.Vivid; + break; + default: + retStyle = GeneratedImageStyle.Natural; + break; + } + + return retStyle; + } } diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/OpenAiChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/OpenAiChatCompletionProvider.cs index a13ffcce..7b5125b9 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/OpenAiChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/OpenAiChatCompletionProvider.cs @@ -1,7 +1,3 @@ -using BotSharp.Plugin.AzureOpenAI.Settings; -using Microsoft.Extensions.Logging; -using System; - namespace BotSharp.Plugin.AzureOpenAI.Providers; public class OpenAiChatCompletionProvider : ChatCompletionProvider diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/OpenAiImageGenerationProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/OpenAiImageGenerationProvider.cs index 5a02b6ae..e57db4b7 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/OpenAiImageGenerationProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/OpenAiImageGenerationProvider.cs @@ -1,7 +1,3 @@ -using BotSharp.Plugin.AzureOpenAI.Settings; -using Microsoft.Extensions.Logging; -using System; - namespace BotSharp.Plugin.AzureOpenAI.Providers; public class OpenAiImageGenerationProvider : ImageGenerationProvider diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ProviderHelper.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ProviderHelper.cs index 8ac4bc34..606372a0 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ProviderHelper.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ProviderHelper.cs @@ -1,10 +1,7 @@ using Azure.AI.OpenAI; using Azure; -using System; -using BotSharp.Abstraction.Conversations.Models; -using System.Collections.Generic; -using Microsoft.Extensions.DependencyInjection; -using BotSharp.Abstraction.MLTasks; +using OpenAI; +using System.ClientModel; namespace BotSharp.Plugin.AzureOpenAI.Providers; @@ -15,8 +12,8 @@ public class ProviderHelper var settingsService = services.GetRequiredService(); var settings = settingsService.GetSetting(provider, model); var client = provider == "openai" ? - new OpenAIClient($"{settings.ApiKey}") : - new OpenAIClient(new Uri(settings.Endpoint), new AzureKeyCredential(settings.ApiKey)); + new OpenAIClient(new ApiKeyCredential(settings.ApiKey)) : + new AzureOpenAIClient(new Uri(settings.Endpoint), new AzureKeyCredential(settings.ApiKey)); return client; } diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/TextCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/TextCompletionProvider.cs index 6ecc59a9..155a8bf6 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/TextCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/TextCompletionProvider.cs @@ -1,16 +1,4 @@ -using Azure.AI.OpenAI; -using BotSharp.Abstraction.MLTasks; -using System; -using System.Threading.Tasks; -using BotSharp.Plugin.AzureOpenAI.Settings; -using BotSharp.Abstraction.Conversations; -using Microsoft.Extensions.DependencyInjection; -using BotSharp.Abstraction.Conversations.Models; -using BotSharp.Abstraction.Agents.Enums; -using System.Linq; -using System.Collections.Generic; -using BotSharp.Abstraction.Agents.Models; -using BotSharp.Abstraction.Loggers; +using OpenAI.Chat; namespace BotSharp.Plugin.AzureOpenAI.Providers; @@ -51,28 +39,26 @@ public class TextCompletionProvider : ITextCompletion })).ToArray()); var client = ProviderHelper.GetClient(Provider, _model, _services); + var chatClient = client.GetChatClient(_model); - var completionsOptions = new CompletionsOptions() + var messages = new List() { - Prompts = - { - text - }, - MaxTokens = 256, + new UserChatMessage(text) }; - completionsOptions.StopSequences.Add($"{AgentRole.Assistant}:"); var state = _services.GetRequiredService(); var temperature = float.Parse(state.GetState("temperature", "0.0")); - var samplingFactor = float.Parse(state.GetState("sampling_factor", "0.0")); - completionsOptions.Temperature = temperature; - completionsOptions.NucleusSamplingFactor = samplingFactor; - completionsOptions.DeploymentName = _model; - var response = await client.GetCompletionsAsync(completionsOptions); + var completionOptions = new ChatCompletionOptions() + { + MaxTokens = 256, + Temperature = temperature + }; + + var response = await chatClient.CompleteChatAsync(messages, completionOptions); // OpenAI var completion = ""; - foreach (var t in response.Value.Choices) + foreach (var t in response.Value.Content) { completion += t.Text; }; @@ -89,8 +75,8 @@ public class TextCompletionProvider : ITextCompletion Prompt = text, Provider = Provider, Model = _model, - PromptCount = response.Value.Usage.PromptTokens, - CompletionCount = response.Value.Usage.CompletionTokens + PromptCount = response.Value.Usage.InputTokens, + CompletionCount = response.Value.Usage.OutputTokens })).ToArray()); return completion.Trim(); diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Using.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Using.cs new file mode 100644 index 00000000..80a43499 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Using.cs @@ -0,0 +1,17 @@ +global using System; +global using System.Collections.Generic; +global using System.Linq; +global using System.IO; +global using System.Threading.Tasks; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Logging; +global using BotSharp.Abstraction.Agents.Enums; +global using BotSharp.Abstraction.Agents.Models; +global using BotSharp.Abstraction.Conversations; +global using BotSharp.Abstraction.Conversations.Models; +global using BotSharp.Abstraction.Loggers; +global using BotSharp.Abstraction.MLTasks; +global using BotSharp.Abstraction.Agents; +global using BotSharp.Abstraction.Files; +global using BotSharp.Abstraction.Utilities; +global using BotSharp.Plugin.AzureOpenAI.Settings; \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.HttpHandler/Enums/Tool.cs b/src/Plugins/BotSharp.Plugin.HttpHandler/Enums/Tool.cs new file mode 100644 index 00000000..893f068a --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.HttpHandler/Enums/Tool.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Plugin.HttpHandler.Enums; + +public class Tool +{ + public const string HttpHandler = "http-handler"; +} diff --git a/src/Plugins/BotSharp.Plugin.HttpHandler/Hooks/HttpHandlerHook.cs b/src/Plugins/BotSharp.Plugin.HttpHandler/Hooks/HttpHandlerHook.cs new file mode 100644 index 00000000..d5405d3d --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.HttpHandler/Hooks/HttpHandlerHook.cs @@ -0,0 +1,59 @@ +using BotSharp.Abstraction.Agents; +using BotSharp.Abstraction.Agents.Settings; +using BotSharp.Abstraction.Functions.Models; +using BotSharp.Abstraction.Repositories; +using BotSharp.Plugin.HttpHandler.Enums; + +namespace BotSharp.Plugin.HttpHandler.Hooks; + +public class HttpHandlerHook : AgentHookBase +{ + private static string TOOL_ASSISTANT = Guid.Empty.ToString(); + + public override string SelfId => string.Empty; + + public HttpHandlerHook(IServiceProvider services, AgentSettings settings) + : base(services, settings) + { + } + + public override void OnAgentLoaded(Agent agent) + { + var conv = _services.GetRequiredService(); + var isConvMode = conv.IsConversationMode(); + var isEnabled = !agent.Tools.IsNullOrEmpty() && agent.Tools.Contains(Tool.HttpHandler); + + if (isConvMode && isEnabled) + { + var (prompt, fn) = GetPromptAndFunction(); + if (fn != null) + { + if (!string.IsNullOrWhiteSpace(prompt)) + { + agent.Instruction += $"\r\n\r\n{prompt}\r\n\r\n"; + } + + if (agent.Functions == null) + { + agent.Functions = new List { fn }; + } + else + { + agent.Functions.Add(fn); + } + } + } + + base.OnAgentLoaded(agent); + } + + private (string, FunctionDef?) GetPromptAndFunction() + { + var fn = "handle_http_request"; + var db = _services.GetRequiredService(); + var agent = db.GetAgent(TOOL_ASSISTANT); + var prompt = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo($"{fn}.fn"))?.Content ?? string.Empty; + var loadAttachmentFn = agent?.Functions?.FirstOrDefault(x => x.Name.IsEqualTo(fn)); + return (prompt, loadAttachmentFn); + } +} diff --git a/src/Plugins/BotSharp.Plugin.HttpHandler/Hooks/HttpHandlerToolHook.cs b/src/Plugins/BotSharp.Plugin.HttpHandler/Hooks/HttpHandlerToolHook.cs new file mode 100644 index 00000000..1d14beb9 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.HttpHandler/Hooks/HttpHandlerToolHook.cs @@ -0,0 +1,12 @@ +using BotSharp.Abstraction.Agents; +using BotSharp.Plugin.HttpHandler.Enums; + +namespace BotSharp.Plugin.HttpHandler.Hooks; + +public class HttpHandlerToolHook : IAgentToolHook +{ + public void AddTools(List tools) + { + tools.Add(Tool.HttpHandler); + } +} diff --git a/src/Plugins/BotSharp.Plugin.HttpHandler/HttpHandlerPlugin.cs b/src/Plugins/BotSharp.Plugin.HttpHandler/HttpHandlerPlugin.cs index 3e31c096..c3427615 100644 --- a/src/Plugins/BotSharp.Plugin.HttpHandler/HttpHandlerPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.HttpHandler/HttpHandlerPlugin.cs @@ -1,5 +1,6 @@ -using BotSharp.Abstraction.Http.Settings; +using BotSharp.Abstraction.Agents; using BotSharp.Abstraction.Settings; +using BotSharp.Plugin.HttpHandler.Hooks; using Microsoft.Extensions.Configuration; namespace BotSharp.Plugin.HttpHandler; @@ -19,5 +20,8 @@ public class HttpHandlerPlugin : IBotSharpPlugin var settingService = provider.GetRequiredService(); return settingService.Bind("Http"); }); + + services.AddScoped(); + services.AddScoped(); } } diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 3618bcf2..b5a15122 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -292,7 +292,8 @@ "BotSharp.Plugin.WebDriver", "BotSharp.Plugin.LLamaSharp", "BotSharp.Plugin.SparkDesk", - "BotSharp.Plugin.MetaGLM" + "BotSharp.Plugin.MetaGLM", + "BotSharp.Plugin.HttpHandler" ] } }