diff --git a/BotSharp.sln b/BotSharp.sln index ec114349..1d78c41a 100644 --- a/BotSharp.sln +++ b/BotSharp.sln @@ -45,7 +45,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.MetaMesseng EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.OpenAPI", "src\Infrastructure\BotSharp.OpenAPI\BotSharp.OpenAPI.csproj", "{7E63F5F8-4EA0-498B-ABFE-2BBE4D7DDBA7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.LLamaSharp", "src\Plugins\BotSharp.Plugin.LLamaSharp\BotSharp.Plugin.LLamaSharp.csproj", "{46B7B54F-1425-4C9D-824A-9B826855D249}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.LLamaSharp", "src\Plugins\BotSharp.Plugin.LLamaSharp\BotSharp.Plugin.LLamaSharp.csproj", "{46B7B54F-1425-4C9D-824A-9B826855D249}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.PizzaBot", "tests\BotSharp.Plugin.PizzaBot\BotSharp.Plugin.PizzaBot.csproj", "{A1118A2C-C6D7-4E22-9462-964AEC7CC46E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{51AFE054-AE99-497D-A593-69BAEFB5106F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.RoutingSpeeder", "src\Plugins\BotSharp.Plugin.RoutingSpeeder\BotSharp.Plugin.RoutingSpeeder.csproj", "{631D9C12-86C4-44F0-99C3-D32C0754BF37}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -167,6 +173,22 @@ Global {46B7B54F-1425-4C9D-824A-9B826855D249}.Release|Any CPU.Build.0 = Release|Any CPU {46B7B54F-1425-4C9D-824A-9B826855D249}.Release|x64.ActiveCfg = Release|Any CPU {46B7B54F-1425-4C9D-824A-9B826855D249}.Release|x64.Build.0 = Release|Any CPU + {A1118A2C-C6D7-4E22-9462-964AEC7CC46E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1118A2C-C6D7-4E22-9462-964AEC7CC46E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1118A2C-C6D7-4E22-9462-964AEC7CC46E}.Debug|x64.ActiveCfg = Debug|Any CPU + {A1118A2C-C6D7-4E22-9462-964AEC7CC46E}.Debug|x64.Build.0 = Debug|Any CPU + {A1118A2C-C6D7-4E22-9462-964AEC7CC46E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1118A2C-C6D7-4E22-9462-964AEC7CC46E}.Release|Any CPU.Build.0 = Release|Any CPU + {A1118A2C-C6D7-4E22-9462-964AEC7CC46E}.Release|x64.ActiveCfg = Release|Any CPU + {A1118A2C-C6D7-4E22-9462-964AEC7CC46E}.Release|x64.Build.0 = Release|Any CPU + {631D9C12-86C4-44F0-99C3-D32C0754BF37}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {631D9C12-86C4-44F0-99C3-D32C0754BF37}.Debug|Any CPU.Build.0 = Debug|Any CPU + {631D9C12-86C4-44F0-99C3-D32C0754BF37}.Debug|x64.ActiveCfg = Debug|Any CPU + {631D9C12-86C4-44F0-99C3-D32C0754BF37}.Debug|x64.Build.0 = Debug|Any CPU + {631D9C12-86C4-44F0-99C3-D32C0754BF37}.Release|Any CPU.ActiveCfg = Release|Any CPU + {631D9C12-86C4-44F0-99C3-D32C0754BF37}.Release|Any CPU.Build.0 = Release|Any CPU + {631D9C12-86C4-44F0-99C3-D32C0754BF37}.Release|x64.ActiveCfg = Release|Any CPU + {631D9C12-86C4-44F0-99C3-D32C0754BF37}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -190,6 +212,9 @@ Global {8300F66D-9EB8-438A-BF0F-70DFBE07D9DE} = {64264688-0F5C-4AB0-8F2B-B59B717CCE00} {7E63F5F8-4EA0-498B-ABFE-2BBE4D7DDBA7} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749} {46B7B54F-1425-4C9D-824A-9B826855D249} = {D5293208-2BEF-42FC-A64C-5954F61720BA} + {A1118A2C-C6D7-4E22-9462-964AEC7CC46E} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC} + {51AFE054-AE99-497D-A593-69BAEFB5106F} = {2635EC9B-2E5F-4313-AC21-0B847F31F36C} + {631D9C12-86C4-44F0-99C3-D32C0754BF37} = {51AFE054-AE99-497D-A593-69BAEFB5106F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19} diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs index 60307439..bf242ecc 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Settings/AgentSettings.cs @@ -2,16 +2,6 @@ namespace BotSharp.Abstraction.Agents.Settings; public class AgentSettings { - /// - /// Router Agent Id - /// - public string RouterId { get; set; } - - /// - /// Reasoner Agent Id - /// - public string ReasonerId { get; set; } - public string DataDir { get; set; } public string TemplateFormat { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj index 802652e9..80a83846 100644 --- a/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj +++ b/src/Infrastructure/BotSharp.Abstraction/BotSharp.Abstraction.csproj @@ -4,7 +4,7 @@ netstandard2.1 enable 10.0 - 0.9.4 + 0.10.0 Icon.png diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs index 4e8de8b3..3634255a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs @@ -1,5 +1,3 @@ -using BotSharp.Abstraction.Conversations.Models; - namespace BotSharp.Abstraction.Conversations; public abstract class ConversationHookBase : IConversationHook @@ -13,6 +11,9 @@ public abstract class ConversationHookBase : IConversationHook protected List _dialogs; public List Dialogs => _dialogs; + protected int _priority = 0; + public int Priority => _priority; + public IConversationHook SetAgent(Agent agent) { _agent = agent; @@ -35,7 +36,7 @@ public abstract class ConversationHookBase : IConversationHook return Task.CompletedTask; } - public virtual Task BeforeCompletion() + public virtual Task BeforeCompletion(RoleDialogModel message) { return Task.CompletedTask; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs index 6b2fdccb..8e55dab7 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs @@ -1,10 +1,8 @@ -using BotSharp.Abstraction.Conversations.Models; -using BotSharp.Abstraction.MLTasks; - namespace BotSharp.Abstraction.Conversations; public interface IConversationHook { + int Priority { get; } Agent Agent { get; } IConversationHook SetAgent(Agent agent); @@ -22,7 +20,7 @@ public interface IConversationHook Task OnStateLoaded(ConversationState state); Task OnStateChanged(string name, string preValue, string currentValue); - Task BeforeCompletion(); + Task BeforeCompletion(RoleDialogModel message); Task OnFunctionExecuting(RoleDialogModel message); Task OnFunctionExecuted(RoleDialogModel message); Task AfterCompletion(RoleDialogModel message); diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs index e80c9427..2740c8c8 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs @@ -30,6 +30,16 @@ public class RoleDialogModel /// public object ExecutionData { get; set; } + /// + /// Intent name + /// + public string IntentName { get; set; } + + /// + /// Stop conversation completion + /// + public bool StopCompletion { get; set; } + /// /// Channel name /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/ConversationSetting.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/ConversationSetting.cs index c9836d4b..7cad03ff 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/ConversationSetting.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Settings/ConversationSetting.cs @@ -7,5 +7,4 @@ public class ConversationSetting public bool EnableKnowledgeBase { get; set; } public bool ShowVerboseLog { get; set; } public int MaxRecursiveDepth { get; set; } = 3; - public bool EnableReasoning { get; set; } } diff --git a/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/KnowledgeBaseSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Settings/KnowledgeBaseSettings.cs similarity index 81% rename from src/Infrastructure/BotSharp.Core/Plugins/Knowledges/KnowledgeBaseSettings.cs rename to src/Infrastructure/BotSharp.Abstraction/Knowledges/Settings/KnowledgeBaseSettings.cs index 0cb08a48..97f7f55c 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/KnowledgeBaseSettings.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Knowledges/Settings/KnowledgeBaseSettings.cs @@ -1,4 +1,4 @@ -namespace BotSharp.Core.Plugins.Knowledges; +namespace BotSharp.Abstraction.Knowledges.Settings; public class KnowledgeBaseSettings { diff --git a/src/Infrastructure/BotSharp.Abstraction/Plugins/PluginLoaderSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Plugins/PluginLoaderSettings.cs index f23b70f8..95b1ea28 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Plugins/PluginLoaderSettings.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Plugins/PluginLoaderSettings.cs @@ -3,5 +3,4 @@ namespace BotSharp.Abstraction.Plugins; public class PluginLoaderSettings { public string[] Assemblies { get; set; } - public string[] Plugins { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingProfileRecord.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingProfileRecord.cs new file mode 100644 index 00000000..753881ec --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingProfileRecord.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Abstraction.Routing.Models; + +public class RoutingProfileRecord +{ + [JsonPropertyName("name")] + public string Name { get; set; } + + [JsonPropertyName("agent_ids")] + public string[] AgentIds { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingRecord.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingRecord.cs index f0d6d742..73fd4d1e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingRecord.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingRecord.cs @@ -14,7 +14,7 @@ public class RoutingRecord public string Description { get; set; } [JsonPropertyName("required")] - public List RequiredFields { get; set; } + public List RequiredFields { get; set; } = new List(); [JsonPropertyName("redirect_to")] public string RedirectTo { get; set; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/RoutingSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/RoutingSettings.cs new file mode 100644 index 00000000..b9cc51a9 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/RoutingSettings.cs @@ -0,0 +1,14 @@ +namespace BotSharp.Abstraction.Routing.Settings; + +public class RoutingSettings +{ + /// + /// Router Agent Id + /// + public string RouterId { get; set; } + + /// + /// Reasoner Agent Id + /// + public string ReasonerId { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Templating/IResponseTemplateService.cs b/src/Infrastructure/BotSharp.Abstraction/Templating/IResponseTemplateService.cs new file mode 100644 index 00000000..da52c432 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Templating/IResponseTemplateService.cs @@ -0,0 +1,8 @@ +namespace BotSharp.Abstraction.Templating; + +public interface IResponseTemplateService +{ + Task RenderFunctionResponse(string agentId, RoleDialogModel message); + + Task RenderIntentResponse(string agentId, RoleDialogModel message); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs b/src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs index 61ba9ff7..e5ddeb61 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Templating/ITemplateRender.cs @@ -2,5 +2,5 @@ namespace BotSharp.Abstraction.Templating; public interface ITemplateRender { - bool Render(Agent agent, Dictionary dict); + string Render(string template, Dictionary dict); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Using.cs b/src/Infrastructure/BotSharp.Abstraction/Using.cs index 3a545a0a..1a298f42 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Using.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Using.cs @@ -4,4 +4,5 @@ global using System.Text; global using System.Linq; global using System.Threading.Tasks; global using System.ComponentModel.DataAnnotations; -global using BotSharp.Abstraction.Agents.Models; \ No newline at end of file +global using BotSharp.Abstraction.Agents.Models; +global using BotSharp.Abstraction.Conversations.Models; \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs index ec89334f..e2f13567 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Templating; using BotSharp.Core.Templating; namespace BotSharp.Core.Agents.Services; @@ -45,8 +46,8 @@ public partial class AgentService } // render liquid template - var render = _services.GetRequiredService(); - render.Render(agent, templateDict); + var render = _services.GetRequiredService(); + agent.Instruction = render.Render(agent.Instruction, templateDict); _logger.LogInformation($"Loaded agent {agent}."); diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index f59e6493..21f5e111 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -4,7 +4,7 @@ netstandard2.1 10.0 false - 0.9.4 + 0.10.0 diff --git a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs index 6c081daf..45121b49 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs @@ -8,7 +8,8 @@ using BotSharp.Core.Templating; using BotSharp.Core.Plugins.Knowledges.Services; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; -using DatabaseSettings = BotSharp.Abstraction.Repositories.DatabaseSettings; +using BotSharp.Abstraction.Routing.Settings; +using BotSharp.Abstraction.Templating; namespace BotSharp.Core; @@ -43,16 +44,18 @@ public static class BotSharpServiceCollectionExtensions RegisterPlugins(services, config); // Register template render - services.AddSingleton(); + services.AddSingleton(); + services.AddScoped(); // Register router + var routingSettings = new RoutingSettings(); + config.Bind("Router", routingSettings); + services.AddSingleton((IServiceProvider x) => routingSettings); + services.AddScoped(); services.AddScoped(); - services.AddScoped(p => - { - var setting = p.GetRequiredService(); - return setting.EnableReasoning ? p.GetRequiredService() : p.GetRequiredService(); - }); + services.AddScoped(); + services.AddScoped(); // Register function callback services.AddScoped(); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.CallFunctions.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.CallFunctions.cs index 66f0a25d..02a81350 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 { private async Task CallFunctions(RoleDialogModel msg) { - var hooks = _services.GetServices().ToList(); + var hooks = _services.GetServices() + .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 4e5a0d23..21e8950c 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs @@ -1,7 +1,7 @@ using BotSharp.Abstraction.Agents.Enums; using BotSharp.Abstraction.Agents.Models; -using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.MLTasks; +using BotSharp.Abstraction.Templating; namespace BotSharp.Core.Conversations.Services; @@ -68,22 +68,48 @@ public partial class ConversationService { var agentService = _services.GetRequiredService(); agent = await agentService.LoadAgent(fn.CurrentAgentId); + + wholeDialogs.Add(fn); + + await GetChatCompletionsAsyncRecursively(chatCompletion, + conversationId, + agent, + wholeDialogs, + onMessageReceived, + onFunctionExecuting, + onFunctionExecuted); } + else + { + // Find response template + var templateService = _services.GetRequiredService(); + var response = await templateService.RenderFunctionResponse(agent.Id, fn); + if (!string.IsNullOrEmpty(response)) + { + await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, response) + { + CurrentAgentId = agent.Id, + Channel = wholeDialogs.Last().Channel + }, onMessageReceived); - // Add to dialog history - // The server had an error processing your request. Sorry about that! - // _storage.Append(conversationId, preAgentId, fn); + return; + } + + // Add to dialog history + // The server had an error processing your request. Sorry about that! + // _storage.Append(conversationId, preAgentId, fn); - // After function is executed, pass the result to LLM to get a natural response - wholeDialogs.Add(fn); + // After function is executed, pass the result to LLM to get a natural response + wholeDialogs.Add(fn); - await GetChatCompletionsAsyncRecursively(chatCompletion, - conversationId, - agent, - wholeDialogs, - onMessageReceived, - onFunctionExecuting, - onFunctionExecuted); + await GetChatCompletionsAsyncRecursively(chatCompletion, + conversationId, + agent, + wholeDialogs, + onMessageReceived, + onFunctionExecuting, + onFunctionExecuted); + } }); return result; diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index 24376c21..d44eb56a 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -2,6 +2,7 @@ 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; namespace BotSharp.Core.Conversations.Services; @@ -33,8 +34,8 @@ public partial class ConversationService stateService.Load(); stateService.SetState("channel", lastDialog.Channel); - var router = _services.GetRequiredService(); - Agent agent = await router.LoadRouter(); + var agentService = _services.GetRequiredService(); + Agent agent = await agentService.LoadAgent(agentId); _logger.LogInformation($"[{agent.Name}] {lastDialog.Role}: {lastDialog.Content}"); @@ -65,11 +66,21 @@ public partial class ConversationService .SetConversation(converation); await hook.OnDialogsLoaded(wholeDialogs); - await hook.BeforeCompletion(); + await hook.BeforeCompletion(lastDialog); + + // Interrupted by hook + if (lastDialog.StopCompletion) + { + var response = new RoleDialogModel(AgentRole.Assistant, lastDialog.Content); + await onMessageReceived(response); + _storage.Append(conversationId, agent.Id, response); + return true; + } } // reasoning - if (_settings.EnableReasoning) + var settings = _services.GetRequiredService(); + if (settings.ReasonerId == agent.Id) { var simulator = _services.GetRequiredService(); var reasonedContext = await simulator.Enter(agent, wholeDialogs); @@ -96,7 +107,6 @@ public partial class ConversationService { if (reasonedContext.CurrentAgentId != agent.Id) { - var agentService = _services.GetRequiredService(); agent = await agentService.LoadAgent(reasonedContext.CurrentAgentId); } } diff --git a/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/KnowledgeBasePlugin.cs b/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/KnowledgeBasePlugin.cs index e0c74e83..8f862ca4 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/KnowledgeBasePlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/KnowledgeBasePlugin.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Knowledges.Settings; using BotSharp.Core.Plugins.Knowledges.Services; using Microsoft.Extensions.Configuration; diff --git a/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/Services/KnowledgeService.cs b/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/Services/KnowledgeService.cs index 9ed87e6a..3a627ec9 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/Services/KnowledgeService.cs +++ b/src/Infrastructure/BotSharp.Core/Plugins/Knowledges/Services/KnowledgeService.cs @@ -1,7 +1,7 @@ using BotSharp.Abstraction.Knowledges.Models; +using BotSharp.Abstraction.Knowledges.Settings; using BotSharp.Abstraction.MLTasks; using BotSharp.Abstraction.VectorStorage; -using System.Text.Json; namespace BotSharp.Core.Plugins.Knowledges.Services; diff --git a/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs b/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs index 60bc3d3d..1287a6d9 100644 --- a/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs +++ b/src/Infrastructure/BotSharp.Core/Plugins/PluginLoader.cs @@ -40,15 +40,8 @@ public class PluginLoader foreach (var module in modules) { - if (_settings.Plugins.Contains(module.GetType().Name)) - { - module.RegisterDI(_services, _config); - Console.WriteLine($"Loaded plugin {module.GetType().Name} from {assemblyName}.", Color.Green); - } - else - { - Console.WriteLine($"Skipped plugin {module.GetType().Name} from {assemblyName}.", Color.Yellow); - } + module.RegisterDI(_services, _config); + Console.WriteLine($"Loaded plugin {module.GetType().Name} from {assemblyName}.", Color.Green); } _modules.AddRange(modules); @@ -62,7 +55,7 @@ public class PluginLoader public void Configure(IApplicationBuilder app) { - if(_modules.Count == 0) + if (_modules.Count == 0) { Console.WriteLine($"No plugin loaded. Please check whether the Load() method is called.", Color.Yellow); } @@ -71,10 +64,7 @@ public class PluginLoader { if (module.GetType().GetInterface(nameof(IBotSharpAppPlugin)) != null) { - if (_settings.Plugins.Contains(module.GetType().Name)) - { - (module as IBotSharpAppPlugin).Configure(app); - } + (module as IBotSharpAppPlugin).Configure(app); } }); } diff --git a/src/Infrastructure/BotSharp.Core/Routing/Reasoner.cs b/src/Infrastructure/BotSharp.Core/Routing/Reasoner.cs index c08f83ac..182bedb6 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Reasoner.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Reasoner.cs @@ -1,3 +1,5 @@ +using BotSharp.Abstraction.Routing.Settings; + namespace BotSharp.Core.Routing; public class Reasoner : Router @@ -6,7 +8,7 @@ public class Reasoner : Router public Reasoner(IServiceProvider services, ILogger logger, - AgentSettings settings) : base(services, logger, settings) + RoutingSettings settings) : base(services, logger, settings) { } } diff --git a/src/Infrastructure/BotSharp.Core/Routing/Router.cs b/src/Infrastructure/BotSharp.Core/Routing/Router.cs index 996e83bc..c92d26e4 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Router.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Router.cs @@ -1,6 +1,7 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Repositories; using BotSharp.Abstraction.Routing.Models; +using BotSharp.Abstraction.Routing.Settings; using System.IO; namespace BotSharp.Core.Routing; @@ -9,13 +10,13 @@ public class Router : IAgentRouting { protected readonly IServiceProvider _services; protected readonly ILogger _logger; - protected readonly AgentSettings _settings; + protected readonly RoutingSettings _settings; public virtual string AgentId => _settings.RouterId; public Router(IServiceProvider services, ILogger logger, - AgentSettings settings) + RoutingSettings settings) { _services = services; _logger = logger; @@ -32,20 +33,24 @@ public class Router : IAgentRouting { var agentSettings = _services.GetRequiredService(); var dbSettings = _services.GetRequiredService(); - var filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir, agentSettings.RouterId, "route.json"); + var filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir, _settings.RouterId, "route.json"); + var records = JsonSerializer.Deserialize(File.ReadAllText(filePath)); - var db = _services.GetRequiredService(); - var agent = db.Agent.FirstOrDefault(x => x.Id == agentSettings.RouterId); - var routes = agent?.Routes ?? new List(); - var routingRecords = new RoutingRecord[routes.Count]; - - for (int i = 0; i < routes.Count; i++) + // check if routing profile is specified + filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir, "routing-profile.json"); + if (File.Exists(filePath)) { - if (string.IsNullOrEmpty(routes[i])) continue; - routingRecords[i] = JsonSerializer.Deserialize(routes[i]); + var state = _services.GetRequiredService(); + var name = state.GetState("channel"); + var profiles = JsonSerializer.Deserialize(File.ReadAllText(filePath)); + var spcificedProfile = profiles.FirstOrDefault(x => x.Name == name); + if (spcificedProfile != null) + { + records = records.Where(x => spcificedProfile.AgentIds.Contains(x.AgentId)).ToArray(); + } } - return routingRecords; + return records; } public RoutingRecord GetRecordByName(string name) diff --git a/src/Infrastructure/BotSharp.Core/Templating/ResponseTemplateService.cs b/src/Infrastructure/BotSharp.Core/Templating/ResponseTemplateService.cs new file mode 100644 index 00000000..cac30ff7 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Templating/ResponseTemplateService.cs @@ -0,0 +1,120 @@ +using BotSharp.Abstraction.Templating; +using System.IO; +using System.Reflection; + +namespace BotSharp.Core.Templating; + +public class ResponseTemplateService : IResponseTemplateService +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + + public ResponseTemplateService(IServiceProvider services, ILogger logger) + { + _services = services; + _logger = logger; + } + + public async Task RenderFunctionResponse(string agentId, RoleDialogModel message) + { + // Find response template + var agentService = _services.GetRequiredService(); + var dir = Path.Combine(agentService.GetAgentDataDir(agentId), "responses"); + var responses = Directory.GetFiles(dir) + .Where(f => f.Split(Path.DirectorySeparatorChar).Last().Split('.')[1] == message.FunctionName) + .ToList(); + + if (responses.Count == 0) + { + return string.Empty; + } + + var randomIndex = new Random().Next(0, responses.Count); + var template = File.ReadAllText(responses[randomIndex]); + + var render = _services.GetRequiredService(); + + // Convert args and execute data to dictionary + var dict = new Dictionary(); + + if (message.FunctionArgs != null) + { + ExtractArgs(JsonSerializer.Deserialize(message.FunctionArgs), dict); + } + + if (message.ExecutionData != null) + { + ExtractExecuteData(message.ExecutionData, dict); + } + + var text = render.Render(template, dict); + + return text; + } + + public async Task RenderIntentResponse(string agentId, RoleDialogModel message) + { + // Find response template + var agentService = _services.GetRequiredService(); + var dir = Path.Combine(agentService.GetAgentDataDir(agentId), "responses"); + if (!Directory.Exists(dir)) + { + return string.Empty; + } + var responses = Directory.GetFiles(dir) + .Where(f => f.Split(Path.DirectorySeparatorChar).Last().Split('.')[1] == message.IntentName) + .ToList(); + + if (responses.Count == 0) + { + return string.Empty; + } + + var randomIndex = new Random().Next(0, responses.Count); + var template = File.ReadAllText(responses[randomIndex]); + + var render = _services.GetRequiredService(); + + // Convert args and execute data to dictionary + var dict = new Dictionary(); + if (!string.IsNullOrEmpty(message.FunctionArgs)) + { + ExtractArgs(JsonSerializer.Deserialize(message.FunctionArgs), dict); + } + + if (message.ExecutionData != null) + { + ExtractExecuteData(message.ExecutionData, dict); + } + + var text = render.Render(template, dict); + + return text; + } + + private void ExtractArgs(JsonDocument args, Dictionary dict) + { + if (args.RootElement is JsonElement root) + { + foreach (JsonProperty property in root.EnumerateObject()) + { + if (!string.IsNullOrEmpty(property.Value.ToString())) + { + dict[property.Name] = property.Value.ToString(); + } + } + } + } + + private void ExtractExecuteData(object data, Dictionary dict) + { + foreach (PropertyInfo property in data.GetType().GetProperties()) + { + var value = property.GetValue(data, null); + if (value != null) + { + dict[property.Name] = value; + } + } + } +} diff --git a/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs b/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs index 97263119..d268db4c 100644 --- a/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs +++ b/src/Infrastructure/BotSharp.Core/Templating/TemplateRender.cs @@ -1,8 +1,6 @@ -using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Routing.Models; using BotSharp.Abstraction.Templating; using Fluid; -using Microsoft.Extensions.Options; namespace BotSharp.Core.Templating; @@ -22,19 +20,18 @@ public class TemplateRender : ITemplateRender _options.MemberAccessStrategy.Register(); } - public bool Render(Agent agent, Dictionary dict) + public string Render(string template, Dictionary dict) { - var template = agent.Instruction; if (_parser.TryParse(template, out var t, out var error)) { var context = new TemplateContext(dict, _options); - agent.Instruction = t.Render(context); - return true; + template = t.Render(context); + return template; } else { - return false; + return template; } } } diff --git a/src/Infrastructure/BotSharp.Core/Using.cs b/src/Infrastructure/BotSharp.Core/Using.cs index 0c26934d..6349e6f7 100644 --- a/src/Infrastructure/BotSharp.Core/Using.cs +++ b/src/Infrastructure/BotSharp.Core/Using.cs @@ -13,11 +13,12 @@ global using BotSharp.Abstraction.Conversations; global using BotSharp.Abstraction.Knowledges; global using BotSharp.Abstraction.Users; global using BotSharp.Abstraction.Utilities; +global using BotSharp.Abstraction.Conversations.Models; +global using BotSharp.Abstraction.Agents.Settings; +global using BotSharp.Abstraction.Conversations.Settings; 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.Plugins; -global using BotSharp.Core.Users.Services; -global using BotSharp.Abstraction.Agents.Settings; -global using BotSharp.Abstraction.Conversations.Settings; \ No newline at end of file +global using BotSharp.Core.Users.Services; \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj b/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj index 7b8290d6..316f4488 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj +++ b/src/Infrastructure/BotSharp.OpenAPI/BotSharp.OpenAPI.csproj @@ -1,10 +1,10 @@ - + net6.0 enable enable - 0.9.0 + 0.10.0 diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 026a7874..364416cd 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -40,7 +40,8 @@ public class ConversationController : ControllerBase, IApiAdapter [HttpPost("/conversation/{agentId}/{conversationId}")] public async Task SendMessage([FromRoute] string agentId, [FromRoute] string conversationId, - [FromBody] NewMessageModel input) + [FromBody] NewMessageModel input, + [FromQuery] string? channel = "openapi") { var conv = _services.GetRequiredService(); @@ -50,7 +51,7 @@ public class ConversationController : ControllerBase, IApiAdapter await conv.SendMessage(agentId, conversationId, new RoleDialogModel("user", input.Text) { - Channel = "webapi" + Channel = channel }, async msg => { diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeController.cs index 3824f476..bea5597b 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/KnowledgeController.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Http; using UglyToad.PdfPig.Content; using UglyToad.PdfPig; using BotSharp.Core.Plugins.Knowledges; - +using BotSharp.Abstraction.Knowledges.Settings; namespace BotSharp.OpenAPI.Controllers; diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/BotSharp.Plugin.AzureOpenAI.csproj b/src/Plugins/BotSharp.Plugin.AzureOpenAI/BotSharp.Plugin.AzureOpenAI.csproj index dfc28a95..05ba639e 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/BotSharp.Plugin.AzureOpenAI.csproj +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/BotSharp.Plugin.AzureOpenAI.csproj @@ -4,11 +4,11 @@ netstandard2.1 enable 10 - 0.9.0 + 0.10.0 - + diff --git a/src/Plugins/BotSharp.Plugin.ChatbotUI/BotSharp.Plugin.ChatbotUI.csproj b/src/Plugins/BotSharp.Plugin.ChatbotUI/BotSharp.Plugin.ChatbotUI.csproj index e9e233a9..d69c44a5 100644 --- a/src/Plugins/BotSharp.Plugin.ChatbotUI/BotSharp.Plugin.ChatbotUI.csproj +++ b/src/Plugins/BotSharp.Plugin.ChatbotUI/BotSharp.Plugin.ChatbotUI.csproj @@ -4,7 +4,7 @@ netstandard2.1 enable 10 - 0.9.0 + 0.10.0 diff --git a/src/Plugins/BotSharp.Plugin.MetaAI/Providers/fastTextEmbeddingProvider.cs b/src/Plugins/BotSharp.Plugin.MetaAI/Providers/fastTextEmbeddingProvider.cs index d6e1824d..3388f04c 100644 --- a/src/Plugins/BotSharp.Plugin.MetaAI/Providers/fastTextEmbeddingProvider.cs +++ b/src/Plugins/BotSharp.Plugin.MetaAI/Providers/fastTextEmbeddingProvider.cs @@ -15,10 +15,7 @@ public class fastTextEmbeddingProvider : ITextEmbedding { get { - if (!_fastText.IsModelReady()) - { - _fastText.LoadModel(_settings.ModelPath); - } + LoadModel(); return _fastText.GetModelDimension(); } } diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/BotSharp.Plugin.RoutingSpeeder.csproj b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/BotSharp.Plugin.RoutingSpeeder.csproj new file mode 100644 index 00000000..fb145e8d --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/BotSharp.Plugin.RoutingSpeeder.csproj @@ -0,0 +1,18 @@ + + + + netstandard2.1 + enable + 10 + 0.11.0 + + + + + + + + + + + diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs new file mode 100644 index 00000000..3c3a33f2 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/IntentClassifier.cs @@ -0,0 +1,258 @@ +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; +using static Tensorflow.Binding; +using Tensorflow.Keras.Callbacks; +using System.Text.RegularExpressions; +using BotSharp.Plugin.RoutingSpeeder.Settings; +using BotSharp.Abstraction.MLTasks; +using BotSharp.Plugin.RoutingSpeeder.Providers.Models; +using Microsoft.Extensions.DependencyInjection; +using System.Linq; +using Tensorflow.Keras; +using BotSharp.Abstraction.Knowledges.Settings; +using System.Numerics; +using Newtonsoft.Json; +using Tensorflow.Keras.Layers; +using BotSharp.Abstraction.Agents; + +namespace BotSharp.Plugin.RoutingSpeeder.Providers; + +public class IntentClassifier +{ + private readonly IServiceProvider _services; + Model _model; + public Model model => _model; + private bool _isModelReady; + public bool isModelReady => _isModelReady; + private ClassifierSetting _settings; + + public IntentClassifier(IServiceProvider services, ClassifierSetting settings) + { + _services = services; + _settings = settings; + } + + private void Reset() + { + keras.backend.clear_session(); + _isModelReady = false; + } + + private void Build() + { + if (_isModelReady) + { + return; + } + + var vector = _services.GetRequiredService(); + + var labels = GetLabels(); + + var layers = new List + { + keras.layers.InputLayer((vector.Dimension), name: "Input"), + keras.layers.Dense(256, activation:"relu"), + keras.layers.Dense(256, activation:"relu"), + keras.layers.Dense(labels.Length, activation: keras.activations.Softmax) + }; + _model = keras.Sequential(layers); + +#if DEBUG + Console.WriteLine(); + _model.summary(); +#endif + _isModelReady = true; + } + + private void Fit(NDArray x, NDArray y, TrainingParams trainingParams) + { + _model.compile(optimizer: keras.optimizers.Adam(trainingParams.LearningRate), + loss: keras.losses.SparseCategoricalCrossentropy(), + metrics: new[] { "accuracy" } + ); + + CallbackParams callback_parameters = new CallbackParams + { + Model = _model, + Epochs = trainingParams.Epochs, + Verbose = 1, + Steps = 10 + }; + + ICallback earlyStop = new EarlyStopping(callback_parameters, "accuracy"); + + var callbacks = new List() { earlyStop }; + + var weights = LoadWeights(); + + _model.fit(x, y, + batch_size: trainingParams.BatchSize, + epochs: trainingParams.Epochs, + callbacks: callbacks, + // validation_split: 0.1f, + shuffle: true); + + _model.save_weights(weights); + + _isModelReady = true; + } + + public string LoadWeights() + { + var agentService = _services.CreateScope().ServiceProvider.GetRequiredService(); + + var weightsFile = Path.Combine(agentService.GetDataDir(), _settings.MODEL_DIR, $"intent-classifier.h5"); + if (File.Exists(weightsFile)) + { + _model.load_weights(weightsFile); + _isModelReady = true; + Console.WriteLine($"Successfully load the weights!"); + } + else + { + Console.WriteLine("No available weights."); + } + return weightsFile; + } + + public (NDArray x, NDArray y) Vectorize(List items) + { + var vector = _services.GetRequiredService(); + + var x = np.zeros((items.Count, vector.Dimension), dtype: np.float32); + var y = np.zeros((items.Count, 1), dtype: np.float32); + + for (int i = 0; i < items.Count; i++) + { + x[i] = vector.GetVector(TextClean(items[i].text)); + if (_settings.LabelMappingDict.ContainsKey(items[i].label)) + { + y[i] = _settings.LabelMappingDict[items[i].label]; + } + } + return (x, y); + } + + public NDArray GetTextEmbedding(string text) + { + var knowledgeSettings = _services.GetRequiredService(); + var embedding = _services.GetServices() + .FirstOrDefault(x => x.GetType().FullName.EndsWith(knowledgeSettings.TextEmbedding)); + + var x = np.zeros((1, embedding.Dimension), dtype: np.float32); + x[0] = embedding.GetVector(text); + return x; + } + + 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); + + if (!Directory.Exists(rootDirectory)) + { + throw new Exception($"No training data found! Please put training data in this path: {rootDirectory}"); + } + + var vector = _services.GetRequiredService(); + + + var vectorList = new List(); + + var labelList = new List(); + foreach (var filePath in GetFiles()) + { + 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()); + } + + // Write label into local file + 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); + + 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() + { + var agentService = _services.CreateScope().ServiceProvider.GetRequiredService(); + string rootDirectory = Path.Combine(agentService.GetDataDir(), _settings.RAW_DATA_DIR); + return Directory.GetFiles(rootDirectory).OrderBy(x => x).ToArray(); + } + + public string[] GetLabels() + { + var agentService = _services.CreateScope().ServiceProvider.GetRequiredService(); + string rootDirectory = Path.Combine(agentService.GetDataDir(), _settings.MODEL_DIR, _settings.LABEL_FILE_NAME); + var labelText = File.ReadAllLines(rootDirectory); + return labelText.OrderBy(x => x).ToArray(); + } + + public string TextClean(string text) + { + // Remove punctuation + // 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; + } + + public string Predict(NDArray vector, float confidenceScore = 0.9f) + { + if (!_isModelReady) + { + InitClassifer(); + } + + var prob = _model.predict(vector).numpy(); + + var probLabel = tf.arg_max(prob, -1).numpy().ToArray(); + prob = np.squeeze(prob, axis: 0); + + if (prob[probLabel[0]] < confidenceScore) + { + return string.Empty; + } + + var prediction = GetLabels()[probLabel[0]]; + + return prediction; + } + public void InitClassifer() + { + Reset(); + Build(); + LoadWeights(); + } + + public void Train() + { + var trainingParams = new TrainingParams(); + Reset(); + (var x, var y) = PrepareLoadData(); + Build(); + Fit(x, y, trainingParams); + + } +} diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/Models/DialoguePredictionModel.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/Models/DialoguePredictionModel.cs new file mode 100644 index 00000000..4641b9cd --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/Models/DialoguePredictionModel.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.Plugin.RoutingSpeeder.Providers.Models; + +public class DialoguePredictionModel +{ + public int Id { get; set; } + public string text { get; set; } + public string? label { get; set; } + public string? prediction { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/Models/TrainingParams.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/Models/TrainingParams.cs new file mode 100644 index 00000000..f3c822ac --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Providers/Models/TrainingParams.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.Plugin.RoutingSpeeder.Providers.Models; + +public class TrainingParams +{ + public int ClientId { get; set; } + public int Epochs { get; set; } = 10; + public int BatchSize { get; set; } = 16; + public float LearningRate { get; set; } = 1.0e-4f; +} diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs new file mode 100644 index 00000000..869afe94 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs @@ -0,0 +1,75 @@ +using BotSharp.Abstraction.Agents.Enums; +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Conversations; +using BotSharp.Abstraction.Conversations.Models; +using BotSharp.Abstraction.MLTasks; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Linq; +using System.Threading.Tasks; +using BotSharp.Plugin.RoutingSpeeder.Settings; +using BotSharp.Abstraction.Templating; +using BotSharp.Plugin.RoutingSpeeder.Providers; +using System.Runtime.InteropServices; +using BotSharp.Abstraction.Agents; +using System.IO; +using BotSharp.Abstraction.Routing.Settings; + +namespace BotSharp.Plugin.RoutingSpeeder; + +public class RoutingConversationHook: ConversationHookBase +{ + private readonly IServiceProvider _services; + private RouterSpeederSettings _settings; + public RoutingConversationHook(IServiceProvider service, RouterSpeederSettings settings) + { + _services = service; + _settings = settings; + } + public override async Task BeforeCompletion(RoleDialogModel message) + { + var intentClassifier = _services.GetRequiredService(); + var vector = intentClassifier.GetTextEmbedding(message.Content); + + // intentClassifier.Train(); + // Utilize local discriminative model to predict intent + var predText = intentClassifier.Predict(vector); + + message.IntentName = predText; + + // Render by template + var templateService = _services.GetRequiredService(); + var response = await templateService.RenderIntentResponse(_agent.Id, message); + + if (!string.IsNullOrEmpty(response)) + { + message.Content = response; + message.StopCompletion = true; + } + } + + public override async Task AfterCompletion(RoleDialogModel message) + { + var routerSettings = _services.GetRequiredService(); + bool saveFlag = (message.CurrentAgentId != routerSettings.RouterId) && (message.CurrentAgentId != routerSettings.ReasonerId); + + if (saveFlag) + { + // save train data + var agentService = _services.CreateScope().ServiceProvider.GetRequiredService(); + var rootDataPath = agentService.GetDataDir(); + + string rawDataDir = Path.Combine(rootDataPath, "raw_data", $"{message.CurrentAgentId}.txt"); + var lastThreeDialogs = _dialogs.Where(x => x.Role == AgentRole.User).Select(x => x.Content).Reverse().Take(3).ToArray(); + + if (!File.Exists(rawDataDir)) + { + await File.WriteAllLinesAsync(rawDataDir, lastThreeDialogs); + } + else + { + await File.AppendAllLinesAsync(rawDataDir, lastThreeDialogs); + } + } + } +} diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingSpeederPlugin.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingSpeederPlugin.cs new file mode 100644 index 00000000..c3dac3a2 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingSpeederPlugin.cs @@ -0,0 +1,24 @@ +using BotSharp.Abstraction.Conversations; +using BotSharp.Abstraction.MLTasks; +using BotSharp.Abstraction.Plugins; +using BotSharp.Plugin.RoutingSpeeder.Settings; +using BotSharp.Plugin.RoutingSpeeder.Providers; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace BotSharp.Plugin.RoutingSpeeder; + +public class RoutingSpeederPlugin : IBotSharpPlugin +{ + public void RegisterDI(IServiceCollection services, IConfiguration config) + { + var settings = new RouterSpeederSettings(); + config.Bind("RouterSpeeder", settings); + services.AddSingleton(x => settings); + + services.AddSingleton(); + + services.AddScoped(); + services.AddSingleton(); + } +} diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/classifierSetting.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/classifierSetting.cs new file mode 100644 index 00000000..09bda6e8 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/classifierSetting.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.Plugin.RoutingSpeeder.Settings; + +public class ClassifierSetting +{ + public Dictionary LabelMappingDict { get; set; } = new Dictionary() + { + {"goodbye", 0f}, + {"greeting", 1f}, + {"other", 2f} + }; + + public string RAW_DATA_DIR { get; set; } = "raw_data"; + public string MODEL_DIR { get; set; } = "models"; + public string LABEL_FILE_NAME { get; set; } = "label.txt"; +} diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/routerSpeedSettings.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/routerSpeedSettings.cs new file mode 100644 index 00000000..fbedf581 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/Settings/routerSpeedSettings.cs @@ -0,0 +1,9 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace BotSharp.Plugin.RoutingSpeeder.Settings; + +public class RouterSpeederSettings +{ +} diff --git a/src/WebStarter/WebStarter.csproj b/src/WebStarter/WebStarter.csproj index e8ef139f..0cb02ead 100644 --- a/src/WebStarter/WebStarter.csproj +++ b/src/WebStarter/WebStarter.csproj @@ -34,6 +34,7 @@ + @@ -43,6 +44,7 @@ + diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 9657f072..72502bf7 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -13,14 +13,20 @@ "Key": "31ba6052aa6f4569901facc3a41fcb4a" }, + "Router": { + "RouterId": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a", + "ReasonerId": "03d3fb55-9ada-423b-a6b4-f9ecddf4b26e" + }, + "Agent": { - "RouterId": "", "DataDir": "agents", - "TemplateFormat": "liquid" + "TemplateFormat": "liquid", + "MaxRecursiveDepth": 3 }, "Conversation": { "DataDir": "conversations", + "ShowVerboseLog": false, "ChatCompletion": "AzureOpenAI.Providers.ChatCompletionProvider" // "ChatCompletion": "LLamaSharp.ChatCompletionProvider" }, @@ -43,10 +49,13 @@ "MetaAi": { "fastText": { - "ModelPath": "crawl-300d-2M-subword.bin" + "ModelPath": "dbpedia.ftz" } }, + "RoutingSpeeder": { + }, + "MetaMessenger": { "Endpoint": "https://graph.facebook.com", "ApiVersion": "v17.0", @@ -98,17 +107,9 @@ "BotSharp.Plugin.MetaAI", "BotSharp.Plugin.Qdrant", "BotSharp.Plugin.PaddleSharp", - "BotSharp.Plugin.WeChat" - ], - "Plugins": [ - "KnowledgeBasePlugin", - "MemVecDbPlugin", - "LLamaSharpPlugin", - "AzureOpenAiPlugin", - "MetaAiPlugin", - "QdrantPlugin", - "PaddleSharpPlugin", - "WeChatPlugin" + "BotSharp.Plugin.WeChat", + "BotSharp.Plugin.RoutingSpeeder", + "BotSharp.Plugin.PizzaBot" ] } } diff --git a/src/WebStarter/data/models/dbpedia.ftz b/src/WebStarter/data/models/dbpedia.ftz new file mode 100644 index 00000000..8f649cc9 Binary files /dev/null and b/src/WebStarter/data/models/dbpedia.ftz differ diff --git a/src/WebStarter/data/models/intent-classifier.h5 b/src/WebStarter/data/models/intent-classifier.h5 new file mode 100644 index 00000000..13f2ebed Binary files /dev/null and b/src/WebStarter/data/models/intent-classifier.h5 differ diff --git a/src/WebStarter/data/models/label.txt b/src/WebStarter/data/models/label.txt new file mode 100644 index 00000000..03fb5a19 --- /dev/null +++ b/src/WebStarter/data/models/label.txt @@ -0,0 +1,3 @@ +goodbye +greeting +other diff --git a/tests/BotSharp.Plugin.PizzaBot/BotSharp.Plugin.PizzaBot.csproj b/tests/BotSharp.Plugin.PizzaBot/BotSharp.Plugin.PizzaBot.csproj new file mode 100644 index 00000000..a451378c --- /dev/null +++ b/tests/BotSharp.Plugin.PizzaBot/BotSharp.Plugin.PizzaBot.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.1 + enable + 10 + + + + + + + diff --git a/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaPricesFn.cs b/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaPricesFn.cs new file mode 100644 index 00000000..625e9ec4 --- /dev/null +++ b/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaPricesFn.cs @@ -0,0 +1,19 @@ +using BotSharp.Abstraction.Conversations.Models; +using System.Text.Json; + +namespace BotSharp.Plugin.PizzaBot.Functions; + +public class GetPizzaPricesFn : IFunctionCallback +{ + public string Name => "get_pizza_price"; + + public async Task Execute(RoleDialogModel message) + { + message.ExecutionData = new + { + cheese = "3.5" + }; + message.ExecutionResult = "Pepperoni Pizza: $3.5/slice, Cheese Pizza: $2.5/slice, Margherita Pizza: $3.0/slice"; + return true; + } +} diff --git a/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaTypesFn.cs b/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaTypesFn.cs new file mode 100644 index 00000000..0e505d09 --- /dev/null +++ b/tests/BotSharp.Plugin.PizzaBot/Functions/GetPizzaTypesFn.cs @@ -0,0 +1,14 @@ +using BotSharp.Abstraction.Conversations.Models; + +namespace BotSharp.Plugin.PizzaBot.Functions; + +public class GetPizzaTypesFn : IFunctionCallback +{ + public string Name => "get_pizza_types"; + + public async Task Execute(RoleDialogModel message) + { + message.ExecutionResult = "Pepperoni Pizza, Cheese Pizza, Margherita Pizza"; + return true; + } +} diff --git a/tests/BotSharp.Plugin.PizzaBot/Functions/MakeOrderFn.cs b/tests/BotSharp.Plugin.PizzaBot/Functions/MakeOrderFn.cs new file mode 100644 index 00000000..68405157 --- /dev/null +++ b/tests/BotSharp.Plugin.PizzaBot/Functions/MakeOrderFn.cs @@ -0,0 +1,14 @@ +using BotSharp.Abstraction.Conversations.Models; + +namespace BotSharp.Plugin.PizzaBot.Functions; + +public class MakeOrderFn : IFunctionCallback +{ + public string Name => "make_order"; + + public async Task Execute(RoleDialogModel message) + { + message.ExecutionResult = "The order number is P123-01"; + return true; + } +} diff --git a/tests/BotSharp.Plugin.PizzaBot/Functions/OrderFoundFn.cs b/tests/BotSharp.Plugin.PizzaBot/Functions/OrderFoundFn.cs new file mode 100644 index 00000000..90a85b50 --- /dev/null +++ b/tests/BotSharp.Plugin.PizzaBot/Functions/OrderFoundFn.cs @@ -0,0 +1,14 @@ +using BotSharp.Abstraction.Conversations.Models; + +namespace BotSharp.Plugin.PizzaBot.Functions; + +public class OrderFoundFn : IFunctionCallback +{ + public string Name => "order_found"; + + public async Task Execute(RoleDialogModel message) + { + message.ExecutionResult = "The order number is P123-01"; + return true; + } +} diff --git a/tests/BotSharp.Plugin.PizzaBot/Hooks/PizzaBotAgentHook.cs b/tests/BotSharp.Plugin.PizzaBot/Hooks/PizzaBotAgentHook.cs new file mode 100644 index 00000000..be1bbe6f --- /dev/null +++ b/tests/BotSharp.Plugin.PizzaBot/Hooks/PizzaBotAgentHook.cs @@ -0,0 +1,17 @@ +namespace BotSharp.Plugin.PizzaBot.Hooks; + +public class PizzaBotAgentHook : AgentHookBase +{ + public PizzaBotAgentHook(IServiceProvider services, AgentSettings settings) + : base(services, settings) + { + } + + 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_weekday"] = $"{DateTime.Now:dddd}"; + return true; + } +} diff --git a/tests/BotSharp.Plugin.PizzaBot/PizzaBotPlugin.cs b/tests/BotSharp.Plugin.PizzaBot/PizzaBotPlugin.cs new file mode 100644 index 00000000..87b2c7ae --- /dev/null +++ b/tests/BotSharp.Plugin.PizzaBot/PizzaBotPlugin.cs @@ -0,0 +1,19 @@ +using BotSharp.Abstraction.Agents; +using BotSharp.Plugin.PizzaBot.Hooks; + +namespace BotSharp.Plugin.PizzaBot; + +public class PizzaBotPlugin : IBotSharpPlugin +{ + public void RegisterDI(IServiceCollection services, IConfiguration config) + { + // Register callback function + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + // Register hooks + services.AddScoped(); + } +} diff --git a/tests/BotSharp.Plugin.PizzaBot/Using.cs b/tests/BotSharp.Plugin.PizzaBot/Using.cs new file mode 100644 index 00000000..e1f3009b --- /dev/null +++ b/tests/BotSharp.Plugin.PizzaBot/Using.cs @@ -0,0 +1,12 @@ +global using System; +global using System.Collections.Generic; +global using System.Text; +global using System.Threading.Tasks; +global using System.ComponentModel.DataAnnotations; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.DependencyInjection; +global using BotSharp.Abstraction.Plugins; +global using BotSharp.Abstraction.Functions; +global using BotSharp.Plugin.PizzaBot.Functions; +global using BotSharp.Abstraction.Agents.Settings; +global using BotSharp.Core.Agents.Services; \ No newline at end of file