Skip to content
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

Hang up phone with audio #952

Merged
merged 1 commit into from
Mar 18, 2025
Merged
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
@@ -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
@@ -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);
}

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;

@@ -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>();
@@ -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
@@ -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
@@ -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
@@ -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();
Original file line number Diff line number Diff line change
@@ -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
Oops, something went wrong.