Merge pull request #593 from seplz/update/twilioPlugin · SciSharp/BotSharp@081b20c · GitHub
Skip to content

Commit 081b20c

Browse files
authored
Merge pull request #593 from seplz/update/twilioPlugin
update twilioPlugin
2 parents d5daa92 + 694fcbf commit 081b20c

9 files changed

Lines changed: 138 additions & 107 deletions

File tree

‎src/Infrastructure/BotSharp.Core/Files/Services/Storage/LocalFileStorageService.Audio.cs‎

Lines changed: 5 additions & 3 deletions

‎src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioVoiceController.cs‎

Lines changed: 54 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using BotSharp.Abstraction.Files;
2-
using BotSharp.Abstraction.Routing;
32
using BotSharp.Core.Infrastructures;
43
using BotSharp.Plugin.Twilio.Models;
54
using BotSharp.Plugin.Twilio.Services;
@@ -10,7 +9,7 @@
109
namespace BotSharp.Plugin.Twilio.Controllers;
1110

1211
[AllowAnonymous]
13-
[Route("[controller]")]
12+
[Route("twilio/voice")]
1413
public class TwilioVoiceController : TwilioController
1514
{
1615
private readonly TwilioSetting _settings;
@@ -38,73 +37,25 @@ public Token GetAccessToken()
3837
};
3938
}
4039

41-
[HttpPost("/twilio/voice/welcome")]
42-
public async Task<TwiMLResult> StartConversation(VoiceRequest request)
43-
{
44-
string sessionId = $"TwilioVoice_{request.CallSid}";
45-
var twilio = _services.GetRequiredService<TwilioService>();
46-
var response = twilio.ReturnInstructions("Hello, how may I help you?");
47-
return TwiML(response);
48-
}
49-
50-
[HttpPost("/twilio/voice/{agentId}")]
51-
public async Task<TwiMLResult> ReceivedVoiceMessage([FromRoute] string agentId, VoiceRequest input)
52-
{
53-
string sessionId = $"TwilioVoice_{input.CallSid}";
54-
55-
var inputMsg = new RoleDialogModel(AgentRole.User, input.SpeechResult);
56-
var conv = _services.GetRequiredService<IConversationService>();
57-
var routing = _services.GetRequiredService<IRoutingService>();
58-
routing.Context.SetMessageId(sessionId, inputMsg.MessageId);
59-
60-
conv.SetConversationId(sessionId, new List<MessageState>
61-
{
62-
new MessageState("channel", ConversationChannel.Phone),
63-
new MessageState("calling_phone", input.DialCallSid)
64-
});
65-
66-
var twilio = _services.GetRequiredService<TwilioService>();
67-
VoiceResponse response = default;
68-
69-
var result = await conv.SendMessage(agentId,
70-
inputMsg,
71-
replyMessage: null,
72-
async msg =>
73-
{
74-
response = twilio.ReturnInstructions(msg.Content);
75-
if (msg.FunctionName == "conversation_end")
76-
{
77-
response = twilio.HangUp(msg.Content);
78-
}
79-
}, async functionExecuting =>
80-
{
81-
}, async functionExecuted =>
82-
{
83-
});
84-
85-
return TwiML(response);
86-
}
87-
88-
89-
[HttpPost("start")]
90-
public TwiMLResult InitiateConversation(VoiceRequest request)
40+
[HttpPost("welcome")]
41+
public TwiMLResult InitiateConversation(VoiceRequest request, [FromQuery] string states)
9142
{
9243
if (request?.CallSid == null) throw new ArgumentNullException(nameof(VoiceRequest.CallSid));
93-
string sessionId = $"TwilioVoice_{request.CallSid}";
44+
string conversationId = $"TwilioVoice_{request.CallSid}";
9445
var twilio = _services.GetRequiredService<TwilioService>();
95-
var url = $"twiliovoice/{sessionId}/send/0";
96-
var response = twilio.ReturnInstructions("twilio/welcome.mp3", url, false);
46+
var url = $"twilio/voice/{conversationId}/receive/0?states={states}";
47+
var response = twilio.ReturnInstructions("twilio/welcome.mp3", url, true);
9748
return TwiML(response);
9849
}
9950

