Hang up phone with audio

This commit is contained in:
Haiping Chen 2025-03-18 13:10:35 -05:00
parent 1c9f76947a
commit 72abb965de
6 changed files with 80 additions and 50 deletions

View file

@ -164,7 +164,7 @@ public class TwilioVoiceController : TwilioController
OnlyOnce = true
});
response = twilio.HangUp(null);
response = twilio.HangUp(string.Empty);
}
// keep waiting for user response
else
@ -433,8 +433,18 @@ public class TwilioVoiceController : TwilioController
[HttpPost("twilio/voice/hang-up")]
public async Task<TwiMLResult> Hangup(ConversationalVoiceRequest request)
{
var instruction = new ConversationalVoiceResponse
{
ConversationId = request.ConversationId
};
if (request.InitAudioFile != null)
{
instruction.SpeechPaths.Add(request.InitAudioFile);
}
var twilio = _services.GetRequiredService<TwilioService>();
var response = twilio.HangUp("twilio/bye.mp3");
var response = twilio.HangUp(instruction);
return TwiML(response);
}

View file

@ -1,4 +1,6 @@
using BotSharp.Abstraction.Files;
using BotSharp.Abstraction.Routing;
using BotSharp.Core.Infrastructures;
using BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.LlmContexts;
using Twilio.Rest.Api.V2010.Account;
@ -27,6 +29,7 @@ public class HangupPhoneCallFn : IFunctionCallback
{
var args = JsonSerializer.Deserialize<HangupPhoneCallArgs>(message.FunctionArgs);
var fileStorage = _services.GetRequiredService<IFileStorageService>();
var routing = _services.GetRequiredService<IRoutingService>();
var conversationId = routing.Context.ConversationId;
var states = _services.GetRequiredService<IConversationStateService>();
@ -39,21 +42,28 @@ public class HangupPhoneCallFn : IFunctionCallback
return false;
}
if (args.AnythingElseToHelp)
{
message.Content = "Tell me how I can help.";
}
else
{
var call = CallResource.Update(
url: new Uri($"{_twilioSetting.CallbackHost}/twilio/voice/hang-up?conversation-id={conversationId}"),
pathSid: callSid
);
var processUrl = $"{_twilioSetting.CallbackHost}/twilio/voice/hang-up?conversation-id={conversationId}";
message.Content = "The call is ending.";
message.StopCompletion = true;
// Generate initial assistant audio
string initAudioFile = null;
if (!string.IsNullOrEmpty(args.ResponseContent))
{
var completion = CompletionProvider.GetAudioCompletion(_services, "openai", "tts-1");
var data = await completion.GenerateAudioFromTextAsync(args.ResponseContent);
initAudioFile = "ending.mp3";
fileStorage.SaveSpeechFile(conversationId, initAudioFile, data);
processUrl += $"&init-audio-file={initAudioFile}";
}
var call = CallResource.Update(
url: new Uri(processUrl),
pathSid: callSid
);
message.Content = args.Reason;
message.StopCompletion = true;
return true;
}
}

View file

@ -35,44 +35,35 @@ public class TransferPhoneCallFn : IFunctionCallback
var fileStorage = _services.GetRequiredService<IFileStorageService>();
var states = _services.GetRequiredService<IConversationStateService>();
var routing = _services.GetRequiredService<IRoutingService>();
var conversationId = routing.Context.ConversationId;
var processUrl = $"{_twilioSetting.CallbackHost}/twilio/voice/transfer-call?conversation-id={conversationId}&transfer-to={args.PhoneNumber}";
// Generate initial assistant audio
string initAudioFile = null;
if (!string.IsNullOrEmpty(args.TransitionMessage))
{
var completion = CompletionProvider.GetAudioCompletion(_services, "openai", "tts-1");
var data = await completion.GenerateAudioFromTextAsync(args.TransitionMessage);
initAudioFile = "transfer.mp3";
fileStorage.SaveSpeechFile(conversationId, initAudioFile, data);
processUrl += $"&init-audio-file={initAudioFile}";
}
if (!string.IsNullOrEmpty(initAudioFile))
{
processUrl += $"&init-audio-file={initAudioFile}";
}
// Forward call
var sid = states.GetState("twilio_call_sid");
if (string.IsNullOrEmpty(sid))
{
_logger.LogError("Twilio call sid is empty.");
message.Content = "There is an error when transferring the phone call.";
return false;
}
else
{
var call = CallResource.Update(
pathSid: sid,
url: new Uri(processUrl));
message.Content = args.TransitionMessage;
return true;
var routing = _services.GetRequiredService<IRoutingService>();
var conversationId = routing.Context.ConversationId;
var processUrl = $"{_twilioSetting.CallbackHost}/twilio/voice/transfer-call?conversation-id={conversationId}&transfer-to={args.PhoneNumber}";
// Generate initial assistant audio
if (!string.IsNullOrEmpty(args.TransitionMessage))
{
var completion = CompletionProvider.GetAudioCompletion(_services, "openai", "tts-1");
var data = await completion.GenerateAudioFromTextAsync(args.TransitionMessage);
var initAudioFile = "transfer.mp3";
fileStorage.SaveSpeechFile(conversationId, initAudioFile, data);
processUrl += $"&init-audio-file={initAudioFile}";
}
// Transfer call
var call = CallResource.Update(
pathSid: sid,
url: new Uri(processUrl));
message.Content = args.TransitionMessage;
return true;
}
}

View file

@ -4,6 +4,9 @@ namespace BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.LlmContexts;
public class HangupPhoneCallArgs
{
[JsonPropertyName("anything_else_to_help")]
public bool AnythingElseToHelp { get; set; } = true;
[JsonPropertyName("reason")]
public string Reason { get; set; } = null!;
[JsonPropertyName("response_content")]
public string ResponseContent { get; set; } = null!;
}

View file

@ -149,6 +149,22 @@ public class TwilioService
return response;
}
public VoiceResponse HangUp(ConversationalVoiceResponse voiceResponse)
{
var response = new VoiceResponse();
var conversationId = voiceResponse.ConversationId;
if (voiceResponse.SpeechPaths != null && voiceResponse.SpeechPaths.Any())
{
foreach (var speechPath in voiceResponse.SpeechPaths)
{
var uri = GetSpeechPath(conversationId, speechPath);
response.Play(new Uri(uri));
}
}
response.Hangup();
return response;
}
public VoiceResponse DialCsrAgent(string speechPath)
{
var response = new VoiceResponse();

View file

@ -9,11 +9,11 @@
"type": "string",
"description": "The reason why user wants to end the phone call."
},
"anything_else_to_help": {
"type": "boolean",
"description": "Check if user has anything else to help."
"response_content": {
"type": "string",
"description": "A statement said to the user when politely ending a conversation."
}
},
"required": [ "reason", "anything_else_to_help" ]
"required": [ "reason", "response_content" ]
}
}