Skip to content

Hang up phone with audio #952

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ await HookEmitter.Emit<ITwilioSessionHook>(_services, async hook =>
OnlyOnce = true
});

response = twilio.HangUp(null);
response = twilio.HangUp(string.Empty);
}
// keep waiting for user response
else
Expand Down Expand Up @@ -433,8 +433,18 @@ public async Task<FileContentResult> GetSpeechFile([FromRoute] string conversati
[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);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -27,6 +29,7 @@ public async Task<bool> Execute(RoleDialogModel message)
{
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>();
Expand All @@ -39,21 +42,28 @@ public async Task<bool> Execute(RoleDialogModel message)
return false;
}

if (args.AnythingElseToHelp)
{
message.Content = "Tell me how I can help.";
}
else
var processUrl = $"{_twilioSetting.CallbackHost}/twilio/voice/hang-up?conversation-id={conversationId}";

// Generate initial assistant audio
string initAudioFile = null;
if (!string.IsNullOrEmpty(args.ResponseContent))
{
var call = CallResource.Update(
url: new Uri($"{_twilioSetting.CallbackHost}/twilio/voice/hang-up?conversation-id={conversationId}"),
pathSid: callSid
);
var completion = CompletionProvider.GetAudioCompletion(_services, "openai", "tts-1");
var data = await completion.GenerateAudioFromTextAsync(args.ResponseContent);
initAudioFile = "ending.mp3";
fileStorage.SaveSpeechFile(conversationId, initAudioFile, data);

message.Content = "The call is ending.";
message.StopCompletion = true;
processUrl += $"&init-audio-file={initAudioFile}";
}

var call = CallResource.Update(
url: new Uri(processUrl),
pathSid: callSid
);

message.Content = args.Reason;
message.StopCompletion = true;

return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,44 +35,35 @@ public async Task<bool> Execute(RoleDialogModel message)

var fileStorage = _services.GetRequiredService<IFileStorageService>();
var states = _services.GetRequiredService<IConversationStateService>();
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;
}

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";
var 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");
// Transfer call
var call = CallResource.Update(
pathSid: sid,
url: new Uri(processUrl));

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;
}
message.Content = args.TransitionMessage;
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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!;
}
16 changes: 16 additions & 0 deletions src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,22 @@ public VoiceResponse HangUp(string speechPath)
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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" ]
}
}
Loading