From 58a05007f7e3562b4ce916549ed60c5c5a442aed Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Wed, 19 Mar 2025 14:37:08 -0500 Subject: [PATCH] util-twilio-leave_voicemail --- .../BotSharp.Plugin.Twilio.csproj | 15 +---- .../Controllers/TwilioStreamController.cs | 29 +++++---- .../Controllers/TwilioVoiceController.cs | 3 +- .../Interfaces/ITwilioCallStatusHook.cs | 1 + .../Functions/LeaveVoicemailFn.cs | 59 +++++++++++++++++++ .../OutboundPhoneCallHandlerUtilityHook.cs | 6 +- .../LlmContexts/LeaveVoicemailArgs.cs | 12 ++++ .../util-twilio-leave_voicemail.json | 19 ++++++ .../util-twilio-hangup_phone_call.fn.liquid | 3 - .../util-twilio-outbound_phone_call.fn.liquid | 1 - 10 files changed, 116 insertions(+), 32 deletions(-) create mode 100644 src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/LeaveVoicemailFn.cs create mode 100644 src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/LlmContexts/LeaveVoicemailArgs.cs create mode 100644 src/Plugins/BotSharp.Plugin.Twilio/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-twilio-leave_voicemail.json delete mode 100644 src/Plugins/BotSharp.Plugin.Twilio/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-twilio-hangup_phone_call.fn.liquid delete mode 100644 src/Plugins/BotSharp.Plugin.Twilio/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-twilio-outbound_phone_call.fn.liquid diff --git a/src/Plugins/BotSharp.Plugin.Twilio/BotSharp.Plugin.Twilio.csproj b/src/Plugins/BotSharp.Plugin.Twilio/BotSharp.Plugin.Twilio.csproj index a3a00f981..775a93848 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/BotSharp.Plugin.Twilio.csproj +++ b/src/Plugins/BotSharp.Plugin.Twilio/BotSharp.Plugin.Twilio.csproj @@ -14,11 +14,12 @@ - - + + PreserveNewest + PreserveNewest @@ -31,12 +32,6 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - @@ -51,8 +46,4 @@ - - - - diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioStreamController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioStreamController.cs index 04133b4c1..007281e6b 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioStreamController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioStreamController.cs @@ -38,16 +38,6 @@ public async Task InitiateStreamConversation(ConversationalVoiceReq var twilio = _services.GetRequiredService(); VoiceResponse response = default!; - if (request.AnsweredBy == "machine_start" && - request.Direction == "outbound-api" && - request.InitAudioFile != null) - { - response = new VoiceResponse(); - var url = twilio.GetSpeechPath(request.ConversationId, request.InitAudioFile); - response.Play(new Uri(url)); - return TwiML(response); - } - var instruction = new ConversationalVoiceResponse { ConversationId = request.ConversationId, @@ -70,7 +60,24 @@ await HookEmitter.Emit(_services, async hook => request.ConversationId = await InitConversation(request); instruction.ConversationId = request.ConversationId; - response = twilio.ReturnBidirectionalMediaStreamsInstructions(instruction); + + if (request.AnsweredBy == "machine_start" && + request.Direction == "outbound-api") + { + response = new VoiceResponse(); + + await HookEmitter.Emit(_services, async hook => + { + await hook.OnVoicemailStarting(request); + }); + + var url = twilio.GetSpeechPath(request.ConversationId, "voicemail.mp3"); + response.Play(new Uri(url)); + } + else + { + response = twilio.ReturnBidirectionalMediaStreamsInstructions(instruction); + } await HookEmitter.Emit(_services, async hook => { diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs index 0a898651e..94a0b9f0d 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs @@ -475,8 +475,7 @@ public async Task PhoneCallStatus(ConversationalVoiceRequest reque if (request.CallStatus == "completed") { if (request.AnsweredBy == "machine_start" && - request.Direction == "outbound-api" && - request.InitAudioFile != null) + request.Direction == "outbound-api") { // voicemail await HookEmitter.Emit(_services, async hook => diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioCallStatusHook.cs b/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioCallStatusHook.cs index c9ed47106..0c9aa83e7 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioCallStatusHook.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Interfaces/ITwilioCallStatusHook.cs @@ -8,4 +8,5 @@ public interface ITwilioCallStatusHook Task OnVoicemailLeft(ConversationalVoiceRequest request); Task OnUserDisconnected(ConversationalVoiceRequest request); Task OnRecordingCompleted(ConversationalVoiceRequest request); + Task OnVoicemailStarting(ConversationalVoiceRequest request); } diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/LeaveVoicemailFn.cs b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/LeaveVoicemailFn.cs new file mode 100644 index 000000000..50b098538 --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/LeaveVoicemailFn.cs @@ -0,0 +1,59 @@ +using BotSharp.Abstraction.Files; +using BotSharp.Abstraction.Routing; +using BotSharp.Core.Infrastructures; +using BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.LlmContexts; + +namespace BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.Functions; + +public class LeaveVoicemailFn : IFunctionCallback +{ + private readonly IServiceProvider _services; + private readonly ILogger _logger; + private readonly TwilioSetting _setting; + + public string Name => "util-twilio-leave_voicemail"; + public string Indication => "leaving a voicemail"; + + public LeaveVoicemailFn( + IServiceProvider services, + ILogger logger, + TwilioSetting setting) + { + _services = services; + _logger = logger; + _setting = setting; + } + + public async Task Execute(RoleDialogModel message) + { + var args = JsonSerializer.Deserialize(message.FunctionArgs); + + var fileStorage = _services.GetRequiredService(); + var routing = _services.GetRequiredService(); + var conversationId = routing.Context.ConversationId; + var states = _services.GetRequiredService(); + var callSid = states.GetState("twilio_call_sid"); + + if (string.IsNullOrEmpty(callSid)) + { + message.Content = "The call has not been initiated."; + _logger.LogError(message.Content); + return false; + } + + // Generate voice message audio + string initAudioFile = null; + if (!string.IsNullOrEmpty(args.VoicemailMessage)) + { + var completion = CompletionProvider.GetAudioCompletion(_services, "openai", "tts-1"); + var data = await completion.GenerateAudioFromTextAsync(args.VoicemailMessage); + initAudioFile = "voicemail.mp3"; + fileStorage.SaveSpeechFile(conversationId, initAudioFile, data); + } + + message.Content = args.VoicemailMessage; + message.StopCompletion = true; + + return true; + } +} diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Hooks/OutboundPhoneCallHandlerUtilityHook.cs b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Hooks/OutboundPhoneCallHandlerUtilityHook.cs index 14fce2901..58999457b 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Hooks/OutboundPhoneCallHandlerUtilityHook.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Hooks/OutboundPhoneCallHandlerUtilityHook.cs @@ -10,6 +10,7 @@ public class OutboundPhoneCallHandlerUtilityHook : IAgentUtilityHook private static string TRANSFER_PHONE_CALL_FN = $"{PREFIX}transfer_phone_call"; private static string HANGUP_PHONE_CALL_FN = $"{PREFIX}hangup_phone_call"; private static string TEXT_MESSAGE_FN = $"{PREFIX}text_message"; + private static string LEAVE_VOICEMAIL_FN = $"{PREFIX}leave_voicemail"; public void AddUtilities(List utilities) { @@ -21,12 +22,11 @@ public void AddUtilities(List utilities) new($"{OUTBOUND_PHONE_CALL_FN}"), new($"{TRANSFER_PHONE_CALL_FN}"), new($"{HANGUP_PHONE_CALL_FN}"), - new($"{TEXT_MESSAGE_FN}") + new($"{TEXT_MESSAGE_FN}"), + new($"{LEAVE_VOICEMAIL_FN}") ], Templates = [ - new($"{OUTBOUND_PHONE_CALL_FN}.fn"), - new($"{HANGUP_PHONE_CALL_FN}.fn") ] }; diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/LlmContexts/LeaveVoicemailArgs.cs b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/LlmContexts/LeaveVoicemailArgs.cs new file mode 100644 index 000000000..85cc3913b --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/LlmContexts/LeaveVoicemailArgs.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.LlmContexts; + +public class LeaveVoicemailArgs +{ + [JsonPropertyName("phone_number")] + public string PhoneNumber { get; set; } = null!; + + [JsonPropertyName("voicemail_message")] + public string VoicemailMessage { get; set; } = null!; +} diff --git a/src/Plugins/BotSharp.Plugin.Twilio/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-twilio-leave_voicemail.json b/src/Plugins/BotSharp.Plugin.Twilio/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-twilio-leave_voicemail.json new file mode 100644 index 000000000..53307a83c --- /dev/null +++ b/src/Plugins/BotSharp.Plugin.Twilio/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/functions/util-twilio-leave_voicemail.json @@ -0,0 +1,19 @@ +{ + "name": "util-twilio-leave_voicemail", + "description": "If the user wants you to leave a voicemail.", + "visibility_expression": "{% if states.channel == 'phone' %}visible{% endif %}", + "parameters": { + "type": "object", + "properties": { + "voicemail_message": { + "type": "string", + "description": "User voicemail with details." + }, + "phone_number": { + "type": "string", + "description": "Phone number to callback." + } + }, + "required": [ "voicemail_message", "phone_number" ] + } +} \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.Twilio/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-twilio-hangup_phone_call.fn.liquid b/src/Plugins/BotSharp.Plugin.Twilio/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-twilio-hangup_phone_call.fn.liquid deleted file mode 100644 index 5e89cbd24..000000000 --- a/src/Plugins/BotSharp.Plugin.Twilio/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-twilio-hangup_phone_call.fn.liquid +++ /dev/null @@ -1,3 +0,0 @@ -{% if channel == 'phone' %} -** If user wants to end the phone call or conversation, ask user if there is anything else to help. If not, end the phone call. -{% endif %} \ No newline at end of file diff --git a/src/Plugins/BotSharp.Plugin.Twilio/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-twilio-outbound_phone_call.fn.liquid b/src/Plugins/BotSharp.Plugin.Twilio/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-twilio-outbound_phone_call.fn.liquid deleted file mode 100644 index d8fa9131a..000000000 --- a/src/Plugins/BotSharp.Plugin.Twilio/data/agents/6745151e-6d46-4a02-8de4-1c4f21c7da95/templates/util-twilio-outbound_phone_call.fn.liquid +++ /dev/null @@ -1 +0,0 @@ -** Please call util-twilio-outbound_phone_call if user wants to make an outbound call. \ No newline at end of file