diff --git a/BotSharp.sln b/BotSharp.sln index a692f403..972dc35f 100644 --- a/BotSharp.sln +++ b/BotSharp.sln @@ -131,8 +131,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.Realtime", "s EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.PizzaBot.MCPServer", "tests\BotSharp.PizzaBot.MCPServer\BotSharp.PizzaBot.MCPServer.csproj", "{8D2AD45F-836A-516F-DE6A-71443CEBB18A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Core.MCP", "src\Infrastructure\BotSharp.Core.MCP\BotSharp.Core.MCP.csproj", "{534D1DD5-9D62-115C-F230-47080D76CE52}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Test.ComputerUse", "tests\BotSharp.Test.ComputerUse\BotSharp.Test.ComputerUse.csproj", "{C19D9AC1-97DD-8E65-E8DB-D295A095AA2D}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BotSharp.Plugin.OsDriver", "src\Plugins\BotSharp.Plugin.OsDriver\BotSharp.Plugin.OsDriver.csproj", "{B268E2F0-060F-8466-7D81-ABA4D735CA59}" @@ -553,14 +551,6 @@ Global {8D2AD45F-836A-516F-DE6A-71443CEBB18A}.Release|Any CPU.Build.0 = Release|Any CPU {8D2AD45F-836A-516F-DE6A-71443CEBB18A}.Release|x64.ActiveCfg = Release|Any CPU {8D2AD45F-836A-516F-DE6A-71443CEBB18A}.Release|x64.Build.0 = Release|Any CPU - {534D1DD5-9D62-115C-F230-47080D76CE52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {534D1DD5-9D62-115C-F230-47080D76CE52}.Debug|Any CPU.Build.0 = Debug|Any CPU - {534D1DD5-9D62-115C-F230-47080D76CE52}.Debug|x64.ActiveCfg = Debug|Any CPU - {534D1DD5-9D62-115C-F230-47080D76CE52}.Debug|x64.Build.0 = Debug|Any CPU - {534D1DD5-9D62-115C-F230-47080D76CE52}.Release|Any CPU.ActiveCfg = Release|Any CPU - {534D1DD5-9D62-115C-F230-47080D76CE52}.Release|Any CPU.Build.0 = Release|Any CPU - {534D1DD5-9D62-115C-F230-47080D76CE52}.Release|x64.ActiveCfg = Release|Any CPU - {534D1DD5-9D62-115C-F230-47080D76CE52}.Release|x64.Build.0 = Release|Any CPU {C19D9AC1-97DD-8E65-E8DB-D295A095AA2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C19D9AC1-97DD-8E65-E8DB-D295A095AA2D}.Debug|Any CPU.Build.0 = Debug|Any CPU {C19D9AC1-97DD-8E65-E8DB-D295A095AA2D}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -672,7 +662,6 @@ Global {AF329442-B48E-4B48-A18A-1C869D1BA6F5} = {D5293208-2BEF-42FC-A64C-5954F61720BA} {781F1465-365C-0F22-1775-25025DAFA4C7} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749} {8D2AD45F-836A-516F-DE6A-71443CEBB18A} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC} - {534D1DD5-9D62-115C-F230-47080D76CE52} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749} {C19D9AC1-97DD-8E65-E8DB-D295A095AA2D} = {32FAFFFE-A4CB-4FEE-BF7C-84518BBC6DCC} {B268E2F0-060F-8466-7D81-ABA4D735CA59} = {51AFE054-AE99-497D-A593-69BAEFB5106F} {970BE341-9AC8-99A5-6572-E703C1E02FCB} = {E29DC6C4-5E57-48C5-BCB0-6B8F84782749} diff --git a/Directory.Packages.props b/Directory.Packages.props index d9fee6f8..01fc6c90 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -60,7 +60,7 @@ - + @@ -114,8 +114,8 @@ - - + + diff --git a/IFunctionExecutor.cs b/IFunctionExecutor.cs new file mode 100644 index 00000000..a3380e70 --- /dev/null +++ b/IFunctionExecutor.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BotSharp.Core.Routing +{ + public interface IFunctionExecutor + { + public Task Execute(RoleDialogModel message); + + public Task GetIndication(RoleDialogModel message); + } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs index ae6a4eaf..19e29642 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/IAgentHook.cs @@ -1,13 +1,10 @@ using BotSharp.Abstraction.Functions.Models; +using BotSharp.Abstraction.Hooks; namespace BotSharp.Abstraction.Agents; -public interface IAgentHook +public interface IAgentHook : IHookBase { - /// - /// Agent Id - /// - string SelfId { get; } Agent Agent { get; } void SetAgent(Agent agent); diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs index b722d3fa..6d967391 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs @@ -1,6 +1,8 @@ +using BotSharp.Abstraction.Hooks; + namespace BotSharp.Abstraction.Conversations; -public interface IConversationHook +public interface IConversationHook : IHookBase { int Priority { get; } Agent Agent { get; } diff --git a/src/Infrastructure/BotSharp.Abstraction/Crontab/ICrontabHook.cs b/src/Infrastructure/BotSharp.Abstraction/Crontab/ICrontabHook.cs index 6ad7e06a..b92fd8f5 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Crontab/ICrontabHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Crontab/ICrontabHook.cs @@ -1,6 +1,8 @@ +using BotSharp.Abstraction.Hooks; + namespace BotSharp.Abstraction.Crontab; -public interface ICrontabHook +public interface ICrontabHook : IHookBase { string[]? Triggers => null; diff --git a/src/Infrastructure/BotSharp.Abstraction/Hooks/IHookBase.cs b/src/Infrastructure/BotSharp.Abstraction/Hooks/IHookBase.cs new file mode 100644 index 00000000..a0ad18e6 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Hooks/IHookBase.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BotSharp.Abstraction.Hooks +{ + public interface IHookBase + { + /// + /// Agent Id + /// + string SelfId => string.Empty; + bool IsMatch(string id) => string.IsNullOrEmpty(SelfId) || SelfId == id; + } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructHook.cs b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructHook.cs index c9fa1cd7..23b8bf60 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Instructs/IInstructHook.cs @@ -1,10 +1,10 @@ +using BotSharp.Abstraction.Hooks; using BotSharp.Abstraction.Instructs.Models; namespace BotSharp.Abstraction.Instructs; -public interface IInstructHook +public interface IInstructHook : IHookBase { - string SelfId { get; } Task BeforeCompletion(Agent agent, RoleDialogModel message); Task AfterCompletion(Agent agent, InstructResult result); Task OnResponseGenerated(InstructResponseModel response); diff --git a/src/Infrastructure/BotSharp.Abstraction/Loggers/IContentGeneratingHook.cs b/src/Infrastructure/BotSharp.Abstraction/Loggers/IContentGeneratingHook.cs index 03b69f5f..79f71d70 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Loggers/IContentGeneratingHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Loggers/IContentGeneratingHook.cs @@ -1,11 +1,12 @@ using BotSharp.Abstraction.Functions.Models; +using BotSharp.Abstraction.Hooks; namespace BotSharp.Abstraction.Loggers; /// /// Model content generating hook, it can be used for logging, metrics and tracing. /// -public interface IContentGeneratingHook +public interface IContentGeneratingHook : IHookBase { /// /// Before content generating. diff --git a/src/Infrastructure/BotSharp.Abstraction/Planning/IPlanningHook.cs b/src/Infrastructure/BotSharp.Abstraction/Planning/IPlanningHook.cs index 4dcff7cb..b65c1a7e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Planning/IPlanningHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Planning/IPlanningHook.cs @@ -1,6 +1,8 @@ +using BotSharp.Abstraction.Hooks; + namespace BotSharp.Abstraction.Planning; -public interface IPlanningHook +public interface IPlanningHook : IHookBase { Task GetSummaryAdditionalRequirements(string planner, RoleDialogModel message) => Task.FromResult(string.Empty); diff --git a/src/Infrastructure/BotSharp.Abstraction/Realtime/IRealtimeHook.cs b/src/Infrastructure/BotSharp.Abstraction/Realtime/IRealtimeHook.cs index 95608ff0..bd131b93 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Realtime/IRealtimeHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Realtime/IRealtimeHook.cs @@ -1,8 +1,9 @@ +using BotSharp.Abstraction.Hooks; using BotSharp.Abstraction.MLTasks; namespace BotSharp.Abstraction.Realtime; -public interface IRealtimeHook +public interface IRealtimeHook : IHookBase { Task OnModelReady(Agent agent, IRealTimeCompletion completer); string[] OnModelTranscriptPrompt(Agent agent); diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHook.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHook.cs index c60bf4f1..b893c8c1 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHook.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingHook.cs @@ -1,8 +1,9 @@ using BotSharp.Abstraction.Functions.Models; +using BotSharp.Abstraction.Hooks; namespace BotSharp.Abstraction.Routing; -public interface IRoutingHook +public interface IRoutingHook : IHookBase { /// /// Routing instruction is received from Router diff --git a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs index 7ff105c9..255faa71 100644 --- a/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs +++ b/src/Infrastructure/BotSharp.Core.Crontab/Services/CrontabService.cs @@ -125,6 +125,6 @@ public class CrontabService : ICrontabService, ITaskFeeder await hook.OnCronTriggered(item); await hook.OnTaskExecuted(item); } - }); + }, item.AgentId); } } diff --git a/src/Infrastructure/BotSharp.Core.MCP/BotSharp.Core.MCP.csproj b/src/Infrastructure/BotSharp.Core.MCP/BotSharp.Core.MCP.csproj deleted file mode 100644 index 5f513dd9..00000000 --- a/src/Infrastructure/BotSharp.Core.MCP/BotSharp.Core.MCP.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - $(TargetFramework) - $(LangVersion) - $(BotSharpVersion) - $(GeneratePackageOnBuild) - $(SolutionDir)packages - - - - - - - - - - diff --git a/src/Infrastructure/BotSharp.Core.MCP/BotSharpMCPExtensions.cs b/src/Infrastructure/BotSharp.Core.MCP/BotSharpMCPExtensions.cs deleted file mode 100644 index 898d5a0f..00000000 --- a/src/Infrastructure/BotSharp.Core.MCP/BotSharpMCPExtensions.cs +++ /dev/null @@ -1,67 +0,0 @@ -using BotSharp.Core.MCP.Functions; -using BotSharp.Core.MCP.Hooks; -using BotSharp.Core.MCP.Managers; -using BotSharp.Core.MCP.Services; -using BotSharp.Core.MCP.Settings; -using Microsoft.Extensions.Configuration; -using ModelContextProtocol.Client; - -namespace BotSharp.Core.MCP; - -public static class BotSharpMcpExtensions -{ - /// - /// Add mcp - /// - /// - /// - /// - public static IServiceCollection AddBotSharpMCP(this IServiceCollection services, IConfiguration config) - { - services.AddScoped(); - var settings = config.GetSection("MCP").Get(); - services.AddScoped(provider => settings); - - if (settings != null && settings.Enabled && !settings.McpServerConfigs.IsNullOrEmpty()) - { - var clientManager = new McpClientManager(settings); - services.AddSingleton(clientManager); - - foreach (var server in settings.McpServerConfigs) - { - RegisterFunctionCall(services, server, clientManager) - .ConfigureAwait(false) - .GetAwaiter() - .GetResult(); - } - - // Register hooks - services.AddScoped(); - } - return services; - } - - private static async Task RegisterFunctionCall(IServiceCollection services, McpServerConfigModel server, McpClientManager clientManager) - { - try - { - var client = await clientManager.GetMcpClientAsync(server.Id); - var tools = await client.ListToolsAsync(); - - foreach (var tool in tools) - { - services.AddScoped(provider => tool); - - services.AddScoped(provider => - { - var funcTool = new McpToolAdapter(provider, server.Name, tool, clientManager); - return funcTool; - }); - } - } - catch (Exception ex) - { - Console.WriteLine($"Error when registering {server?.Name} MCP tools. {ex.Message}\r\n{ex.InnerException}"); - } - } -} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core.MCP/Using.cs b/src/Infrastructure/BotSharp.Core.MCP/Using.cs deleted file mode 100644 index 0c5dcf37..00000000 --- a/src/Infrastructure/BotSharp.Core.MCP/Using.cs +++ /dev/null @@ -1,18 +0,0 @@ -global using System; -global using System.Collections.Generic; -global using System.Linq; -global using System.Threading.Tasks; -global using Microsoft.Extensions.DependencyInjection; - -global using BotSharp.Abstraction.Agents; -global using BotSharp.Abstraction.Agents.Enums; -global using BotSharp.Abstraction.Agents.Models; -global using BotSharp.Abstraction.Agents.Settings; -global using BotSharp.Abstraction.Conversations; -global using BotSharp.Abstraction.Conversations.Models; -global using BotSharp.Abstraction.Functions; -global using BotSharp.Abstraction.Functions.Models; -global using BotSharp.Abstraction.Utilities; -global using BotSharp.Abstraction.MCP.Models; -global using BotSharp.Abstraction.MCP.Services; - diff --git a/src/Infrastructure/BotSharp.Core.Realtime/Services/RealtimeHub.cs b/src/Infrastructure/BotSharp.Core.Realtime/Services/RealtimeHub.cs index 104cd22c..9fed0293 100644 --- a/src/Infrastructure/BotSharp.Core.Realtime/Services/RealtimeHub.cs +++ b/src/Infrastructure/BotSharp.Core.Realtime/Services/RealtimeHub.cs @@ -49,7 +49,8 @@ public class RealtimeHub : IRealtimeHub // Not TriggerModelInference, waiting for user utter. var instruction = await _completer.UpdateSession(_conn, isInit: true); var data = _conn.OnModelReady(); - await HookEmitter.Emit(_services, async hook => await hook.OnModelReady(agent, _completer)); + await HookEmitter.Emit(_services, async hook => await hook.OnModelReady(agent, _completer), + agent.Id); await (init?.Invoke(data) ?? Task.CompletedTask); }, onModelAudioDeltaReceived: async (audioDeltaData, itemId) => @@ -92,7 +93,8 @@ public class RealtimeHub : IRealtimeHub if (message.FunctionName == "route_to_agent") { var instruction = JsonSerializer.Deserialize(message.FunctionArgs, BotSharpOptions.defaultJsonOptions); - await HookEmitter.Emit(_services, async hook => await hook.OnRoutingInstructionReceived(instruction, message)); + await HookEmitter.Emit(_services, async hook => await hook.OnRoutingInstructionReceived(instruction, message), + agent.Id); } await routing.InvokeFunction(message.FunctionName, message); diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs index 636bc811..37e3d445 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.LoadAgent.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Infrastructures; using BotSharp.Abstraction.Routing.Models; using System.Collections.Concurrent; @@ -10,29 +11,12 @@ public partial class AgentService // [SharpCache(10, perInstanceCache: true)] public async Task LoadAgent(string id, bool loadUtility = true) { - if (string.IsNullOrEmpty(id) || id == Guid.Empty.ToString()) - { - return null; - } + if (string.IsNullOrEmpty(id) || id == Guid.Empty.ToString()) return null; - var hooks = _services.GetServices(); - - // Before agent is loaded. - foreach (var hook in hooks) - { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != id) - { - continue; - } - - hook.OnAgentLoading(ref id); - } + HookEmitter.Emit(_services, hook => hook.OnAgentLoading(ref id), id); var agent = await GetAgent(id); - if (agent == null) - { - return null; - } + if (agent == null) return null; await InheritAgent(agent); OverrideInstructionByChannel(agent); @@ -43,13 +27,7 @@ public partial class AgentService PopulateState(agent.TemplateDict); // After agent is loaded - foreach (var hook in hooks) - { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != id) - { - continue; - } - + HookEmitter.Emit(_services, hook => { hook.SetAgent(agent); if (!string.IsNullOrEmpty(agent.Instruction)) @@ -72,13 +50,14 @@ public partial class AgentService hook.OnAgentUtilityLoaded(agent); } - if(!agent.McpTools.IsNullOrEmpty()) + if (!agent.McpTools.IsNullOrEmpty()) { hook.OnAgentMcpToolLoaded(agent); } - + hook.OnAgentLoaded(agent); - } + + }, id); _logger.LogInformation($"Loaded agent {agent}."); diff --git a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs index ef36f4d7..f862482c 100644 --- a/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs +++ b/src/Infrastructure/BotSharp.Core/Agents/Services/AgentService.Rendering.cs @@ -132,9 +132,8 @@ public partial class AgentService agent.TemplateDict[TemplateRenderConstant.RENDER_AGENT] = agent; var content = render.Render(template, agent.TemplateDict); - HookEmitter.Emit(_services, async hook => - await hook.OnRenderingTemplate(agent, templateName, content) - ).Wait(); + HookEmitter.Emit(_services, async hook => await hook.OnRenderingTemplate(agent, templateName, content), + agent.Id).Wait(); return content; } diff --git a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj index b3e29b3f..ddcf68cb 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj +++ b/src/Infrastructure/BotSharp.Core/BotSharp.Core.csproj @@ -228,6 +228,7 @@ + diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index 5ec1ae76..b2b97f4f 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -158,16 +158,14 @@ public partial class ConversationService // Emit conversation ending hook if (response.Instruction.ConversationEnd) { - await HookEmitter.Emit(_services, async hook => - await hook.OnConversationEnding(response) - ); + await HookEmitter.Emit(_services, async hook => await hook.OnConversationEnding(response), + response.CurrentAgentId); response.FunctionName = "conversation_end"; } } - await HookEmitter.Emit(_services, async hook => - await hook.OnResponseGenerated(response) - ); + await HookEmitter.Emit(_services, async hook => await hook.OnResponseGenerated(response), + response.CurrentAgentId); await onResponseReceived(response); diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Image.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Image.cs index defd1459..dd2e02d4 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Image.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Image.cs @@ -1,6 +1,7 @@ using BotSharp.Abstraction.Instructs.Models; using BotSharp.Abstraction.Instructs; using System.IO; +using BotSharp.Abstraction.Infrastructures; namespace BotSharp.Core.Files.Services; @@ -24,14 +25,7 @@ public partial class FileInstructService } }); - var hooks = _services.GetServices(); - foreach (var hook in hooks) - { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != innerAgentId) - { - continue; - } - + await HookEmitter.Emit(_services, async hook => await hook.OnResponseGenerated(new InstructResponseModel { AgentId = innerAgentId, @@ -41,8 +35,7 @@ public partial class FileInstructService UserMessage = text, SystemInstruction = instruction, CompletionText = message.Content - }); - } + }), innerAgentId); return message.Content; } @@ -59,14 +52,7 @@ public partial class FileInstructService Instruction = instruction }, new RoleDialogModel(AgentRole.User, instruction ?? text)); - var hooks = _services.GetServices(); - foreach (var hook in hooks) - { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != innerAgentId) - { - continue; - } - + await HookEmitter.Emit(_services, async hook => await hook.OnResponseGenerated(new InstructResponseModel { AgentId = innerAgentId, @@ -76,8 +62,7 @@ public partial class FileInstructService UserMessage = text, SystemInstruction = instruction, CompletionText = message.Content - }); - } + }), innerAgentId); return message; } @@ -104,14 +89,7 @@ public partial class FileInstructService stream.Close(); - var hooks = _services.GetServices(); - foreach (var hook in hooks) - { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != innerAgentId) - { - continue; - } - + await HookEmitter.Emit(_services, async hook => await hook.OnResponseGenerated(new InstructResponseModel { AgentId = innerAgentId, @@ -119,8 +97,7 @@ public partial class FileInstructService Model = completion.Model, UserMessage = string.Empty, CompletionText = message.Content - }); - } + }), innerAgentId); return message; } @@ -149,14 +126,7 @@ public partial class FileInstructService stream.Close(); - var hooks = _services.GetServices(); - foreach (var hook in hooks) - { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != innerAgentId) - { - continue; - } - + await HookEmitter.Emit(_services, async hook => await hook.OnResponseGenerated(new InstructResponseModel { AgentId = innerAgentId, @@ -166,8 +136,7 @@ public partial class FileInstructService UserMessage = text, SystemInstruction = instruction, CompletionText = message.Content - }); - } + }), innerAgentId); return message; } @@ -205,14 +174,7 @@ public partial class FileInstructService imageStream.Close(); maskStream.Close(); - var hooks = _services.GetServices(); - foreach (var hook in hooks) - { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != innerAgentId) - { - continue; - } - + await HookEmitter.Emit(_services, async hook => await hook.OnResponseGenerated(new InstructResponseModel { AgentId = innerAgentId, @@ -222,8 +184,7 @@ public partial class FileInstructService UserMessage = text, SystemInstruction = instruction, CompletionText = message.Content - }); - } + }), innerAgentId); return message; } diff --git a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs index 2336363a..cd703cc7 100644 --- a/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs +++ b/src/Infrastructure/BotSharp.Core/Files/Services/Instruct/FileInstructService.Pdf.cs @@ -1,6 +1,7 @@ using BotSharp.Abstraction.Files.Converters; using BotSharp.Abstraction.Instructs.Models; using BotSharp.Abstraction.Instructs; +using BotSharp.Abstraction.Infrastructures; namespace BotSharp.Core.Files.Services; @@ -42,14 +43,7 @@ public partial class FileInstructService } }); - var hooks = _services.GetServices(); - foreach (var hook in hooks) - { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != innerAgentId) - { - continue; - } - + await HookEmitter.Emit(_services, async hook => await hook.OnResponseGenerated(new InstructResponseModel { AgentId = innerAgentId, @@ -59,8 +53,7 @@ public partial class FileInstructService UserMessage = text, SystemInstruction = instruction, CompletionText = message.Content - }); - } + }), innerAgentId); return message.Content; } diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/HookEmitter.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/HookEmitter.cs index a4baf81c..95d55596 100644 --- a/src/Infrastructure/BotSharp.Core/Infrastructures/HookEmitter.cs +++ b/src/Infrastructure/BotSharp.Core/Infrastructures/HookEmitter.cs @@ -1,14 +1,15 @@ +using BotSharp.Abstraction.Hooks; using BotSharp.Abstraction.Infrastructures; namespace BotSharp.Core.Infrastructures; public static class HookEmitter { - public static HookEmittedResult Emit(IServiceProvider services, Action action, HookEmitOption? option = null) + public static HookEmittedResult Emit(IServiceProvider services, Action action, string agentId, HookEmitOption? option = null) where T : IHookBase { var logger = services.GetRequiredService>(); var result = new HookEmittedResult(); - var hooks = services.GetServices(); + var hooks = services.GetServices().Where(p => p.IsMatch(agentId)); option = option ?? new(); foreach (var hook in hooks) @@ -35,11 +36,11 @@ public static class HookEmitter return result; } - public static async Task Emit(IServiceProvider services, Func action, HookEmitOption? option = null) + public static async Task Emit(IServiceProvider services, Func action, string agentId, HookEmitOption? option = null) where T : IHookBase { var logger = services.GetRequiredService>(); var result = new HookEmittedResult(); - var hooks = services.GetServices(); + var hooks = services.GetServices().Where(p => p.IsMatch(agentId)); option = option ?? new(); foreach (var hook in hooks) diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Functions/ExecuteTemplateFn.cs b/src/Infrastructure/BotSharp.Core/Instructs/Functions/ExecuteTemplateFn.cs index 346218dd..7d8bcb46 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/Functions/ExecuteTemplateFn.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/Functions/ExecuteTemplateFn.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Functions; +using BotSharp.Abstraction.Infrastructures; using BotSharp.Abstraction.Instructs; using BotSharp.Abstraction.Instructs.Models; @@ -60,14 +61,7 @@ public class ExecuteTemplateFn : IFunctionCallback new(AgentRole.User, text) }); - var hooks = _services.GetServices(); - foreach (var hook in hooks) - { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agent.Id) - { - continue; - } - + await HookEmitter.Emit(_services, async hook => await hook.OnResponseGenerated(new InstructResponseModel { AgentId = agent.Id, @@ -76,8 +70,7 @@ public class ExecuteTemplateFn : IFunctionCallback Model = completion.Model, UserMessage = text, CompletionText = response.Content - }); - } + }), agent.Id); return response.Content; } diff --git a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs index 71a7488b..3bc069d5 100644 --- a/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs +++ b/src/Infrastructure/BotSharp.Core/Instructs/Services/InstructService.Execute.cs @@ -23,14 +23,9 @@ public partial class InstructService } // Trigger before completion hooks - var hooks = _services.GetServices(); + var hooks = _services.GetServices().Where(p => p.IsMatch(agentId)); foreach (var hook in hooks) { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId) - { - continue; - } - await hook.BeforeCompletion(agent, message); // Interrupted by hook @@ -99,11 +94,6 @@ public partial class InstructService foreach (var hook in hooks) { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId) - { - continue; - } - await hook.AfterCompletion(agent, response); await hook.OnResponseGenerated(new InstructResponseModel { diff --git a/src/Infrastructure/BotSharp.Core/MCP/BotSharpMCPExtensions.cs b/src/Infrastructure/BotSharp.Core/MCP/BotSharpMCPExtensions.cs new file mode 100644 index 00000000..b3c4e6de --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/MCP/BotSharpMCPExtensions.cs @@ -0,0 +1,35 @@ +using BotSharp.Core.MCP.Hooks; +using BotSharp.Core.MCP.Managers; +using BotSharp.Core.MCP.Services; +using BotSharp.Core.MCP.Settings; +using Microsoft.Extensions.Configuration; + +namespace BotSharp.Core.MCP; + +public static class BotSharpMcpExtensions +{ + /// + /// Add mcp + /// + /// + /// + /// + public static IServiceCollection AddBotSharpMCP(this IServiceCollection services, IConfiguration config) + { + var settings = config.GetSection("MCP").Get(); + services.AddScoped(provider => settings); + + if (settings != null && settings.Enabled && !settings.McpServerConfigs.IsNullOrEmpty()) + { + services.AddScoped(); + + var clientManager = new McpClientManager(settings); + services.AddScoped(provider => clientManager); + + // Register hooks + services.AddScoped(); + } + return services; + } + +} \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core.MCP/Helpers/AiFunctionHelper.cs b/src/Infrastructure/BotSharp.Core/MCP/Helpers/AiFunctionHelper.cs similarity index 100% rename from src/Infrastructure/BotSharp.Core.MCP/Helpers/AiFunctionHelper.cs rename to src/Infrastructure/BotSharp.Core/MCP/Helpers/AiFunctionHelper.cs diff --git a/src/Infrastructure/BotSharp.Core.MCP/Hooks/MCPToolAgentHook.cs b/src/Infrastructure/BotSharp.Core/MCP/Hooks/MCPToolAgentHook.cs similarity index 100% rename from src/Infrastructure/BotSharp.Core.MCP/Hooks/MCPToolAgentHook.cs rename to src/Infrastructure/BotSharp.Core/MCP/Hooks/MCPToolAgentHook.cs diff --git a/src/Infrastructure/BotSharp.Core.MCP/Managers/McpClientManager.cs b/src/Infrastructure/BotSharp.Core/MCP/Managers/McpClientManager.cs similarity index 100% rename from src/Infrastructure/BotSharp.Core.MCP/Managers/McpClientManager.cs rename to src/Infrastructure/BotSharp.Core/MCP/Managers/McpClientManager.cs diff --git a/src/Infrastructure/BotSharp.Core.MCP/Services/McpService.cs b/src/Infrastructure/BotSharp.Core/MCP/Services/McpService.cs similarity index 69% rename from src/Infrastructure/BotSharp.Core.MCP/Services/McpService.cs rename to src/Infrastructure/BotSharp.Core/MCP/Services/McpService.cs index bbc2c3f4..3bff4442 100644 --- a/src/Infrastructure/BotSharp.Core.MCP/Services/McpService.cs +++ b/src/Infrastructure/BotSharp.Core/MCP/Services/McpService.cs @@ -1,5 +1,7 @@ +using BotSharp.Core.MCP.Managers; using BotSharp.Core.MCP.Settings; using Microsoft.Extensions.Logging; +using ModelContextProtocol.Client; namespace BotSharp.Core.MCP.Services; @@ -7,13 +9,16 @@ public class McpService : IMcpService { private readonly IServiceProvider _services; private readonly ILogger _logger; + private readonly McpClientManager _mcpClientManager; public McpService( IServiceProvider services, - ILogger logger) + ILogger logger, + McpClientManager mcpClient) { _services = services; _logger = logger; + _mcpClientManager = mcpClient; } public IEnumerable GetServerConfigs() @@ -24,9 +29,9 @@ public class McpService : IMcpService foreach (var config in configs) { - var tools = _services.GetServices() - .Where(x => x.Provider == config.Name) - .Select(x => x.Name); + var tools = _mcpClientManager.GetMcpClientAsync(config.Id) + .Result.ListToolsAsync() + .Result.Select(x=> x.Name); options.Add(new McpServerOptionModel { diff --git a/src/Infrastructure/BotSharp.Core.MCP/Settings/MCPSettings.cs b/src/Infrastructure/BotSharp.Core/MCP/Settings/MCPSettings.cs similarity index 100% rename from src/Infrastructure/BotSharp.Core.MCP/Settings/MCPSettings.cs rename to src/Infrastructure/BotSharp.Core/MCP/Settings/MCPSettings.cs diff --git a/src/Infrastructure/BotSharp.Core/Routing/Executor/DummyFunctionExecutor.cs b/src/Infrastructure/BotSharp.Core/Routing/Executor/DummyFunctionExecutor.cs new file mode 100644 index 00000000..d075e185 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Routing/Executor/DummyFunctionExecutor.cs @@ -0,0 +1,37 @@ +using BotSharp.Abstraction.Templating; + +namespace BotSharp.Core.Routing.Executor; + +public class DummyFunctionExecutor: IFunctionExecutor +{ + private FunctionDef functionDef; + private readonly IServiceProvider _services; + + public DummyFunctionExecutor(FunctionDef function, IServiceProvider services) + { + functionDef = function; + _services = services; + } + + + public async Task ExecuteAsync(RoleDialogModel message) + { + var render = _services.GetRequiredService(); + var state = _services.GetRequiredService(); + + var dict = new Dictionary(); + foreach (var item in state.GetStates()) + { + dict[item.Key] = item.Value; + } + + var text = render.Render(functionDef.Output, dict); + message.Content = text; + return true; + } + + public async Task GetIndicatorAsync(RoleDialogModel message) + { + return "Running"; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Routing/Executor/FunctionCallbackExecutor.cs b/src/Infrastructure/BotSharp.Core/Routing/Executor/FunctionCallbackExecutor.cs new file mode 100644 index 00000000..939f4fb3 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Routing/Executor/FunctionCallbackExecutor.cs @@ -0,0 +1,23 @@ +using BotSharp.Abstraction.Functions; + +namespace BotSharp.Core.Routing.Executor; + +public class FunctionCallbackExecutor : IFunctionExecutor +{ + IFunctionCallback functionCallback; + + public FunctionCallbackExecutor(IFunctionCallback functionCallback) + { + this.functionCallback = functionCallback; + } + + public async Task ExecuteAsync(RoleDialogModel message) + { + return await functionCallback.Execute(message); + } + + public async Task GetIndicatorAsync(RoleDialogModel message) + { + return await functionCallback.GetIndication(message); + } +} diff --git a/src/Infrastructure/BotSharp.Core/Routing/Executor/FunctionExecutorFactory.cs b/src/Infrastructure/BotSharp.Core/Routing/Executor/FunctionExecutorFactory.cs new file mode 100644 index 00000000..3fb89509 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Routing/Executor/FunctionExecutorFactory.cs @@ -0,0 +1,41 @@ +using BotSharp.Abstraction.Functions; + +namespace BotSharp.Core.Routing.Executor; + +internal class FunctionExecutorFactory +{ + public static IFunctionExecutor Create(string functionName, Agent agent, IFunctionCallback functioncall, IServiceProvider serviceProvider) + { + if(functioncall != null) + { + return new FunctionCallbackExecutor(functioncall); + } + + var funDef = agent?.Functions?.FirstOrDefault(x => x.Name == functionName); + if (funDef != null) + { + if (!string.IsNullOrWhiteSpace(funDef?.Output)) + { + return new DummyFunctionExecutor(funDef,serviceProvider); + } + } + else + { + funDef = agent?.SecondaryFunctions?.FirstOrDefault(x => x.Name == functionName); + if (funDef != null) + { + if (!string.IsNullOrWhiteSpace(funDef?.Output)) + { + return new DummyFunctionExecutor(funDef, serviceProvider); + } + else + { + var mcpServerId = agent?.McpTools?.Where(x => x.Functions.Any(y => y.Name == funDef.Name)) + .FirstOrDefault().ServerId; + return new MCPToolExecutor(mcpServerId, functionName, serviceProvider); + } + } + } + return null; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Routing/Executor/IFunctionExecutor.cs b/src/Infrastructure/BotSharp.Core/Routing/Executor/IFunctionExecutor.cs new file mode 100644 index 00000000..4ba2e69f --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Routing/Executor/IFunctionExecutor.cs @@ -0,0 +1,8 @@ +namespace BotSharp.Core.Routing.Executor; + +public interface IFunctionExecutor +{ + public Task ExecuteAsync(RoleDialogModel message); + + public Task GetIndicatorAsync(RoleDialogModel message); +} diff --git a/src/Infrastructure/BotSharp.Core.MCP/Functions/McpToolAdapter.cs b/src/Infrastructure/BotSharp.Core/Routing/Executor/MCPToolExecutor.cs similarity index 72% rename from src/Infrastructure/BotSharp.Core.MCP/Functions/McpToolAdapter.cs rename to src/Infrastructure/BotSharp.Core/Routing/Executor/MCPToolExecutor.cs index 0e433597..f7625b48 100644 --- a/src/Infrastructure/BotSharp.Core.MCP/Functions/McpToolAdapter.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Executor/MCPToolExecutor.cs @@ -1,46 +1,34 @@ -using System.Text.Json; using BotSharp.Core.MCP.Managers; using ModelContextProtocol.Client; -namespace BotSharp.Core.MCP.Functions; +namespace BotSharp.Core.Routing.Executor; -public class McpToolAdapter : IFunctionCallback +public class MCPToolExecutor: IFunctionExecutor { - private readonly string _provider; - private readonly McpClientTool _tool; private readonly McpClientManager _clientManager; + private string mcpServer; + private string funcName; private readonly IServiceProvider _services; - public McpToolAdapter( - IServiceProvider services, - string serverName, - McpClientTool tool, - McpClientManager client) - { - _services = services ?? throw new ArgumentNullException(nameof(services)); - _tool = tool ?? throw new ArgumentNullException(nameof(tool)); - _clientManager = client ?? throw new ArgumentNullException(nameof(client)); - _provider = serverName; + public MCPToolExecutor(string mcpserver, string functionName, IServiceProvider services) + { + _services = services; + this.mcpServer = mcpserver; + this.funcName = functionName; + _clientManager = services.GetRequiredService(); } - public string Provider => _provider; - public string Name => _tool.Name; - - public async Task Execute(RoleDialogModel message) + public async Task ExecuteAsync(RoleDialogModel message) { try { // Convert arguments to dictionary format expected by mcpdotnet - Dictionary argDict = JsonToDictionary(message.FunctionArgs); - var currentAgentId = message.CurrentAgentId; - var agentService = _services.GetRequiredService(); - var agent = await agentService.LoadAgent(currentAgentId); - var serverId = agent.McpTools.Where(t => t.Functions.Any(f => f.Name == Name)).FirstOrDefault().ServerId; + Dictionary argDict = JsonToDictionary(message.FunctionArgs); - var client = await _clientManager.GetMcpClientAsync(serverId); + var client = await _clientManager.GetMcpClientAsync(mcpServer); // Call the tool through mcpdotnet - var result = await client.CallToolAsync(_tool.Name, !argDict.IsNullOrEmpty() ? argDict : []); + var result = await client.CallToolAsync(funcName, !argDict.IsNullOrEmpty() ? argDict : []); // Extract the text content from the result var json = string.Join("\n", result.Content.Where(c => c.Type == "text").Select(c => c.Text)); @@ -51,11 +39,17 @@ public class McpToolAdapter : IFunctionCallback } catch (Exception ex) { - message.Content = $"Error when calling tool {Name} of MCP server {Provider}. {ex.Message}"; + message.Content = $"Error when calling tool {funcName} of MCP server {mcpServer}. {ex.Message}"; return false; } } + public async Task GetIndicatorAsync(RoleDialogModel message) + { + return message.Indication ?? string.Empty; + } + + private static Dictionary JsonToDictionary(string? json) { if (string.IsNullOrEmpty(json)) diff --git a/src/Infrastructure/BotSharp.Core/Routing/Reasoning/InstructExecutor.cs b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/InstructExecutor.cs index e911437d..c93e133c 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Reasoning/InstructExecutor.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/InstructExecutor.cs @@ -25,9 +25,8 @@ public class InstructExecutor : IExecutor { inst.OriginalAgent = goalAgent; // Emit hook - await HookEmitter.Emit(_services, async hook => - await hook.OnRoutingInstructionRevised(inst, message) - ); + await HookEmitter.Emit(_services, async hook => await hook.OnRoutingInstructionRevised(inst, message), + message.CurrentAgentId); } message.FunctionArgs = JsonSerializer.Serialize(inst); diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs index 1c4ca518..b422a423 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs @@ -99,9 +99,8 @@ public class RoutingContext : IRoutingContext var preAgentId = _stack.Count == 0 ? agentId : _stack.Peek(); _stack.Push(agentId); - HookEmitter.Emit(_services, async hook => - await hook.OnAgentEnqueued(agentId, preAgentId, reason: reason) - ).Wait(); + HookEmitter.Emit(_services, async hook => await hook.OnAgentEnqueued(agentId, preAgentId, reason: reason), + agentId).Wait(); UpdateLazyRoutingAgent(updateLazyRouting); } @@ -120,9 +119,8 @@ public class RoutingContext : IRoutingContext var agentId = _stack.Pop(); var currentAgentId = GetCurrentAgentId(); - HookEmitter.Emit(_services, async hook => - await hook.OnAgentDequeued(agentId, currentAgentId, reason: reason) - ).Wait(); + HookEmitter.Emit(_services, async hook => await hook.OnAgentDequeued(agentId, currentAgentId, reason: reason), + agentId).Wait(); if (string.IsNullOrEmpty(currentAgentId)) { @@ -203,9 +201,8 @@ public class RoutingContext : IRoutingContext _stack.Pop(); _stack.Push(agentId); - HookEmitter.Emit(_services, async hook => - await hook.OnAgentReplaced(fromAgent, toAgent, reason: reason) - ).Wait(); + HookEmitter.Emit(_services, async hook => await hook.OnAgentReplaced(fromAgent, toAgent, reason: reason), + agentId).Wait(); } UpdateLazyRoutingAgent(updateLazyRouting); @@ -220,9 +217,8 @@ public class RoutingContext : IRoutingContext var agentId = GetCurrentAgentId(); _stack.Clear(); - HookEmitter.Emit(_services, async hook => - await hook.OnAgentQueueEmptied(agentId, reason: reason) - ).Wait(); + HookEmitter.Emit(_services, async hook => await hook.OnAgentQueueEmptied(agentId, reason: reason), + agentId).Wait(); } public void SetMessageId(string conversationId, string messageId) diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InstructLoop.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InstructLoop.cs index 364e7198..128debad 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InstructLoop.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InstructLoop.cs @@ -51,9 +51,8 @@ public partial class RoutingService int loopCount = 1; while (true) { - await HookEmitter.Emit(_services, async hook => - await hook.OnRoutingInstructionReceived(inst, message) - ); + await HookEmitter.Emit(_services, async hook => await hook.OnRoutingInstructionReceived(inst, message), + agent.Id); // Save states states.SaveStateByArgs(inst.Arguments); diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs index 8c37536c..126453e8 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Functions; using BotSharp.Abstraction.Templating; +using BotSharp.Core.Routing.Executor; namespace BotSharp.Core.Routing; @@ -8,22 +9,22 @@ public partial class RoutingService public async Task InvokeFunction(string name, RoleDialogModel message) { var function = _services.GetServices().FirstOrDefault(x => x.Name == name); + + var currentAgentId = message.CurrentAgentId; + var agentService = _services.GetRequiredService(); + var agent = await agentService.GetAgent(currentAgentId); - var isFillDummyContent = false; - var dummyFuncResponse = string.Empty; - if (function == null) + IFunctionExecutor funcExecutor = FunctionExecutorFactory.Create(name, agent, function, _services); + + if (funcExecutor == null) { - dummyFuncResponse = await GetDummyFunctionOutput(name, message); - isFillDummyContent = !string.IsNullOrEmpty(dummyFuncResponse); - if (!isFillDummyContent) - { - message.StopCompletion = true; - message.Content = $"Can't find function implementation of {name}."; - _logger.LogError(message.Content); - return false; - } + message.StopCompletion = true; + message.Content = $"Can't find function implementation of {name}."; + _logger.LogError(message.Content); + return false; } + // Clone message var clonedMessage = RoleDialogModel.From(message); clonedMessage.FunctionName = name; @@ -34,23 +35,13 @@ public partial class RoutingService var progressService = _services.GetService(); - // Before executing functions - if (!isFillDummyContent) - { - clonedMessage.Indication = await function.GetIndication(message); - } - else - { - clonedMessage.Indication = "Running"; - } + clonedMessage.Indication = await funcExecutor.GetIndicatorAsync(message); if (progressService?.OnFunctionExecuting != null) { await progressService.OnFunctionExecuting(clonedMessage); } - - var agentService = _services.GetRequiredService(); - var agent = await agentService.GetAgent(clonedMessage.CurrentAgentId); + foreach (var hook in hooks) { hook.SetAgent(agent); @@ -61,19 +52,7 @@ public partial class RoutingService try { - if (clonedMessage.Handled) - { - clonedMessage.Content = clonedMessage.Content; - } - else if (!isFillDummyContent) - { - result = await function.Execute(clonedMessage); - } - else - { - clonedMessage.Content = dummyFuncResponse; - result = true; - } + result = await funcExecutor.ExecuteAsync(clonedMessage); // After functions have been executed foreach (var hook in hooks) @@ -111,41 +90,6 @@ public partial class RoutingService message.Content = JsonSerializer.Serialize(message.Data); } - // Save to Storage as well - /*if (!message.StopCompletion && message.FunctionName != "route_to_agent") - { - var storage = _services.GetRequiredService(); - storage.Append(Context.ConversationId, message); - }*/ - return result; } - - private async Task GetDummyFunctionOutput(string functionName, RoleDialogModel message) - { - if (string.IsNullOrEmpty(message.CurrentAgentId)) - { - return null; - } - - var agentService = _services.GetRequiredService(); - var agent = await agentService.GetAgent(message.CurrentAgentId); - var found = agent?.Functions?.FirstOrDefault(x => x.Name == functionName); - if (string.IsNullOrWhiteSpace(found?.Output)) - { - return null; - } - - var render = _services.GetRequiredService(); - var state = _services.GetRequiredService(); - - var dict = new Dictionary(); - foreach (var item in state.GetStates()) - { - dict[item.Key] = item.Value; - } - - var text = render.Render(found.Output, dict); - return text; - } } diff --git a/src/Infrastructure/BotSharp.Core/Using.cs b/src/Infrastructure/BotSharp.Core/Using.cs index 8b63f96c..aa5a4595 100644 --- a/src/Infrastructure/BotSharp.Core/Using.cs +++ b/src/Infrastructure/BotSharp.Core/Using.cs @@ -1,48 +1,44 @@ -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 System.Net.Mime; -global using System.Net.Http; -global using System.Threading; -global using Microsoft.Extensions.DependencyInjection; -global using Microsoft.Extensions.Logging; -global using EntityFrameworkCore.BootKit; -global using BotSharp.Abstraction.Routing; -global using BotSharp.Abstraction.Plugins; global using BotSharp.Abstraction.Agents; -global using BotSharp.Abstraction.Conversations; -global using BotSharp.Abstraction.Crontab.Models; -global using BotSharp.Abstraction.Users; -global using BotSharp.Abstraction.Roles; -global using BotSharp.Abstraction.Roles.Models; -global using BotSharp.Abstraction.Utilities; -global using BotSharp.Abstraction.Conversations.Models; -global using BotSharp.Abstraction.Agents.Settings; -global using BotSharp.Abstraction.Conversations.Settings; global using BotSharp.Abstraction.Agents.Enums; global using BotSharp.Abstraction.Agents.Models; +global using BotSharp.Abstraction.Agents.Settings; +global using BotSharp.Abstraction.Conversations; +global using BotSharp.Abstraction.Conversations.Models; +global using BotSharp.Abstraction.Conversations.Settings; +global using BotSharp.Abstraction.Crontab.Models; +global using BotSharp.Abstraction.Files; +global using BotSharp.Abstraction.Files.Enums; +global using BotSharp.Abstraction.Files.Models; +global using BotSharp.Abstraction.Files.Utilities; global using BotSharp.Abstraction.Functions.Models; +global using BotSharp.Abstraction.Infrastructures.Events; +global using BotSharp.Abstraction.Knowledges.Models; +global using BotSharp.Abstraction.Loggers.Services; +global using BotSharp.Abstraction.MCP.Models; +global using BotSharp.Abstraction.MCP.Services; +global using BotSharp.Abstraction.Messaging.Enums; +global using BotSharp.Abstraction.Plugins; global using BotSharp.Abstraction.Repositories; global using BotSharp.Abstraction.Repositories.Filters; -global using BotSharp.Abstraction.Translation; -global using BotSharp.Abstraction.Files; -global using BotSharp.Abstraction.Files.Models; -global using BotSharp.Abstraction.Files.Enums; -global using BotSharp.Abstraction.Files.Utilities; -global using BotSharp.Abstraction.Translation.Attributes; -global using BotSharp.Abstraction.Messaging.Enums; -global using BotSharp.Abstraction.Knowledges.Models; +global using BotSharp.Abstraction.Roles; +global using BotSharp.Abstraction.Roles.Models; +global using BotSharp.Abstraction.Routing; global using BotSharp.Abstraction.SideCar.Attributes; -global using BotSharp.Abstraction.Statistics.Models; global using BotSharp.Abstraction.Statistics.Enums; +global using BotSharp.Abstraction.Statistics.Models; global using BotSharp.Abstraction.Statistics.Services; +global using BotSharp.Abstraction.Translation; +global using BotSharp.Abstraction.Translation.Attributes; +global using BotSharp.Abstraction.Users; +global using BotSharp.Abstraction.Utilities; global using BotSharp.Abstraction.Loggers.Services; global using BotSharp.Abstraction.Infrastructures.Events; global using BotSharp.Abstraction.Templating.Constants; global using BotSharp.Abstraction.Realtime.Models.Session; +global using BotSharp.Core.Agents.Services; +global using BotSharp.Core.Conversations.Services; +global using BotSharp.Core.Infrastructures; +global using BotSharp.Core.Loggers.Services; global using BotSharp.Core.Repository; global using BotSharp.Core.Routing; global using BotSharp.Core.Agents.Services; @@ -51,4 +47,16 @@ global using BotSharp.Core.Infrastructures; global using BotSharp.Core.Infrastructures.Websocket; global using BotSharp.Core.Users.Services; global using BotSharp.Core.Statistics.Services; -global using BotSharp.Core.Loggers.Services; \ No newline at end of file +global using BotSharp.Core.Users.Services; +global using EntityFrameworkCore.BootKit; +global using Microsoft.Extensions.DependencyInjection; +global using Microsoft.Extensions.Logging; +global using System; +global using System.Collections.Generic; +global using System.Linq; +global using System.Net.Http; +global using System.Net.Mime; +global using System.Text; +global using System.Text.Json; +global using System.Threading; +global using System.Threading.Tasks; diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs index a5a96a12..bcb92f74 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/ConversationController.cs @@ -328,7 +328,8 @@ public class ConversationController : ControllerBase }; await HookEmitter.Emit(_services, async hook => - await hook.OnNotificationGenerated(inputMsg) + await hook.OnNotificationGenerated(inputMsg), + routing.Context.GetCurrentAgentId() ); return response; diff --git a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs index ca3ec45a..430a4563 100644 --- a/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs +++ b/src/Infrastructure/BotSharp.OpenAPI/Controllers/InstructModeController.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Files.Utilities; +using BotSharp.Abstraction.Infrastructures; using BotSharp.Abstraction.Instructs; using BotSharp.Abstraction.Instructs.Models; using BotSharp.Core.Infrastructures; @@ -58,14 +59,7 @@ public class InstructModeController : ControllerBase var textCompletion = CompletionProvider.GetTextCompletion(_services); var response = await textCompletion.GetCompletion(input.Text, agentId, Guid.NewGuid().ToString()); - var hooks = _services.GetServices(); - foreach (var hook in hooks) - { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId) - { - continue; - } - + await HookEmitter.Emit(_services, async hook => await hook.OnResponseGenerated(new InstructResponseModel { AgentId = agentId, @@ -74,8 +68,8 @@ public class InstructModeController : ControllerBase TemplateName = input.Template, UserMessage = input.Text, CompletionText = response - }); - } + }), agentId); + return response; } @@ -103,14 +97,7 @@ public class InstructModeController : ControllerBase } }); - var hooks = _services.GetServices(); - foreach (var hook in hooks) - { - if (!string.IsNullOrEmpty(hook.SelfId) && hook.SelfId != agentId) - { - continue; - } - + await HookEmitter.Emit(_services, async hook => await hook.OnResponseGenerated(new InstructResponseModel { AgentId = agentId, @@ -120,8 +107,8 @@ public class InstructModeController : ControllerBase UserMessage = input.Text, SystemInstruction = message.RenderedInstruction, CompletionText = message.Content - }); - } + }), agentId); + return message.Content; } #endregion diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Realtime/RealTimeCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Realtime/RealTimeCompletionProvider.cs index 1cd32215..b3182b9a 100644 --- a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Realtime/RealTimeCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Realtime/RealTimeCompletionProvider.cs @@ -300,7 +300,7 @@ public class GoogleRealTimeProvider : IRealTimeCompletion config.ResponseModalities = [Modality.AUDIO]; var words = new List(); - HookEmitter.Emit(_services, hook => words.AddRange(hook.OnModelTranscriptPrompt(agent))); + HookEmitter.Emit(_services, hook => words.AddRange(hook.OnModelTranscriptPrompt(agent)), agent.Id); config.Temperature = Math.Max(realtimeSetting.Temperature, 0.6f); config.MaxOutputTokens = realtimeSetting.MaxResponseOutputTokens; @@ -320,7 +320,7 @@ public class GoogleRealTimeProvider : IRealTimeCompletion }).ToArray(); await HookEmitter.Emit(_services, - async hook => { await hook.OnSessionUpdated(agent, prompt, functions, isInit); }); + async hook => { await hook.OnSessionUpdated(agent, prompt, functions, isInit); }, agent.Id); if (_settings.Gemini.UseGoogleSearch) { diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs index 29e495ec..22daf3ff 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs @@ -339,7 +339,7 @@ public class RealTimeCompletionProvider : IRealTimeCompletion if (realtimeModelSettings.InputAudioTranscribe) { var words = new List(); - HookEmitter.Emit(_services, hook => words.AddRange(hook.OnModelTranscriptPrompt(agent))); + HookEmitter.Emit(_services, hook => words.AddRange(hook.OnModelTranscriptPrompt(agent)), agent.Id); sessionUpdate.session.InputAudioTranscription = new InputAudioTranscription { @@ -352,7 +352,7 @@ public class RealTimeCompletionProvider : IRealTimeCompletion await HookEmitter.Emit(_services, async hook => { await hook.OnSessionUpdated(agent, instruction, functions, isInit); - }); + }, agent.Id); await SendEventToModel(sessionUpdate); await Task.Delay(300); diff --git a/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Functions/SqlGenerationFn.cs b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Functions/SqlGenerationFn.cs index 4965a67c..11fd2cf7 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Functions/SqlGenerationFn.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Functions/SqlGenerationFn.cs @@ -90,7 +90,7 @@ public class SqlGenerationFn : IFunctionCallback { var requirement = await x.GetSummaryAdditionalRequirements(nameof(SqlGenerationPlanner), message); additionalRequirements.Add(requirement); - }); + }, message.CurrentAgentId); var globalKnowledges = new List(); foreach (var hook in knowledgeHooks) diff --git a/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Functions/SqlReviewFn.cs b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Functions/SqlReviewFn.cs index 6996fb6d..4255dd7f 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Functions/SqlReviewFn.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/SqlGeneration/Functions/SqlReviewFn.cs @@ -30,7 +30,8 @@ public class SqlReviewFn : IFunctionCallback if (args != null && !args.IsSqlTemplate && args.ContainsSqlStatements) { await HookEmitter.Emit(_services, async hook => - await hook.OnSourceCodeGenerated(nameof(SqlGenerationPlanner), message, "sql") + await hook.OnSourceCodeGenerated(nameof(SqlGenerationPlanner), message, "sql"), + message.CurrentAgentId ); } return true; diff --git a/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/Functions/SummaryPlanFn.cs b/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/Functions/SummaryPlanFn.cs index 465f1df2..4212d0c2 100644 --- a/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/Functions/SummaryPlanFn.cs +++ b/src/Plugins/BotSharp.Plugin.Planner/TwoStaging/Functions/SummaryPlanFn.cs @@ -68,7 +68,8 @@ public class SummaryPlanFn : IFunctionCallback message.Content = summary.Content; await HookEmitter.Emit(_services, async hook => - await hook.OnPlanningCompleted(nameof(TwoStageTaskPlanner), message) + await hook.OnPlanningCompleted(nameof(TwoStageTaskPlanner), message), + message.CurrentAgentId ); return true; @@ -88,7 +89,7 @@ public class SummaryPlanFn : IFunctionCallback { var requirement = await x.GetSummaryAdditionalRequirements(nameof(TwoStageTaskPlanner), message); additionalRequirements.Add(requirement); - }); + }, message.CurrentAgentId); var globalKnowledges = new List(); foreach (var hook in knowledgeHooks) diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs index 168929ea..99a7744f 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Hooks/SqlDriverPlanningHook.cs @@ -30,7 +30,7 @@ public class SqlDriverPlanningHook : IPlanningHook await HookEmitter.Emit(_services, async (hook) => { await hook.SqlGenerated(msg); - }); + }, msg.CurrentAgentId); var settings = _services.GetRequiredService(); if (!settings.ExecuteSqlSelectAutonomous) diff --git a/src/Plugins/BotSharp.Plugin.SqlDriver/Interfaces/ISqlDriverHook.cs b/src/Plugins/BotSharp.Plugin.SqlDriver/Interfaces/ISqlDriverHook.cs index b4871e66..63cff60b 100644 --- a/src/Plugins/BotSharp.Plugin.SqlDriver/Interfaces/ISqlDriverHook.cs +++ b/src/Plugins/BotSharp.Plugin.SqlDriver/Interfaces/ISqlDriverHook.cs @@ -1,6 +1,8 @@ +using BotSharp.Abstraction.Hooks; + namespace BotSharp.Plugin.SqlDriver.Interfaces; -public interface ISqlDriverHook +public interface ISqlDriverHook : IHookBase { // Get database type string GetDatabaseType(RoleDialogModel message); diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs index 9d7df9a0..511abb45 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs @@ -1,4 +1,5 @@ using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Infrastructures; using BotSharp.Abstraction.Infrastructures.Enums; using BotSharp.Core.Infrastructures; using BotSharp.Plugin.Twilio.Interfaces; @@ -55,7 +56,7 @@ public class TwilioInboundController : TwilioController await HookEmitter.Emit(_services, async hook => { await hook.OnSessionCreating(request, instruction); - }); + }, request.AgentId); var (agent, conversationId) = await InitConversation(request); request.ConversationId = conversationId.Id; @@ -65,9 +66,9 @@ public class TwilioInboundController : TwilioController if (twilio.MachineDetected(request)) { response = new VoiceResponse(); - + await HookEmitter.Emit(_services, - async hook => await hook.OnVoicemailStarting(request)); + async hook => await hook.OnVoicemailStarting(request), request.AgentId); var url = twilio.GetSpeechPath(request.ConversationId, "voicemail.mp3"); response.Play(new Uri(url)); @@ -118,7 +119,7 @@ public class TwilioInboundController : TwilioController await HookEmitter.Emit(_services, async hook => { await hook.OnSessionCreated(request); - }); + }, request.AgentId); return TwiML(response); } diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioOutboundController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioOutboundController.cs index 4796c02d..5543af38 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioOutboundController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioOutboundController.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Infrastructures; using BotSharp.Core.Infrastructures; using BotSharp.Plugin.Twilio.Interfaces; using BotSharp.Plugin.Twilio.Models; @@ -32,9 +33,8 @@ public class TwilioOutboundController : TwilioController if (twilio.MachineDetected(request)) { response = new VoiceResponse(); - await HookEmitter.Emit(_services, - async hook => await hook.OnVoicemailStarting(request)); + async hook => await hook.OnVoicemailStarting(request), request.AgentId); var url = twilio.GetSpeechPath(request.ConversationId, "voicemail.mp3"); response.Play(new Uri(url)); diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioRecordController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioRecordController.cs index 4e4b1c75..3d13dc73 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioRecordController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioRecordController.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Infrastructures; using BotSharp.Core.Infrastructures; using BotSharp.Plugin.Twilio.Interfaces; using BotSharp.Plugin.Twilio.Models; @@ -36,7 +37,7 @@ public class TwilioRecordController : TwilioController convService.SaveStates(); // recording completed - await HookEmitter.Emit(_services, x => x.OnRecordingCompleted(request)); + await HookEmitter.Emit(_services, x => x.OnRecordingCompleted(request), request.AgentId); } else { diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioTranscribeController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioTranscribeController.cs index 1fa020c0..778aa29d 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioTranscribeController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioTranscribeController.cs @@ -49,7 +49,8 @@ public class TwilioTranscribeController : TwilioController // transcription completed transcript.Language = request.LanguageCode; - await HookEmitter.Emit(_services, async x => await x.OnTranscribeCompleted(message, transcript)); + await HookEmitter.Emit(_services, async x => await x.OnTranscribeCompleted(message, transcript), + request.AgentId); } } diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs index 048d75f3..786f116c 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs @@ -65,7 +65,7 @@ public class TwilioVoiceController : TwilioController await HookEmitter.Emit(_services, async hook => { await hook.OnSessionCreating(request, instruction); - }); + }, request.AgentId); var twilio = _services.GetRequiredService(); if (string.IsNullOrWhiteSpace(request.Intent)) @@ -98,7 +98,7 @@ public class TwilioVoiceController : TwilioController await HookEmitter.Emit(_services, async hook => { await hook.OnSessionCreated(request); - }); + }, request.AgentId); return TwiML(response); } @@ -151,7 +151,7 @@ public class TwilioVoiceController : TwilioController await HookEmitter.Emit(_services, async hook => { await hook.OnReceivedUserMessage(request); - }); + }, request.AgentId); } else { @@ -161,7 +161,7 @@ public class TwilioVoiceController : TwilioController await HookEmitter.Emit(_services, async hook => { await hook.OnAgentHangUp(request); - }); + }, request.AgentId); response = twilio.HangUp(string.Empty); } @@ -185,7 +185,7 @@ public class TwilioVoiceController : TwilioController await HookEmitter.Emit(_services, async hook => { await hook.OnWaitingUserResponse(request, instruction); - }); + }, request.AgentId); response = twilio.ReturnInstructions(instruction); } @@ -223,7 +223,7 @@ public class TwilioVoiceController : TwilioController { request.AIResponseErrorMessage = $"AI response timeout: AIResponseWaitTime greater than {request.AIResponseWaitTime}, please check internal error log!"; await hook.OnAgentHangUp(request); - }); + }, request.AgentId); response = twilio.HangUp($"twilio/error.mp3"); } @@ -238,7 +238,7 @@ public class TwilioVoiceController : TwilioController await HookEmitter.Emit(_services, async hook => { await hook.OnAgentTransferring(request, _settings); - }); + }, request.AgentId); response = twilio.DialCsrAgent($"twilio/voice/speeches/{request.ConversationId}/{reply.SpeechFileName}"); } @@ -249,7 +249,7 @@ public class TwilioVoiceController : TwilioController await HookEmitter.Emit(_services, async hook => { await hook.OnAgentHangUp(request); - }); + }, request.AgentId); } else { @@ -274,7 +274,7 @@ public class TwilioVoiceController : TwilioController await HookEmitter.Emit(_services, async hook => { await hook.OnAgentResponsing(request, instruction); - }); + }, request.AgentId); response = twilio.ReturnInstructions(instruction); } @@ -343,41 +343,35 @@ public class TwilioVoiceController : TwilioController { var twilio = _services.GetRequiredService(); - // Define the options with the predicate - var emitOptions = new HookEmitOption - { - ShouldExecute = hook => hook.IsMatch(request) - }; - switch (request.CallStatus) { case "completed": if (twilio.MachineDetected(request)) { // voicemail - await HookEmitter.Emit(_services, hook => hook.OnVoicemailLeft(request), emitOptions); + await HookEmitter.Emit(_services, hook => hook.OnVoicemailLeft(request), request.AgentId); } else { // phone call completed - await HookEmitter.Emit(_services, hook => hook.OnUserDisconnected(request), emitOptions); + await HookEmitter.Emit(_services, hook => hook.OnUserDisconnected(request), request.AgentId); } break; case "busy": - await HookEmitter.Emit(_services, hook => hook.OnCallBusyStatus(request), emitOptions); + await HookEmitter.Emit(_services, hook => hook.OnCallBusyStatus(request), request.AgentId); break; case "no-answer": - await HookEmitter.Emit(_services, hook => hook.OnCallNoAnswerStatus(request), emitOptions); + await HookEmitter.Emit(_services, hook => hook.OnCallNoAnswerStatus(request), request.AgentId); break; case "canceled": - await HookEmitter.Emit(_services, hook => hook.OnCallCanceledStatus(request), emitOptions); + await HookEmitter.Emit(_services, hook => hook.OnCallCanceledStatus(request), request.AgentId); break; case "failed": - await HookEmitter.Emit(_services, hook => hook.OnCallFailedStatus(request), emitOptions); + await HookEmitter.Emit(_services, hook => hook.OnCallFailedStatus(request), request.AgentId); break; default: _logger.LogError($"Unknown call status: {request.CallStatus}, {request.CallSid}"); diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioCallStatusHook.cs b/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioCallStatusHook.cs index ae461595..e7ce0932 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioCallStatusHook.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioCallStatusHook.cs @@ -1,11 +1,11 @@ +using BotSharp.Abstraction.Hooks; using BotSharp.Plugin.Twilio.Models; using Task = System.Threading.Tasks.Task; namespace BotSharp.Plugin.Twilio.Interfaces; -public interface ITwilioCallStatusHook +public interface ITwilioCallStatusHook : IHookBase { - bool IsMatch(ConversationalVoiceRequest request) => true; Task OnVoicemailLeft(ConversationalVoiceRequest request) => Task.CompletedTask; Task OnUserDisconnected(ConversationalVoiceRequest request) => Task.CompletedTask; Task OnRecordingCompleted(ConversationalVoiceRequest request) => Task.CompletedTask; diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioSessionHook.cs b/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioSessionHook.cs index 40525475..00837b6a 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioSessionHook.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioSessionHook.cs @@ -1,10 +1,11 @@ +using BotSharp.Abstraction.Hooks; using BotSharp.Abstraction.Realtime.Models; using BotSharp.Plugin.Twilio.Models; using Task = System.Threading.Tasks.Task; namespace BotSharp.Plugin.Twilio.Interfaces; -public interface ITwilioSessionHook +public interface ITwilioSessionHook : IHookBase { /// /// Before session creating diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs index 0ca10949..84f88b5b 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs @@ -157,7 +157,8 @@ public class TwilioMessageQueueService : BackgroundService var agentService = sp.GetRequiredService(); var agent = agentService.GetAgent(agentId).Result; var extraWords = new List(); - HookEmitter.Emit(sp, hook => extraWords.AddRange(hook.OnModelTranscriptPrompt(agent))); + HookEmitter.Emit(sp, hook => extraWords.AddRange(hook.OnModelTranscriptPrompt(agent)), + agentId); var phrases = reply.Content.Split(',', StringSplitOptions.RemoveEmptyEntries); int capcity = 100; diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs index 7bc95382..e0618aaf 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs @@ -235,7 +235,8 @@ public class TwilioService if (_settings.TranscribeEnabled) { var words = new List(); - HookEmitter.Emit(_services, hook => words.AddRange(hook.OnModelTranscriptPrompt(agent))); + HookEmitter.Emit(_services, hook => words.AddRange(hook.OnModelTranscriptPrompt(agent)), + agent.Id); var hints = string.Join(", ", words); var start = new Start(); start.Transcription( @@ -323,10 +324,8 @@ public class TwilioService ActionOnEmptyResult = true }; - await HookEmitter.Emit(_services, async hook => - { - await hook.OnWaitingAgentResponse(request, instruction); - }); + await HookEmitter.Emit(_services, async hook => await hook.OnWaitingAgentResponse(request, instruction), + request.AgentId); response = ReturnInstructions(instruction); } diff --git a/src/WebStarter/WebStarter.csproj b/src/WebStarter/WebStarter.csproj index 85ff73ab..5db5199f 100644 --- a/src/WebStarter/WebStarter.csproj +++ b/src/WebStarter/WebStarter.csproj @@ -36,7 +36,6 @@ -