From 3022cf50cb4aae9c00b73b08e56857fa7e706f09 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Sat, 13 Jan 2024 15:17:40 -0600 Subject: [PATCH] Support to set multiple routers. --- Directory.Build.props | 1 + .../Agents/IAgentService.cs | 3 + .../Agents/Models/Agent.cs | 7 + .../Routing/Models/RoutingContext.cs | 8 +- .../Routing/Settings/RoutingSettings.cs | 2 +- .../Agents/Services/AgentService.GetAgents.cs | 15 ++ .../Agents/Services/AgentService.GetPlugin.cs | 24 +++ .../Agents/Services/AgentService.cs | 1 - .../ConversationService.SendMessage.cs | 2 +- .../Conversations/Services/TokenStatistics.cs | 8 + .../Instructs/InstructService.cs | 10 + .../FileRepository/FileRepository.Agent.cs | 4 +- .../Routing/Functions/RouteToAgentFn.cs | 5 + .../Handlers/RouteToAgentRoutingHandler.cs | 15 +- .../Routing/Hooks/RoutingAgentHook.cs | 6 +- .../BotSharp.Core/Routing/RoutingService.cs | 2 +- .../Controllers/ConversationController.cs | 4 +- .../ViewModels/Agents/AgentViewModel.cs | 8 + .../BotSharp.Plugin.MongoStorage.csproj | 2 +- .../Repository/MongoRepository.Agent.cs | 4 +- .../RoutingConversationHook.cs | 2 +- .../Actions/ExecuteQueryAction.cs | 35 ++++ .../BotSharp.Plugin.SqlHero.csproj | 18 ++ .../Models/LlmInputArgs.cs | 9 + .../Settings/SqlHeroSetting.cs | 6 + .../SqlHeroPlugin.cs | 26 +++ .../agent.json | 9 + .../functions.json | 14 ++ .../instruction.liquid | 181 ++++++++++++++++++ .../Services/TwilioService.cs | 9 +- .../Settings/TwilioSetting.cs | 1 + src/WebStarter/appsettings.json | 7 +- 32 files changed, 423 insertions(+), 25 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetPlugin.cs create mode 100644 src/Plugins/BotSharp.Plugin.SqlDriver/Actions/ExecuteQueryAction.cs create mode 100644 src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlHero.csproj create mode 100644 src/Plugins/BotSharp.Plugin.SqlDriver/Models/LlmInputArgs.cs create mode 100644 src/Plugins/BotSharp.Plugin.SqlDriver/Settings/SqlHeroSetting.cs create mode 100644 src/Plugins/BotSharp.Plugin.SqlDriver/SqlHeroPlugin.cs create mode 100644 src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/agent.json create mode 100644 src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions.json create mode 100644 src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/instruction.liquid diff --git a/Directory.Build.props b/Directory.Build.props index 69c26286..064983e6 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,5 +4,6 @@ 10.0 0.21.0 false + false \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs index 2b22d0da..76019e47 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentService.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Plugins.Models; using BotSharp.Abstraction.Repositories.Filters; namespace BotSharp.Abstraction.Agents; @@ -34,4 +35,6 @@ public interface IAgentService Task UpdateAgentFromFile(string id); string GetDataDir(); string GetAgentDataDir(string agentId); + + PluginDef GetPlugin(string agentId); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs index 02294c49..624f8ca8 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/Agent.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Functions.Models; +using BotSharp.Abstraction.Plugins.Models; using BotSharp.Abstraction.Routing.Models; namespace BotSharp.Abstraction.Agents.Models; @@ -56,6 +57,12 @@ public class Agent public bool IsPublic { get; set; } + [JsonIgnore] + public bool IsRouter { get; set; } + + [JsonIgnore] + public PluginDef Plugin { get; set; } + /// /// Allow to be routed /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingContext.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingContext.cs index 18a20713..9168b23f 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingContext.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Models/RoutingContext.cs @@ -19,18 +19,14 @@ public class RoutingContext public string IntentName { get; set; } /// - /// Agent that can handl user original goal. + /// Agent that can handle user original goal. /// public string OriginAgentId - => _stack.Where(x => x != _setting.AgentId).Last(); + => _stack.Where(x => !_setting.AgentIds.Contains(x)).Last(); public bool IsEmpty => !_stack.Any(); public string GetCurrentAgentId() { - if (_stack.Count == 0) - { - _stack.Push(_setting.AgentId); - } return _stack.Peek(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/RoutingSettings.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/RoutingSettings.cs index 8428ac99..eb7f099a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/RoutingSettings.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Settings/RoutingSettings.cs @@ -5,7 +5,7 @@ public class RoutingSettings /// /// Router Agent Id /// - public string AgentId { get; set; } = string.Empty; + public string[] AgentIds { get; set; } = new string[0]; public string Planner { get; set; } = string.Empty; } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs index 17b4f75d..128afc24 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetAgents.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Repositories.Filters; +using BotSharp.Abstraction.Routing.Settings; namespace BotSharp.Core.Agents.Services; @@ -11,6 +12,15 @@ public partial class AgentService public async Task> GetAgents(AgentFilter filter) { var agents = _db.GetAgents(filter); + + // Set IsRouter + var routeSetting = _services.GetRequiredService(); + foreach (var agent in agents) + { + agent.IsRouter = routeSetting.AgentIds.Contains(agent.Id); + agent.Plugin = GetPlugin(agent.Id); + } + return await Task.FromResult(agents); } @@ -35,6 +45,11 @@ public partial class AgentService profile.LlmConfig.IsInherit = true; } + // Set IsRouter + var routeSetting = _services.GetRequiredService(); + profile.IsRouter = routeSetting.AgentIds.Contains(profile.Id); + profile.Plugin = GetPlugin(profile.Id); + return profile; } } diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetPlugin.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetPlugin.cs new file mode 100644 index 00000000..ea2029f8 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.GetPlugin.cs @@ -0,0 +1,24 @@ +using BotSharp.Abstraction.Plugins.Models; +using BotSharp.Core.Plugins; + +namespace BotSharp.Core.Agents.Services; + +public partial class AgentService +{ + public PluginDef GetPlugin(string agentId) + { + var loader = _services.GetRequiredService(); + var plugins = loader.GetPlugins(_services); + return plugins.FirstOrDefault(x => x.AgentIds.Contains(agentId)) ?? + new PluginDef + { + Id = Guid.Empty.ToString(), + AgentIds = new[] + { + agentId + }, + Assembly = typeof(AgentService).Assembly.FullName.Split(',').First(), + Name = "BotSharp.Core" + }; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs index 090059cd..28f9bdd2 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.cs @@ -1,4 +1,3 @@ -using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Repositories; using System.IO; diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index eebf4c00..fd427562 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -59,7 +59,7 @@ public partial class ConversationService var routing = _services.GetRequiredService(); var settings = _services.GetRequiredService(); - response = agentId == settings.AgentId ? + response = settings.AgentIds.Contains(agentId) ? await routing.InstructLoop(message) : await routing.ExecuteDirectly(agent, message); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs index 1f824dd8..25ee82f5 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/TokenStatistics.cs @@ -59,6 +59,14 @@ public class TokenStatistics : ITokenStatistics public void PrintStatistics() { + if (_timer == null) + { + _timer = Stopwatch.StartNew(); + } + else + { + _timer.Start(); + } var stats = $"Token Usage: {_promptTokenCount} prompt + {_completionTokenCount} completion = {Total} total tokens ({_timer.ElapsedMilliseconds / 1000f:f2}s). One-Way cost: {Cost:C4}, accumulated cost: {AccumulatedCost:C4}. [{_model}]"; #if DEBUG Console.WriteLine(stats, Color.DarkGray); diff --git a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs index e3b0df9f..05e54f28 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/InstructService.cs @@ -21,6 +21,16 @@ public partial class InstructService : IInstructService var agentService = _services.GetRequiredService(); Agent agent = await agentService.LoadAgent(agentId); + if (agent.Disabled) + { + var content = $"This agent ({agent.Name}) is disabled, please install the corresponding plugin ({agent.Plugin.Name}) to activate this agent."; + return new InstructResult + { + MessageId = message.MessageId, + Text = content + }; + } + // Trigger before completion hooks var hooks = _services.GetServices(); foreach (var hook in hooks) diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs index ba979e84..8d3302ec 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Agent.cs @@ -349,8 +349,8 @@ namespace BotSharp.Core.Repository { var route = _services.GetRequiredService(); query = filter.IsRouter.Value ? - query.Where(x => x.Id == route.AgentId) : - query.Where(x => x.Id != route.AgentId); + query.Where(x => route.AgentIds.Contains(x.Id)) : + query.Where(x => !route.AgentIds.Contains(x.Id)); } if (filter.IsEvaluator.HasValue) diff --git a/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs b/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs index c58ce3b3..a197b9ba 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs @@ -58,6 +58,11 @@ public class RouteToAgentFn : IFunctionCallback return false; } + if (targetAgent.Disabled) + { + return false; + } + var missingfield = HasMissingRequiredField(message, out var agentId); if (missingfield && message.CurrentAgentId != agentId) { diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs index d42d1349..984df62c 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs @@ -46,7 +46,20 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler { message.Content = inst.Question; } - ret = await routing.InvokeAgent(agentId, _dialogs); + + if (agent.Disabled) + { + var content = $"This agent ({agent.Name}) is disabled, please install the corresponding plugin ({agent.Plugin.Name}) to activate this agent."; + + message = RoleDialogModel.From(message, + role: AgentRole.Assistant, + content: content); + _dialogs.Add(message); + } + else + { + ret = await routing.InvokeAgent(agentId, _dialogs); + } var response = _dialogs.Last(); inst.Response = response.Content; diff --git a/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingAgentHook.cs b/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingAgentHook.cs index 2d3d3ab2..47cfcc38 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingAgentHook.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Hooks/RoutingAgentHook.cs @@ -7,7 +7,7 @@ namespace BotSharp.Core.Routing.Hooks; public class RoutingAgentHook : AgentHookBase { private readonly RoutingSettings _routingSetting; - public override string SelfId => _routingSetting.AgentId; + public override string SelfId => string.Empty; public RoutingAgentHook(IServiceProvider services, AgentSettings settings, RoutingSettings routingSetting) : base(services, settings) @@ -17,6 +17,10 @@ public class RoutingAgentHook : AgentHookBase public override bool OnInstructionLoaded(string template, Dictionary dict) { + if (!_routingSetting.AgentIds.Contains(_agent.Id)) + { + return base.OnInstructionLoaded(template, dict); + } dict["router"] = _agent; var routing = _services.GetRequiredService(); diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs index ee8c50f1..d70cc15f 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs @@ -64,7 +64,7 @@ public partial class RoutingService : IRoutingService public async Task InstructLoop(RoleDialogModel message) { var agentService = _services.GetRequiredService(); - _router = await agentService.LoadAgent(_settings.AgentId); + _router = await agentService.LoadAgent(message.CurrentAgentId); RoleDialogModel response = default; diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 505f794f..58378ba5 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -115,6 +115,8 @@ public class ConversationController : ControllerBase [FromRoute] string conversationId, [FromBody] NewMessageModel input) { + var inputMsg = new RoleDialogModel(AgentRole.User, input.Text); + var conv = _services.GetRequiredService(); conv.SetConversationId(conversationId, input.States); conv.States.SetState("channel", input.Channel) @@ -124,7 +126,7 @@ public class ConversationController : ControllerBase .SetState("sampling_factor", input.SamplingFactor); var response = new ChatResponseModel(); - var inputMsg = new RoleDialogModel(AgentRole.User, input.Text); + await conv.SendMessage(agentId, inputMsg, async msg => { diff --git a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs index 49d7d095..663dfd4a 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/ViewModels/Agents/AgentViewModel.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Functions.Models; +using BotSharp.Abstraction.Plugins.Models; using BotSharp.Abstraction.Routing.Models; using System.Text.Json.Serialization; @@ -18,6 +19,9 @@ public class AgentViewModel [JsonPropertyName("is_public")] public bool IsPublic { get; set; } + [JsonPropertyName("is_router")] + public bool IsRouter { get; set; } + [JsonPropertyName("allow_routing")] public bool AllowRouting { get; set; } public bool Disabled { get; set; } @@ -33,6 +37,8 @@ public class AgentViewModel [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public AgentLlmConfig? LlmConfig { get; set; } + public PluginDef Plugin { get; set; } + [JsonPropertyName("created_datetime")] public DateTime CreatedDateTime { get; set; } @@ -52,12 +58,14 @@ public class AgentViewModel Responses = agent.Responses, Samples = agent.Samples, IsPublic= agent.IsPublic, + IsRouter = agent.IsRouter, Disabled = agent.Disabled, IconUrl = agent.IconUrl, AllowRouting = agent.AllowRouting, Profiles = agent.Profiles, RoutingRules = agent.RoutingRules, LlmConfig = agent.LlmConfig, + Plugin = agent.Plugin, CreatedDateTime = agent.CreatedDateTime, UpdatedDateTime = agent.UpdatedDateTime }; diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/BotSharp.Plugin.MongoStorage.csproj b/src/Plugins/BotSharp.Plugin.MongoStorage/BotSharp.Plugin.MongoStorage.csproj index ac122c52..67b97096 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/BotSharp.Plugin.MongoStorage.csproj +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/BotSharp.Plugin.MongoStorage.csproj @@ -6,7 +6,7 @@ enable $(BotSharpVersion) $(GeneratePackageOnBuild) - True + $(GenerateDocumentationFile) diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs index 8f9e4ce8..8116d0eb 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Agent.cs @@ -304,8 +304,8 @@ public partial class MongoRepository { var route = _services.GetRequiredService(); query = filter.IsRouter.Value ? - query.Where(x => x.Id == route.AgentId) : - query.Where(x => x.Id != route.AgentId); + query.Where(x => route.AgentIds.Contains(x.Id)) : + query.Where(x => !route.AgentIds.Contains(x.Id)); } if (filter.IsEvaluator.HasValue) diff --git a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs index 11744d0d..d3ed4bdf 100644 --- a/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.RoutingSpeeder/RoutingConversationHook.cs @@ -53,7 +53,7 @@ public class RoutingConversationHook: ConversationHookBase public override async Task OnResponseGenerated(RoleDialogModel message) { var routerSettings = _services.GetRequiredService(); - bool saveFlag = message.CurrentAgentId != routerSettings.AgentId; + bool saveFlag = !routerSettings.AgentIds.Contains(message.CurrentAgentId); if (saveFlag) { diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Actions/ExecuteQueryAction.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Actions/ExecuteQueryAction.cs new file mode 100644 index 00000000..df9181af --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Actions/ExecuteQueryAction.cs @@ -0,0 +1,35 @@ +using BotSharp.Abstraction.Conversations.Models; +using BotSharp.Abstraction.Functions; +using BotSharp.Plugin.SqlHero.Models; +using BotSharp.Plugin.SqlHero.Settings; +using Dapper; +using MySqlConnector; +using System.Text.Json; +using System.Threading.Tasks; + +namespace BotSharp.Plugin.SqlHero.Actions; + +public class ExecuteQueryAction : IFunctionCallback +{ + public string Name => "execute_sql"; + + private readonly SqlHeroSetting _setting; + + public ExecuteQueryAction(SqlHeroSetting setting) + { + _setting = setting; + } + + public async Task Execute(RoleDialogModel message) + { + var args = JsonSerializer.Deserialize(message.FunctionArgs); + message.Content = "executed successully"; + /*using var connection = new MySqlConnection(_setting.MySqlConnectionString); + message.Content = JsonSerializer.Serialize(connection.Query(args.SqlStatement), new JsonSerializerOptions + { + WriteIndented = true, + });*/ + // message.StopCompletion = true; + return true; + } +} diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlHero.csproj b/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlHero.csproj new file mode 100644 index 00000000..ba521fa9 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlHero.csproj @@ -0,0 +1,18 @@ + + + + netstandard2.1 + enable + $(MSBuildProjectName.Replace(" ", "_")) + + + + + + + + + + + + diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Models/LlmInputArgs.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Models/LlmInputArgs.cs new file mode 100644 index 00000000..0d12b7af --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Models/LlmInputArgs.cs @@ -0,0 +1,9 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Plugin.SqlHero.Models; + +public class LlmInputArgs +{ + [JsonPropertyName("sql_statement")] + public string SqlStatement { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Settings/SqlHeroSetting.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Settings/SqlHeroSetting.cs new file mode 100644 index 00000000..4ffe9aab --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Settings/SqlHeroSetting.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Plugin.SqlHero.Settings; + +public class SqlHeroSetting +{ + public string MySqlConnectionString { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/SqlHeroPlugin.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/SqlHeroPlugin.cs new file mode 100644 index 00000000..b5cdd2a0 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/SqlHeroPlugin.cs @@ -0,0 +1,26 @@ +using BotSharp.Abstraction.Plugins; +using BotSharp.Plugin.SqlHero.Settings; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Drawing; +using System.Text.RegularExpressions; + +namespace BotSharp.Plugin.SqlHero; + +public class SqlHeroPlugin : IBotSharpPlugin +{ + public string Name => "SQL Hero"; + public string Description => "Convert the requirements into corresponding SQL statements and execute if needed"; + + public void RegisterDI(IServiceCollection services, IConfiguration config) + { + var settings = new SqlHeroSetting(); + config.Bind("SqlHero", settings); + services.AddSingleton(x => + { + Console.WriteLine($"Loaded SqlHero settings:: {Regex.Replace(settings.MySqlConnectionString, "password=.*?;", "password=******;")}", Color.Yellow); + return settings; + }); + } +} diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/agent.json b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/agent.json new file mode 100644 index 00000000..60e200aa --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/agent.json @@ -0,0 +1,9 @@ +{ + "id": "beda4c12-e1ec-4b4b-b328-3df4a6687c4f", + "name": "SQL Expert", + "description": "Convert the requirements into corresponding SQL statements according to the table structure and execute them if needed.", + "createdDateTime": "2023-11-15T13:49:00Z", + "updatedDateTime": "2023-11-15T13:49:00Z", + "isPublic": false, + "allowRouting": false +} \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions.json b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions.json new file mode 100644 index 00000000..e09e23a1 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions.json @@ -0,0 +1,14 @@ +[{ + "name": "execute_sql", + "description": "generate sql statement and execute the query.", + "parameters": { + "type": "object", + "properties": { + "sql_statement": { + "type": "string", + "description": "SQL statement" + } + }, + "required": ["sql_statement"] + } +}] \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/instruction.liquid b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/instruction.liquid new file mode 100644 index 00000000..24966b0e --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/instruction.liquid @@ -0,0 +1,181 @@ +You are a SQL Expert who knows how to convert business requirements to SQL expressions. + +Follow these steps: +1: Look at the table DDL defintions especially for the CONSTRAINT and FOREIGN KEY REFERENCES. +2: Translate user requirements into SQL statements step by step. +3: Double check, don't miss any requirements, all the parameters must have values. +4: Confirm with the user whether to execute the sql statement. + If user confirms to run the query, call function execute_sql to execute it. + + +Table structure guideline: +Table with prefix "data_" represents system enum item. +Table with prefix "client_" represents client specific configuration or client dataset. + +Below are the table DDL information in JSON format: + +CREATE TABLE `data_ServiceCategory` ( + `Id` smallint(6) NOT NULL, + `Name` varchar(50) NOT NULL, + `IsClientVisible` tinyint(1) NOT NULL DEFAULT '1', + `IsTurnService` tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`Id`), + KEY `ID_data_ServiceCategory_Name` (`Name`) +) + +CREATE TABLE `data_ServiceType` ( + `Id` smallint(6) NOT NULL, + `Name` varchar(50) NOT NULL, + `ServcieCategoryId` smallint(6) NOT NULL, + PRIMARY KEY (`Id`), + KEY `FK_data_ServiceType_ServcieCategoryId` (`ServcieCategoryId`), + CONSTRAINT `FK_data_ServiceType_ServcieCategoryId` FOREIGN KEY (`ServcieCategoryId`) REFERENCES `data_ServiceCategory` (`Id`) +) + +CREATE TABLE `data_ServiceCode` ( + `Id` smallint(6) NOT NULL, + `Name` varchar(100) NOT NULL, + `ServiceTypeId` smallint(6) NOT NULL, + `AbbrName` varchar(100) DEFAULT NULL, + `IsClientVisible` tinyint(1) NOT NULL DEFAULT '1', + PRIMARY KEY (`Id`), + KEY `FK_data_ServiceCode_ServiceCodeId` (`ServiceTypeId`), + KEY `FK_data_ServiceCode_SkillLevelId` (`SkillLevelId`), + CONSTRAINT `FK_data_ServiceCode_ServiceCodeId` FOREIGN KEY (`ServiceTypeId`) REFERENCES `data_ServiceType` (`Id`), + CONSTRAINT `FK_data_ServiceCode_SkillLevelId` FOREIGN KEY (`SkillLevelId`) REFERENCES `data_SkillLevel` (`Id`) +) + +CREATE TABLE `client_ServiceCode` ( + `Id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `ClientServiceTypeId` int(10) unsigned NOT NULL, + `ServiceCodeId` smallint(6) NOT NULL, + `IsContract` tinyint(1) DEFAULT '0', + `IsHidden` tinyint(1) DEFAULT '0', + `IsPersonal` tinyint(1) NOT NULL DEFAULT '0', + `IsHiddenForClient` tinyint(1) NOT NULL DEFAULT '0', + PRIMARY KEY (`Id`), + UNIQUE KEY `UK_client_ServiceCode` (`ClientServiceTypeId`,`ServiceCodeId`,`IsPersonal`), + KEY `FK_client_ServiceCode_ServiceCodeId_idx` (`ServiceCodeId`), + CONSTRAINT `FK_client_ServiceCode_ClientServiceTypeId` FOREIGN KEY (`ClientServiceTypeId`) REFERENCES `client_ServiceType` (`Id`), + CONSTRAINT `FK_client_ServiceCode_ServiceCodeId` FOREIGN KEY (`ServiceCodeId`) REFERENCES `data_ServiceCode` (`Id`) +) + +CREATE TABLE `client_ServiceType` ( + `Id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `ClientServiceCategoryId` int(10) unsigned NOT NULL, + `ServiceTypeId` smallint(6) NOT NULL, + `IsHidden` tinyint(1) DEFAULT '0', + PRIMARY KEY (`Id`), + UNIQUE KEY `UK_client_ServiceType` (`ClientServiceCategoryId`,`ServiceTypeId`), + KEY `FK_client_ServiceType_ServiceTypeId` (`ServiceTypeId`), + CONSTRAINT `FK_client_ServiceType_ClientServiceCategoryId` FOREIGN KEY (`ClientServiceCategoryId`) REFERENCES `client_ServiceCategory` (`Id`), + CONSTRAINT `FK_client_ServiceType_ServiceTypeId` FOREIGN KEY (`ServiceTypeId`) REFERENCES `data_ServiceType` (`Id`) +) + +CREATE TABLE `client_ServiceCategory` ( + `Id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `ClientId` int(10) unsigned NOT NULL, + `ServiceCategoryId` smallint(6) NOT NULL, + `IsHidden` tinyint(1) DEFAULT '0', + PRIMARY KEY (`Id`), + UNIQUE KEY `UK_client_ServiceCategory` (`ClientId`,`ServiceCategoryId`), + KEY `FK_client_ServiceCategory_ServiceCategoryId` (`ServiceCategoryId`), + CONSTRAINT `FK_client_ServiceCategory_ClientId` FOREIGN KEY (`ClientId`) REFERENCES `client_Profile` (`Id`), + CONSTRAINT `FK_client_ServiceCategory_ServiceCategoryId` FOREIGN KEY (`ServiceCategoryId`) REFERENCES `data_ServiceCategory` (`Id`) +) + +CREATE TABLE `client_Profile` ( + `Id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `Name` varchar(100) NOT NULL, + `Active` tinyint(1) NOT NULL DEFAULT '1', + `ClientCode` varchar(20) DEFAULT NULL, + PRIMARY KEY (`Id`), + UNIQUE KEY `UK_client_Profile` (`ClientCode`), + KEY `FK_client_Location_CustomerTypeId` (`CustomerTypeId`), + KEY `IDX_client_Profile_BusinessTypeId` (`BusinessTypeId`), + CONSTRAINT `FK_client_Location_CustomerTypeId` FOREIGN KEY (`CustomerTypeId`) REFERENCES `data_CustomerType` (`Id`) +) + +CREATE TABLE `client_WOReactive` ( + `Id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `WONum` varchar(40) NOT NULL, + `LocationId` int(10) unsigned NOT NULL, + `AffiliateId` int(10) unsigned DEFAULT NULL, + `ServiceCodeId` smallint(6) NOT NULL, + `StatusId` smallint(6) NOT NULL, + `ClientNTE` decimal(18,2) DEFAULT NULL, + `ReferWONum` varchar(512) DEFAULT NULL, + `WOCategoryId` smallint(6) DEFAULT NULL, + `WOTypeId` smallint(6) DEFAULT NULL, + `WOServiceCategoryId` smallint(6) DEFAULT NULL, + `WOServiceTypeId` smallint(6) DEFAULT NULL, + `WOClientId` int(10) unsigned DEFAULT NULL + PRIMARY KEY (`Id`), + CONSTRAINT `FK_client_WOReactive_ServiceCodeId` FOREIGN KEY (`ServiceCodeId`) REFERENCES `data_ServiceCode` (`Id`), + CONSTRAINT `FK_client_WOReactive_StatusId` FOREIGN KEY (`StatusId`) REFERENCES `data_WOStatus` (`Id`), + CONSTRAINT `FK_client_WOReactive_WOCategoryId` FOREIGN KEY (`WOCategoryId`) REFERENCES `data_WOCategory` (`Id`), + CONSTRAINT `FK_client_WOReactive_WOTypeId` FOREIGN KEY (`WOTypeId`) REFERENCES `data_WOType` (`Id`) +) + +CREATE TABLE `client_ServiceCodeNTE` ( + `Id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `ClientServiceCodeId` int(10) unsigned NOT NULL, + `ClientNTE` decimal(18,2) DEFAULT NULL, + `AffiliateNTE` decimal(18,2) DEFAULT NULL, + PRIMARY KEY (`Id`), + CONSTRAINT `FK_client_ServiceCodeNTE_ClientServiceCodeId` FOREIGN KEY (`ClientServiceCodeId`) REFERENCES `client_ServiceCode` (`Id`) +) + +CREATE TABLE `data_Priority` ( + `Id` smallint(6) NOT NULL, + `Name` varchar(50) NOT NULL, + `AbbrName` varchar(50) NOT NULL, + PRIMARY KEY (`Id`) + ) + + CREATE TABLE `data_Trade` ( + `Id` smallint(6) NOT NULL, + `Name` varchar(50) NOT NULL, + PRIMARY KEY (`Id`), + KEY `ID_data_Trade_Name` (`Name`) + ) + + CREATE TABLE `data_TradeServiceCode` ( + `Id` int(10) NOT NULL AUTO_INCREMENT, + `TradeId` smallint(6) NOT NULL, + `ServiceCodeId` smallint(6) NOT NULL, + PRIMARY KEY (`Id`), + UNIQUE KEY `UK_data_TradeServiceCode` (`ServiceCodeId`,`CustomerTypeId`), + KEY `FK_data_TradeServiceCode_TradeId` (`TradeId`), + KEY `FK_data_TradeServiceCode_ServiceCodeId` (`ServiceCodeId`), + CONSTRAINT `FK_data_TradeServiceCode_ServiceCodeId` FOREIGN KEY (`ServiceCodeId`) REFERENCES `data_ServiceCode` (`Id`), + CONSTRAINT `FK_data_TradeServiceCode_TradeId` FOREIGN KEY (`TradeId`) REFERENCES `data_Trade` (`Id`) + ) + +CREATE TABLE `Client_ServiceCodePriority` ( + `Id` int(10) unsigned NOT NULL AUTO_INCREMENT, + `ClientServiceCodeId` int(10) unsigned NOT NULL, + `PriorityId` smallint(6) NOT NULL, + PRIMARY KEY (`Id`), + CONSTRAINT `FK_Client_ServiceCodePriority_ClientServiceCodeId` FOREIGN KEY (`ClientServiceCodeId`) REFERENCES `client_ServiceCode` (`Id`), + CONSTRAINT `FK_Client_ServiceCodePriority_PriorityId` FOREIGN KEY (`PriorityId`) REFERENCES `data_Priority` (`Id`) +) + + +==== +User Task: + +Find all the NTE for all the service combination of client 'IH'. +Out put the service combination and NTE and priority. + +==== +Examples: + +user: Create a new service category named 'DSC Equipment' for client 'Signet'. +assistant: Steps: 1. Check if the service category is in data_ServiceCategory. + 2. If exists, get the service category id from data_ServiceCategory. + 3. If not exists, insert a new record and get the id. + 4. Insert a new record to client_ServiceCategory based on the FOREIGN KEY and REFERENCES. + +user: Create a new service type and service code. +assistant: You can follow the process same as service category creation. \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs index 374a00ad..f01a7e29 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs @@ -1,3 +1,4 @@ +using BotSharp.Plugin.Twilio.Settings; using Twilio.Jwt.AccessToken; using Token = Twilio.Jwt.AccessToken.Token; @@ -47,7 +48,7 @@ public class TwilioService public VoiceResponse ReturnInstructions(string message) { - var routingSetting = _services.GetRequiredService(); + var twilioSetting = _services.GetRequiredService(); var response = new VoiceResponse(); var gather = new Gather() @@ -56,7 +57,7 @@ public class TwilioService { Gather.InputEnum.Speech }, - Action = new Uri($"{_settings.CallbackHost}/twilio/voice/{routingSetting.AgentId}") + Action = new Uri($"{_settings.CallbackHost}/twilio/voice/{twilioSetting.AgentId}") }; gather.Say(message); response.Append(gather); @@ -76,13 +77,13 @@ public class TwilioService public VoiceResponse HoldOn(int interval, string message = null) { - var routingSetting = _services.GetRequiredService(); + var twilioSetting = _services.GetRequiredService(); var response = new VoiceResponse(); var gather = new Gather() { Input = new List() { Gather.InputEnum.Speech }, - Action = new Uri($"{_settings.CallbackHost}/twilio/voice/{routingSetting.AgentId}"), + Action = new Uri($"{_settings.CallbackHost}/twilio/voice/{twilioSetting.AgentId}"), ActionOnEmptyResult = true }; if (!string.IsNullOrEmpty(message)) diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs b/src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs index 3da775cf..c7170ee2 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs @@ -9,4 +9,5 @@ public class TwilioSetting public string ApiKeySID { get; set; } public string ApiSecret { get; set; } public string CallbackHost { get; set; } + public string AgentId { get; set; } } diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index 2cb7389a..c500511a 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -38,7 +38,9 @@ ], "Router": { - "AgentId": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a", + "AgentIds": [ + "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a" + ], "Planner": "NaivePlanner" }, @@ -107,7 +109,8 @@ "PhoneNumber": "+1", "AccountSID": "", "AuthToken": "", - "CallbackHost": "https://" + "CallbackHost": "https://", + "AgentId": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a" }, "Database": {