From d72204cd2230f3b0ccf430001b832b06648ed5fc Mon Sep 17 00:00:00 2001 From: hchen Date: Wed, 26 Jul 2023 16:05:30 -0500 Subject: [PATCH 1/5] Add FunctionDefinition. --- .../Agents/Models/Agent.cs | 10 +++--- .../BotSharp.Abstraction.csproj | 1 + .../Conversations/Models/FunctionDef.cs | 10 ++++++ .../BotSharp.Core/Agents/AgentController.cs | 5 +-- .../Agents/Services/AgentService.GetAgents.cs | 21 +++++++++++- .../Services/AgentService.UpdateAgent.cs | 2 +- .../Agents/Services/AgentService.cs | 5 ++- .../Agents/ViewModels/AgentUpdateModel.cs | 34 ++++++++++++++----- .../Providers/ChatCompletionProvider.cs | 26 ++++++++++++++ 9 files changed, 93 insertions(+), 21 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionDef.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs index 7330e31b..93276f70 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs @@ -18,13 +18,13 @@ public class Agent /// public string Samples { get; set; } + /// + /// Functions + /// + public string Functions { get; set; } + /// /// Domain knowledges /// public string Knowledges { get; set;} - - /// - /// Owner user id - /// - public string OwerId { get; set; } = string.Empty; } diff --git a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj index 6825140b..0553f6c5 100644 --- a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj +++ b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj @@ -26,6 +26,7 @@ + diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionDef.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionDef.cs new file mode 100644 index 00000000..5c050b5a --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionDef.cs @@ -0,0 +1,10 @@ +using System.Text.Json; + +namespace BotSharp.Abstraction.Conversations.Models; + +public class FunctionDef +{ + public string Name { get; set; } + public string Description { get; set; } + public JsonDocument Parameters { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Core/Agents/AgentController.cs b/src/Infrastructure/BotSharp.Core/Agents/AgentController.cs index d668345e..3e17e193 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/AgentController.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/AgentController.cs @@ -10,11 +10,9 @@ namespace BotSharp.Core.Agents; public class AgentController : ControllerBase, IApiAdapter { private readonly IAgentService _agentService; - private readonly IUserIdentity _user; - public AgentController(IAgentService agentService, IUserIdentity user) + public AgentController(IAgentService agentService) { _agentService = agentService; - _user = user; } [HttpPost("/agent")] @@ -30,7 +28,6 @@ public class AgentController : ControllerBase, IApiAdapter { var model = agent.ToAgent(); model.Id = agentId; - model.OwerId = _user.Id; await _agentService.UpdateAgent(model); } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs index 583bfc27..e20b5bcc 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Agents.Models; +using Microsoft.Extensions.Logging; using System.IO; namespace BotSharp.Core.Agents.Services; @@ -30,11 +31,29 @@ public partial class AgentService { profile.Instruction = File.ReadAllText(instructionFile); } + else + { + _logger.LogError($"Can't find instruction file from {instructionFile}"); + } var samplesFile = Path.Combine(dir, "samples.txt"); if (File.Exists(samplesFile)) { - profile.Samples = File.ReadAllText(Path.Combine(dir, "samples.txt")); + profile.Samples = File.ReadAllText(samplesFile); + } + else + { + _logger.LogWarning($"Can't find samples file from {samplesFile}"); + } + + var functionsFile = Path.Combine(dir, "functions.json"); + if (File.Exists(functionsFile)) + { + profile.Functions = File.ReadAllText(functionsFile); + } + else + { + _logger.LogInformation($"Can't find functions file from {functionsFile}"); } return profile; diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs index 572b795e..dfbd1fd9 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.UpdateAgent.cs @@ -13,7 +13,7 @@ public partial class AgentService { var record = (from a in db.Agent join ua in db.UserAgent on a.Id equals ua.AgentId - where ua.UserId == agent.OwerId && a.Id == agent.Id + where ua.UserId == _user.Id && a.Id == agent.Id select a).First(); record.Name = agent.Name; diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs index 55238f8d..4b2ad947 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs @@ -1,3 +1,4 @@ +using Microsoft.Extensions.Logging; using System.IO; namespace BotSharp.Core.Agents.Services; @@ -5,12 +6,14 @@ namespace BotSharp.Core.Agents.Services; public partial class AgentService : IAgentService { private readonly IServiceProvider _services; + private readonly ILogger _logger; private readonly IUserIdentity _user; private readonly AgentSettings _settings; - public AgentService(IServiceProvider services, IUserIdentity user, AgentSettings settings) + public AgentService(IServiceProvider services, ILogger logger, IUserIdentity user, AgentSettings settings) { _services = services; + _logger = logger; _user = user; _settings = settings; } diff --git a/src/Infrastructure/BotSharp.Core/Agents/ViewModels/AgentUpdateModel.cs b/src/Infrastructure/BotSharp.Core/Agents/ViewModels/AgentUpdateModel.cs index 0dc886e0..5b519953 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/ViewModels/AgentUpdateModel.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/ViewModels/AgentUpdateModel.cs @@ -4,27 +4,43 @@ namespace BotSharp.Core.Agents.ViewModels; public class AgentUpdateModel { - public string Name { get; set; } - public string Description { get; set; } + public string Name { get; set; } = string.Empty; + public string? Description { get; set; } /// /// Instruction /// - public string Instruction { get; set; } + public string? Instruction { get; set; } /// /// Samples /// - public string Samples { get; set; } + public string? Samples { get; set; } + + /// + /// Functions + /// + public string? Functions { get; set; } public Agent ToAgent() { - return new Agent + var agent = new Agent { - Name = Name, - Description = Description, - Instruction = Instruction, - Samples = Samples + Name = Name }; + + if (Description != null) + agent.Description = Description; + + if (Instruction != null) + agent.Instruction = Instruction; + + if (Samples != null) + agent.Samples = Samples; + + if (Functions != null) + agent.Functions = Functions; + + return agent; } } diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs index b2a47a67..1de10c9a 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs @@ -6,6 +6,7 @@ using BotSharp.Abstraction.MLTasks; using BotSharp.Plugin.AzureOpenAI.Settings; using System; using System.Collections.Generic; +using System.Text.Json; using System.Threading.Tasks; namespace BotSharp.Plugin.AzureOpenAI.Providers; @@ -70,6 +71,20 @@ public class ChatCompletionProvider : IChatCompletion return samples; } + public List GetFunctions(string functionsJson) + { + var functions = new List(); + if (!string.IsNullOrEmpty(functionsJson)) + { + functions = JsonSerializer.Deserialize>(functionsJson, new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + AllowTrailingCommas = true + }); + } + + return functions; + } public async Task GetChatCompletionsStreamingAsync(Agent agent, List conversations) { @@ -114,6 +129,17 @@ public class ChatCompletionProvider : IChatCompletion chatCompletionsOptions.Messages.Add(new ChatMessage(message.Role, message.Content)); } + var functions = GetFunctions(agent.Functions); + foreach (var function in functions) + { + chatCompletionsOptions.Functions.Add(new FunctionDefinition + { + Name = function.Name, + Description = function.Description, + Parameters = BinaryData.FromObjectAsJson(function.Parameters) + }); + } + foreach (var message in conversations) { chatCompletionsOptions.Messages.Add(new ChatMessage(message.Role, message.Content)); From 0d7c5675028ed3f5418f2516d958a2824b32b582 Mon Sep 17 00:00:00 2001 From: hchen Date: Thu, 27 Jul 2023 10:07:39 -0500 Subject: [PATCH 2/5] Add onMessageReceived to support streaming. --- .../ConversationCompletionHookBase.cs | 4 ++-- .../IConversationCompletionHook.cs | 2 +- .../Conversations/IConversationService.cs | 4 ++-- .../Conversations/Models/RoleDialogModel.cs | 7 +++++- .../MLTasks/IChatCompletion.cs | 2 +- .../Conversations/ConversationController.cs | 10 ++++---- .../Services/ConversationService.cs | 23 ++++++++++--------- .../LLamaSharp/ChatCompletionProvider.cs | 4 ++-- .../Providers/ChatCompletionProvider.cs | 11 +++++++-- .../ChatbotUiController.cs | 14 ++++++----- 10 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationCompletionHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationCompletionHookBase.cs index 6d832fe6..d885e7f1 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationCompletionHookBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationCompletionHookBase.cs @@ -46,8 +46,8 @@ public abstract class ConversationCompletionHookBase : IConversationCompletionHo return Task.CompletedTask; } - public virtual Task AfterCompletion(string response) + public virtual Task AfterCompletion(RoleDialogModel message) { - return Task.FromResult(response); + return Task.CompletedTask; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationCompletionHook.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationCompletionHook.cs index fadc055f..ece8d785 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationCompletionHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationCompletionHook.cs @@ -18,5 +18,5 @@ public interface IConversationCompletionHook IConversationCompletionHook SetChatCompletion(IChatCompletion chatCompletion); Task BeforeCompletion(); - Task AfterCompletion(string response); + Task AfterCompletion(RoleDialogModel message); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs index 8d87c51e..e3a1684d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs @@ -8,8 +8,8 @@ public interface IConversationService Task GetConversation(string id); Task> GetConversations(); Task DeleteConversation(string id); - Task SendMessage(string agentId, string conversationId, RoleDialogModel lastDalog); - Task SendMessage(string agentId, string conversationId, List wholeDialogs); + Task SendMessage(string agentId, string conversationId, RoleDialogModel lastDalog, Func onMessageReceived); + Task SendMessage(string agentId, string conversationId, List wholeDialogs, Func onMessageReceived); List GetDialogHistory(string agentId, string conversationId); Task CleanHistory(string agentId); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs index 04f3e2e4..3f1d6530 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs @@ -3,11 +3,16 @@ namespace BotSharp.Abstraction.Conversations.Models; public class RoleDialogModel { /// - /// user, system, assistant + /// user, system, assistant, function /// public string Role { get; set; } public string Content { get; set; } + /// + /// Function name if LLM response function call + /// + public string? Name { get; set; } + public RoleDialogModel(string role, string text) { Role = role; diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/IChatCompletion.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/IChatCompletion.cs index 91dea670..8c997ef8 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MLTasks/IChatCompletion.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MLTasks/IChatCompletion.cs @@ -5,5 +5,5 @@ namespace BotSharp.Abstraction.MLTasks; public interface IChatCompletion { string GetChatCompletions(Agent agent, List conversations); - Task GetChatCompletionsStreamingAsync(Agent agent, List conversations); + Task GetChatCompletionsStreamingAsync(Agent agent, List conversations, Func onMessageReceived); } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationController.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationController.cs index debb5db3..7d8ae3f9 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationController.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationController.cs @@ -46,11 +46,13 @@ public class ConversationController : ControllerBase, IApiAdapter { var conv = _services.GetRequiredService(); - var result = await conv.SendMessage(agentId, conversationId, new RoleDialogModel("user", input.Text)); + var response = new MessageResponseModel(); - return new MessageResponseModel + await conv.SendMessage(agentId, conversationId, new RoleDialogModel("user", input.Text), async msg => { - Text = result - }; + response.Text += msg.Content; + }); + + return response; } } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index d07dc31f..d608524b 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -67,20 +67,22 @@ public class ConversationService : IConversationService return record.ToConversation(); } - public async Task SendMessage(string agentId, string conversationId, RoleDialogModel lastDalog) + public async Task SendMessage(string agentId, string conversationId, RoleDialogModel lastDalog, Func onMessageReceived) { _storage.Append(agentId, conversationId, lastDalog); var wholeDialogs = GetDialogHistory(agentId, conversationId); - var response = await SendMessage(agentId, conversationId, wholeDialogs); - - _storage.Append(agentId, conversationId, new RoleDialogModel("assistant", response)); + var response = await SendMessage(agentId, conversationId, wholeDialogs, async msg => + { + await onMessageReceived(msg); + _storage.Append(agentId, conversationId, new RoleDialogModel(msg.Role, msg.Content)); + }); return response; } - public async Task SendMessage(string agentId, string conversationId, List wholeDialogs) + public async Task SendMessage(string agentId, string conversationId, List wholeDialogs, Func onMessageReceived) { var agent = await _services.GetRequiredService().GetAgent(agentId); var converation = await GetConversation(conversationId); @@ -110,15 +112,14 @@ public class ConversationService : IConversationService .BeforeCompletion(); }); - var response = await chatCompletion.GetChatCompletionsStreamingAsync(agent, wholeDialogs); - - // After chat completion hook - hooks.ForEach(async hook => + var result = await chatCompletion.GetChatCompletionsStreamingAsync(agent, wholeDialogs, async msg => { - response = await hook.AfterCompletion(response); + // After chat completion hook + hooks.ForEach(async hook => await hook.AfterCompletion(msg)); + await onMessageReceived(msg); }); - return response; + return result; } public IChatCompletion GetChatCompletion() diff --git a/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/ChatCompletionProvider.cs b/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/ChatCompletionProvider.cs index 35cdc80f..01636aff 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/ChatCompletionProvider.cs +++ b/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/ChatCompletionProvider.cs @@ -19,7 +19,7 @@ public class ChatCompletionProvider : IChatCompletion throw new NotImplementedException(); } - public Task GetChatCompletionsStreamingAsync(Agent agent, List conversations) + public async Task GetChatCompletionsStreamingAsync(Agent agent, List conversations, Func onMessageReceived) { string totalResponse = ""; var content = string.Join("\n", conversations.Select(x => $"{x.Role}: {x.Content.Replace("user:", "")}")).Trim(); @@ -36,6 +36,6 @@ public class ChatCompletionProvider : IChatCompletion totalResponse += response; } - return Task.FromResult(totalResponse.Trim()); + return true; } } diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs index 1de10c9a..cf1c6de7 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs @@ -86,7 +86,7 @@ public class ChatCompletionProvider : IChatCompletion return functions; } - public async Task GetChatCompletionsStreamingAsync(Agent agent, List conversations) + public async Task GetChatCompletionsStreamingAsync(Agent agent, List conversations, Func onMessageReceived) { var client = new OpenAIClient(new Uri(_settings.Endpoint), new AzureKeyCredential(_settings.ApiKey)); var chatCompletionsOptions = PrepareOptions(agent, conversations); @@ -97,16 +97,23 @@ public class ChatCompletionProvider : IChatCompletion string output = ""; await foreach (var choice in streaming.GetChoicesStreaming()) { + if (choice.FinishReason == CompletionsFinishReason.FunctionCall) + { + } + await foreach (var message in choice.GetMessageStreaming()) { if (message.Content == null) continue; Console.Write(message.Content); output += message.Content; + await onMessageReceived(new RoleDialogModel(message.Role.ToString(), message.Content)); } + + output = ""; } - return output.Trim(); + return true; } private ChatCompletionsOptions PrepareOptions(Agent agent, List conversations) diff --git a/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs b/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs index 6e7ad937..8b763392 100644 --- a/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs +++ b/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs @@ -78,22 +78,24 @@ public class ChatbotUiController : ControllerBase, IApiAdapter converation = await conversationService.NewConversation(sess); } - var result = await conversationService.SendMessage(input.AgentId, input.ConversationId, conversations); + var result = await conversationService.SendMessage(input.AgentId, + input.ConversationId, + conversations, + async msg => + await OnChunkReceived(outputStream, msg)); - await OnChunkReceived(outputStream, result); await OnEventCompleted(outputStream); } - private async Task OnChunkReceived(Stream outputStream, string content) + private async Task OnChunkReceived(Stream outputStream, RoleDialogModel message) { - var response = new OpenAiChatOutput { Choices = new List { new OpenAiChoice { - Delta = new RoleDialogModel("assistant", content) + Delta = new RoleDialogModel(message.Role, message.Content) } } }; @@ -105,7 +107,7 @@ public class ChatbotUiController : ControllerBase, IApiAdapter var buffer = Encoding.UTF8.GetBytes($"data:{json}\n"); await outputStream.WriteAsync(buffer, 0, buffer.Length); - await Task.Delay(100); + await Task.Delay(10); buffer = Encoding.UTF8.GetBytes("\n"); await outputStream.WriteAsync(buffer, 0, buffer.Length); From 8c20b48759c894bfc76c4bd403ea320f3568a014 Mon Sep 17 00:00:00 2001 From: hchen Date: Thu, 27 Jul 2023 16:56:57 -0500 Subject: [PATCH 3/5] Function execution status. --- .../ConversationCompletionHookBase.cs | 5 ++ .../IConversationCompletionHook.cs | 1 + .../Conversations/Models/FunctionDef.cs | 5 ++ .../Models/FunctionExecutionStatus.cs | 7 +++ .../FunctionExecutionValidationResult.cs | 21 ++++++++ .../Models/IFunctionExecutionResult.cs | 9 ++++ .../Conversations/Models/RoleDialogModel.cs | 7 ++- .../MLTasks/IChatCompletion.cs | 1 + .../Services/ConversationService.cs | 48 ++++++++++++++----- .../LLamaSharp/ChatCompletionProvider.cs | 5 ++ .../Providers/ChatCompletionProvider.cs | 46 ++++++++++++++++++ 11 files changed, 143 insertions(+), 12 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionExecutionStatus.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionExecutionValidationResult.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IFunctionExecutionResult.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationCompletionHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationCompletionHookBase.cs index d885e7f1..32f22aa0 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationCompletionHookBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationCompletionHookBase.cs @@ -46,6 +46,11 @@ public abstract class ConversationCompletionHookBase : IConversationCompletionHo return Task.CompletedTask; } + public virtual async Task OnFunctionExecution(string name, string args) + { + return new FunctionExecutionValidationResult("true", ""); + } + public virtual Task AfterCompletion(RoleDialogModel message) { return Task.CompletedTask; diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationCompletionHook.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationCompletionHook.cs index ece8d785..766ad824 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationCompletionHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationCompletionHook.cs @@ -18,5 +18,6 @@ public interface IConversationCompletionHook IConversationCompletionHook SetChatCompletion(IChatCompletion chatCompletion); Task BeforeCompletion(); + Task OnFunctionExecution(string name, string args); Task AfterCompletion(RoleDialogModel message); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionDef.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionDef.cs index 5c050b5a..719f581d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionDef.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionDef.cs @@ -7,4 +7,9 @@ public class FunctionDef public string Name { get; set; } public string Description { get; set; } public JsonDocument Parameters { get; set; } + + public override string ToString() + { + return $"{Name}: {Description}"; + } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionExecutionStatus.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionExecutionStatus.cs new file mode 100644 index 00000000..3d8224cd --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionExecutionStatus.cs @@ -0,0 +1,7 @@ +namespace BotSharp.Abstraction.Conversations.Models; + +public enum FunctionExecutionStatus +{ + Success = 1, + Failure = 2 +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionExecutionValidationResult.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionExecutionValidationResult.cs new file mode 100644 index 00000000..81e250e4 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionExecutionValidationResult.cs @@ -0,0 +1,21 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Abstraction.Conversations.Models; + +public class FunctionExecutionValidationResult : IFunctionExecutionResult +{ + private string _validationStatus; + public string _validationMessage; + + public FunctionExecutionValidationResult(string validationStatus, string validationMessage = "") + { + _validationStatus = validationStatus; + _validationMessage = validationMessage; + } + + [JsonPropertyName("validation_status")] + public string ValidationStatus => _validationStatus; + + [JsonPropertyName("validation_message")] + public string ValidationMessage => _validationMessage; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IFunctionExecutionResult.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IFunctionExecutionResult.cs new file mode 100644 index 00000000..636b13ca --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IFunctionExecutionResult.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Abstraction.Conversations.Models; + +public class IFunctionExecutionResult +{ + [JsonPropertyName("execution_status")] + public FunctionExecutionStatus ExecutionStatus { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs index 3f1d6530..b5ef0338 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs @@ -11,7 +11,12 @@ public class RoleDialogModel /// /// Function name if LLM response function call /// - public string? Name { get; set; } + public string? Function { get; set; } + + /// + /// Function execution result + /// + public string? ExecutionResult { get; set; } public RoleDialogModel(string role, string text) { diff --git a/src/Infrastructure/BotSharp.Abstraction/MLTasks/IChatCompletion.cs b/src/Infrastructure/BotSharp.Abstraction/MLTasks/IChatCompletion.cs index 8c997ef8..2e8f6438 100644 --- a/src/Infrastructure/BotSharp.Abstraction/MLTasks/IChatCompletion.cs +++ b/src/Infrastructure/BotSharp.Abstraction/MLTasks/IChatCompletion.cs @@ -5,5 +5,6 @@ namespace BotSharp.Abstraction.MLTasks; public interface IChatCompletion { string GetChatCompletions(Agent agent, List conversations); + Task GetChatCompletionsAsync(Agent agent, List conversations, Func onMessageReceived); Task GetChatCompletionsStreamingAsync(Agent agent, List conversations, Func onMessageReceived); } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index d608524b..513ae48f 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -2,6 +2,7 @@ using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.Conversations.Settings; using BotSharp.Abstraction.Knowledges.Models; using BotSharp.Abstraction.MLTasks; +using System.Text.Json; namespace BotSharp.Core.Conversations.Services; @@ -75,8 +76,17 @@ public class ConversationService : IConversationService var response = await SendMessage(agentId, conversationId, wholeDialogs, async msg => { - await onMessageReceived(msg); - _storage.Append(agentId, conversationId, new RoleDialogModel(msg.Role, msg.Content)); + var content = msg.Content.Replace("\r", " ").Replace("\n", " "); + if (msg.Role == "function") + { + content += $"[{msg.Function}] {content}"; + _storage.Append(agentId, conversationId, new RoleDialogModel(msg.Role, content)); + } + else + { + await onMessageReceived(msg); + _storage.Append(agentId, conversationId, new RoleDialogModel(msg.Role, content)); + } }); return response; @@ -100,23 +110,39 @@ public class ConversationService : IConversationService var chatCompletion = GetChatCompletion(); - // Before chat completion hook var hooks = _services.GetServices().ToList(); - hooks.ForEach(hook => + // Before chat completion hook + foreach (var hook in hooks) { - hook.SetAgent(agent) + await hook.SetAgent(agent) .SetConversation(converation) .SetDialogs(wholeDialogs) .SetChatCompletion(chatCompletion) .BeforeCompletion(); - }); - - var result = await chatCompletion.GetChatCompletionsStreamingAsync(agent, wholeDialogs, async msg => + } + + var result = await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs, async msg => { - // After chat completion hook - hooks.ForEach(async hook => await hook.AfterCompletion(msg)); - await onMessageReceived(msg); + if (msg.Role == "function") + { + // Execute functions + foreach (var hook in hooks) + { + var executionResult = await hook.OnFunctionExecution(msg.Function, msg.Content); + msg.ExecutionResult = JsonSerializer.Serialize(executionResult); + } + } + else + { + // After chat completion hook + foreach (var hook in hooks) + { + await hook.AfterCompletion(msg); + } + + await onMessageReceived(msg); + } }); return result; diff --git a/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/ChatCompletionProvider.cs b/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/ChatCompletionProvider.cs index 01636aff..ea32da04 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/ChatCompletionProvider.cs +++ b/src/Infrastructure/BotSharp.Core/Plugins/LLamaSharp/ChatCompletionProvider.cs @@ -19,6 +19,11 @@ public class ChatCompletionProvider : IChatCompletion throw new NotImplementedException(); } + public Task GetChatCompletionsAsync(Agent agent, List conversations, Func onMessageReceived) + { + throw new NotImplementedException(); + } + public async Task GetChatCompletionsStreamingAsync(Agent agent, List conversations, Func onMessageReceived) { string totalResponse = ""; diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs index cf1c6de7..e0a9fb5c 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs @@ -86,6 +86,41 @@ public class ChatCompletionProvider : IChatCompletion return functions; } + public async Task GetChatCompletionsAsync(Agent agent, List conversations, Func onMessageReceived) + { + var client = new OpenAIClient(new Uri(_settings.Endpoint), new AzureKeyCredential(_settings.ApiKey)); + var chatCompletionsOptions = PrepareOptions(agent, conversations); + + var response = await client.GetChatCompletionsAsync(_settings.DeploymentModel.ChatCompletionModel, chatCompletionsOptions); + var choice = response.Value.Choices[0]; + var message = choice.Message; + + if (choice.FinishReason == CompletionsFinishReason.FunctionCall) + { + if (message.FunctionCall == null || message.FunctionCall.Arguments == null) + { + return false; + } + Console.Write(message.FunctionCall.Name); + Console.Write(message.FunctionCall.Arguments); + var funcContextIn = new RoleDialogModel(ChatRole.Function.ToString(), message.FunctionCall.Arguments) + { + Function = message.FunctionCall.Name + }; + await onMessageReceived(funcContextIn); + + // After function is executed, pass the result to LLM + throw new NotImplementedException(); + } + else + { + Console.Write(message.Content); + await onMessageReceived(new RoleDialogModel(ChatRole.Assistant.ToString(), message.Content)); + } + + return true; + } + public async Task GetChatCompletionsStreamingAsync(Agent agent, List conversations, Func onMessageReceived) { var client = new OpenAIClient(new Uri(_settings.Endpoint), new AzureKeyCredential(_settings.ApiKey)); @@ -99,6 +134,17 @@ public class ChatCompletionProvider : IChatCompletion { if (choice.FinishReason == CompletionsFinishReason.FunctionCall) { + var args = ""; + await foreach (var message in choice.GetMessageStreaming()) + { + if (message.FunctionCall == null || message.FunctionCall.Arguments == null) + continue; + Console.Write(message.FunctionCall.Arguments); + args += message.FunctionCall.Arguments; + + } + await onMessageReceived(new RoleDialogModel(ChatRole.Assistant.ToString(), args)); + continue; } await foreach (var message in choice.GetMessageStreaming()) From 861cbb7fe2d86d19707752dd5e84a4f1d060f151 Mon Sep 17 00:00:00 2001 From: hchen2020 <101423@smsassist.com> Date: Thu, 27 Jul 2023 22:12:12 -0500 Subject: [PATCH 4/5] Print Azure AI settings. --- .../Utilities/StringExtensions.cs | 11 +++++++++++ .../BotSharp.Plugin.AzureOpenAI/AzureOpenAiPlugin.cs | 8 +++++++- .../Settings/DeploymentModelSetting.cs | 5 +++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Utilities/StringExtensions.cs b/src/Infrastructure/BotSharp.Abstraction/Utilities/StringExtensions.cs index 8352bcff..0da63437 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Utilities/StringExtensions.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Utilities/StringExtensions.cs @@ -4,4 +4,15 @@ public static class StringExtensions { public static string IfNullOrEmptyAs(this string str, string defaultValue) => string.IsNullOrEmpty(str) ? defaultValue : str; + + public static string SubstringMax(this string str, int maxLength) + { + if (string.IsNullOrEmpty(str)) + return str; + + if (str.Length > maxLength) + return str.Substring(0, maxLength); + else + return str; + } } diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiPlugin.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiPlugin.cs index dfc3d00b..1232e668 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiPlugin.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/AzureOpenAiPlugin.cs @@ -1,9 +1,11 @@ using BotSharp.Abstraction.MLTasks; using BotSharp.Abstraction.Plugins; +using BotSharp.Abstraction.Utilities; using BotSharp.Plugin.AzureOpenAI.Providers; using BotSharp.Plugin.AzureOpenAI.Settings; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using System; namespace BotSharp.Platform.AzureAi; @@ -13,7 +15,11 @@ public class AzureOpenAiPlugin : IBotSharpPlugin { var settings = new AzureOpenAiSettings(); config.Bind("AzureOpenAi", settings); - services.AddSingleton(x => settings); + services.AddSingleton(x => + { + Console.WriteLine($"Loaded AzureOpenAi settings: {settings.DeploymentModel} ({settings.Endpoint}) {settings.ApiKey.SubstringMax(4)}"); + return settings; + }); services.AddScoped(); services.AddScoped(); diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Settings/DeploymentModelSetting.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Settings/DeploymentModelSetting.cs index 24618190..9eb5f63e 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Settings/DeploymentModelSetting.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Settings/DeploymentModelSetting.cs @@ -4,4 +4,9 @@ public class DeploymentModelSetting { public string? ChatCompletionModel { get; set; } public string? TextCompletionModel { get; set; } + + public override string ToString() + { + return $"ChatCompletion - {ChatCompletionModel}, TextCompletion - {TextCompletionModel}"; + } } From 5340ee278e820cba6bd8da9fed1012899087e3d1 Mon Sep 17 00:00:00 2001 From: hchen2020 <101423@smsassist.com> Date: Thu, 27 Jul 2023 23:27:12 -0500 Subject: [PATCH 5/5] Change OnFunctionExecution return type. --- .../ConversationCompletionHookBase.cs | 4 ++-- .../IConversationCompletionHook.cs | 2 +- .../FunctionExecutionValidationResult.cs | 13 +++++------- .../Models/IFunctionExecutionResult.cs | 9 -------- .../Conversations/Models/RoleDialogModel.cs | 2 +- .../Services/ConversationService.cs | 21 ++++++++++++------- .../Providers/ChatCompletionProvider.cs | 19 ++++++++++------- 7 files changed, 34 insertions(+), 36 deletions(-) delete mode 100644 src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IFunctionExecutionResult.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationCompletionHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationCompletionHookBase.cs index 32f22aa0..edbea27f 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationCompletionHookBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationCompletionHookBase.cs @@ -46,9 +46,9 @@ public abstract class ConversationCompletionHookBase : IConversationCompletionHo return Task.CompletedTask; } - public virtual async Task OnFunctionExecution(string name, string args) + public virtual async Task OnFunctionExecution(string name, string args) { - return new FunctionExecutionValidationResult("true", ""); + return "{}"; } public virtual Task AfterCompletion(RoleDialogModel message) diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationCompletionHook.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationCompletionHook.cs index 766ad824..333b40fc 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationCompletionHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationCompletionHook.cs @@ -18,6 +18,6 @@ public interface IConversationCompletionHook IConversationCompletionHook SetChatCompletion(IChatCompletion chatCompletion); Task BeforeCompletion(); - Task OnFunctionExecution(string name, string args); + Task OnFunctionExecution(string name, string args); Task AfterCompletion(RoleDialogModel message); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionExecutionValidationResult.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionExecutionValidationResult.cs index 81e250e4..d7221828 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionExecutionValidationResult.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/FunctionExecutionValidationResult.cs @@ -2,20 +2,17 @@ using System.Text.Json.Serialization; namespace BotSharp.Abstraction.Conversations.Models; -public class FunctionExecutionValidationResult : IFunctionExecutionResult +public class FunctionExecutionValidationResult { - private string _validationStatus; - public string _validationMessage; - public FunctionExecutionValidationResult(string validationStatus, string validationMessage = "") { - _validationStatus = validationStatus; - _validationMessage = validationMessage; + ValidationStatus = validationStatus; + ValidationMessage = validationMessage; } [JsonPropertyName("validation_status")] - public string ValidationStatus => _validationStatus; + public string ValidationStatus { get; set; } [JsonPropertyName("validation_message")] - public string ValidationMessage => _validationMessage; + public string ValidationMessage { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IFunctionExecutionResult.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IFunctionExecutionResult.cs deleted file mode 100644 index 636b13ca..00000000 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IFunctionExecutionResult.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace BotSharp.Abstraction.Conversations.Models; - -public class IFunctionExecutionResult -{ - [JsonPropertyName("execution_status")] - public FunctionExecutionStatus ExecutionStatus { get; set; } -} diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs index b5ef0338..0556b8ad 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs @@ -11,7 +11,7 @@ public class RoleDialogModel /// /// Function name if LLM response function call /// - public string? Function { get; set; } + public string? FunctionName { get; set; } /// /// Function execution result diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index 513ae48f..067c5624 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -2,6 +2,8 @@ using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.Conversations.Settings; using BotSharp.Abstraction.Knowledges.Models; using BotSharp.Abstraction.MLTasks; +using MongoDB.Bson.IO; +using Newtonsoft.Json; using System.Text.Json; namespace BotSharp.Core.Conversations.Services; @@ -76,16 +78,21 @@ public class ConversationService : IConversationService var response = await SendMessage(agentId, conversationId, wholeDialogs, async msg => { - var content = msg.Content.Replace("\r", " ").Replace("\n", " "); if (msg.Role == "function") { - content += $"[{msg.Function}] {content}"; - _storage.Append(agentId, conversationId, new RoleDialogModel(msg.Role, content)); + var result = msg.ExecutionResult.Replace("\r", " ").Replace("\n", " "); + var content = $"{msg.FunctionName} {result}"; + Console.WriteLine(content); + /*_storage.Append(agentId, conversationId, new RoleDialogModel(msg.Role, content) + { + FunctionName = msg.FunctionName, + });*/ } else { - await onMessageReceived(msg); + var content = msg.Content.Replace("\r", " ").Replace("\n", " "); _storage.Append(agentId, conversationId, new RoleDialogModel(msg.Role, content)); + await onMessageReceived(msg); } }); @@ -129,8 +136,7 @@ public class ConversationService : IConversationService // Execute functions foreach (var hook in hooks) { - var executionResult = await hook.OnFunctionExecution(msg.Function, msg.Content); - msg.ExecutionResult = JsonSerializer.Serialize(executionResult); + msg.ExecutionResult = await hook.OnFunctionExecution(msg.FunctionName, msg.Content); } } else @@ -140,9 +146,8 @@ public class ConversationService : IConversationService { await hook.AfterCompletion(msg); } - - await onMessageReceived(msg); } + await onMessageReceived(msg); }); return result; diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs index e0a9fb5c..94620412 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs @@ -105,19 +105,24 @@ public class ChatCompletionProvider : IChatCompletion Console.Write(message.FunctionCall.Arguments); var funcContextIn = new RoleDialogModel(ChatRole.Function.ToString(), message.FunctionCall.Arguments) { - Function = message.FunctionCall.Name + FunctionName = message.FunctionCall.Name }; + await onMessageReceived(funcContextIn); // After function is executed, pass the result to LLM - throw new NotImplementedException(); - } - else - { - Console.Write(message.Content); - await onMessageReceived(new RoleDialogModel(ChatRole.Assistant.ToString(), message.Content)); + chatCompletionsOptions.Messages.Add(new ChatMessage(ChatRole.Function, funcContextIn.ExecutionResult) + { + Name = funcContextIn.FunctionName + }); + response = client.GetChatCompletions(_settings.DeploymentModel.ChatCompletionModel, chatCompletionsOptions); } + choice = response.Value.Choices[0]; + message = choice.Message; + Console.Write(message.Content); + await onMessageReceived(new RoleDialogModel(ChatRole.Assistant.ToString(), message.Content)); + return true; }