diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs index 5bceefaf3..4cf63cdf3 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs @@ -59,6 +59,9 @@ public virtual Task OnFunctionExecuted(RoleDialogModel message) public virtual Task OnMessageReceived(RoleDialogModel message) => Task.CompletedTask; + public virtual Task OnPostbackMessageReceived(RoleDialogModel message, PostbackMessageModel replyMsg) + => Task.CompletedTask; + public virtual Task OnResponseGenerated(RoleDialogModel message) => Task.CompletedTask; diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs index 8552aa20d..1143c4921 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs @@ -46,6 +46,7 @@ public interface IConversationHook Task OnStateChanged(string name, string preValue, string currentValue); Task OnMessageReceived(RoleDialogModel message); + Task OnPostbackMessageReceived(RoleDialogModel message, PostbackMessageModel replyMsg); /// /// Triggered before LLM calls function. diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs index 203e9053d..884ba9c0d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs @@ -30,6 +30,7 @@ public interface IConversationService /// Task SendMessage(string agentId, RoleDialogModel lastDalog, + PostbackMessageModel? replyMessage, Func onResponseReceived, Func onFunctionExecuting, Func onFunctionExecuted); diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IncomingMessageModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IncomingMessageModel.cs index 005cb0b28..2176c7fae 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IncomingMessageModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/IncomingMessageModel.cs @@ -4,4 +4,9 @@ public class IncomingMessageModel : MessageConfig { public string Text { get; set; } = string.Empty; public virtual string Channel { get; set; } = string.Empty; + + /// + /// Postback message + /// + public PostbackMessageModel? Postback { get; set; } } diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/PostbackMessageModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/PostbackMessageModel.cs new file mode 100644 index 000000000..afa1d6d34 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/PostbackMessageModel.cs @@ -0,0 +1,11 @@ +namespace BotSharp.Abstraction.Conversations.Models; + +public class PostbackMessageModel +{ + public string FunctionName { get; set; } = string.Empty; + public string Payload { get; set; } = string.Empty; + /// + /// Parent message id + /// + public string ParentId { get; set; } = string.Empty; +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs index 771223ef6..74845e1f9 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs @@ -31,7 +31,7 @@ public interface IRoutingService List GetHandlers(Agent router); void ResetRecursiveCounter(); Task InvokeAgent(string agentId, List dialogs); - Task InvokeFunction(string name, RoleDialogModel message, bool restoreOriginalFunctionName = true); + Task InvokeFunction(string name, RoleDialogModel message); Task InstructLoop(RoleDialogModel message); /// diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index ae5a803b8..9aaa75eff 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -10,6 +10,7 @@ public partial class ConversationService { public async Task SendMessage(string agentId, RoleDialogModel message, + PostbackMessageModel? replyMessage, Func onMessageReceived, Func onFunctionExecuting, Func onFunctionExecuted) @@ -51,7 +52,14 @@ public async Task SendMessage(string agentId, hook.SetAgent(agent) .SetConversation(conversation); - await hook.OnMessageReceived(message); + if (replyMessage == null) + { + await hook.OnMessageReceived(message); + } + else + { + await hook.OnPostbackMessageReceived(message, replyMessage); + } // Interrupted by hook if (message.StopCompletion) diff --git a/src/Infrastructure/BotSharp.Core/Evaluations/EvaluatingService.cs b/src/Infrastructure/BotSharp.Core/Evaluations/EvaluatingService.cs index 340b8f7e3..a87bc9503 100644 --- a/src/Infrastructure/BotSharp.Core/Evaluations/EvaluatingService.cs +++ b/src/Infrastructure/BotSharp.Core/Evaluations/EvaluatingService.cs @@ -97,9 +97,10 @@ private async Task SendMessage(string agentId, string conversat await conv.SendMessage(agentId, new RoleDialogModel(AgentRole.User, text), + replyMessage: null, async msg => response = msg, - fnExecuting => Task.CompletedTask, - fnExecuted => Task.CompletedTask); + _ => Task.CompletedTask, + _ => Task.CompletedTask); return response; } diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs index 5ff9894e3..462ee0761 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs @@ -10,11 +10,11 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler public List Parameters => new List { - new ParameterPropertyDef("next_action_reason", "the reason why route to this agent") + new ParameterPropertyDef("next_action_reason", "the reason why route to this agent, if user is replying last agent's question, you must route to this agent") { Required = true }, - new ParameterPropertyDef("next_action_agent", "agent for next action based on user latest response") + new ParameterPropertyDef("next_action_agent", "agent for next action based on user latest response, if user is replying last agent's question, you must route to this agent") { Required = true }, diff --git a/src/Infrastructure/BotSharp.Core/Routing/Planning/NaivePlanner.cs b/src/Infrastructure/BotSharp.Core/Routing/Planning/NaivePlanner.cs index bca5105c0..0b1708c57 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Planning/NaivePlanner.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Planning/NaivePlanner.cs @@ -1,3 +1,4 @@ +using Amazon.Runtime.Internal.Transform; using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Repositories.Filters; @@ -111,9 +112,11 @@ private string GetNextStepPrompt(Agent router) { var template = router.Templates.First(x => x.Name == "planner_prompt.naive").Content; + var states = _services.GetRequiredService(); var render = _services.GetRequiredService(); return render.Render(template, new Dictionary { + { "expected_next_action_agent", states.GetState("expected_next_action_agent")} }); } diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs index 6f70e0638..77659d69e 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs @@ -4,7 +4,7 @@ namespace BotSharp.Core.Routing; public partial class RoutingService { - public async Task InvokeFunction(string name, RoleDialogModel message, bool restoreOriginalFunctionName = true) + public async Task InvokeFunction(string name, RoleDialogModel message) { var function = _services.GetServices().FirstOrDefault(x => x.Name == name); if (function == null) @@ -57,8 +57,7 @@ public async Task InvokeFunction(string name, RoleDialogModel message, boo // restore original function name if (!message.StopCompletion && - message.FunctionName != originalFunctionName && - restoreOriginalFunctionName) + message.FunctionName != originalFunctionName) { message.FunctionName = originalFunctionName; } diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/planner_prompt.naive.liquid b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/planner_prompt.naive.liquid index 449bd4d7b..2bf2698c0 100644 --- a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/planner_prompt.naive.liquid +++ b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/templates/planner_prompt.naive.liquid @@ -1,3 +1,7 @@ -What is the next step based on the CONVERSATION? -Route to the Agent that last handled the conversation if necessary. +What is the next step based on the CONVERSATION? +Route to the appropriate agent last handled agent based on the context. +{% if expected_next_action_agent != empty -%} +Expected next action agent is {{ expected_next_action_agent }}. +{%- endif %} +Try to keep the User Goal Agent be consistent as previous goal agent. If user wants to speak to customer service, use function human_intervention_needed. \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index fee57e189..8ef208aff 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -171,6 +171,7 @@ public async Task SendMessage([FromRoute] string agentId, var response = new ChatResponseModel(); await conv.SendMessage(agentId, inputMsg, + replyMessage: input.Postback, async msg => { response.Text = msg.Content; @@ -179,14 +180,8 @@ await conv.SendMessage(agentId, inputMsg, response.Instruction = msg.Instruction; response.Data = msg.Data; }, - async fnExecuting => - { - - }, - async fnExecuted => - { - - }); + _ => Task.CompletedTask, + _ => Task.CompletedTask); var state = _services.GetRequiredService(); response.States = state.GetStates(); diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs index 254520e78..1dac0af1d 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/StreamingLogHook.cs @@ -59,6 +59,22 @@ public override async Task OnMessageReceived(RoleDialogModel message) await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input)); } + public override async Task OnPostbackMessageReceived(RoleDialogModel message, PostbackMessageModel replyMsg) + { + var conversationId = _state.GetConversationId(); + var log = $"{message.Content}"; + var replyContent = JsonSerializer.Serialize(replyMsg, _serializerOptions); + log += $"\r\n```json\r\n{replyContent}\r\n```"; + + var input = new ContentLogInputModel(conversationId, message) + { + Name = _user.UserName, + Source = ContentLogSource.UserInput, + Log = log + }; + await _chatHub.Clients.User(_user.Id).SendAsync("OnConversationContentLogGenerated", BuildContentLog(input)); + } + public async Task BeforeGenerating(Agent agent, List conversations) { if (!_convSettings.ShowVerboseLog) return; diff --git a/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs b/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs index 4dbf304d2..132856b97 100644 --- a/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs +++ b/src/Plugins/BotSharp.Plugin.ChatbotUI/ChatbotUiController.cs @@ -80,13 +80,12 @@ public async Task SendMessage([FromBody] OpenAiMessageInput input) .SetState("sampling_factor", input.SamplingFactor); var result = await conv.SendMessage(input.AgentId, - message, + message, + replyMessage: null, async msg => await OnChunkReceived(outputStream, msg), - async fn - => await Task.CompletedTask, - async fn - => await Task.CompletedTask); + _ => Task.CompletedTask, + _ => Task.CompletedTask); await OnEventCompleted(outputStream); } diff --git a/src/Plugins/BotSharp.Plugin.MetaMessenger/Services/MessageHandleService.cs b/src/Plugins/BotSharp.Plugin.MetaMessenger/Services/MessageHandleService.cs index 164c38f0d..e6aa19e25 100644 --- a/src/Plugins/BotSharp.Plugin.MetaMessenger/Services/MessageHandleService.cs +++ b/src/Plugins/BotSharp.Plugin.MetaMessenger/Services/MessageHandleService.cs @@ -60,7 +60,9 @@ await messenger.SendMessage(setting.ApiVersion, setting.PageId, var replies = new List(); var result = await conv.SendMessage(agentId, - new RoleDialogModel(AgentRole.User, message), async msg => + new RoleDialogModel(AgentRole.User, message), + replyMessage: null, + async msg => { if (msg.RichContent != null) { diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs index acea57523..0dc43aa69 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs @@ -59,6 +59,7 @@ public async Task ReceivedVoiceMessage([FromRoute] string agentId, var result = await conv.SendMessage(agentId, new RoleDialogModel(AgentRole.User, input.SpeechResult), + replyMessage: null, async msg => { response = twilio.ReturnInstructions(msg.Content); diff --git a/src/Plugins/BotSharp.Plugin.WeChat/WeChatBackgroundService.cs b/src/Plugins/BotSharp.Plugin.WeChat/WeChatBackgroundService.cs index 5013100e1..0e1057dd7 100644 --- a/src/Plugins/BotSharp.Plugin.WeChat/WeChatBackgroundService.cs +++ b/src/Plugins/BotSharp.Plugin.WeChat/WeChatBackgroundService.cs @@ -61,16 +61,15 @@ private async Task HandleTextMessageAsync(string openid, string message) AgentId = AgentId }))?.Id; - var result = await conversationService.SendMessage(AgentId, new RoleDialogModel("user", message), async msg => - { - await ReplyTextMessageAsync(openid, msg.Content); - }, async functionExecuting => - { - - }, async functionExecuted => - { - - }); + var result = await conversationService.SendMessage(AgentId, + new RoleDialogModel("user", message), + replyMessage: null, + async msg => + { + await ReplyTextMessageAsync(openid, msg.Content); + }, + _ => Task.CompletedTask, + _ => Task.CompletedTask); } private async Task GetWeChatAccountUserAsync(string openId, IServiceProvider service) diff --git a/tests/BotSharp.Plugin.PizzaBot/Hooks/PizzaTypeConversationHook.cs b/tests/BotSharp.Plugin.PizzaBot/Hooks/PizzaTypeConversationHook.cs new file mode 100644 index 000000000..558fa0472 --- /dev/null +++ b/tests/BotSharp.Plugin.PizzaBot/Hooks/PizzaTypeConversationHook.cs @@ -0,0 +1,16 @@ +using BotSharp.Abstraction.Conversations; +using BotSharp.Abstraction.Conversations.Models; + +namespace BotSharp.Plugin.PizzaBot.Hooks; + +public class PizzaTypeConversationHook : ConversationHookBase +{ + public override async Task OnPostbackMessageReceived(RoleDialogModel message, PostbackMessageModel replyMsg) + { + if (replyMsg.FunctionName == "get_pizza_types") + { + // message.StopCompletion = true; + } + return; + } +}