Skip to content

Commit 54767dc

Browse files
author
Jicheng Lu
committed
Merge branch 'master' of https://github.com/SciSharp/BotSharp
2 parents 7646030 + 14f7c4b commit 54767dc

File tree

13 files changed

+105
-38
lines changed

13 files changed

+105
-38
lines changed

src/Infrastructure/BotSharp.Abstraction/Browsing/Settings/WebBrowsingSettings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ public class WebBrowsingSettings
66
public bool Headless { get; set; }
77
// Default timeout in milliseconds
88
public float DefaultTimeout { get; set; } = 30000;
9+
public bool IsEnableScreenshot { get; set; }
910
}

src/Infrastructure/BotSharp.Abstraction/Conversations/ConversationHookBase.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,7 @@ public virtual Task OnBreakpointUpdated(string conversationId, bool resetStates)
7878

7979
public virtual Task OnNotificationGenerated(RoleDialogModel message)
8080
=> Task.CompletedTask;
81+
82+
public Task OnUserDisconnected(Conversation conversation)
83+
=> Task.CompletedTask;
8184
}

src/Infrastructure/BotSharp.Abstraction/Conversations/IConversationHook.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,13 @@ public interface IConversationHook
2525
/// <returns></returns>
2626
Task OnUserAgentConnectedInitially(Conversation conversation);
2727

28+
/// <summary>
29+
/// Triggered when user disconnects with agent.
30+
/// </summary>
31+
/// <param name="conversation"></param>
32+
/// <returns></returns>
33+
Task OnUserDisconnected(Conversation conversation);
34+
2835
/// <summary>
2936
/// Triggered once for every new conversation.
3037
/// </summary>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace BotSharp.Abstraction.Realtime;
2+
3+
public interface IRealtimeHook
4+
{
5+
string[] OnModelTranscriptPrompt(Agent agent);
6+
}

src/Infrastructure/BotSharp.Core.Realtime/BotSharp.Core.Realtime.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
<ItemGroup>
1010
<ProjectReference Include="..\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
11+
<ProjectReference Include="..\BotSharp.Core\BotSharp.Core.csproj" />
1112
</ItemGroup>
1213

1314
</Project>

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

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using BotSharp.Abstraction.Utilities;
2+
using BotSharp.Core.Infrastructures;
3+
using Microsoft.AspNetCore.Cors.Infrastructure;
24

35
namespace BotSharp.Core.Realtime.Services;
46

@@ -86,11 +88,14 @@ private async Task ConnectToModel(WebSocket userWebSocket)
8688
var routing = _services.GetRequiredService<IRoutingService>();
8789
routing.Context.Push(agent.Id);
8890

91+
var storage = _services.GetRequiredService<IConversationStorage>();
8992
var dialogs = convService.GetDialogHistory();
9093
if (dialogs.Count == 0)
9194
{
9295
dialogs.Add(new RoleDialogModel(AgentRole.User, "Hi"));
96+
storage.Append(_conn.ConversationId, dialogs.First());
9397
}
98+
9499
routing.Context.SetDialogs(dialogs);
95100

