diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructHook.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructHook.cs new file mode 100644 index 00000000..8d5dea5e --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructHook.cs @@ -0,0 +1,9 @@ +using BotSharp.Abstraction.Instructs.Models; + +namespace BotSharp.Abstraction.Instructs; + +public interface IInstructHook +{ + Task BeforeCompletion(RoleDialogModel message); + Task AfterCompletion(InstructResult result); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs index 0adb2be8..76346165 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructService.cs @@ -1,9 +1,11 @@ +using BotSharp.Abstraction.Instructs.Models; + namespace BotSharp.Abstraction.Instructs; public interface IInstructService { - Task ExecuteInstructionRecursively(Agent agent, - List wholeDialogs, + Task ExecuteInstruction(Agent agent, + RoleDialogModel message, Func onMessageReceived, Func onFunctionExecuting, Func onFunctionExecuted); diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/InstructHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/InstructHookBase.cs new file mode 100644 index 00000000..fc1f1dd5 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/InstructHookBase.cs @@ -0,0 +1,16 @@ +using BotSharp.Abstraction.Instructs.Models; + +namespace BotSharp.Abstraction.Instructs; + +public class InstructHookBase : IInstructHook +{ + public virtual async Task AfterCompletion(InstructResult result) + { + return; + } + + public virtual async Task BeforeCompletion(RoleDialogModel message) + { + return; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs index f7b9f690..122b52b0 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs @@ -5,6 +5,9 @@ namespace BotSharp.Core.Agents.Services; public partial class AgentService { +#if !DEBUG + [MemoryCache(10 * 60)] +#endif public async Task LoadAgent(string id) { var hooks = _services.GetServices(); diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index 470589d1..c19da7ae 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -76,6 +76,7 @@ + diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs index c1780c60..c42d63fe 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs @@ -59,6 +59,21 @@ public partial class ConversationService }, onMessageReceived); return; } + else if (fn.StopCompletion) + { + var message = new RoleDialogModel(AgentRole.Assistant, fn.Content) + { + CurrentAgentId = fn.CurrentAgentId, + Channel = fn.Channel, + ExecutionData = fn.ExecutionData, + ExecutionResult = fn.ExecutionResult + }; + + await HandleAssistantMessage(message, onMessageReceived); + + _storage.Append(_conversationId, agent.Id, message); + return; + } fn.Content = fn.FunctionArgs.Replace("\r", " ").Replace("\n", " ").Trim() + " => " + fn.ExecutionResult; diff --git a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs index c3ad6346..2d0f5a5d 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs @@ -1,8 +1,10 @@ using BotSharp.Abstraction.Agents.Enums; using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Instructs; +using BotSharp.Abstraction.Instructs.Models; using BotSharp.Abstraction.MLTasks; using BotSharp.Abstraction.Templating; +using System.IO; namespace BotSharp.Core.Instructs; @@ -17,7 +19,53 @@ public partial class InstructService : IInstructService _logger = logger; } - public async Task ExecuteInstructionRecursively(Agent agent, + public async Task ExecuteInstruction(Agent agent, + RoleDialogModel message, + Func onMessageReceived, + Func onFunctionExecuting, + Func onFunctionExecuted) + { + var response = new InstructResult(); + + var wholeDialogs = new List + { + new RoleDialogModel("user", message.Content) + }; + + // Trigger before completion hooks + var hooks = _services.GetServices(); + foreach (var hook in hooks) + { + await hook.BeforeCompletion(message); + } + + await ExecuteInstructionRecursively(agent, + wholeDialogs, + async msg => + { + response.Text = msg.Content; + await onMessageReceived(msg); + }, + async fn => + { + response.Function = fn.FunctionName; + await onFunctionExecuting(fn); + }, + async fn => + { + response.Data = fn.ExecutionData; + await onFunctionExecuted(fn); + }); + + foreach (var hook in hooks) + { + await hook.AfterCompletion(response); + } + + return response; + } + + private async Task ExecuteInstructionRecursively(Agent agent, List wholeDialogs, Func onMessageReceived, Func onFunctionExecuting, @@ -28,6 +76,8 @@ public partial class InstructService : IInstructService var result = await chatCompletion.GetChatCompletionsAsync(agent, wholeDialogs, async msg => { await onMessageReceived(msg); + + wholeDialogs.Add(msg); }, async fn => { var preAgentId = agent.Id; diff --git a/src/Infrastructure/BotSharp.Core/Routing/Router.cs b/src/Infrastructure/BotSharp.Core/Routing/Router.cs index fb4c67bb..9e4e2ccc 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Router.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Router.cs @@ -1,3 +1,4 @@ +using Aspects.Cache; using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Repositories; using BotSharp.Abstraction.Routing.Models; @@ -29,7 +30,8 @@ public class Router : IAgentRouting return await agentService.LoadAgent(AgentId); } - public RoutingItem[] GetRoutingRecords() + [MemoryCache(10 * 60)] + public RoutingRecord[] GetRoutingRecords() { var db = _services.GetRequiredService(); diff --git a/src/Infrastructure/BotSharp.Core/Using.cs b/src/Infrastructure/BotSharp.Core/Using.cs index 3db63c82..d0de3336 100644 --- a/src/Infrastructure/BotSharp.Core/Using.cs +++ b/src/Infrastructure/BotSharp.Core/Using.cs @@ -20,4 +20,5 @@ global using BotSharp.Core.Repository; global using BotSharp.Core.Agents.Services; global using BotSharp.Core.Conversations.Services; global using BotSharp.Core.Infrastructures; -global using BotSharp.Core.Users.Services; \ No newline at end of file +global using BotSharp.Core.Users.Services; +global using Aspects.Cache; \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs index 798d4786..3eeac672 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs @@ -1,9 +1,10 @@ +using BotSharp.Abstraction.Agents.Enums; using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.ApiAdapters; using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.Instructs; using BotSharp.Abstraction.Instructs.Models; -using BotSharp.OpenAPI.ViewModels.Conversations; +using BotSharp.OpenAPI.ViewModels.Instructs; namespace BotSharp.OpenAPI.Controllers; @@ -12,43 +13,32 @@ namespace BotSharp.OpenAPI.Controllers; public class InstructModeController : ControllerBase, IApiAdapter { private readonly IServiceProvider _services; - private readonly IUserIdentity _user; - public InstructModeController(IServiceProvider services, - IUserIdentity user) + public InstructModeController(IServiceProvider services) { _services = services; - _user = user; } [HttpPost("/instruct/{agentId}")] public async Task NewConversation([FromRoute] string agentId, - [FromBody] NewMessageModel input) + [FromBody] InstructMessageModel input) { - var response = new InstructResult(); var instructor = _services.GetRequiredService(); var agentService = _services.GetRequiredService(); Agent agent = await agentService.LoadAgent(agentId); - await instructor.ExecuteInstructionRecursively(agent, - new List - { - new RoleDialogModel("user", input.Text) - }, - async msg => - { - response.Text = msg.Content; - }, - async fnExecuting => - { + // switch to different instruction template + if (!string.IsNullOrEmpty(input.TemplateName)) + { + var agentSettings = _services.GetRequiredService(); + var filePath = Path.Combine(agentService.GetAgentDataDir(agentId), $"{input.TemplateName}.{agentSettings.TemplateFormat}"); + agent.Instruction = System.IO.File.ReadAllText(filePath); + } - }, - async fnExecuted => - { - response.Function = fnExecuted.FunctionName; - response.Data = fnExecuted.ExecutionData; - }); - - return response; + return await instructor.ExecuteInstruction(agent, + new RoleDialogModel(AgentRole.User, input.Text), + fn => Task.CompletedTask, + fn => Task.CompletedTask, + fn => Task.CompletedTask); } } diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/InstructMessageModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/InstructMessageModel.cs new file mode 100644 index 00000000..dffd3cf0 --- /dev/null +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Instructs/InstructMessageModel.cs @@ -0,0 +1,8 @@ +using BotSharp.OpenAPI.ViewModels.Conversations; + +namespace BotSharp.OpenAPI.ViewModels.Instructs; + +public class InstructMessageModel : NewMessageModel +{ + public string? TemplateName { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs index 6e685905..08acb673 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs @@ -7,7 +7,6 @@ using Tensorflow.Keras.Engine; using Tensorflow.NumPy; using static Tensorflow.Binding; using Tensorflow.Keras.Callbacks; -using System.Text.RegularExpressions; using BotSharp.Plugin.RoutingSpeeder.Settings; using BotSharp.Abstraction.MLTasks; using BotSharp.Abstraction.Knowledges.Settings; @@ -151,11 +150,11 @@ public class IntentClassifier .ServiceProvider .GetRequiredService(); string rootDirectory = Path.Combine( - agentService.GetDataDir(), + agentService.GetDataDir(), _settings.RAW_DATA_DIR); string saveLabelDirectory = Path.Combine( - agentService.GetDataDir(), - _settings.MODEL_DIR, + agentService.GetDataDir(), + _settings.MODEL_DIR, _settings.LABEL_FILE_NAME); if (!Directory.Exists(rootDirectory)) @@ -170,18 +169,15 @@ 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).ToList(); vectorList.AddRange(vector.GetVectors(texts)); string fileName = Path.GetFileNameWithoutExtension(filePath); labelList.AddRange(Enumerable.Repeat(fileName, texts.Count).ToList()); } - // Write label into local file + // Sort label to keep the same order var uniqueLabelList = labelList.Distinct().OrderBy(x => x).ToArray(); - File.WriteAllLines(saveLabelDirectory, uniqueLabelList); var x = np.zeros((vectorList.Count, vector.Dimension), dtype: np.float32); var y = np.zeros((vectorList.Count, 1), dtype: np.float32); @@ -195,13 +191,20 @@ public class IntentClassifier return (x, y); } - public string[] GetFiles(string prefix = "intent") + public string[] GetFiles(string prefix = "") { var agentService = _services.CreateScope() .ServiceProvider .GetRequiredService(); string rootDirectory = Path.Combine(agentService.GetDataDir(), _settings.RAW_DATA_DIR); + if (string.IsNullOrEmpty(prefix)) + { + return Directory.GetFiles(rootDirectory) + .OrderBy(x => Path.GetFileName(x).Split(".")[^2]) + .ToArray(); + } + return Directory.GetFiles(rootDirectory) .Where(x => Path.GetFileNameWithoutExtension(x) .StartsWith(prefix)) @@ -216,32 +219,24 @@ public class IntentClassifier var agentService = _services.CreateScope() .ServiceProvider .GetRequiredService(); - string rootDirectory = Path.Combine( - agentService.GetDataDir(), + + string[] labels = GetFiles() + .Select(x => Path.GetFileName(x).Split(".")[^2]) + .ToArray(); + + string writePath = Path.Combine( + agentService.GetDataDir(), _settings.MODEL_DIR, - _settings.LABEL_FILE_NAME - ); + _settings.LABEL_FILE_NAME); - var labelText = File.ReadAllLines(rootDirectory); - _labels = labelText.OrderBy(x => x).ToArray(); + _labels = labels.OrderBy(x => x).ToArray(); + + // Write labels into the local txt file + File.WriteAllLines(writePath, _labels); } - return _labels; } - public string TextClean(string text) - { - // Remove punctuation - // Remove digits - // To lowercase - var processedText = Regex.Replace(text, "[AB0-9]", " "); - 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) { if (!_isModelReady) @@ -260,7 +255,6 @@ public class IntentClassifier } var labelIndex = probLabel[0]; - return _labels[labelIndex]; } public void InitClassifer(bool inference = true)