From f6dec6e6e2b2eac9664d3ec1da9b45062cd49092 Mon Sep 17 00:00:00 2001 From: hchen Date: Mon, 14 Aug 2023 17:14:05 -0500 Subject: [PATCH] Redefine IFunctionCallback.Execute. --- .../Conversations/IConversationService.cs | 36 ++++- .../Functions/IFunctionCallback.cs | 4 +- .../Conversations/ConversationController.cs | 13 +- .../Services/ConversationService.cs | 130 ++++++++++-------- .../Services/ConversationStateService.cs | 2 +- .../Services/ConversationStorage.cs | 14 +- src/Infrastructure/BotSharp.Core/Using.cs | 4 +- .../ChatbotUiController.cs | 4 +- 8 files changed, 129 insertions(+), 78 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs index b602f5f92..13647bef5 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Conversations.Models; +using BotSharp.Abstraction.MLTasks; namespace BotSharp.Abstraction.Conversations; @@ -8,8 +9,39 @@ public interface IConversationService Task GetConversation(string id); Task> GetConversations(); Task DeleteConversation(string id); - Task SendMessage(string agentId, string conversationId, RoleDialogModel lastDalog, Func onMessageReceived, Func onFunctionExecuting); - Task SendMessage(string agentId, string conversationId, List wholeDialogs, Func onMessageReceived); + + IChatCompletion GetChatCompletion(); + + /// + /// Send message to LLM + /// + /// + /// + /// + /// + /// This delegate is useful when you want to report progress on UI + /// + Task SendMessage(string agentId, + string conversationId, + RoleDialogModel lastDalog, + Func onMessageReceived, + Func onFunctionExecuting); + + /// + /// Send message to LLM if frontend passed over the dialog history + /// + /// + /// + /// + /// + /// This delegate is useful when you want to report progress on UI + /// + Task SendMessage(string agentId, + string conversationId, + List wholeDialogs, + Func onMessageReceived, + Func onFunctionExecuting); + List GetDialogHistory(string conversationId, int lastCount = 20); Task CleanHistory(string agentId); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Functions/IFunctionCallback.cs b/src/Infrastructure/BotSharp.Abstraction/Functions/IFunctionCallback.cs index 5b9605c8e..707f580d0 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Functions/IFunctionCallback.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Functions/IFunctionCallback.cs @@ -1,7 +1,9 @@ +using BotSharp.Abstraction.Conversations.Models; + namespace BotSharp.Abstraction.Functions; public interface IFunctionCallback { string Name { get; } - Task Execute(string args); + Task Execute(RoleDialogModel message); } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationController.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationController.cs index 4c1090f6f..a576c26c7 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationController.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationController.cs @@ -48,13 +48,12 @@ public async Task SendMessage([FromRoute] string agentId, var response = new MessageResponseModel(); - await conv.SendMessage(agentId, conversationId, new RoleDialogModel("user", input.Text), async msg => - { - response.Text += msg.Content; - }, async fn => - { - - }); + await conv.SendMessage(agentId, conversationId, + new RoleDialogModel("user", input.Text), + async msg => + response.Text += msg.Content, + async fn + => await Task.CompletedTask); return response; } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index d733872bc..68057d35f 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -1,11 +1,7 @@ using BotSharp.Abstraction.Agents.Enums; -using BotSharp.Abstraction.Conversations; using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.Functions; -using BotSharp.Abstraction.Knowledges.Models; using BotSharp.Abstraction.MLTasks; -using Microsoft.Extensions.Logging; -using System.Text.Json; namespace BotSharp.Core.Conversations.Services; @@ -74,7 +70,8 @@ public async Task NewConversation(Conversation sess) 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, Func onFunctionExecuting) { @@ -82,41 +79,17 @@ public async Task SendMessage(string agentId, string conversationId, RoleD var wholeDialogs = GetDialogHistory(conversationId); - var response = await SendMessage(agentId, conversationId, wholeDialogs, async msg => - { - if (msg.Role == "function") - { - // Invoke functions - var functions = _services.GetServices().Where(x => x.Name == msg.FunctionName); - foreach (var fn in functions) - { - await onFunctionExecuting(msg); - - msg.ExecutionResult = await fn.Execute(msg.Content); - - var result = msg.ExecutionResult.Replace("\r", " ").Replace("\n", " "); - var content = $"{result}"; - // Console.WriteLine($"{msg.Role}: {content}"); - _storage.Append(conversationId, new RoleDialogModel(msg.Role, content) - { - FunctionName = msg.FunctionName, - }); - } - } - else - { - var content = msg.Content.Replace("\r", " ").Replace("\n", " "); - // Console.WriteLine($"{msg.Role}: {content}"); - _storage.Append(conversationId, new RoleDialogModel(msg.Role, content)); - - await onMessageReceived(msg); - } - }); + var response = await SendMessage(agentId, conversationId, wholeDialogs, + onMessageReceived: onMessageReceived, + onFunctionExecuting: onFunctionExecuting); return response; } - public async Task SendMessage(string agentId, string conversationId, List wholeDialogs, Func onMessageReceived) + public async Task SendMessage(string agentId, string conversationId, + List wholeDialogs, + Func onMessageReceived, + Func onFunctionExecuting) { var converation = await GetConversation(conversationId); @@ -152,8 +125,6 @@ public async Task SendMessage(string agentId, string conversationId, List< }); }*/ - var chatCompletion = GetChatCompletion(); - var hooks = _services.GetServices().ToList(); // Before chat completion hook @@ -166,48 +137,83 @@ public async Task SendMessage(string agentId, string conversationId, List< await hook.BeforeCompletion(); } + var chatCompletion = GetChatCompletion(); var result = await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs, async msg => { if (msg.Role == "function") { - // Before executing functions - foreach (var hook in hooks) - { - await hook.OnFunctionExecuting(msg); - } // Save states - var jo = JsonSerializer.Deserialize(msg.Content); - if (jo is JsonElement root) - { - foreach (JsonProperty property in root.EnumerateObject()) - { - stateService.SetState(property.Name, property.Value.ToString()); - } - } + SaveStateByArgs(msg.Content); + + // Call functions + await onFunctionExecuting(msg); + await CallFunctions(conversationId, msg); } else { + // Add to dialog history + _storage.Append(conversationId, new RoleDialogModel(msg.Role, msg.Content)); + // After chat completion hook foreach (var hook in hooks) { await hook.AfterCompletion(msg); } - } - await onMessageReceived(msg); - if (msg.Role == AgentRole.Function) - { - // After functions have been executed - foreach (var hook in hooks) - { - await hook.OnFunctionExecuted(msg); - } + await onMessageReceived(msg); } }); return result; } + private void SaveStateByArgs(string args) + { + var stateService = _services.GetRequiredService(); + var jo = JsonSerializer.Deserialize(args); + if (jo is JsonElement root) + { + foreach (JsonProperty property in root.EnumerateObject()) + { + stateService.SetState(property.Name, property.Value.ToString()); + } + } + } + + private async Task CallFunctions(string conversationId, RoleDialogModel msg) + { + var hooks = _services.GetServices().ToList(); + + // Invoke functions + var functions = _services.GetServices() + .Where(x => x.Name == msg.FunctionName) + .ToList(); + + foreach (var fn in functions) + { + // Before executing functions + foreach (var hook in hooks) + { + await hook.OnFunctionExecuting(msg); + } + + // Execute function + await fn.Execute(msg); + + // Add to dialog history + _storage.Append(conversationId, new RoleDialogModel(msg.Role, msg.ExecutionResult) + { + FunctionName = msg.FunctionName, + }); + + // After functions have been executed + foreach (var hook in hooks) + { + await hook.OnFunctionExecuted(msg); + } + } + } + public IChatCompletion GetChatCompletion() { var completions = _services.GetServices(); @@ -222,6 +228,8 @@ public Task CleanHistory(string agentId) public List GetDialogHistory(string conversationId, int lastCount = 20) { var dialogs = _storage.GetDialogs(conversationId); - return dialogs.TakeLast(lastCount).ToList(); + return dialogs + .Where(x => x.CreatedAt > DateTime.UtcNow.AddHours(-8)) + .TakeLast(lastCount).ToList(); } } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs index b11ed9d24..e37c6999f 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs @@ -34,7 +34,7 @@ public void SetState(string name, string value) { var currentValue = value; _state[name] = currentValue; - _logger.LogInformation($"Set state: {name} - {value}"); + _logger.LogInformation($"Set state: {name} = {value}"); foreach (var hook in hooks) { hook.OnStateChanged(name, preValue, currentValue).Wait(); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs index df7f37037..ca98268c7 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStorage.cs @@ -5,11 +5,9 @@ namespace BotSharp.Core.Conversations.Services; public class ConversationStorage : IConversationStorage { - private readonly IAgentService _agent; private readonly MyDatabaseSettings _dbSettings; - public ConversationStorage(IAgentService agent, MyDatabaseSettings dbSettings) + public ConversationStorage(MyDatabaseSettings dbSettings) { - _agent = agent; _dbSettings = dbSettings; } @@ -18,7 +16,15 @@ public void Append(string conversationId, RoleDialogModel dialog) var conversationFile = GetStorageFile(conversationId); var sb = new StringBuilder(); sb.AppendLine($"{dialog.Role}|{dialog.CreatedAt}|{dialog.FunctionName}"); - sb.AppendLine($" - {dialog.Content}"); + + var content = dialog.Content.Trim().Replace("\r", " ").Replace("\n", " "); + if (string.IsNullOrEmpty(content)) + { + return; + } + + sb.AppendLine($" - {content}"); + var conversation = sb.ToString(); File.AppendAllText(conversationFile, conversation); } diff --git a/src/Infrastructure/BotSharp.Core/Using.cs b/src/Infrastructure/BotSharp.Core/Using.cs index 45712028e..eae0a5a5e 100644 --- a/src/Infrastructure/BotSharp.Core/Using.cs +++ b/src/Infrastructure/BotSharp.Core/Using.cs @@ -3,9 +3,11 @@ global using System.Text; global using System.Threading.Tasks; global using System.Linq; +global using System.Text.Json; global using Microsoft.Extensions.DependencyInjection; -global using BotSharp.Abstraction.Plugins; +global using Microsoft.Extensions.Logging; global using EntityFrameworkCore.BootKit; +global using BotSharp.Abstraction.Plugins; global using BotSharp.Abstraction.Agents; global using BotSharp.Abstraction.Conversations; global using BotSharp.Abstraction.Knowledges; diff --git a/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs b/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs index 4b728a3ac..8d6b9de17 100644 --- a/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs +++ b/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs @@ -70,7 +70,9 @@ public async Task SendMessage([FromBody] OpenAiMessageInput input) input.ConversationId, conversations, async msg => - await OnChunkReceived(outputStream, msg)); + await OnChunkReceived(outputStream, msg), + async fn + => await Task.CompletedTask); await OnEventCompleted(outputStream); }