96101
await _completer.Connect(_conn,
@@ -155,6 +160,7 @@ await _completer.Connect(_conn,
155160
{
156161
// append output audio transcript to conversation
157162
dialogs.Add(message);
163+
storage.Append(_conn.ConversationId, message);
158164

159165
foreach (var hook in hookProvider.HooksOrderByPriority)
160166
{
@@ -174,6 +180,7 @@ await _completer.Connect(_conn,
174180
{
175181
// append input audio transcript to conversation
176182
dialogs.Add(message);
183+
storage.Append(_conn.ConversationId, message);
177184

178185
foreach (var hook in hookProvider.HooksOrderByPriority)
179186
{
@@ -224,6 +231,9 @@ private async Task HandleUserDtmfReceived()
224231
};
225232
dialogs.Add(message);
226233

234+
var storage = _services.GetRequiredService<IConversationStorage>();
235+
storage.Append(_conn.ConversationId, message);
236+
227237
foreach (var hook in hookProvider.HooksOrderByPriority)
228238
{
229239
hook.SetAgent(agent)
@@ -239,14 +249,9 @@ private async Task HandleUserDtmfReceived()
239249

240250
private async Task HandleUserDisconnected()
241251
{
242-
// Save dialog history
243-
var routing = _services.GetRequiredService<IRoutingService>();
244-
var storage = _services.GetRequiredService<IConversationStorage>();
245-
var dialogs = routing.Context.GetDialogs();
246-
foreach (var item in dialogs)
247-
{
248-
storage.Append(_conn.ConversationId, item);
249-
}
252+
var convService = _services.GetRequiredService<IConversationService>();
253+
var conversation = await convService.GetConversation(_conn.ConversationId);
254+
await HookEmitter.Emit<IConversationHook>(_services, x => x.OnUserDisconnected(conversation));
250255
}
251256

252257
private async Task SendEventToUser(WebSocket webSocket, object message)

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,11 @@ public class InputAudioTranscription
7272
{
7373
[JsonPropertyName("model")]
7474
public string Model { get; set; } = null!;
75+
76+
[JsonPropertyName("language")]
77+
public string Language { get; set; } = "en";
78+
79+
[JsonPropertyName("prompt")]
80+
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
81+
public string? Prompt { get; set; }
7582
}

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

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
using BotSharp.Abstraction.Files.Utilities;
33
using BotSharp.Abstraction.Functions.Models;
44
using BotSharp.Abstraction.Options;
5+
using BotSharp.Abstraction.Realtime;
56
using BotSharp.Abstraction.Realtime.Models;
7+
using BotSharp.Abstraction.Routing;
68
using BotSharp.Core.Infrastructures;
79
using BotSharp.Plugin.OpenAI.Models.Realtime;
810
using OpenAI.Chat;
@@ -42,7 +44,7 @@ public async Task Connect(RealtimeHubConnection conn,
4244
Action onModelReady,
4345
Action<string,string> onModelAudioDeltaReceived,
4446
Action onModelAudioResponseDone,
45-
Action<string> onAudioTranscriptDone,
47+
Action<string> onModelAudioTranscriptDone,
4648
Action<List<RoleDialogModel>> onModelResponseDone,
4749
Action<string> onConversationItemCreated,
4850
Action<RoleDialogModel> onInputAudioTranscriptionCompleted,
@@ -64,7 +66,7 @@ public async Task Connect(RealtimeHubConnection conn,
6466
onModelReady,
6567
onModelAudioDeltaReceived,
6668
onModelAudioResponseDone,
67-
onAudioTranscriptDone,
69+
onModelAudioTranscriptDone,
6870
onModelResponseDone,
6971
onConversationItemCreated,
7072
onInputAudioTranscriptionCompleted,
@@ -125,10 +127,10 @@ private async Task ReceiveMessage(RealtimeHubConnection conn,
125127
Action onModelReady,
126128
Action<string,string> onModelAudioDeltaReceived,
127129
Action onModelAudioResponseDone,
128-
Action<string> onAudioTranscriptDone,
130+
Action<string> onModelAudioTranscriptDone,
129131
Action<List<RoleDialogModel>> onModelResponseDone,
130132
Action<string> onConversationItemCreated,
131-
Action<RoleDialogModel> onInputAudioTranscriptionCompleted,
133+
Action<RoleDialogModel> onUserAudioTranscriptionCompleted,
132134
Action onUserInterrupted)
133135
{
134136
var buffer = new byte[1024 * 32];
@@ -138,7 +140,7 @@ private async Task ReceiveMessage(RealtimeHubConnection conn,
138140
{
139141
result = await _webSocket.ReceiveAsync(
140142
new ArraySegment<byte>(buffer), CancellationToken.None);
141-
143+
142144
// Convert received data to text/audio (Twilio sends Base64-encoded audio)
143145
string receivedText = Encoding.UTF8.GetString(buffer, 0, result.Count);
144146
if (string.IsNullOrEmpty(receivedText))
@@ -171,7 +173,7 @@ private async Task ReceiveMessage(RealtimeHubConnection conn,
171173
_logger.LogInformation($"{response.Type}: {receivedText}");
172174
var data = JsonSerializer.Deserialize<ResponseAudioTranscript>(receivedText);
173175
await Task.Delay(1000);
174-
onAudioTranscriptDone(data.Transcript);
176+
onModelAudioTranscriptDone(data.Transcript);
175177
}
176178
else if (response.Type == "response.audio.delta")
177179
{
@@ -201,8 +203,11 @@ private async Task ReceiveMessage(RealtimeHubConnection conn,
201203
else if (response.Type == "conversation.item.input_audio_transcription.completed")
202204
{
203205
_logger.LogInformation($"{response.Type}: {receivedText}");
204-
var message = await OnInputAudioTranscriptionCompleted(conn, receivedText);
205-
onInputAudioTranscriptionCompleted(message);
206+
var message = await OnUserAudioTranscriptionCompleted(conn, receivedText);
207+
if (!string.IsNullOrEmpty(message.Content))
208+
{
209+
onUserAudioTranscriptionCompleted(message);
210+
}
206211
}
207212
else if (response.Type == "input_audio_buffer.speech_started")
208213
{
@@ -309,6 +314,9 @@ public async Task<string> UpdateSession(RealtimeHubConnection conn, bool turnDet
309314
return fn;
310315
}).ToArray();
311316

317+
var words = new List<string>();
318+
HookEmitter.Emit<IRealtimeHook>(_services, hook => words.AddRange(hook.OnModelTranscriptPrompt(agent)));
319+
312320
var sessionUpdate = new
313321
{
314322
type = "session.update",
@@ -319,6 +327,8 @@ public async Task<string> UpdateSession(RealtimeHubConnection conn, bool turnDet
319327
InputAudioTranscription = new InputAudioTranscription
320328
{
321329
Model = "whisper-1",
330+
Language = "en",
331+
Prompt = string.Join(", ", words.Select(x => x.ToLower().Trim()).Distinct()).SubstringMax(1024)
322332
},
323333
Voice = "alloy",
324334
Instructions = instruction,
@@ -329,7 +339,7 @@ public async Task<string> UpdateSession(RealtimeHubConnection conn, bool turnDet
329339
MaxResponseOutputTokens = 512,
330340
TurnDetection = new RealtimeSessionTurnDetection
331341
{
332-
Threshold = 0.8f,
342+
Threshold = 0.9f,
333343
PrefixPadding = 300,
334344
SilenceDuration = 800
335345
}
@@ -662,7 +672,7 @@ await hook.AfterGenerated(new RoleDialogModel(AgentRole.Assistant, "response.don
662672
return outputs;
663673
}
664674

665-
public async Task<RoleDialogModel> OnInputAudioTranscriptionCompleted(RealtimeHubConnection conn, string response)
675+
public async Task<RoleDialogModel> OnUserAudioTranscriptionCompleted(RealtimeHubConnection conn, string response)
666676
{
667677
var data = JsonSerializer.Deserialize<ResponseAudioTranscript>(response);
668678
return new RoleDialogModel(AgentRole.User, data.Transcript)

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

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
using BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.LlmContexts;
2+
using Microsoft.VisualBasic;
13
using Twilio.Rest.Api.V2010.Account;
4+
using Task = System.Threading.Tasks.Task;
25

36
namespace BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.Functions;
47

@@ -20,6 +23,7 @@ public HangupPhoneCallFn(
2023

2124
public async Task<bool> Execute(RoleDialogModel message)
2225
{
26+
var args = JsonSerializer.Deserialize<HangupPhoneCallArgs>(message.FunctionArgs);
2327
var states = _services.GetRequiredService<IConversationStateService>();
2428
var callSid = states.GetState("twilio_call_sid");
2529

@@ -30,14 +34,20 @@ public async Task<bool> Execute(RoleDialogModel message)
3034
return false;
3135
}
3236

33-
// Have to find the SID by the phone number
34-
var call = CallResource.Update(
35-
status: CallResource.UpdateStatusEnum.Completed,
36-
pathSid: callSid
37-
);
37+
message.Content = args.GoodbyeMessage;
3838

39-
message.Content = "The call has ended.";
40-
message.StopCompletion = true;
39+
_ = Task.Run(async () =>
40+
{
41+
await Task.Delay(args.GoodbyeMessage.Split(' ').Length * 400);
42+
// Have to find the SID by the phone number
43+
var call = CallResource.Update(
44+
status: CallResource.UpdateStatusEnum.Completed,
45+
pathSid: callSid
46+
);
47+
48+
message.Content = "The call has been ended.";
49+
message.StopCompletion = true;
50+
});
4151

4252
return true;
4353
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System.Text.Json.Serialization;
2+
3+
namespace BotSharp.Plugin.Twilio.OutboundPhoneCallHandler.LlmContexts;
4+
5+
public class HangupPhoneCallArgs
6+
{
7+
[JsonPropertyName("goodbye_message")]
8+
public string? GoodbyeMessage { get; set; }
9+
}

0 commit comments

Comments
 (0)