From b68c311acb9a1c3f6d6061d5a9c56aed51c55b75 Mon Sep 17 00:00:00 2001 From: hchen Date: Sun, 20 Aug 2023 17:53:53 -0500 Subject: [PATCH] Support Messenger quick reply. --- ...vice.GetChatCompletionsAsyncRecursively.cs | 6 ++- .../ConversationService.SendMessage.cs | 7 ++- .../Controllers/ConversationController.cs | 5 ++- .../Providers/ChatCompletionProvider.cs | 6 ++- .../ChatbotUiController.cs | 5 ++- .../Controllers/WebhookController.cs | 45 +++++++++++-------- .../MessagingModels/QuickReplyMessage.cs | 16 +++++++ .../MessagingModels/QuickReplyMessageItem.cs | 18 ++++++++ .../WebhookModels/WebhookMessageBody.cs | 3 ++ 9 files changed, 85 insertions(+), 26 deletions(-) create mode 100644 src/Plugins/BotSharp.Plugin.MetaMessenger/MessagingModels/QuickReplyMessage.cs create mode 100644 src/Plugins/BotSharp.Plugin.MetaMessenger/MessagingModels/QuickReplyMessageItem.cs diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs index c28ca63d..71698ebd 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs @@ -24,7 +24,8 @@ public partial class ConversationService _logger.LogError($"Exceed max current recursive depth."); await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, "System has exception, please try later.") { - CurrentAgentId = agent.Id + CurrentAgentId = agent.Id, + Channel = wholeDialogs.Last().Channel }, onMessageReceived); return false; } @@ -46,7 +47,8 @@ public partial class ConversationService { await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, fn.Content) { - CurrentAgentId = fn.CurrentAgentId + CurrentAgentId = fn.CurrentAgentId, + Channel = fn.Channel }, onMessageReceived); return; } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index d4a44406..521f3054 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -28,6 +28,7 @@ public partial class ConversationService var stateService = _services.GetRequiredService(); stateService.SetConversation(conversationId); stateService.Load(); + stateService.SetState("channel", lastDialog.Channel); var router = _services.GetRequiredService(); var agent = await router.LoadRouter(); @@ -35,9 +36,11 @@ public partial class ConversationService _logger.LogInformation($"[{agent.Name}] {lastDialog.Role}: {lastDialog.Content}"); lastDialog.CurrentAgentId = agent.Id; - _storage.Append(conversationId, agent.Id, lastDialog); - + var wholeDialogs = GetDialogHistory(conversationId); + wholeDialogs.Add(lastDialog); + + _storage.Append(conversationId, agent.Id, lastDialog); // Get relevant domain knowledge /*if (_settings.EnableKnowledgeBase) diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index 469a1c88..026a7874 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -48,7 +48,10 @@ public class ConversationController : ControllerBase, IApiAdapter var stackMsg = new List(); await conv.SendMessage(agentId, conversationId, - new RoleDialogModel("user", input.Text), + new RoleDialogModel("user", input.Text) + { + Channel = "webapi" + }, async msg => { stackMsg.Add(msg); diff --git a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs index 33166ac8..0e8438d8 100644 --- a/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.AzureOpenAI/Providers/ChatCompletionProvider.cs @@ -104,7 +104,8 @@ public class ChatCompletionProvider : IChatCompletion { CurrentAgentId = agent.Id, FunctionName = message.FunctionCall.Name, - FunctionArgs = message.FunctionCall.Arguments + FunctionArgs = message.FunctionCall.Arguments, + Channel = conversations.Last().Channel }; // Execute functions @@ -116,7 +117,8 @@ public class ChatCompletionProvider : IChatCompletion var msg = new RoleDialogModel(AgentRole.Assistant, message.Content) { - CurrentAgentId= agent.Id + CurrentAgentId= agent.Id, + Channel = conversations.Last().Channel }; // Text response received diff --git a/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs b/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs index 2f7fa97e..89bd7a95 100644 --- a/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs +++ b/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs @@ -64,7 +64,10 @@ public class ChatbotUiController : ControllerBase, IApiAdapter var conversation = input.Messages .Where(x => x.Role == AgentRole.User) - .Select(x => new RoleDialogModel(x.Role, x.Content)) + .Select(x => new RoleDialogModel(x.Role, x.Content) + { + Channel = "webchat" + }) .Last(); var conversationService = _services.GetRequiredService(); diff --git a/src/Plugins/BotSharp.Plugin.MetaMessenger/Controllers/WebhookController.cs b/src/Plugins/BotSharp.Plugin.MetaMessenger/Controllers/WebhookController.cs index 138788b7..47e87dc3 100644 --- a/src/Plugins/BotSharp.Plugin.MetaMessenger/Controllers/WebhookController.cs +++ b/src/Plugins/BotSharp.Plugin.MetaMessenger/Controllers/WebhookController.cs @@ -15,7 +15,7 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Refit; -using BotSharp.Abstraction.Agents.Enums; +using Microsoft.Extensions.Logging; namespace BotSharp.Plugin.MetaMessenger.Controllers; @@ -27,10 +27,12 @@ namespace BotSharp.Plugin.MetaMessenger.Controllers; public class WebhookController : ControllerBase { private readonly IServiceProvider _services; + private readonly ILogger _logger; - public WebhookController(IServiceProvider services) + public WebhookController(IServiceProvider services, ILogger logger) { _services = services; + _logger = logger; } [HttpGet("/messenger/webhook/{agentId}")] @@ -67,7 +69,7 @@ public class WebhookController : ControllerBase { var conv = _services.GetRequiredService(); - string content = ""; + var reply = new QuickReplyMessage(); var senderId = req.Entry[0].Messaging[0].Sender.Id; var input = req.Entry[0].Messaging[0].Message.Text; @@ -78,11 +80,13 @@ public class WebhookController : ControllerBase PropertyNamingPolicy = JsonNamingPolicy.CamelCase, }; + var recipient = JsonSerializer.Serialize(new { Id = senderId }, jsonOpt); + // Marking seen await messenger.SendMessage(setting.ApiVersion, setting.PageId, new SendingMessageRequest { AccessToken = setting.PageAccessToken, - Recipient = JsonSerializer.Serialize(new { Id = senderId }, jsonOpt), + Recipient = recipient, SenderAction = SenderActionEnum.MarkSeen }); @@ -90,7 +94,7 @@ public class WebhookController : ControllerBase await messenger.SendMessage(setting.ApiVersion, setting.PageId, new SendingMessageRequest { AccessToken = setting.PageAccessToken, - Recipient = JsonSerializer.Serialize(new { Id = senderId }, jsonOpt), + Recipient = recipient, SenderAction = SenderActionEnum.TypingOn }); @@ -100,11 +104,7 @@ public class WebhookController : ControllerBase Channel = "messenger" }, async msg => { - if (msg.Role == AgentRole.Function) - { - - } - content = msg.Content; + reply.Text = msg.Content; }, async functionExecuting => { /*await messenger.SendMessage(setting.ApiVersion, setting.PageId, new SendingMessageRequest @@ -116,27 +116,36 @@ public class WebhookController : ControllerBase }, async functionExecuted => { // Render structured data - /*await messenger.SendMessage(setting.ApiVersion, setting.PageId, new SendingMessageRequest + if (functionExecuted.ExecutionData != null) { - AccessToken = setting.PageAccessToken, - Recipient = JsonSerializer.Serialize(new { Id = senderId }, jsonOpt), - SenderAction = SenderActionEnum.TypingOn - });*/ + // validate data format + var json = JsonSerializer.Serialize(functionExecuted.ExecutionData, jsonOpt); + + try + { + var parsed = JsonSerializer.Deserialize(json, jsonOpt); + reply.QuickReplies = parsed; + } + catch(Exception ex) + { + _logger.LogError(ex, ex.Message); + } + } }); // Response to user await messenger.SendMessage(setting.ApiVersion, setting.PageId, new SendingMessageRequest { AccessToken = setting.PageAccessToken, - Recipient = JsonSerializer.Serialize(new { Id = senderId }, jsonOpt), - Message = JsonSerializer.Serialize(new { Text = content }, jsonOpt) + Recipient = recipient, + Message = JsonSerializer.Serialize(reply, jsonOpt) }); // Typing off await messenger.SendMessage(setting.ApiVersion, setting.PageId, new SendingMessageRequest { AccessToken = setting.PageAccessToken, - Recipient = JsonSerializer.Serialize(new { Id = senderId }, jsonOpt), + Recipient = recipient, SenderAction = SenderActionEnum.TypingOff }); } diff --git a/src/Plugins/BotSharp.Plugin.MetaMessenger/MessagingModels/QuickReplyMessage.cs b/src/Plugins/BotSharp.Plugin.MetaMessenger/MessagingModels/QuickReplyMessage.cs new file mode 100644 index 00000000..cefc74d2 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.MetaMessenger/MessagingModels/QuickReplyMessage.cs @@ -0,0 +1,16 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Plugin.MetaMessenger.MessagingModels; + +/// +/// Quick Replies +/// https://developers.facebook.com/docs/messenger-platform/send-messages/quick-replies +/// +public class QuickReplyMessage +{ + [JsonPropertyName("text")] + public string Text { get; set; } + + [JsonPropertyName("quick_replies")] + public QuickReplyMessageItem[] QuickReplies { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.MetaMessenger/MessagingModels/QuickReplyMessageItem.cs b/src/Plugins/BotSharp.Plugin.MetaMessenger/MessagingModels/QuickReplyMessageItem.cs new file mode 100644 index 00000000..a8782d69 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.MetaMessenger/MessagingModels/QuickReplyMessageItem.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Plugin.MetaMessenger.MessagingModels; + +public class QuickReplyMessageItem +{ + [JsonPropertyName("content_type")] + public string ContentType { get; set; } = "text"; + + [JsonPropertyName("title")] + public string Title { get; set; } + + [JsonPropertyName("payload")] + public string Payload { get; set; } + + [JsonPropertyName("image_url")] + public string ImageUrl { get; set; } +} diff --git a/src/Plugins/BotSharp.Plugin.MetaMessenger/WebhookModels/WebhookMessageBody.cs b/src/Plugins/BotSharp.Plugin.MetaMessenger/WebhookModels/WebhookMessageBody.cs index 3da17142..0c745c9e 100644 --- a/src/Plugins/BotSharp.Plugin.MetaMessenger/WebhookModels/WebhookMessageBody.cs +++ b/src/Plugins/BotSharp.Plugin.MetaMessenger/WebhookModels/WebhookMessageBody.cs @@ -1,3 +1,4 @@ +using BotSharp.Plugin.MetaMessenger.MessagingModels; using System; using System.Collections.Generic; using System.Text; @@ -10,4 +11,6 @@ public class WebhookMessageBody [JsonPropertyName("mid")] public string Id { get;set; } public string Text { get;set; } + [JsonPropertyName("quick_reply")] + public QuickReplyMessageItem QuickReply { get;set; } }