diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/Enums/UtilityName.cs b/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/Enums/UtilityName.cs new file mode 100644 index 00000000..266b76d4 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/Enums/UtilityName.cs @@ -0,0 +1,7 @@ +namespace BotSharp.Plugin.Twilio.OutboundCallHandler.Enums +{ + public class UtilityName + { + public const string TwilioOutboundCaller = "twilio-outbound-caller"; + } +} diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/Functions/HandleOutboundCallFn.cs b/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/Functions/HandleOutboundCallFn.cs new file mode 100644 index 00000000..5fa1cf2e --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/Functions/HandleOutboundCallFn.cs @@ -0,0 +1,34 @@ +using BotSharp.Abstraction.Options; +using BotSharp.Plugin.Twilio.OutboundCallHandler.LlmContexts; + +namespace BotSharp.Plugin.Twilio.OutboundCallHandler.Functions +{ + public class HandleOutboundCallFn : IFunctionCallback + { + private readonly IServiceProvider _services; + private readonly ILogger _logger; + private readonly BotSharpOptions _options; + private readonly TwilioSetting _twilioSetting; + + public string Name => "handle_outbound_call"; + public string Indication => "Dialing the number"; + + public HandleOutboundCallFn( + IServiceProvider services, + ILogger logger, + BotSharpOptions options, + TwilioSetting twilioSetting) + { + _services = services; + _logger = logger; + _options = options; + _twilioSetting = twilioSetting; + } + + public async Task Execute(RoleDialogModel message) + { + var args = JsonSerializer.Deserialize(message.FunctionArgs, _options.JsonSerializerOptions); + return true; + } + } +} diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/Hooks/OutboundCallHandlerHook.cs b/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/Hooks/OutboundCallHandlerHook.cs new file mode 100644 index 00000000..e57c0bc3 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/Hooks/OutboundCallHandlerHook.cs @@ -0,0 +1,58 @@ +using BotSharp.Abstraction.Agents.Models; +using BotSharp.Abstraction.Agents.Settings; +using BotSharp.Abstraction.Repositories; +using BotSharp.Abstraction.Utilities; +using BotSharp.Plugin.Twilio.OutboundCallHandler.Enums; + +namespace BotSharp.Plugin.Twilio.OutboundCallHandler.Hooks +{ + internal class OutboundCallHandlerHook : AgentHookBase + { + private static string FUNCTION_NAME = "handle_outbound_call"; + + public override string SelfId => string.Empty; + + public OutboundCallHandlerHook(IServiceProvider services, AgentSettings settings) : base(services, settings) + { + } + + public override void OnAgentLoaded(Agent agent) + { + var conv = _services.GetRequiredService(); + var isConvMode = conv.IsConversationMode(); + var isEnabled = !agent.Utilities.IsNullOrEmpty() && agent.Utilities.Contains(UtilityName.TwilioOutboundCaller); + + if (isConvMode && isEnabled) + { + var (prompt, fn) = GetPromptAndFunction(); + if (fn != null) + { + if (!string.IsNullOrWhiteSpace(prompt)) + { + agent.Instruction += $"\r\n\r\n{prompt}\r\n\r\n"; + } + + if (agent.Functions == null) + { + agent.Functions = new List { fn }; + } + else + { + agent.Functions.Add(fn); + } + } + } + + base.OnAgentLoaded(agent); + } + + private (string, FunctionDef) GetPromptAndFunction() + { + var db = _services.GetRequiredService(); + var agent = db.GetAgent(BuiltInAgentId.UtilityAssistant); + var prompt = agent?.Templates?.FirstOrDefault(x => x.Name.IsEqualTo($"{FUNCTION_NAME}.fn"))?.Content ?? string.Empty; + var loadAttachmentFn = agent?.Functions?.FirstOrDefault(x => x.Name.IsEqualTo(FUNCTION_NAME)); + return (prompt, loadAttachmentFn); + } + } +} diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/Hooks/OutboundCallHandlerUtilityHook.cs b/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/Hooks/OutboundCallHandlerUtilityHook.cs new file mode 100644 index 00000000..75f864ce --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/Hooks/OutboundCallHandlerUtilityHook.cs @@ -0,0 +1,12 @@ +using BotSharp.Plugin.Twilio.OutboundCallHandler.Enums; + +namespace BotSharp.Plugin.Twilio.OutboundCallHandler.Hooks +{ + public class OutboundCallHandlerUtilityHook : IAgentUtilityHook + { + public void AddUtilities(List utilities) + { + utilities.Add(UtilityName.TwilioOutboundCaller); + } + } +} diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/LlmContexts/LlmContextIn.cs b/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/LlmContexts/LlmContextIn.cs new file mode 100644 index 00000000..7d2d9adc --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/LlmContexts/LlmContextIn.cs @@ -0,0 +1,13 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Plugin.Twilio.OutboundCallHandler.LlmContexts +{ + public class LlmContextIn + { + [JsonPropertyName("phone_number")] + public string PhoneNumber { get; set; } + + [JsonPropertyName("first_message")] + public string FirstMessage { get; set; } + } +} diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/LlmContexts/LlmContextOut.cs b/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/LlmContexts/LlmContextOut.cs new file mode 100644 index 00000000..37041568 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/LlmContexts/LlmContextOut.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Plugin.Twilio.OutboundCallHandler.LlmContexts +{ + public class LlmContextOut + { + [JsonPropertyName("conversation_id")] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string ConversationId { get; set; } + } +} diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/OutboundCallHandlerPlugin.cs b/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/OutboundCallHandlerPlugin.cs new file mode 100644 index 00000000..eca25b50 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/OutboundCallHandlerPlugin.cs @@ -0,0 +1,18 @@ +using BotSharp.Plugin.Twilio.OutboundCallHandler.Hooks; + +namespace BotSharp.Plugin.Twilio.OutboundCallHandler +{ + public class OutboundCallHandlerPlugin : IBotSharpPlugin + { + public string Id => "901ec364-01da-4602-944f-2bc49e03b8b2"; + public string Name => "Outbound Call Handler"; + public string Description => "Empower agent to make outbound call via Twilio"; + public string IconUrl => "https://cdn-icons-png.freepik.com/512/6711/6711567.png"; + + public void RegisterDI(IServiceCollection services, IConfiguration config) + { + services.AddScoped(); + services.AddScoped(); + } + } +} diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/data/agents/3c88e946-ef5d-44ee-86e9-76e682cdab3e/functions/handle_outbound_call.json b/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/data/agents/3c88e946-ef5d-44ee-86e9-76e682cdab3e/functions/handle_outbound_call.json new file mode 100644 index 00000000..341b0ef1 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/data/agents/3c88e946-ef5d-44ee-86e9-76e682cdab3e/functions/handle_outbound_call.json @@ -0,0 +1,18 @@ +{ + "name": "handle_outbound_call", + "description": "If the user wants to initiate a phone call, you need to capture the phone number and the message the users wants to send. Then call this function to make an outbound call via Twilio.", + "parameters": { + "type": "object", + "properties": { + "phone_number": { + "to_read": "string", + "description": "The phone number which will be dialed. It needs to be a valid phone number starting with +1." + }, + "first_message": { + "to_read": "string", + "description": "The first message which will be sent." + } + }, + "required": [ "phone_number", "first_message" ] + } +} \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/data/agents/3c88e946-ef5d-44ee-86e9-76e682cdab3e/templates/handle_outbound_call_fn.liquid b/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/data/agents/3c88e946-ef5d-44ee-86e9-76e682cdab3e/templates/handle_outbound_call_fn.liquid new file mode 100644 index 00000000..55210a34 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundCallHandler/data/agents/3c88e946-ef5d-44ee-86e9-76e682cdab3e/templates/handle_outbound_call_fn.liquid @@ -0,0 +1,2 @@ +** Please take a look at the conversation and decide whether user wants to make an outbound call. +** Please call handle_outbound_call if user wants to make an outbound call. \ No newline at end of file