From 9a1653635903d08e1c1eb8692b560c12251d2576 Mon Sep 17 00:00:00 2001 From: Yanan Wang <103942@smsassist.com> Date: Tue, 7 Nov 2023 16:22:49 -0600 Subject: [PATCH 1/5] Add MultiSelectTemplate --- .../RichContent/Template/MultiSelectTemplate.cs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/MultiSelectTemplate.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/MultiSelectTemplate.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/MultiSelectTemplate.cs new file mode 100644 index 00000000..d0ec8ffb --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/MultiSelectTemplate.cs @@ -0,0 +1,17 @@ + +namespace BotSharp.Abstraction.Messaging.Models.RichContent.Template +{ + public class MultiSelectTemplate : TextMessage + { + [JsonPropertyName("template_type")] + public string TemplateType => "multi-select"; + public List Options { get; set; } = new List(); + } + + public class OptionElement + { + public string Title { get; set; } = string.Empty; + public string Type { get; set; } = string.Empty; + public string? Payload { get; set; } + } +} \ No newline at end of file From 3483fd42a1a7305a4c52756b8963c5fa4db31410 Mon Sep 17 00:00:00 2001 From: hchen Date: Wed, 8 Nov 2023 16:27:42 -0600 Subject: [PATCH 2/5] Adjust rich content data structure. --- .../Conversations/Models/RoleDialogModel.cs | 4 +++- .../Functions/Models/FunctionCallFromLlm.cs | 2 ++ .../Messaging/Models/IMessageTemplate.cs | 6 ++++++ .../Models/RichContent/QuickReplyElement.cs | 1 - .../Models/RichContent/QuickReplyMessage.cs | 4 +++- .../Messaging/Models/RichContent/RichContent.cs | 2 +- .../RichContent/Template/AttachmentTemplate.cs | 9 --------- .../RichContent/Template/ButtonTemplate.cs | 4 +++- .../RichContent/Template/MultiSelectTemplate.cs | 4 +++- .../RichContent/Template/TemplateMessage.cs | 11 ----------- .../Messaging/Models/RichContent/TextMessage.cs | 9 ++++----- .../Models/RichContentJsonConverter .cs | 16 ++++++++++++++++ .../BotSharp.Core/BotSharp.Core.csproj | 1 - .../Services/ConversationService.SendMessage.cs | 3 ++- .../BotSharp.Core/Planning/InstructExecutor.cs | 10 ++++++++++ .../Handlers/RouteToAgentRoutingHandler.cs | 4 ++++ .../BotSharp.Core/Routing/RoutingService.cs | 4 +++- 17 files changed, 60 insertions(+), 34 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Messaging/Models/IMessageTemplate.cs delete mode 100644 src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/AttachmentTemplate.cs delete mode 100644 src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/TemplateMessage.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContentJsonConverter .cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs index afae2e00..804e21ef 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs @@ -1,4 +1,6 @@ using BotSharp.Abstraction.Functions.Models; +using BotSharp.Abstraction.Messaging.Models; +using BotSharp.Abstraction.Messaging.Models.RichContent; namespace BotSharp.Abstraction.Conversations.Models; @@ -35,7 +37,7 @@ public class RoleDialogModel : ITrackableMessage [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public object Data { get; set; } [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public object? RichContent { get; set; } + public RichContent? RichContent { get; set; } /// /// Stop conversation completion diff --git a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs index 7ea31a47..92b37efa 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Functions/Models/FunctionCallFromLlm.cs @@ -12,6 +12,8 @@ public class FunctionCallFromLlm : RoutingArgs [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public JsonDocument? Arguments { get; set; } + public bool IsExecutionOnce { get; set; } + public override string ToString() { var route = string.IsNullOrEmpty(AgentName) ? "" : $""; diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/IMessageTemplate.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/IMessageTemplate.cs new file mode 100644 index 00000000..357dc937 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/IMessageTemplate.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Abstraction.Messaging.Models; + +public interface IMessageTemplate +{ + string Text { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/QuickReplyElement.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/QuickReplyElement.cs index 1be8f208..cea110f1 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/QuickReplyElement.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/QuickReplyElement.cs @@ -9,7 +9,6 @@ namespace BotSharp.Abstraction.Messaging.Models.RichContent public string? Payload { get; set; } [JsonPropertyName("image_url")] public string? ImageUrl { get; set; } - [JsonPropertyName("postback_url")] public string? PostBackUrl { get; set; } } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/QuickReplyMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/QuickReplyMessage.cs index b14b27e1..b7c273e8 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/QuickReplyMessage.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/QuickReplyMessage.cs @@ -1,8 +1,10 @@ namespace BotSharp.Abstraction.Messaging.Models.RichContent { - public class QuickReplyMessage : TextMessage + public class QuickReplyMessage : IMessageTemplate { + public string Text { get; set; } = string.Empty; + [JsonPropertyName("quick_replies")] public List QuickReplies { get; set; } = new List(); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/RichContent.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/RichContent.cs index 221dc96d..18afce0e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/RichContent.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/RichContent.cs @@ -1,6 +1,6 @@ namespace BotSharp.Abstraction.Messaging.Models.RichContent { - public class RichContent + public class RichContent where T : IMessageTemplate { public Recipient Recipient { get; set; } = new Recipient(); /// diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/AttachmentTemplate.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/AttachmentTemplate.cs deleted file mode 100644 index 89f2137e..00000000 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/AttachmentTemplate.cs +++ /dev/null @@ -1,9 +0,0 @@ - -namespace BotSharp.Abstraction.Messaging.Models.RichContent.Template -{ - public class AttachmentTemplate - { - public string Type => "template"; - public T Payload { get; set; } - } -} diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ButtonTemplate.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ButtonTemplate.cs index f610a682..9119901a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ButtonTemplate.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/ButtonTemplate.cs @@ -1,8 +1,10 @@ namespace BotSharp.Abstraction.Messaging.Models.RichContent.Template { - public class ButtonTemplate : TextMessage + public class ButtonTemplate : IMessageTemplate { + public string Text { get; set; } = string.Empty; + [JsonPropertyName("template_type")] public string TemplateType => "button"; public List Buttons { get; set; } = new List(); diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/MultiSelectTemplate.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/MultiSelectTemplate.cs index d0ec8ffb..caab4ca3 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/MultiSelectTemplate.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/MultiSelectTemplate.cs @@ -1,8 +1,10 @@ namespace BotSharp.Abstraction.Messaging.Models.RichContent.Template { - public class MultiSelectTemplate : TextMessage + public class MultiSelectTemplate : IMessageTemplate { + public string Text { get; set; } = string.Empty; + [JsonPropertyName("template_type")] public string TemplateType => "multi-select"; public List Options { get; set; } = new List(); diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/TemplateMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/TemplateMessage.cs deleted file mode 100644 index 77b4a55f..00000000 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/Template/TemplateMessage.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; - -namespace BotSharp.Abstraction.Messaging.Models.RichContent.Template -{ - public class TemplateMessage - { - public T Attachment { get; set; } - } -} diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/TextMessage.cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/TextMessage.cs index 94d85894..1cc8918a 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/TextMessage.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContent/TextMessage.cs @@ -1,8 +1,7 @@ -namespace BotSharp.Abstraction.Messaging.Models.RichContent +namespace BotSharp.Abstraction.Messaging.Models.RichContent; + +public class TextMessage : IMessageTemplate { - public class TextMessage - { - public string Text { get; set; } = string.Empty; - } + public string Text { get; set; } = string.Empty; } \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContentJsonConverter .cs b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContentJsonConverter .cs new file mode 100644 index 00000000..d194d687 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Messaging/Models/RichContentJsonConverter .cs @@ -0,0 +1,16 @@ +using System.Text.Json; + +namespace BotSharp.Abstraction.Messaging.Models; + +public class RichContentJsonConverter : JsonConverter +{ + public override IMessageTemplate? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + public override void Write(Utf8JsonWriter writer, IMessageTemplate value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, value, value.GetType(), options); + } +} diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index 1b789f1f..8099a315 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -56,7 +56,6 @@ - diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index 19ddfee9..2e087cf2 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Messaging.Models; using BotSharp.Abstraction.Messaging.Models.RichContent; using BotSharp.Abstraction.Routing; using BotSharp.Abstraction.Routing.Settings; @@ -102,7 +103,7 @@ public partial class ConversationService // Only read content from RichContent for UI rendering. When richContent is null, create a basic text message for richContent. var state = _services.GetRequiredService(); - response.RichContent = response.RichContent ?? new RichContent + response.RichContent = response.RichContent ?? new RichContent { Recipient = new Recipient { Id = state.GetConversationId() }, Message = new TextMessage { Text = response.Content } diff --git a/src/Infrastructure/BotSharp.Core/Planning/InstructExecutor.cs b/src/Infrastructure/BotSharp.Core/Planning/InstructExecutor.cs index 0d02cd95..47d6ccce 100644 --- a/src/Infrastructure/BotSharp.Core/Planning/InstructExecutor.cs +++ b/src/Infrastructure/BotSharp.Core/Planning/InstructExecutor.cs @@ -1,4 +1,6 @@ using BotSharp.Abstraction.Functions.Models; +using BotSharp.Abstraction.Messaging.Models; +using BotSharp.Abstraction.Messaging.Models.RichContent; using BotSharp.Abstraction.Planning; using BotSharp.Abstraction.Routing; @@ -33,6 +35,14 @@ public class InstructExecutor : IExecutor response.MessageId = message.MessageId; response.Instruction = inst; + // Process rich content + if (response.RichContent != null && + response.RichContent is RichContent template && + string.IsNullOrEmpty(template.Message.Text)) + { + template.Message.Text = response.Content; + } + return response; } } diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs index 0f03955a..39b53285 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs @@ -36,6 +36,10 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler var ret = await function.Execute(message); var agentId = context.GetCurrentAgentId(); + if (inst.IsExecutionOnce) + { + message.Content = inst.Question; + } ret = await routing.InvokeAgent(agentId, _dialogs); var response = _dialogs.Last(); diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs index bba2c30c..1ce7cfb7 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs @@ -44,7 +44,9 @@ public partial class RoutingService : IRoutingService Function = "route_to_agent", Question = message.Content, Reason = message.Content, - AgentName = agent.Name + AgentName = agent.Name, + OriginalAgent = agent.Name, + IsExecutionOnce = true }; var result = await handler.Handle(this, inst, message); From ad606e6710d06b4ae095179dc2554021c3f4f6f1 Mon Sep 17 00:00:00 2001 From: hchen Date: Wed, 8 Nov 2023 16:47:23 -0600 Subject: [PATCH 3/5] Fix rich content text in InstructExecutor. --- .../Services/ConversationService.SendMessage.cs | 8 ++++++++ .../BotSharp.Core/Planning/InstructExecutor.cs | 10 ---------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index 2e087cf2..f67307c4 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -101,6 +101,14 @@ public partial class ConversationService _logger.LogInformation(text); #endif + // Process rich content + if (response.RichContent != null && + response.RichContent is RichContent template && + string.IsNullOrEmpty(template.Message.Text)) + { + template.Message.Text = response.Content; + } + // Only read content from RichContent for UI rendering. When richContent is null, create a basic text message for richContent. var state = _services.GetRequiredService(); response.RichContent = response.RichContent ?? new RichContent diff --git a/src/Infrastructure/BotSharp.Core/Planning/InstructExecutor.cs b/src/Infrastructure/BotSharp.Core/Planning/InstructExecutor.cs index 47d6ccce..0d02cd95 100644 --- a/src/Infrastructure/BotSharp.Core/Planning/InstructExecutor.cs +++ b/src/Infrastructure/BotSharp.Core/Planning/InstructExecutor.cs @@ -1,6 +1,4 @@ using BotSharp.Abstraction.Functions.Models; -using BotSharp.Abstraction.Messaging.Models; -using BotSharp.Abstraction.Messaging.Models.RichContent; using BotSharp.Abstraction.Planning; using BotSharp.Abstraction.Routing; @@ -35,14 +33,6 @@ public class InstructExecutor : IExecutor response.MessageId = message.MessageId; response.Instruction = inst; - // Process rich content - if (response.RichContent != null && - response.RichContent is RichContent template && - string.IsNullOrEmpty(template.Message.Text)) - { - template.Message.Text = response.Content; - } - return response; } } From 74259ee6bc3144042cb58ddd261feba8bd93bd18 Mon Sep 17 00:00:00 2001 From: hchen Date: Thu, 9 Nov 2023 14:02:57 -0600 Subject: [PATCH 4/5] Add Twilio plugin. --- BotSharp.sln | 11 ++ README.md | 37 +++++- .../BotSharp.Plugin.Twilio.csproj | 19 +++ .../Controllers/TwilioVoiceController.cs | 115 ++++++++++++++++++ .../Settings/TwilioSetting.cs | 9 ++ .../BotSharp.Plugin.Twilio/TwilioPlugin.cs | 16 +++ src/Plugins/BotSharp.Plugin.Twilio/Using.cs | 17 +++ src/WebStarter/WebStarter.csproj | 1 + src/WebStarter/appsettings.json | 8 ++ 9 files changed, 227 insertions(+), 6 deletions(-) create mode 100644 src/Plugins/BotSharp.Plugin.Twilio/BotSharp.Plugin.Twilio.csproj create mode 100644 src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs create mode 100644 src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs create mode 100644 src/Plugins/BotSharp.Plugin.Twilio/TwilioPlugin.cs create mode 100644 src/Plugins/BotSharp.Plugin.Twilio/Using.cs diff --git a/BotSharp.sln b/BotSharp.sln index d0a61d4d..29e7cb8e 100644 --- a/BotSharp.sln +++ b/BotSharp.sln @@ -63,6 +63,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.MongoStorag EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.GoogleAI", "src\Plugins\BotSharp.Plugin.GoogleAI\BotSharp.Plugin.GoogleAI.csproj", "{8BC29F8A-78D6-422C-B522-10687ADC38ED}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Plugin.Twilio", "src\Plugins\BotSharp.Plugin.Twilio\BotSharp.Plugin.Twilio.csproj", "{E627F1E3-BE03-443A-83A2-86A855A278EB}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -223,6 +225,14 @@ Global {8BC29F8A-78D6-422C-B522-10687ADC38ED}.Release|Any CPU.Build.0 = Release|Any CPU {8BC29F8A-78D6-422C-B522-10687ADC38ED}.Release|x64.ActiveCfg = Release|Any CPU {8BC29F8A-78D6-422C-B522-10687ADC38ED}.Release|x64.Build.0 = Release|Any CPU + {E627F1E3-BE03-443A-83A2-86A855A278EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E627F1E3-BE03-443A-83A2-86A855A278EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E627F1E3-BE03-443A-83A2-86A855A278EB}.Debug|x64.ActiveCfg = Debug|Any CPU + {E627F1E3-BE03-443A-83A2-86A855A278EB}.Debug|x64.Build.0 = Debug|Any CPU + {E627F1E3-BE03-443A-83A2-86A855A278EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E627F1E3-BE03-443A-83A2-86A855A278EB}.Release|Any CPU.Build.0 = Release|Any CPU + {E627F1E3-BE03-443A-83A2-86A855A278EB}.Release|x64.ActiveCfg = Release|Any CPU + {E627F1E3-BE03-443A-83A2-86A855A278EB}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -254,6 +264,7 @@ Global {5CD330E1-9E5A-4112-8346-6E31CA98EF78} = {2635EC9B-2E5F-4313-AC21-0B847F31F36C} {DB3DE37B-1208-4ED3-9615-A52AD0AAD69C} = {5CD330E1-9E5A-4112-8346-6E31CA98EF78} {8BC29F8A-78D6-422C-B522-10687ADC38ED} = {D5293208-2BEF-42FC-A64C-5954F61720BA} + {E627F1E3-BE03-443A-83A2-86A855A278EB} = {64264688-0F5C-4AB0-8F2B-B59B717CCE00} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A9969D89-C98B-40A5-A12B-FC87E55B3A19} diff --git a/README.md b/README.md index 7c000621..af398158 100644 --- a/README.md +++ b/README.md @@ -39,14 +39,39 @@ It's written in C# running on .Net Core that is full cross-platform framework, t ``` 2. Run UI project, reference to [Chatbot UI](src/Plugins/BotSharp.Plugin.ChatbotUI/Chatbot-UI.md). -### Extension Libraries +### Plugins -BotSharp uses component design, the kernel is kept to a minimum, and business functions are implemented by external components. The modular design also allows contributors to better participate. +BotSharp uses component design, the kernel is kept to a minimum, and business functions are implemented by external components. The modular design also allows contributors to better participate. Below are the bulit-in plugins: -* Chatbot UI connector. -* A channel module of BotSharp for Facebook Messenger. -* A channel module of BotSharp for Tencent Wechat. -* A channel module of BotSharp for Telegram. +#### Data Storages +- BotSharp.Plugin.MongoStorage + +#### LLMs +- BotSharp.Plugin.AzureOpenAI +- BotSharp.Plugin.GoogleAI +- BotSharp.Plugin.MetaAI +- BotSharp.Plugin.HuggingFace +- BotSharp.Plugin.LLamaSharp + +#### Messaging / Channel +- BotSharp.OpenAPI +- BotSharp.Plugin.MetaMessenger +- BotSharp.Plugin.Twilio +- BotSharp.Plugin.WeChat + +#### RAGS +- BotSharp.Plugin.KnowledgeBase +- BotSharp.Plugin.Qdrant + +#### Visions +- BotSharp.Plugin.PaddleSharp + +#### Tools +- BotSharp.Plugin.RoutingSpeeder +- BotSharp.Plugin.PizzaBot + +#### UIs +- BotSharp.Plugin.ChatbotUI ### Documents diff --git a/src/Plugins/BotSharp.Plugin.Twilio/BotSharp.Plugin.Twilio.csproj b/src/Plugins/BotSharp.Plugin.Twilio/BotSharp.Plugin.Twilio.csproj new file mode 100644 index 00000000..99c35ca7 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Twilio/BotSharp.Plugin.Twilio.csproj @@ -0,0 +1,19 @@ + + + + net6.0 + $(LangVersion) + $(BotSharpVersion) + $(GeneratePackageOnBuild) + + + + + + + + + + + + diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs new file mode 100644 index 00000000..7913c056 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs @@ -0,0 +1,115 @@ +using BotSharp.Abstraction.Conversations; +using BotSharp.Plugin.Twilio.Settings; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using System.Collections.Generic; +using System; +using Twilio.AspNet.Common; +using Twilio.AspNet.Core; +using Twilio.TwiML; +using Twilio.TwiML.Voice; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using BotSharp.Abstraction.Routing.Settings; +using Twilio.Http; +using Twilio.TwiML.Messaging; +using BotSharp.Abstraction.Agents.Enums; +using BotSharp.Abstraction.Conversations.Models; + +namespace BotSharp.Plugin.Twilio.Controllers; + +[AllowAnonymous] +public class TwilioVoiceController : TwilioController +{ + private readonly TwilioSetting _settings; + private readonly IServiceProvider _services; + + public TwilioVoiceController(TwilioSetting settings, IServiceProvider services) + { + _settings = settings; + _services = services; + } + + [HttpPost("/twilio/voice/welcome")] + public async Task StartConversation(VoiceRequest request) + { + string sessionId = $"TwilioVoice_{request.CallSid}"; + var response = ReturnInstructions("Hello, how may I help you?"); + return TwiML(response); + } + + [HttpPost("/twilio/voice/{agentId}")] + public async Task ReceivedVoiceMessage([FromRoute] string agentId, VoiceRequest input) + { + string sessionId = $"TwilioVoice_{input.CallSid}"; + + var conv = _services.GetRequiredService(); + conv.SetConversationId(sessionId, new List + { + "channel=phone", + $"calling_phone={input.DialCallSid}" + }); + + VoiceResponse response = default; + + var result = await conv.SendMessage(agentId, new RoleDialogModel(AgentRole.User, input.SpeechResult), async msg => + { + response = HangUp(msg.Content); + }, async functionExecuting => + { + }, async functionExecuted => + { + }); + + return TwiML(response); + } + + private VoiceResponse ReturnInstructions(string message) + { + var routingSetting = _services.GetRequiredService(); + + var response = new VoiceResponse(); + var gather = new Gather() + { + Input = new List() + { + Gather.InputEnum.Speech + }, + Action = new Uri($"{_settings.CallbackHost}/twilio/voice/{routingSetting.RouterId}") + }; + gather.Say(message); + response.Append(gather); + return response; + } + + private VoiceResponse HangUp(string message) + { + var response = new VoiceResponse(); + if (!string.IsNullOrEmpty(message)) + { + response.Say(message); + } + response.Hangup(); + return response; + } + + private VoiceResponse HoldOn(int interval, string message = null) + { + var routingSetting = _services.GetRequiredService(); + + var response = new VoiceResponse(); + var gather = new Gather() + { + Input = new List() { Gather.InputEnum.Speech }, + Action = new Uri($"{_settings.CallbackHost}/twilio/voice/{routingSetting.RouterId}"), + ActionOnEmptyResult = true + }; + if (!string.IsNullOrEmpty(message)) + { + gather.Say(message); + } + gather.Pause(interval); + response.Append(gather); + return response; + } +} diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs b/src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs new file mode 100644 index 00000000..a1048b8c --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Twilio/Settings/TwilioSetting.cs @@ -0,0 +1,9 @@ +namespace BotSharp.Plugin.Twilio.Settings; + +public class TwilioSetting +{ + public string PhoneNumber { get; set; } + public string AccountSID { get; set; } + public string AuthToken { get; set; } + public string CallbackHost { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.Twilio/TwilioPlugin.cs b/src/Plugins/BotSharp.Plugin.Twilio/TwilioPlugin.cs new file mode 100644 index 00000000..ff8884b2 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Twilio/TwilioPlugin.cs @@ -0,0 +1,16 @@ +using BotSharp.Abstraction.Plugins; +using BotSharp.Plugin.Twilio.Settings; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace BotSharp.Plugin.Twilio; + +public class TwilioPlugin : IBotSharpPlugin +{ + public void RegisterDI(IServiceCollection services, IConfiguration config) + { + var setting = new TwilioSetting(); + config.Bind("Twilio", setting); + services.AddSingleton(setting); + } +} diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Using.cs b/src/Plugins/BotSharp.Plugin.Twilio/Using.cs new file mode 100644 index 00000000..5026d491 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Twilio/Using.cs @@ -0,0 +1,17 @@ +global using System; +global using System.Collections.Generic; +global using System.Text; +global using System.Threading.Tasks; +global using System.Linq; +global using System.Text.Json; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Configuration; +global using Microsoft.Extensions.Logging; +global using BotSharp.Abstraction.Functions; +global using BotSharp.Abstraction.Functions.Models; +global using BotSharp.Abstraction.Agents.Enums; +global using BotSharp.Abstraction.Models; +global using BotSharp.Abstraction.Agents; +global using BotSharp.Abstraction.Conversations; +global using BotSharp.Abstraction.Plugins; +global using BotSharp.Abstraction.Conversations.Models; \ No newline at end of file diff --git a/src/WebStarter/WebStarter.csproj b/src/WebStarter/WebStarter.csproj index 3cb6b16b..8c14beff 100644 --- a/src/WebStarter/WebStarter.csproj +++ b/src/WebStarter/WebStarter.csproj @@ -63,6 +63,7 @@ + diff --git a/src/WebStarter/appsettings.json b/src/WebStarter/appsettings.json index ed51f673..157755ea 100644 --- a/src/WebStarter/appsettings.json +++ b/src/WebStarter/appsettings.json @@ -89,6 +89,13 @@ "PageAccessToken": "" }, + "Twilio": { + "PhoneNumber": "+1", + "AccountSID": "", + "AuthToken": "", + "CallbackHost": "https://" + }, + "Database": { "Default": "FileRepository", "TablePrefix": "BotSharp", @@ -132,6 +139,7 @@ "BotSharp.Plugin.AzureOpenAI", "BotSharp.Plugin.GoogleAI", "BotSharp.Plugin.MetaAI", + // "BotSharp.Plugin.Twilio", "BotSharp.Plugin.HuggingFace", "BotSharp.Plugin.LLamaSharp", "BotSharp.Plugin.KnowledgeBase", From b55abdf4dcdf77770b6df8dd1ca6237d577bc336 Mon Sep 17 00:00:00 2001 From: hchen Date: Thu, 9 Nov 2023 15:11:36 -0600 Subject: [PATCH 5/5] IConversationAttachmentService --- README.md | 2 +- .../IConversationAttachmentService.cs | 6 ++++ .../Conversations/Models/Attachment.cs | 21 ++++++++++++++ .../BotSharpServiceCollectionExtensions.cs | 2 ++ .../Services/ConversationAttachmentService.cs | 28 ++++++++++++++++++ .../Controllers/ConversationController.cs | 29 +++++++++++++++++++ 6 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationAttachmentService.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Attachment.cs create mode 100644 src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationAttachmentService.cs diff --git a/README.md b/README.md index af398158..fc024a1f 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ It's written in C# running on .Net Core that is full cross-platform framework, t * Built-in multi-agents and conversation with state management. * Support multiple LLM Planning approaches to handle different tasks. * Built-in RAG related interfaces, Memeory based vector searching. -* Support multiple LLM platforms (ChatGPT 3.5 / 4.0, PaLM 2, LLaMA 2). +* Support multiple LLM platforms (ChatGPT 3.5 / 4.0, PaLM 2, LLaMA 2, HuggingFace). * Allow multiple agents with different responsibilities cooperate to complete complex tasks. * Build, test, evaluate and audit your LLM agent in one place. * Support different open source UI [Chatbot UI](src/Plugins/BotSharp.Plugin.ChatbotUI/Chatbot-UI.md), [HuggingChat UI](src/Plugins/BotSharp.Plugin.HuggingFace/HuggingChat-UI.md). diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationAttachmentService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationAttachmentService.cs new file mode 100644 index 00000000..feec3fab --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationAttachmentService.cs @@ -0,0 +1,6 @@ +namespace BotSharp.Abstraction.Conversations; + +public interface IConversationAttachmentService +{ + string GetDirectory(string conversationId); +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Attachment.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Attachment.cs new file mode 100644 index 00000000..47cdf30d --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/Attachment.cs @@ -0,0 +1,21 @@ +namespace BotSharp.Abstraction.Conversations.Models; + +public class Attachment +{ + public string ContentType { get; set; } + + // + // Summary: + // Gets the raw Content-Disposition header of the uploaded file. + public string ContentDisposition { get; set; } + + // + // Summary: + // Gets the file length in bytes. + public long Length { get; set; } + + // + // Summary: + // Gets the file name from the Content-Disposition header. + public string FileName { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs index 52c70488..7ba9dbb1 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs @@ -102,6 +102,8 @@ public static class BotSharpServiceCollectionExtensions services.AddScoped(); services.AddScoped(); + services.AddScoped(); + return services; } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationAttachmentService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationAttachmentService.cs new file mode 100644 index 00000000..b0625fa8 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationAttachmentService.cs @@ -0,0 +1,28 @@ +using BotSharp.Abstraction.Repositories; +using System.IO; + +namespace BotSharp.Core.Conversations.Services; + +public class ConversationAttachmentService : IConversationAttachmentService +{ + private readonly BotSharpDatabaseSettings _dbSettings; + private readonly IServiceProvider _services; + + public ConversationAttachmentService( + BotSharpDatabaseSettings dbSettings, + IServiceProvider services) + { + _dbSettings = dbSettings; + _services = services; + } + + public string GetDirectory(string conversationId) + { + var dir = Path.Combine(_dbSettings.FileRepository, "conversations", conversationId, "attachments"); + if (!Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + return dir; + } +} diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 4635c8cd..7b5d83b0 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -2,6 +2,8 @@ using BotSharp.Abstraction.ApiAdapters; using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.Models; using BotSharp.OpenAPI.ViewModels.Conversations; +using Microsoft.AspNetCore.Http; +using System.Net.Http.Headers; namespace BotSharp.OpenAPI.Controllers; @@ -78,4 +80,31 @@ public class ConversationController : ControllerBase, IApiAdapter return response; } + + [HttpPost("/conversation/{agentId}/{conversationId}/attachments")] + public IActionResult UploadAttachments([FromRoute] string agentId, + [FromRoute] string conversationId, + IFormFile[] files) + { + if (files != null && files.Length > 0) + { + var attachmentService = _services.GetRequiredService(); + var dir = attachmentService.GetDirectory(conversationId); + foreach (var file in files) + { + // Save the file, process it, etc. + var fileName = ContentDispositionHeaderValue.Parse(file.ContentDisposition).FileName.Trim('"'); + var filePath = Path.Combine(dir, fileName); + + using (var stream = new FileStream(filePath, FileMode.Create)) + { + file.CopyTo(stream); + } + } + + return Ok(new { message = "File uploaded successfully." }); + } + + return BadRequest(new { message = "Invalid file." }); + } }