100-
[HttpPost("{sessionId}/send/{seqNum}")]
101-
public async Task<TwiMLResult> SendCallerMessage([FromRoute] string sessionId, [FromRoute] int seqNum, VoiceRequest request)
51+
[HttpPost("{conversationId}/receive/{seqNum}")]
52+
public async Task<TwiMLResult> ReceiveCallerMessage([FromRoute] string conversationId, [FromRoute] int seqNum, [FromQuery] string states, VoiceRequest request)
10253
{
10354
var twilio = _services.GetRequiredService<TwilioService>();
10455
var messageQueue = _services.GetRequiredService<TwilioMessageQueue>();
10556
var sessionManager = _services.GetRequiredService<ITwilioSessionManager>();
106-
var url = $"twiliovoice/{sessionId}/reply/{seqNum}";
107-
var messages = await sessionManager.RetrieveStagedCallerMessagesAsync(sessionId, seqNum);
57+
var url = $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}";
58+
var messages = await sessionManager.RetrieveStagedCallerMessagesAsync(conversationId, seqNum);
10859
if (!string.IsNullOrWhiteSpace(request.SpeechResult))
10960
{
11061
messages.Add(request.SpeechResult);
@@ -113,48 +64,78 @@ public async Task<TwiMLResult> SendCallerMessage([FromRoute] string sessionId, [
11364
VoiceResponse response;
11465
if (!string.IsNullOrWhiteSpace(messageContent))
11566
{
67+
11668
var callerMessage = new CallerMessage()
11769
{
118-
SessionId = sessionId,
70+
ConversationId = conversationId,
11971
SeqNumber = seqNum,
12072
Content = messageContent,
12173
From = request.From
12274
};
75+
if (!string.IsNullOrEmpty(states))
76+
{
77+
var kvp = states.Split(':');
78+
if (kvp.Length == 2)
79+
{
80+
callerMessage.States.Add(kvp[0], kvp[1]);
81+
}
82+
}
12383
await messageQueue.EnqueueAsync(callerMessage);
124-
response = twilio.ReturnInstructions("twilio/holdon.mp3", url, true);
84+
response = twilio.ReturnInstructions(null, url, true, 1);
12585
}
12686
else
12787
{
128-
response = twilio.HangUp("twilio/holdon.mp3");
88+
var speechPath = seqNum > 0 ? $"twilio/voice/speeches/{conversationId}/{seqNum - 1}.mp3" : "twilio/welcome.mp3";
89+
response = twilio.ReturnInstructions(speechPath, $"twilio/voice/{conversationId}/receive/{seqNum}?states={states}", true);
12990
}
13091
return TwiML(response);
13192
}
13293

133-
[HttpPost("{sessionId}/reply/{seqNum}")]
134-
public async Task<TwiMLResult> ReplyCallerMessage([FromRoute] string sessionId, [FromRoute] int seqNum, VoiceRequest request)
94+
[HttpPost("{conversationId}/reply/{seqNum}")]
95+
public async Task<TwiMLResult> ReplyCallerMessage([FromRoute] string conversationId, [FromRoute] int seqNum, [FromQuery] string states, VoiceRequest request)
13596
{
13697
var nextSeqNum = seqNum + 1;
13798
var sessionManager = _services.GetRequiredService<ITwilioSessionManager>();
13899
var twilio = _services.GetRequiredService<TwilioService>();
139100
if (request.SpeechResult != null)
140101
{
141-
await sessionManager.StageCallerMessageAsync(sessionId, nextSeqNum, request.SpeechResult);
102+
await sessionManager.StageCallerMessageAsync(conversationId, nextSeqNum, request.SpeechResult);
142103
}
143-
var reply = await sessionManager.GetAssistantReplyAsync(sessionId, seqNum);
104+
var reply = await sessionManager.GetAssistantReplyAsync(conversationId, seqNum);
144105
VoiceResponse response;
145-
if (string.IsNullOrEmpty(reply))
106+
if (reply == null)
146107
{
147-
response = twilio.ReturnInstructions(null, $"twiliovoice/{sessionId}/reply/{seqNum}", true);
108+
var indication = await sessionManager.GetReplyIndicationAsync(conversationId, seqNum);
109+
if (indication != null)
110+
{
111+
var textToSpeechService = CompletionProvider.GetTextToSpeech(_services, "openai", "tts-1");
112+
var fileService = _services.GetRequiredService<IFileStorageService>();
113+
var data = await textToSpeechService.GenerateSpeechFromTextAsync(indication);
114+
var fileName = $"indication_{seqNum}.mp3";
115+
await fileService.SaveSpeechFileAsync(conversationId, fileName, data);
116+
response = twilio.ReturnInstructions($"twilio/voice/speeches/{conversationId}/{fileName}", $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}", true, 2);
117+
}
118+
else
119+
{
120+
response = twilio.ReturnInstructions(null, $"twilio/voice/{conversationId}/reply/{seqNum}?states={states}", true, 1);
121+
}
148122
}
149123
else
150124
{
151-
152125
var textToSpeechService = CompletionProvider.GetTextToSpeech(_services, "openai", "tts-1");
153126
var fileService = _services.GetRequiredService<IFileStorageService>();
154-
var data = await textToSpeechService.GenerateSpeechFromTextAsync(reply);
155-
var fileName = $"{seqNum}.mp3";
156-
await fileService.SaveSpeechFileAsync(sessionId, fileName, data);
157-
response = twilio.ReturnInstructions($"twiliovoice/speeches/{sessionId}/{fileName}", $"twiliovoice/{sessionId}/send/{nextSeqNum}", true);
127+
var data = await textToSpeechService.GenerateSpeechFromTextAsync(reply.Content);
128+
var fileName = $"reply_{seqNum}.mp3";
129+
await fileService.SaveSpeechFileAsync(conversationId, fileName, data);
130+
if (reply.ConversationEnd)
131+
{
132+
response = twilio.HangUp($"twilio/voice/speeches/{conversationId}/{fileName}");
133+
}
134+
else
135+
{
136+
response = twilio.ReturnInstructions($"twilio/voice/speeches/{conversationId}/{fileName}", $"twilio/voice/{conversationId}/receive/{nextSeqNum}?states={states}", true);
137+
}
138+
158139
}
159140
return TwiML(response);
160141
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace BotSharp.Plugin.Twilio.Models
2+
{
3+
public class AssistantMessage
4+
{
5+
public bool ConversationEnd { get; set; }
6+
public string Content { get; set; }
7+
}
8+
}

‎src/Plugins/BotSharp.Plugin.Twilio/Models/CallerMessage.cs‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ namespace BotSharp.Plugin.Twilio.Models
22
{
33
public class CallerMessage
44
{
5-
public string SessionId { get; set; }
5+
public string ConversationId { get; set; }
66
public int SeqNumber { get; set; }
77
public string Content { get; set; }
88
public string From { get; set; }
9+
public Dictionary<string, string> States { get; set; } = new();
910

1011
public override string ToString()
1112
{
12-
return $"{SessionId}-{SeqNumber}";
13+
return $"{ConversationId}-{SeqNumber}";
1314
}
1415
}
1516
}
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
1+
using BotSharp.Plugin.Twilio.Models;
12
using Task = System.Threading.Tasks.Task;
23

34
namespace BotSharp.Plugin.Twilio.Services
45
{
56
public interface ITwilioSessionManager
67
{
7-
Task SetAssistantReplyAsync(string sessionId, int seqNum, string message);
8-
Task<string> GetAssistantReplyAsync(string sessionId, int seqNum);
9-
Task StageCallerMessageAsync(string sessionId, int seqNum, string message);
10-
Task<List<string>> RetrieveStagedCallerMessagesAsync(string sessionId, int seqNum);
8+
Task SetAssistantReplyAsync(string conversationId, int seqNum, AssistantMessage message);
9+
Task<AssistantMessage> GetAssistantReplyAsync(string conversationId, int seqNum);
10+
Task StageCallerMessageAsync(string conversationId, int seqNum, string message);
11+
Task<List<string>> RetrieveStagedCallerMessagesAsync(string conversationId, int seqNum);
12+
Task SetReplyIndicationAsync(string conversationId, int seqNum, string indication);
13+
Task<string> GetReplyIndicationAsync(string conversationId, int seqNum);
1114
}
1215
}

‎src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioMessageQueueService.cs‎

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,35 +55,53 @@ private async Task ProcessUserMessageAsync(CallerMessage message)
5555
{
5656
using var scope = _serviceProvider.CreateScope();
5757
var sp = scope.ServiceProvider;
58-
string reply = null;
58+
AssistantMessage reply = null;
5959
var inputMsg = new RoleDialogModel(AgentRole.User, message.Content);
6060
var conv = sp.GetRequiredService<IConversationService>();
6161
var routing = sp.GetRequiredService<IRoutingService>();
6262
var config = sp.GetRequiredService<TwilioSetting>();
63-
routing.Context.SetMessageId(message.SessionId, inputMsg.MessageId);
64-
conv.SetConversationId(message.SessionId, new List<MessageState>
63+
routing.Context.SetMessageId(message.ConversationId, inputMsg.MessageId);
64+
var states = new List<MessageState>
6565
{
6666
new MessageState("channel", ConversationChannel.Phone),
6767
new MessageState("calling_phone", message.From)
68-
});
68+
};
69+
foreach (var kvp in message.States)
70+
{
71+
states.Add(new MessageState(kvp.Key, kvp.Value));
72+
}
73+
conv.SetConversationId(message.ConversationId, states);
74+
var sessionManager = sp.GetRequiredService<ITwilioSessionManager>();
6975
var result = await conv.SendMessage(config.AgentId,
7076
inputMsg,
7177
replyMessage: null,
7278
async msg =>
7379
{
74-
reply = msg.Content;
80+
reply = new AssistantMessage()
81+
{
82+
ConversationEnd = msg.Instruction.ConversationEnd,
83+
Content = msg.Content
84+
};
85+
},
86+
async msg =>
87+
{
88+
if (!string.IsNullOrEmpty(msg.Indication))
89+
{
90+
await sessionManager.SetReplyIndicationAsync(message.ConversationId, message.SeqNumber, msg.Indication);
91+
}
7592
},
76-
async functionExecuting =>
77-
{ },
7893
async functionExecuted =>
7994
{ }
8095
);
81-
if (string.IsNullOrWhiteSpace(reply))
96+
if (reply == null || string.IsNullOrWhiteSpace(reply.Content))
8297
{
83-
reply = "Sorry, something was wrong.";
84-
}
85-
var sessionManager = sp.GetRequiredService<ITwilioSessionManager>();
86-
await sessionManager.SetAssistantReplyAsync(message.SessionId, message.SeqNumber, reply);
98+
reply = new AssistantMessage()
99+
{
100+
ConversationEnd = true,
101+
Content = "Sorry, something was wrong."
102+
};
103+
}
104+
await sessionManager.SetAssistantReplyAsync(message.ConversationId, message.SeqNumber, reply);
87105
}
88106
}
89107
}

‎src/Plugins/BotSharp.Plugin.Twilio/Services/TwilioService.cs‎

Lines changed: 4 additions & 2 deletions

0 commit comments

Comments
 (0)