From aedaaea2296723b3bf99b1b42bcd91f911c02cd8 Mon Sep 17 00:00:00 2001 From: Wenbo Cao <104199@smsassist.com> Date: Mon, 4 Sep 2023 17:14:39 -0500 Subject: [PATCH 1/4] re-format the code --- .../Providers/IntentClassifier.cs | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs index 440bebf0..5956f0c4 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs @@ -35,7 +35,6 @@ public class IntentClassifier private ClassifierSetting _settings; private string[] _labels; - public string[] Labels => GetLabels(); private int _numLabels @@ -67,7 +66,7 @@ public class IntentClassifier } var vector = _services.GetServices() - .FirstOrDefault(x => x.GetType().FullName.EndsWith(_knowledgeBaseSettings.TextEmbedding)); + .FirstOrDefault(x => x.GetType().FullName.EndsWith(_knowledgeBaseSettings.TextEmbedding)); var layers = new List { @@ -89,10 +88,9 @@ public class IntentClassifier { _model.compile(optimizer: keras.optimizers.Adam(trainingParams.LearningRate), loss: keras.losses.SparseCategoricalCrossentropy(), - metrics: new[] { "accuracy" } - ); + metrics: new[] { "accuracy" }); - CallbackParams callback_parameters = new CallbackParams + var callback_parameters = new CallbackParams { Model = _model, Epochs = trainingParams.Epochs, @@ -100,9 +98,12 @@ public class IntentClassifier Steps = 10 }; - ICallback earlyStop = new EarlyStopping(callback_parameters, "accuracy"); + var earlyStop = new EarlyStopping(callback_parameters, "accuracy"); - var callbacks = new List() { earlyStop }; + var callbacks = new List() + { + earlyStop + }; var weights = LoadWeights(trainingParams.Inference); @@ -120,7 +121,9 @@ public class IntentClassifier public string LoadWeights(bool inference = true) { - var agentService = _services.CreateScope().ServiceProvider.GetRequiredService(); + var agentService = _services.CreateScope() + .ServiceProvider + .GetRequiredService(); var weightsFile = Path.Combine(agentService.GetDataDir(), _settings.MODEL_DIR, $"intent-classifier.h5"); @@ -129,13 +132,13 @@ public class IntentClassifier _model.load_weights(weightsFile); _isModelReady = true; Console.WriteLine($"Successfully load the weights!"); - } else { var logInfo = inference ? "No available weights." : "Will implement model training process and write trained weights into local"; Console.WriteLine(logInfo); } + return weightsFile; } @@ -161,10 +164,9 @@ public class IntentClassifier throw new Exception($"No training data found! Please put training data in this path: {rootDirectory}"); } + // Do embedding and store results var vector = _services.GetRequiredService(); - var vectorList = new List(); - var labelList = new List(); foreach (var filePath in GetFiles()) @@ -195,7 +197,11 @@ public class IntentClassifier { var agentService = _services.CreateScope().ServiceProvider.GetRequiredService(); string rootDirectory = Path.Combine(agentService.GetDataDir(), _settings.RAW_DATA_DIR); - return Directory.GetFiles(rootDirectory).Where(x => Path.GetFileNameWithoutExtension(x).StartsWith(prefix)).OrderBy(x => x).ToArray(); + return Directory.GetFiles(rootDirectory) + .Where(x => Path.GetFileNameWithoutExtension(x) + .StartsWith(prefix)) + .OrderBy(x => x) + .ToArray(); } public string[] GetLabels() From 310576045f8c65108515000187f61035cf0d651c Mon Sep 17 00:00:00 2001 From: Wenbo Cao <104199@smsassist.com> Date: Mon, 4 Sep 2023 17:36:32 -0500 Subject: [PATCH 2/4] re-format code style --- .../Providers/IntentClassifier.cs | 55 +++++++++++++------ 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs index 5956f0c4..ccf89423 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs @@ -33,10 +33,8 @@ public class IntentClassifier private bool _isModelReady; public bool isModelReady => _isModelReady; private ClassifierSetting _settings; - private string[] _labels; public string[] Labels => GetLabels(); - private int _numLabels { get @@ -111,7 +109,6 @@ public class IntentClassifier batch_size: trainingParams.BatchSize, epochs: trainingParams.Epochs, callbacks: callbacks, - // validation_split: 0.1f, shuffle: true); _model.save_weights(weights); @@ -155,9 +152,18 @@ public class IntentClassifier public (NDArray, NDArray) PrepareLoadData() { - var agentService = _services.CreateScope().ServiceProvider.GetRequiredService(); - string rootDirectory = Path.Combine(agentService.GetDataDir(), _settings.RAW_DATA_DIR); - string saveLabelDirectory = Path.Combine(agentService.GetDataDir(), _settings.MODEL_DIR, _settings.LABEL_FILE_NAME); + var agentService = _services.CreateScope() + .ServiceProvider + .GetRequiredService(); + string rootDirectory = Path.Combine( + agentService.GetDataDir(), + _settings.RAW_DATA_DIR + ); + string saveLabelDirectory = Path.Combine( + agentService.GetDataDir(), + _settings.MODEL_DIR, + _settings.LABEL_FILE_NAME + ); if (!Directory.Exists(rootDirectory)) { @@ -171,7 +177,10 @@ public class IntentClassifier foreach (var filePath in GetFiles()) { - var texts = File.ReadAllLines(filePath, Encoding.UTF8).Select(x => TextClean(x)).ToList(); + var texts = File.ReadAllLines(filePath, Encoding.UTF8) + .Select(x => TextClean(x)) + .ToList(); + vectorList.AddRange(vector.GetVectors(texts)); string fileName = Path.GetFileNameWithoutExtension(filePath); labelList.AddRange(Enumerable.Repeat(fileName, texts.Count).ToList()); @@ -187,16 +196,19 @@ public class IntentClassifier for (int i = 0; i < vectorList.Count; i++) { x[i] = vectorList[i]; - // y[i] = (float)uniqueLabelList.IndexOf(labelList[i]); y[i] = (float)Array.IndexOf(uniqueLabelList, labelList[i]); } + return (x, y); } public string[] GetFiles(string prefix = "intent") { - var agentService = _services.CreateScope().ServiceProvider.GetRequiredService(); + var agentService = _services.CreateScope() + .ServiceProvider + .GetRequiredService(); string rootDirectory = Path.Combine(agentService.GetDataDir(), _settings.RAW_DATA_DIR); + return Directory.GetFiles(rootDirectory) .Where(x => Path.GetFileNameWithoutExtension(x) .StartsWith(prefix)) @@ -208,8 +220,15 @@ public class IntentClassifier { if (_labels == null) { - var agentService = _services.CreateScope().ServiceProvider.GetRequiredService(); - string rootDirectory = Path.Combine(agentService.GetDataDir(), _settings.MODEL_DIR, _settings.LABEL_FILE_NAME); + var agentService = _services.CreateScope() + .ServiceProvider + .GetRequiredService(); + string rootDirectory = Path.Combine( + agentService.GetDataDir(), + _settings.MODEL_DIR, + _settings.LABEL_FILE_NAME + ); + var labelText = File.ReadAllLines(rootDirectory); _labels = labelText.OrderBy(x => x).ToArray(); } @@ -223,9 +242,11 @@ public class IntentClassifier // Remove digits // To lowercase var processedText = Regex.Replace(text, "[AB0-9]", " "); - processedText = string.Join("", processedText.Select(c => char.IsPunctuation(c) ? ' ' : c).ToList()); - processedText = processedText.Replace(" ", " ").ToLower(); - return processedText; + var replacedTextList = processedText.Select(c => char.IsPunctuation(c) ? ' ' : c).ToList(); + + return string.Join("", replacedTextList) + .Replace(" ", " ") + .ToLower(); } public string Predict(NDArray vector, float confidenceScore = 0.9f) @@ -235,8 +256,8 @@ public class IntentClassifier InitClassifer(); } + // Generate and post-process prediction var prob = _model.predict(vector).numpy(); - var probLabel = tf.arg_max(prob, -1).numpy().ToArray(); prob = np.squeeze(prob, axis: 0); @@ -245,9 +266,9 @@ public class IntentClassifier return string.Empty; } - var prediction = _labels[probLabel[0]]; + var labelIndex = probLabel[0]; - return prediction; + return _labels[labelIndex]; } public void InitClassifer(bool inference = true) { From f7f9e96f0e94f0165561b223980f197b2c062209 Mon Sep 17 00:00:00 2001 From: Wenbo Cao <104199@smsassist.com> Date: Mon, 4 Sep 2023 18:16:13 -0500 Subject: [PATCH 3/4] remove unused libraries --- .../Providers/IntentClassifier.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs index ccf89423..6e685905 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs @@ -2,7 +2,6 @@ using System; using System.IO; using System.Text; using System.Collections.Generic; -using Tensorflow; using static Tensorflow.KerasApi; using Tensorflow.Keras.Engine; using Tensorflow.NumPy; @@ -16,11 +15,7 @@ using BotSharp.Plugin.RoutingSpeeder.Providers.Models; using Microsoft.Extensions.DependencyInjection; using System.Linq; using Tensorflow.Keras; -using System.Numerics; -using Newtonsoft.Json; -using Tensorflow.Keras.Layers; using BotSharp.Abstraction.Agents; -using BotSharp.Abstraction.Knowledges; namespace BotSharp.Plugin.RoutingSpeeder.Providers; @@ -157,13 +152,11 @@ public class IntentClassifier .GetRequiredService(); string rootDirectory = Path.Combine( agentService.GetDataDir(), - _settings.RAW_DATA_DIR - ); + _settings.RAW_DATA_DIR); string saveLabelDirectory = Path.Combine( agentService.GetDataDir(), _settings.MODEL_DIR, - _settings.LABEL_FILE_NAME - ); + _settings.LABEL_FILE_NAME); if (!Directory.Exists(rootDirectory)) { From 05b8db8547eaf69fa801325c52ed3fa2531373b7 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Tue, 5 Sep 2023 22:19:36 -0500 Subject: [PATCH 4/4] Support states from inbound api. --- docs/architecture/assets/diagram.drawio | 43 +++++---- docs/architecture/assets/overview.drawio | 73 +++++++++++++++ .../assets/routing-reasoning.drawio | 92 +++++++++++++++++++ docs/architecture/assets/routing.drawio | 59 ++++++++++++ .../Conversations/IConversationService.cs | 5 +- .../IConversationStateService.cs | 6 +- .../Conversations/IConversationStorage.cs | 2 - .../Routing/Models/RetrievalArgs.cs | 4 +- .../Agents/Services/AgentService.LoadAgent.cs | 6 +- .../ConversationService.CallFunctions.cs | 3 +- ...vice.GetChatCompletionsAsyncRecursively.cs | 5 +- .../ConversationService.SendMessage.cs | 54 +++++------ .../Services/ConversationService.cs | 22 ++++- .../Services/ConversationStateService.cs | 47 ++++------ .../BotSharp.Core/Routing/Simulator.cs | 4 +- .../Controllers/ConversationController.cs | 6 +- .../Conversations/NewMessageModel.cs | 5 + .../ChatbotUiController.cs | 14 +-- .../ViewModels/OpenAiMessageInput.cs | 4 + .../Controllers/WebhookController.cs | 7 +- .../WeChatBackgroundService.cs | 7 +- .../Functions/GetBakingTimeFn.cs | 14 +++ .../Functions/GetPizzaPricesFn.cs | 1 - .../{MakeOrderFn.cs => PlaceOrderFn.cs} | 4 +- .../Hooks/PizzaBotAgentHook.cs | 2 +- .../PizzaBotPlugin.cs | 3 +- 26 files changed, 372 insertions(+), 120 deletions(-) create mode 100644 docs/architecture/assets/overview.drawio create mode 100644 docs/architecture/assets/routing-reasoning.drawio create mode 100644 docs/architecture/assets/routing.drawio create mode 100644 tests/BotSharp.Plugin.PizzaBot/Functions/GetBakingTimeFn.cs rename tests/BotSharp.Plugin.PizzaBot/Functions/{MakeOrderFn.cs => PlaceOrderFn.cs} (74%) diff --git a/docs/architecture/assets/diagram.drawio b/docs/architecture/assets/diagram.drawio index 1170e476..291cd8f2 100644 --- a/docs/architecture/assets/diagram.drawio +++ b/docs/architecture/assets/diagram.drawio @@ -1,69 +1,72 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + diff --git a/docs/architecture/assets/overview.drawio b/docs/architecture/assets/overview.drawio new file mode 100644 index 00000000..b6dacd1a --- /dev/null +++ b/docs/architecture/assets/overview.drawio @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/architecture/assets/routing-reasoning.drawio b/docs/architecture/assets/routing-reasoning.drawio new file mode 100644 index 00000000..cc9b5643 --- /dev/null +++ b/docs/architecture/assets/routing-reasoning.drawio @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/architecture/assets/routing.drawio b/docs/architecture/assets/routing.drawio new file mode 100644 index 00000000..7df280ab --- /dev/null +++ b/docs/architecture/assets/routing.drawio @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs index 48107244..7ddf8ad8 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs @@ -4,7 +4,9 @@ namespace BotSharp.Abstraction.Conversations; public interface IConversationService { + IConversationStateService States { get; } Task NewConversation(Conversation conversation); + void SetConversationId(string conversationId, string channel); Task GetConversation(string id); Task> GetConversations(); Task DeleteConversation(string id); @@ -22,13 +24,12 @@ public interface IConversationService /// This delegate is useful when you want to report progress on UI /// Task SendMessage(string agentId, - string conversationId, RoleDialogModel lastDalog, Func onMessageReceived, Func onFunctionExecuting, Func onFunctionExecuted); - List GetDialogHistory(string conversationId, int lastCount = 20); + List GetDialogHistory(int lastCount = 20); Task CleanHistory(string agentId); Task CallFunctions(RoleDialogModel msg); diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs index 420f1191..72dc54f2 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStateService.cs @@ -1,5 +1,3 @@ -using BotSharp.Abstraction.Conversations.Models; - namespace BotSharp.Abstraction.Conversations; /// @@ -7,9 +5,9 @@ namespace BotSharp.Abstraction.Conversations; /// public interface IConversationStateService { - void SetConversation(string conversationId); - ConversationState Load(); + ConversationState Load(string conversationId); string GetState(string name); + ConversationState GetStates(); void SetState(string name, string value); void CleanState(); void Save(); diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStorage.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStorage.cs index ed5044b0..1805bbe0 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStorage.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationStorage.cs @@ -1,5 +1,3 @@ -using BotSharp.Abstraction.Conversations.Models; - namespace BotSharp.Abstraction.Conversations; public interface IConversationStorage diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs index e0b3f9a1..5786b73f 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RetrievalArgs.cs @@ -11,8 +11,8 @@ public class RetrievalArgs : RoutingArgs [JsonPropertyName("answer")] public string Answer { get; set; } - [JsonPropertyName("reason")] - public string Reason { get; set; } + [JsonPropertyName("response")] + public string Response { get; set; } [JsonPropertyName("args")] public JsonDocument Arguments { get; set; } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs index e2f13567..7ea90864 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs @@ -1,6 +1,5 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Templating; -using BotSharp.Core.Templating; namespace BotSharp.Core.Agents.Services; @@ -56,9 +55,8 @@ public partial class AgentService private void PopulateState(Dictionary dict) { - var stateService = _services.GetRequiredService(); - var state = stateService.Load(); - foreach (var t in state) + var conv = _services.GetRequiredService(); + foreach (var t in conv.States.GetStates()) { dict[t.Key] = t.Value; } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.CallFunctions.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.CallFunctions.cs index 01b3444b..5c3f941a 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.CallFunctions.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.CallFunctions.cs @@ -7,7 +7,8 @@ public partial class ConversationService public async Task CallFunctions(RoleDialogModel msg) { var hooks = _services.GetServices() - .OrderBy(x => x.Priority).ToList(); + .OrderBy(x => x.Priority) + .ToList(); // Invoke functions var functions = _services.GetServices() diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs index 21e8950c..c1780c60 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs @@ -10,7 +10,6 @@ public partial class ConversationService int currentRecursiveDepth = 0; private async Task GetChatCompletionsAsyncRecursively(IChatCompletion chatCompletion, - string conversationId, Agent agent, List wholeDialogs, Func onMessageReceived, @@ -43,7 +42,7 @@ public partial class ConversationService await HandleAssistantMessage(msg, onMessageReceived); // Add to dialog history - _storage.Append(conversationId, agent.Id, msg); + _storage.Append(_conversationId, agent.Id, msg); }, async fn => { var preAgentId = agent.Id; @@ -72,7 +71,6 @@ public partial class ConversationService wholeDialogs.Add(fn); await GetChatCompletionsAsyncRecursively(chatCompletion, - conversationId, agent, wholeDialogs, onMessageReceived, @@ -103,7 +101,6 @@ public partial class ConversationService wholeDialogs.Add(fn); await GetChatCompletionsAsyncRecursively(chatCompletion, - conversationId, agent, wholeDialogs, onMessageReceived, diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index d44eb56a..d9ce0967 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -1,6 +1,5 @@ using BotSharp.Abstraction.Agents.Enums; using BotSharp.Abstraction.Agents.Models; -using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.MLTasks; using BotSharp.Abstraction.Routing.Settings; using BotSharp.Core.Routing; @@ -9,30 +8,13 @@ namespace BotSharp.Core.Conversations.Services; public partial class ConversationService { - public async Task SendMessage(string agentId, string conversationId, + public async Task SendMessage(string agentId, RoleDialogModel lastDialog, Func onMessageReceived, Func onFunctionExecuting, Func onFunctionExecuted) { - var converation = await GetConversation(conversationId); - - // Create conversation if this conversation not exists - if (converation == null) - { - var sess = new Conversation - { - Id = conversationId, - AgentId = agentId - }; - converation = await NewConversation(sess); - } - - // conversation state - var stateService = _services.GetRequiredService(); - stateService.SetConversation(conversationId); - stateService.Load(); - stateService.SetState("channel", lastDialog.Channel); + var conversation = await GetConversationRecord(agentId); var agentService = _services.GetRequiredService(); Agent agent = await agentService.LoadAgent(agentId); @@ -41,10 +23,10 @@ public partial class ConversationService lastDialog.CurrentAgentId = agent.Id; - var wholeDialogs = GetDialogHistory(conversationId); + var wholeDialogs = GetDialogHistory(); wholeDialogs.Add(lastDialog); - _storage.Append(conversationId, agent.Id, lastDialog); + _storage.Append(_conversationId, agent.Id, lastDialog); // Get relevant domain knowledge /*if (_settings.EnableKnowledgeBase) @@ -63,7 +45,7 @@ public partial class ConversationService foreach (var hook in hooks) { hook.SetAgent(agent) - .SetConversation(converation); + .SetConversation(conversation); await hook.OnDialogsLoaded(wholeDialogs); await hook.BeforeCompletion(lastDialog); @@ -73,7 +55,7 @@ public partial class ConversationService { var response = new RoleDialogModel(AgentRole.Assistant, lastDialog.Content); await onMessageReceived(response); - _storage.Append(conversationId, agent.Id, response); + _storage.Append(_conversationId, agent.Id, response); return true; } } @@ -114,13 +96,15 @@ public partial class ConversationService simulator.Dialogs.ForEach(x => { wholeDialogs.Add(x); - _storage.Append(conversationId, agent.Id, x); + if (x.Content != null) + { + _storage.Append(_conversationId, agent.Id, x); + } }); } var chatCompletion = GetChatCompletion(); var result = await GetChatCompletionsAsyncRecursively(chatCompletion, - conversationId, agent, wholeDialogs, onMessageReceived, @@ -130,6 +114,24 @@ public partial class ConversationService return result; } + private async Task GetConversationRecord(string agentId) + { + var converation = await GetConversation(_conversationId); + + // Create conversation if this conversation not exists + if (converation == null) + { + var sess = new Conversation + { + Id = _conversationId, + AgentId = agentId + }; + converation = await NewConversation(sess); + } + + return converation; + } + private void SaveStateByArgs(string args) { var stateService = _services.GetRequiredService(); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs index 299ad8a6..6f409ce8 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.cs @@ -1,5 +1,3 @@ -using BotSharp.Abstraction.Conversations.Models; - namespace BotSharp.Core.Conversations.Services; public partial class ConversationService : IConversationService @@ -9,17 +7,23 @@ public partial class ConversationService : IConversationService private readonly IUserIdentity _user; private readonly ConversationSetting _settings; private readonly IConversationStorage _storage; + private readonly IConversationStateService _state; + private string _conversationId; + + public IConversationStateService States => _state; public ConversationService(IServiceProvider services, IUserIdentity user, ConversationSetting settings, IConversationStorage storage, + IConversationStateService state, ILogger logger) { _services = services; _user = user; _settings = settings; _storage = storage; + _state = state; _logger = logger; } @@ -72,11 +76,19 @@ public partial class ConversationService : IConversationService throw new NotImplementedException(); } - public List GetDialogHistory(string conversationId, int lastCount = 20) + public List GetDialogHistory(int lastCount = 20) { - var dialogs = _storage.GetDialogs(conversationId); + var dialogs = _storage.GetDialogs(_conversationId); return dialogs .Where(x => x.CreatedAt > DateTime.UtcNow.AddHours(-8)) - .TakeLast(lastCount).ToList(); + .TakeLast(lastCount) + .ToList(); + } + + public void SetConversationId(string conversationId, string channel) + { + _conversationId = conversationId; + _state.Load(_conversationId); + _state.SetState("channel", channel); } } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs index 98f2bedf..65f37fa7 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs @@ -1,4 +1,3 @@ -using BotSharp.Abstraction.Conversations.Models; using System.IO; namespace BotSharp.Core.Conversations.Services; @@ -10,7 +9,7 @@ public class ConversationStateService : IConversationStateService, IDisposable { private readonly ILogger _logger; private readonly IServiceProvider _services; - private ConversationState _state; + private ConversationState _states; private MyDatabaseSettings _dbSettings; private string _conversationId; private string _file; @@ -22,16 +21,17 @@ public class ConversationStateService : IConversationStateService, IDisposable _logger = logger; _services = services; _dbSettings = dbSettings; + _states = new ConversationState(); } public void SetState(string name, string value) { var hooks = _services.GetServices(); - string preValue = _state.ContainsKey(name) ? _state[name] : ""; - if (!_state.ContainsKey(name) || _state[name] != value) + string preValue = _states.ContainsKey(name) ? _states[name] : ""; + if (!_states.ContainsKey(name) || _states[name] != value) { var currentValue = value; - _state[name] = currentValue; + _states[name] = currentValue; _logger.LogInformation($"Set state: {name} = {value}"); foreach (var hook in hooks) { @@ -40,24 +40,9 @@ public class ConversationStateService : IConversationStateService, IDisposable } } - public void SetConversation(string conversationId) + public ConversationState Load(string conversationId) { _conversationId = conversationId; - } - - public ConversationState Load() - { - if (_state != null) - { - return _state; - } - - _state = new ConversationState(); - - if (_conversationId == null) - { - return _state; - } _file = GetStorageFile(_conversationId); @@ -66,18 +51,19 @@ public class ConversationStateService : IConversationStateService, IDisposable var dict = File.ReadAllLines(_file); foreach (var line in dict) { - _state[line.Split('=')[0]] = line.Split('=')[1]; + _states[line.Split('=')[0]] = line.Split('=')[1]; + _logger.LogInformation($"Loaded state: {line}"); } } - _logger.LogInformation($"Loaded state {_conversationId}"); + _logger.LogInformation($"Loaded conversation states: {_conversationId}"); var hooks = _services.GetServices(); foreach (var hook in hooks) { - hook.OnStateLoaded(_state).Wait(); + hook.OnStateLoaded(_states).Wait(); } - return _state; + return _states; } public void Save() @@ -89,7 +75,7 @@ public class ConversationStateService : IConversationStateService, IDisposable var states = new List(); - foreach (var dic in _state) + foreach (var dic in _states) { states.Add($"{dic.Key}={dic.Value}"); } @@ -112,13 +98,16 @@ public class ConversationStateService : IConversationStateService, IDisposable return Path.Combine(dir, "state.dict"); } + public ConversationState GetStates() + => _states; + public string GetState(string name) { - if (!_state.ContainsKey(name)) + if (!_states.ContainsKey(name)) { - _state[name] = ""; + _states[name] = ""; } - return _state[name]; + return _states[name]; } public void Dispose() diff --git a/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs b/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs index 6e88bc7d..3bbbf8ff 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Simulator.cs @@ -45,8 +45,8 @@ public class Simulator } else if (args.Function == "interrupt_task_execution") { - response.Content = args.Parameters.Reason; - response.ExecutionResult = args.Parameters.Reason; + response.Content = args.Parameters.Response; + response.ExecutionResult = args.Parameters.Response; } else if (args.Function == "response_to_user") { diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 364416cd..5ad55c78 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -44,11 +44,13 @@ public class ConversationController : ControllerBase, IApiAdapter [FromQuery] string? channel = "openapi") { var conv = _services.GetRequiredService(); - + conv.SetConversationId(conversationId, channel); + input.States.ForEach(x => conv.States.SetState(x.Split('=')[0], x.Split('=')[1])); + var response = new MessageResponseModel(); var stackMsg = new List(); - await conv.SendMessage(agentId, conversationId, + await conv.SendMessage(agentId, new RoleDialogModel("user", input.Text) { Channel = channel diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/NewMessageModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/NewMessageModel.cs index 45716e21..0f66d7be 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/NewMessageModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Conversations/NewMessageModel.cs @@ -3,4 +3,9 @@ namespace BotSharp.OpenAPI.ViewModels.Conversations; public class NewMessageModel { public string Text { get; set; } + + /// + /// Conversation states from input + /// + public List States { get; set; } = new List(); } diff --git a/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs b/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs index 89bd7a95..cee7428d 100644 --- a/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs +++ b/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs @@ -16,7 +16,6 @@ using Microsoft.Extensions.DependencyInjection; using BotSharp.Abstraction.Conversations; using BotSharp.Abstraction.Conversations.Models; using Microsoft.AspNetCore.Authorization; -using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Agents.Enums; namespace BotSharp.Plugin.ChatbotUI.Controllers; @@ -62,18 +61,19 @@ public class ChatbotUiController : ControllerBase, IApiAdapter Response.Headers.Add(HeaderNames.Connection, "keep-alive"); var outputStream = Response.Body; + var channel = "webchat"; var conversation = input.Messages .Where(x => x.Role == AgentRole.User) .Select(x => new RoleDialogModel(x.Role, x.Content) { - Channel = "webchat" - }) - .Last(); + Channel = channel + }).Last(); - var conversationService = _services.GetRequiredService(); + var conv = _services.GetRequiredService(); + conv.SetConversationId(input.ConversationId, channel); + input.States.ForEach(x => conv.States.SetState(x.Split('=')[0], x.Split('=')[1])); - var result = await conversationService.SendMessage(input.AgentId, - input.ConversationId, + var result = await conv.SendMessage(input.AgentId, conversation, async msg => await OnChunkReceived(outputStream, msg), diff --git a/src/Plugins/BotSharp.Plugin.ChatbotUI/ViewModels/OpenAiMessageInput.cs b/src/Plugins/BotSharp.Plugin.ChatbotUI/ViewModels/OpenAiMessageInput.cs index 419ccc6c..76771738 100644 --- a/src/Plugins/BotSharp.Plugin.ChatbotUI/ViewModels/OpenAiMessageInput.cs +++ b/src/Plugins/BotSharp.Plugin.ChatbotUI/ViewModels/OpenAiMessageInput.cs @@ -14,6 +14,10 @@ public class OpenAiMessageInput public int MaxTokens { get; set; } = 4000; public bool Stream { get; set; } = true; public float Temperature { get; set; } = 0.9f; + /// + /// Conversation states from input + /// + public List States { get; set; } = new List(); public override string ToString() { diff --git a/src/Plugins/BotSharp.Plugin.MetaMessenger/Controllers/WebhookController.cs b/src/Plugins/BotSharp.Plugin.MetaMessenger/Controllers/WebhookController.cs index a83e424a..6440ac9f 100644 --- a/src/Plugins/BotSharp.Plugin.MetaMessenger/Controllers/WebhookController.cs +++ b/src/Plugins/BotSharp.Plugin.MetaMessenger/Controllers/WebhookController.cs @@ -67,8 +67,6 @@ public class WebhookController : ControllerBase // received message if (req.Entry[0].Messaging[0].Message != null) { - var conv = _services.GetRequiredService(); - var reply = new QuickReplyMessage(); var senderId = req.Entry[0].Messaging[0].Sender.Id; var input = req.Entry[0].Messaging[0].Message.Text; @@ -99,7 +97,10 @@ public class WebhookController : ControllerBase }); // Go to LLM - var result = await conv.SendMessage(agentId, senderId, new RoleDialogModel("user", input) + var conv = _services.GetRequiredService(); + conv.SetConversationId(senderId, "messenger"); + + var result = await conv.SendMessage(agentId, new RoleDialogModel("user", input) { Channel = "messenger" }, async msg => diff --git a/src/Plugins/BotSharp.Plugin.WeChat/WeChatBackgroundService.cs b/src/Plugins/BotSharp.Plugin.WeChat/WeChatBackgroundService.cs index ac0dab42..529b2142 100644 --- a/src/Plugins/BotSharp.Plugin.WeChat/WeChatBackgroundService.cs +++ b/src/Plugins/BotSharp.Plugin.WeChat/WeChatBackgroundService.cs @@ -48,7 +48,10 @@ namespace BotSharp.Plugin.WeChat var conversationService = scoped.GetRequiredService(); - var latestConversationId = (await conversationService.GetConversations()).OrderByDescending(_ => _.CreatedTime).FirstOrDefault()?.Id; + var latestConversationId = (await conversationService.GetConversations()) + .OrderByDescending(_ => _.CreatedTime) + .FirstOrDefault()?.Id; + conversationService.SetConversationId(latestConversationId, "wechat"); latestConversationId ??= (await conversationService.NewConversation(new Conversation() { @@ -56,7 +59,7 @@ namespace BotSharp.Plugin.WeChat AgentId = AgentId }))?.Id; - var result = await conversationService.SendMessage(AgentId, latestConversationId, new RoleDialogModel("user", message), async msg => + var result = await conversationService.SendMessage(AgentId, new RoleDialogModel("user", message), async msg => { await ReplyTextMessageAsync(openid, msg.Content); }, async functionExecuting => diff --git a/tests/BotSharp.Plugin.PizzaBot/Functions/GetBakingTimeFn.cs b/tests/BotSharp.Plugin.PizzaBot/Functions/GetBakingTimeFn.cs new file mode 100644 index 00000000..1d72dfbb --- /dev/null +++ b/tests/BotSharp.Plugin.PizzaBot/Functions/GetBakingTimeFn.cs @@ -0,0 +1,14 @@ +using BotSharp.Abstraction.Conversations.Models; + +namespace BotSharp.Plugin.PizzaBot.Functions; + +public class GetBakingTimeFn : IFunctionCallback +{ + public string Name => "get_cooking_remaing_time"; + + public async Task Execute(RoleDialogModel message) + { + message.ExecutionResult = "15 minutes remaining"; + return true; + } +} diff --git a/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaPricesFn.cs b/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaPricesFn.cs index 625e9ec4..4852d4dd 100644 --- a/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaPricesFn.cs +++ b/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaPricesFn.cs @@ -1,5 +1,4 @@ using BotSharp.Abstraction.Conversations.Models; -using System.Text.Json; namespace BotSharp.Plugin.PizzaBot.Functions; diff --git a/tests/BotSharp.Plugin.PizzaBot/Functions/MakeOrderFn.cs b/tests/BotSharp.Plugin.PizzaBot/Functions/PlaceOrderFn.cs similarity index 74% rename from tests/BotSharp.Plugin.PizzaBot/Functions/MakeOrderFn.cs rename to tests/BotSharp.Plugin.PizzaBot/Functions/PlaceOrderFn.cs index 68405157..bc398dbd 100644 --- a/tests/BotSharp.Plugin.PizzaBot/Functions/MakeOrderFn.cs +++ b/tests/BotSharp.Plugin.PizzaBot/Functions/PlaceOrderFn.cs @@ -2,9 +2,9 @@ using BotSharp.Abstraction.Conversations.Models; namespace BotSharp.Plugin.PizzaBot.Functions; -public class MakeOrderFn : IFunctionCallback +public class PlaceOrderFn : IFunctionCallback { - public string Name => "make_order"; + public string Name => "place_an_order"; public async Task Execute(RoleDialogModel message) { diff --git a/tests/BotSharp.Plugin.PizzaBot/Hooks/PizzaBotAgentHook.cs b/tests/BotSharp.Plugin.PizzaBot/Hooks/PizzaBotAgentHook.cs index be1bbe6f..8ee49399 100644 --- a/tests/BotSharp.Plugin.PizzaBot/Hooks/PizzaBotAgentHook.cs +++ b/tests/BotSharp.Plugin.PizzaBot/Hooks/PizzaBotAgentHook.cs @@ -10,7 +10,7 @@ public class PizzaBotAgentHook : AgentHookBase public override bool OnInstructionLoaded(string template, Dictionary dict) { dict["current_date"] = $"{DateTime.Now:MMM dd, yyyy}"; - dict["current_time"] = $"{DateTime.Now:hh:mm t}"; + dict["current_time"] = $"{DateTime.Now:hh:mm tt}"; dict["current_weekday"] = $"{DateTime.Now:dddd}"; return true; } diff --git a/tests/BotSharp.Plugin.PizzaBot/PizzaBotPlugin.cs b/tests/BotSharp.Plugin.PizzaBot/PizzaBotPlugin.cs index 87b2c7ae..fbd3a6a2 100644 --- a/tests/BotSharp.Plugin.PizzaBot/PizzaBotPlugin.cs +++ b/tests/BotSharp.Plugin.PizzaBot/PizzaBotPlugin.cs @@ -10,8 +10,9 @@ public class PizzaBotPlugin : IBotSharpPlugin // Register callback function services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.AddScoped(); services.AddScoped(); + services.AddScoped(); // Register hooks services.AddScoped();