Skip to content

Commit ead10ba

Browse files
authored
Merge pull request #946 from hchen2020/master
Optimize phone hang-up
2 parents be70c3c + 292da2a commit ead10ba

File tree

14 files changed

+97
-51
lines changed

14 files changed

+97
-51
lines changed

src/Infrastructure/BotSharp.Abstraction/MLTasks/IRealTimeCompletion.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Task Connect(RealtimeHubConnection conn,
2323
Task Disconnect();
2424

2525
Task<RealtimeSession> CreateSession(Agent agent, List<RoleDialogModel> conversations);
26-
Task<string> UpdateSession(RealtimeHubConnection conn, bool turnDetection = true);
26+
Task<string> UpdateSession(RealtimeHubConnection conn, bool interruptResponse = true);
2727
Task InsertConversationItem(RoleDialogModel message);
2828
Task RemoveConversationItem(string itemId);
2929
Task TriggerModelInference(string? instructions = null);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace BotSharp.Abstraction.Realtime.Models;
2+
3+
public class ModelTurnDetection
4+
{
5+
public int PrefixPadding { get; set; } = 300;
6+
7+
public int SilenceDuration { get; set; } = 800;
8+
9+
public float Threshold { get; set; } = 0.8f;
10+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace BotSharp.Abstraction.Realtime.Models;
2+
3+
public class RealtimeModelSettings
4+
{
5+
public float Temperature { get; set; } = 0.8f;
6+
public int MaxResponseOutputTokens { get; set; } = 512;
7+
public ModelTurnDetection TurnDetection { get; set; } = new();
8+
}

src/Infrastructure/BotSharp.Core.Realtime/RealtimePlugin.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using BotSharp.Abstraction.Plugins;
2+
using BotSharp.Abstraction.Settings;
23
using BotSharp.Core.Realtime.Hooks;
34
using BotSharp.Core.Realtime.Services;
45
using Microsoft.Extensions.Configuration;
@@ -14,6 +15,12 @@ public class RealtimePlugin : IBotSharpPlugin
1415

1516
public void RegisterDI(IServiceCollection services, IConfiguration config)
1617
{
18+
services.AddScoped(provider =>
19+
{
20+
var settingService = provider.GetRequiredService<ISettingService>();
21+
return settingService.Bind<RealtimeModelSettings>("RealtimeModel");
22+
});
23+
1724
services.AddScoped<IRealtimeHub, RealtimeHub>();
1825
services.AddScoped<IConversationHook, RealtimeConversationHook>();
1926
}

src/Infrastructure/BotSharp.Core.Realtime/Services/RealtimeHub.cs

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,18 @@ private async Task ConnectToModel(WebSocket userWebSocket)
9898
await _completer.Connect(_conn,
9999
onModelReady: async () =>
100100
{
101-
if (states.ContainsState("init_audio_file"))
101+
// Not TriggerModelInference, waiting for user utter.
102+
await _completer.UpdateSession(_conn);
103+
104+
// Push dialogs into model context
105+
foreach (var message in dialogs)
102106
{
103-
await _completer.UpdateSession(_conn, turnDetection: true);
107+
await _completer.InsertConversationItem(message);
104108
}
105-
else
106-
{
107-
// Control initial session, prevent initial response interruption
108-
await _completer.UpdateSession(_conn, turnDetection: false);
109109

110+
// Trigger model inference if there is no audio file in the conversation
111+
if (!states.ContainsState("init_audio_file"))
112+
{
110113
if (dialogs.LastOrDefault()?.Role == AgentRole.Assistant)
111114
{
112115
await _completer.TriggerModelInference($"Rephase your last response:\r\n{dialogs.LastOrDefault()?.Content}");
@@ -115,10 +118,6 @@ await _completer.Connect(_conn,
115118
{
116119
await _completer.TriggerModelInference("Reply based on the conversation context.");
117120
}
118-
119-
// Start turn detection
120-
await Task.Delay(1000 * 8);
121-
await _completer.UpdateSession(_conn, turnDetection: true);
122121
}
123122
},
124123
onModelAudioDeltaReceived: async (audioDeltaData, itemId) =>

src/Plugins/BotSharp.Plugin.OpenAI/Models/Realtime/RealtimeSessionBody.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ public class RealtimeSessionBody
5252

5353
public class RealtimeSessionTurnDetection
5454
{
55+
[JsonPropertyName("interrupt_response")]
56+
public bool InterruptResponse { get; set; } = true;
57+
5558
/// <summary>
5659
/// Milliseconds
5760
/// </summary>

src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ public async Task<RealtimeSession> CreateSession(Agent agent, List<RoleDialogMod
290290
return session;
291291
}
292292

293-
public async Task<string> UpdateSession(RealtimeHubConnection conn, bool turnDetection = true)
293+
public async Task<string> UpdateSession(RealtimeHubConnection conn, bool interruptResponse = true)
294294
{
295295
var convService = _services.GetRequiredService<IConversationService>();
296296
var conv = await convService.GetConversation(conn.ConversationId);
@@ -317,6 +317,8 @@ public async Task<string> UpdateSession(RealtimeHubConnection conn, bool turnDet
317317
var words = new List<string>();
318318
HookEmitter.Emit<IRealtimeHook>(_services, hook => words.AddRange(hook.OnModelTranscriptPrompt(agent)));
319319

320+
var realitmeModelSettings = _services.GetRequiredService<RealtimeModelSettings>();
321+
320322
var sessionUpdate = new
321323
{
322324
type = "session.update",
@@ -335,22 +337,18 @@ public async Task<string> UpdateSession(RealtimeHubConnection conn, bool turnDet
335337
ToolChoice = "auto",
336338
Tools = functions,
337339
Modalities = [ "text", "audio" ],
338-
Temperature = Math.Max(options.Temperature ?? 0f, 0.6f),
339-
MaxResponseOutputTokens = 512,
340+
Temperature = Math.Max(options.Temperature ?? realitmeModelSettings.Temperature, 0.6f),
341+
MaxResponseOutputTokens = realitmeModelSettings.MaxResponseOutputTokens,
340342
TurnDetection = new RealtimeSessionTurnDetection
341343
{
342-
Threshold = 0.9f,
343-
PrefixPadding = 300,
344-
SilenceDuration = 800
344+
InterruptResponse = interruptResponse,
345+
Threshold = realitmeModelSettings.TurnDetection.Threshold,
346+
PrefixPadding = realitmeModelSettings.TurnDetection.PrefixPadding,
347+
SilenceDuration = realitmeModelSettings.TurnDetection.SilenceDuration
345348
}
346349
}
347350
};
348351

349-
if (!turnDetection)
350-
{
351-
sessionUpdate.session.TurnDetection = null;
352-
}
353-
354352
await HookEmitter.Emit<IContentGeneratingHook>(_services, async hook =>
355353
{
356354
await hook.OnSessionUpdated(agent, instruction, functions);

src/Plugins/BotSharp.Plugin.Twilio/BotSharp.Plugin.Twilio.csproj

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,6 @@
1919
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\functions\util-twilio-outbound_phone_call.json">
2020
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
2121
</Content>
22-
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-twilio-hangup_phone_call.fn.liquid">
23-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
24-
</Content>
25-
<Content Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\util-twilio-outbound_phone_call.fn.liquid">
26-
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
27-
</Content>
2822
</ItemGroup>
2923

3024
<ItemGroup>
@@ -39,4 +33,8 @@
3933
<ProjectReference Include="..\..\Infrastructure\BotSharp.Core\BotSharp.Core.csproj" />
4034
</ItemGroup>
4135

36+
<ItemGroup>
37+
<Folder Include="data\agents\6745151e-6d46-4a02-8de4-1c4f21c7da95\templates\" />
38+
</ItemGroup>
39+
4240
</Project>

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,15 @@ public async Task<FileContentResult> GetSpeechFile([FromRoute] string conversati
429429
return result;
430430
}
431431

432+
[ValidateRequest]
433+
[HttpPost("twilio/voice/hang-up")]
434+
public async Task<TwiMLResult> Hangup(ConversationalVoiceRequest request)
435+
{
436+
var twilio = _services.GetRequiredService<TwilioService>();
437+
var response = twilio.HangUp("twilio/bye.mp3");
438+
return TwiML(response);
439+
}
440+
432441
[ValidateRequest]
433442
[HttpPost("twilio/voice/status")]
434443
public async Task<ActionResult> PhoneCallStatus(ConversationalVoiceRequest request)

src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/HangupPhoneCallFn.cs

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,34 @@
1+
using BotSharp.Abstraction.Routing;
12
using BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.LlmContexts;
23
using Twilio.Rest.Api.V2010.Account;
3-
using Task = System.Threading.Tasks.Task;
44

55
namespace BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.Functions;
66

77
public class HangupPhoneCallFn : IFunctionCallback
88
{
99
private readonly IServiceProvider _services;
1010
private readonly ILogger<HangupPhoneCallFn> _logger;
11+
private readonly TwilioSetting _twilioSetting;
1112

1213
public string Name => "util-twilio-hangup_phone_call";
1314
public string Indication => "Hangup";
1415

1516
public HangupPhoneCallFn(
1617
IServiceProvider services,
17-
ILogger<HangupPhoneCallFn> logger)
18+
ILogger<HangupPhoneCallFn> logger,
19+
TwilioSetting twilioSetting)
1820
{
1921
_services = services;
2022
_logger = logger;
23+
_twilioSetting = twilioSetting;
2124
}
2225

2326
public async Task<bool> Execute(RoleDialogModel message)
2427
{
2528
var args = JsonSerializer.Deserialize<HangupPhoneCallArgs>(message.FunctionArgs);
29+
30+
var routing = _services.GetRequiredService<IRoutingService>();
31+
var conversationId = routing.Context.ConversationId;
2632
var states = _services.GetRequiredService<IConversationStateService>();
2733
var callSid = states.GetState("twilio_call_sid");
2834

@@ -33,20 +39,20 @@ public async Task<bool> Execute(RoleDialogModel message)
3339
return false;
3440
}
3541

36-
message.Content = args.GoodbyeMessage;
37-
38-
_ = Task.Run(async () =>
42+
if (args.AnythingElseToHelp)
43+
{
44+
message.Content = "Tell me how I can help.";
45+
}
46+
else
3947
{
40-
await Task.Delay(args.GoodbyeMessage.Split(' ').Length * 400);
41-
// Have to find the SID by the phone number
4248
var call = CallResource.Update(
43-
status: CallResource.UpdateStatusEnum.Completed,
49+
url: new Uri($"{_twilioSetting.CallbackHost}/twilio/voice/hang-up?conversation-id={conversationId}"),
4450
pathSid: callSid
4551
);
4652

47-
message.Content = "The call has been ended.";
53+
message.Content = "The call is ending.";
4854
message.StopCompletion = true;
49-
});
55+
}
5056

5157
return true;
5258
}

0 commit comments

Comments
 (0)