diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationProgressService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationProgressService.cs new file mode 100644 index 00000000..8958cab5 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationProgressService.cs @@ -0,0 +1,10 @@ +namespace BotSharp.Abstraction.Conversations; + +public delegate Task FunctionExecuting(RoleDialogModel msg); +public delegate Task FunctionExecuted(RoleDialogModel msg); + +public interface IConversationProgressService +{ + FunctionExecuted OnFunctionExecuted { get; set; } + FunctionExecuting OnFunctionExecuting { get; set; } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs index 6b29a72b..bddc5426 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationService.cs @@ -39,9 +39,7 @@ public interface IConversationService Task SendMessage(string agentId, RoleDialogModel lastDalog, PostbackMessageModel? replyMessage, - Func onResponseReceived, - Func onFunctionExecuting, - Func onFunctionExecuted); + Func onResponseReceived); List GetDialogHistory(int lastCount = 100, bool fromBreakpoint = true); Task CleanHistory(string agentId); diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs index e82ea706..95cddf2e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHandler.cs @@ -16,5 +16,5 @@ public interface IRoutingHandler void SetDialogs(List dialogs); - Task Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message, Func onFunctionExecuting); + Task Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message); } diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs index eed00812..3b221f4d 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs @@ -28,9 +28,9 @@ public interface IRoutingService List GetHandlers(Agent router); void ResetRecursiveCounter(); - Task InvokeAgent(string agentId, List dialogs, Func onFunctionExecuting); - Task InvokeFunction(string name, RoleDialogModel messages, Func? onFunctionExecuting = null); - Task InstructLoop(RoleDialogModel message, List dialogs, Func onFunctionExecuting); + Task InvokeAgent(string agentId, List dialogs); + Task InvokeFunction(string name, RoleDialogModel messages); + Task InstructLoop(RoleDialogModel message, List dialogs); /// /// Talk to a specific Agent directly, bypassing the Router diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IExecutor.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IExecutor.cs index 14c02034..c8bffe6c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IExecutor.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/Planning/IExecutor.cs @@ -7,6 +7,5 @@ public interface IExecutor Task Execute(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message, - List dialogs, - Func onFunctionExecuting); + List dialogs); } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs index f06e2ba2..a9be6fd2 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/ConversationPlugin.cs @@ -41,6 +41,7 @@ public class ConversationPlugin : IBotSharpPlugin services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationProgressService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationProgressService.cs new file mode 100644 index 00000000..d7e6440c --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationProgressService.cs @@ -0,0 +1,11 @@ +namespace BotSharp.Core.Conversations.Services +{ + public class ConversationProgressService : IConversationProgressService + { + + public FunctionExecuting OnFunctionExecuting { get; set; } + + + public FunctionExecuted OnFunctionExecuted { get; set; } + } +} diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index c9c5c4a2..b0410750 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -10,9 +10,7 @@ public partial class ConversationService public async Task SendMessage(string agentId, RoleDialogModel message, PostbackMessageModel? replyMessage, - Func onMessageReceived, - Func onFunctionExecuting, - Func onFunctionExecuted) + Func onMessageReceived) { var conversation = await GetConversationRecordOrCreateNew(agentId); var agentService = _services.GetRequiredService(); @@ -78,7 +76,7 @@ public partial class ConversationService if (agent.Type == AgentType.Routing) { - response = await routing.InstructLoop(message, dialogs, onFunctionExecuting); + response = await routing.InstructLoop(message, dialogs); } else { diff --git a/src/Infrastructure/BotSharp.Core/Evaluations/EvaluatingService.cs b/src/Infrastructure/BotSharp.Core/Evaluations/EvaluatingService.cs index cc3b568c..58da8688 100644 --- a/src/Infrastructure/BotSharp.Core/Evaluations/EvaluatingService.cs +++ b/src/Infrastructure/BotSharp.Core/Evaluations/EvaluatingService.cs @@ -103,9 +103,7 @@ public class EvaluatingService : IEvaluatingService await conv.SendMessage(agentId, inputMsg, replyMessage: null, - async msg => response = msg, - _ => Task.CompletedTask, - _ => Task.CompletedTask); + async msg => response = msg); return response; } diff --git a/src/Infrastructure/BotSharp.Core/Routing/Functions/FallbackToRouterFn.cs b/src/Infrastructure/BotSharp.Core/Routing/Functions/FallbackToRouterFn.cs index 16deb178..59685719 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Functions/FallbackToRouterFn.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Functions/FallbackToRouterFn.cs @@ -34,7 +34,7 @@ public class FallbackToRouterFn : IFunctionCallback routing.Context.Replace(targetAgent.Id); message.CurrentAgentId = targetAgent.Id; - var response = await routing.InstructLoop(message, dialogs, null); + var response = await routing.InstructLoop(message, dialogs); message.Content = response.Content; message.StopCompletion = true; diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs index 1bd8ca18..5b5b1025 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RetrieveDataFromAgentRoutingHandler.cs @@ -34,7 +34,7 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase//, IRoutin { } - public async Task Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message, Func onFunctionExecuting) + public async Task Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message) { var context = _services.GetRequiredService(); var agentId = context.GetCurrentAgentId(); @@ -47,7 +47,7 @@ public class RetrieveDataFromAgentRoutingHandler : RoutingHandlerBase//, IRoutin } }; - var ret = await routing.InvokeAgent(agentId, dialogs, onFunctionExecuting); + var ret = await routing.InvokeAgent(agentId, dialogs); var response = dialogs.Last(); inst.Response = response.Content; diff --git a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs index adc1a1c3..c26174a1 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Handlers/RouteToAgentRoutingHandler.cs @@ -40,7 +40,7 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler { } - public async Task Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message, Func onFunctionExecuting) + public async Task Handle(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message) { var states = _services.GetRequiredService(); var goalAgent = states.GetState(StateConst.EXPECTED_GOAL_AGENT); @@ -88,7 +88,7 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler } else { - var ret = await routing.InvokeAgent(agentId, _dialogs, onFunctionExecuting); + var ret = await routing.InvokeAgent(agentId, _dialogs); } var response = _dialogs.Last(); diff --git a/src/Infrastructure/BotSharp.Core/Routing/Planning/InstructExecutor.cs b/src/Infrastructure/BotSharp.Core/Routing/Planning/InstructExecutor.cs index 5b1c8fcb..a1d413f0 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Planning/InstructExecutor.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Planning/InstructExecutor.cs @@ -16,8 +16,7 @@ public class InstructExecutor : IExecutor public async Task Execute(IRoutingService routing, FunctionCallFromLlm inst, RoleDialogModel message, - List dialogs, - Func onFunctionExecuting) + List dialogs) { message.Instruction = inst; @@ -25,7 +24,7 @@ public class InstructExecutor : IExecutor var handler = handlers.FirstOrDefault(x => x.Name == inst.Function); handler.SetDialogs(dialogs); - var handled = await handler.Handle(routing, inst, message, onFunctionExecuting); + var handled = await handler.Handle(routing, inst, message); // For client display purpose var response = dialogs.Last(); diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs index 6046c5bb..ccd708c3 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeAgent.cs @@ -5,7 +5,7 @@ namespace BotSharp.Core.Routing; public partial class RoutingService { private int _currentRecursionDepth = 0; - public async Task InvokeAgent(string agentId, List dialogs, Func onFunctionExecuting) + public async Task InvokeAgent(string agentId, List dialogs) { var agentService = _services.GetRequiredService(); var agent = await agentService.LoadAgent(agentId); @@ -47,7 +47,7 @@ public partial class RoutingService message.FunctionArgs = response.FunctionArgs; message.CurrentAgentId = agent.Id; - await InvokeFunction(message, dialogs, onFunctionExecuting); + await InvokeFunction(message, dialogs); } else { @@ -67,7 +67,7 @@ public partial class RoutingService return true; } - private async Task InvokeFunction(RoleDialogModel message, List dialogs, Func? onFunctionExecuting = null) + private async Task InvokeFunction(RoleDialogModel message, List dialogs) { // execute function // Save states @@ -76,7 +76,7 @@ public partial class RoutingService var routing = _services.GetRequiredService(); // Call functions - await routing.InvokeFunction(message.FunctionName, message, onFunctionExecuting); + await routing.InvokeFunction(message.FunctionName, message); // Pass execution result to LLM to get response if (!message.StopCompletion) @@ -101,7 +101,7 @@ public partial class RoutingService // Send to Next LLM var agentId = routing.Context.GetCurrentAgentId(); - await InvokeAgent(agentId, dialogs, onFunctionExecuting); + await InvokeAgent(agentId, dialogs); } } else diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs index 027d2723..2f19cdb3 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs @@ -1,10 +1,9 @@ using BotSharp.Abstraction.Functions; - namespace BotSharp.Core.Routing; public partial class RoutingService { - public async Task InvokeFunction(string name, RoleDialogModel message, Func? onFunctionExecuting = null) + public async Task InvokeFunction(string name, RoleDialogModel message) { var function = _services.GetServices().FirstOrDefault(x => x.Name == name); if (function == null) @@ -23,11 +22,13 @@ public partial class RoutingService .OrderBy(x => x.Priority) .ToList(); + var progressService = _services.GetService(); + // Before executing functions clonedMessage.Indication = function.Indication; - if (onFunctionExecuting != null) + if (progressService?.OnFunctionExecuting != null) { - await onFunctionExecuting(clonedMessage); + await progressService.OnFunctionExecuting(clonedMessage); } foreach (var hook in hooks) diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs index 284821ff..b7c489e1 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.cs @@ -55,7 +55,7 @@ public partial class RoutingService : IRoutingService ExecutingDirectly = true }; - var result = await handler.Handle(this, inst, message, null); + var result = await handler.Handle(this, inst, message); var response = dialogs.Last(); response.MessageId = message.MessageId; @@ -64,7 +64,7 @@ public partial class RoutingService : IRoutingService return response; } - public async Task InstructLoop(RoleDialogModel message, List dialogs, Func onFunctionExecuting) + public async Task InstructLoop(RoleDialogModel message, List dialogs) { RoleDialogModel response = default; @@ -125,12 +125,12 @@ public partial class RoutingService : IRoutingService if (inst.HandleDialogsByPlanner) { var dialogWithoutContext = planner.BeforeHandleContext(inst, message, dialogs); - response = await executor.Execute(this, inst, message, dialogWithoutContext, onFunctionExecuting); + response = await executor.Execute(this, inst, message, dialogWithoutContext); planner.AfterHandleContext(dialogs, dialogWithoutContext); } else { - response = await executor.Execute(this, inst, message, dialogs, onFunctionExecuting); + response = await executor.Execute(this, inst, message, dialogs); } await planner.AgentExecuted(_router, inst, response, dialogs); diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index b4aa32fb..5521b5e6 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -281,9 +281,7 @@ public class ConversationController : ControllerBase response.RichContent = msg.SecondaryRichContent ?? msg.RichContent; response.Instruction = msg.Instruction; response.Data = msg.Data; - }, - _ => Task.CompletedTask, - _ => Task.CompletedTask); + }); var state = _services.GetRequiredService(); response.States = state.GetStates(); @@ -321,6 +319,7 @@ public class ConversationController : ControllerBase Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.ContentType, "text/event-stream"); Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.CacheControl, "no-cache"); Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.Connection, "keep-alive"); + InitProgressService(conversationId); await conv.SendMessage(agentId, inputMsg, replyMessage: input.Postback, @@ -335,25 +334,6 @@ public class ConversationController : ControllerBase response.States = state.GetStates(); await OnChunkReceived(Response, response); - }, - // executing - async msg => - { - var indicator = new ChatResponseModel - { - ConversationId = conversationId, - MessageId = msg.MessageId, - Text = msg.Indication, - Function = "indicating", - Instruction = msg.Instruction, - States = new Dictionary() - }; - await OnChunkReceived(Response, indicator); - }, - // executed - async msg => - { - }); response.States = state.GetStates(); @@ -362,6 +342,25 @@ public class ConversationController : ControllerBase // await OnEventCompleted(Response); } + + private void InitProgressService(string conversationId) + { + var progressService = _services.GetService(); + progressService.OnFunctionExecuting = async msg => + { + var indicator = new ChatResponseModel + { + ConversationId = conversationId, + MessageId = msg.MessageId, + Text = msg.Indication, + Function = "indicating", + Instruction = msg.Instruction, + States = new Dictionary() + }; + await OnChunkReceived(Response, indicator); + }; + progressService.OnFunctionExecuted = async msg => { }; + } #endregion #region Files and attachments diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs index 6543e22c..7c5a8831 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs @@ -63,6 +63,9 @@ namespace BotSharp.Plugin.Twilio.Services var conv = sp.GetRequiredService(); var routing = sp.GetRequiredService(); var config = sp.GetRequiredService(); + var sessionManager = sp.GetRequiredService(); + var progressService = sp.GetRequiredService(); + InitProgressService(message, sessionManager, progressService); routing.Context.SetMessageId(message.ConversationId, inputMsg.MessageId); var states = new List @@ -77,7 +80,6 @@ namespace BotSharp.Plugin.Twilio.Services } conv.SetConversationId(message.ConversationId, states); - var sessionManager = sp.GetRequiredService(); var result = await conv.SendMessage(config.AgentId, inputMsg, replyMessage: null, @@ -90,15 +92,7 @@ namespace BotSharp.Plugin.Twilio.Services Content = msg.Content, MessageId = msg.MessageId }; - }, - async msg => - { - if (!string.IsNullOrEmpty(msg.Indication)) - { - await sessionManager.SetReplyIndicationAsync(message.ConversationId, message.SeqNumber, msg.Indication); - } - }, - async functionExecuted => { } + } ); var completion = CompletionProvider.GetAudioCompletion(sp, "openai", "tts-1"); @@ -130,5 +124,17 @@ namespace BotSharp.Plugin.Twilio.Services reply.Content = null; await sessionManager.SetAssistantReplyAsync(message.ConversationId, message.SeqNumber, reply); } + + private static void InitProgressService(CallerMessage message, ITwilioSessionManager sessionManager, IConversationProgressService progressService) + { + progressService.OnFunctionExecuting = async msg => + { + if (!string.IsNullOrEmpty(msg.Indication)) + { + await sessionManager.SetReplyIndicationAsync(message.ConversationId, message.SeqNumber, msg.Indication); + } + }; + progressService.OnFunctionExecuted = async msg => { }; + } } }