From da097de0538eba291e967d641668cf7afe698f9f Mon Sep 17 00:00:00 2001 From: Jicheng Lu Date: Tue, 8 Jul 2025 00:16:21 -0500 Subject: [PATCH 1/5] refine chat stream code --- .../BotSharp.Plugin.ChatHub/ChatStreamMiddleware.cs | 8 ++++++-- .../Providers/Realtime/RealTimeCompletionProvider.cs | 12 ++++-------- .../Providers/Realtime/RealTimeCompletionProvider.cs | 12 ++++-------- 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/ChatStreamMiddleware.cs b/src/Plugins/BotSharp.Plugin.ChatHub/ChatStreamMiddleware.cs index cba35a15..171bf0f2 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/ChatStreamMiddleware.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/ChatStreamMiddleware.cs @@ -62,6 +62,7 @@ public class ChatStreamMiddleware var hub = services.GetRequiredService(); var conn = hub.SetHubConnection(conversationId); conn.CurrentAgentId = agentId; + InitEvents(conn); // load conversation and state var convService = services.GetRequiredService(); @@ -128,6 +129,11 @@ public class ChatStreamMiddleware break; } + return (response.Event, data); + } + + private void InitEvents(RealtimeHubConnection conn) + { conn.OnModelMessageReceived = message => JsonSerializer.Serialize(new { @@ -147,7 +153,5 @@ public class ChatStreamMiddleware { @event = "clear" }); - - return (response.Event, data); } } diff --git a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Realtime/RealTimeCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Realtime/RealTimeCompletionProvider.cs index 2e95cfa2..6838c284 100644 --- a/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Realtime/RealTimeCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.GoogleAI/Providers/Realtime/RealTimeCompletionProvider.cs @@ -329,17 +329,13 @@ public class GoogleRealTimeProvider : IRealTimeCompletion var words = new List(); HookEmitter.Emit(_services, hook => words.AddRange(hook.OnModelTranscriptPrompt(agent)), agent.Id); - var functions = request.Tools?.SelectMany(s => s.FunctionDeclarations).Select(x => + var functions = request.Tools?.SelectMany(s => s.FunctionDeclarations).Select(x => new FunctionDef { - var fn = new FunctionDef - { - Name = x.Name ?? string.Empty, - Description = x.Description ?? string.Empty, - Parameters = x.Parameters != null + Name = x.Name ?? string.Empty, + Description = x.Description ?? string.Empty, + Parameters = x.Parameters != null ? JsonSerializer.Deserialize(JsonSerializer.Serialize(x.Parameters)) : null - }; - return fn; }).ToArray(); await HookEmitter.Emit(_services, diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs index 92fa6f84..3a0902d6 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs @@ -326,15 +326,11 @@ public class RealTimeCompletionProvider : IRealTimeCompletion var (prompt, messages, options) = PrepareOptions(agent, []); var instruction = messages.FirstOrDefault()?.Content.FirstOrDefault()?.Text ?? agent?.Description ?? string.Empty; - var functions = options.Tools.Select(x => + var functions = options.Tools.Select(x => new FunctionDef { - var fn = new FunctionDef - { - Name = x.FunctionName, - Description = x.FunctionDescription - }; - fn.Parameters = JsonSerializer.Deserialize(x.FunctionParameters); - return fn; + Name = x.FunctionName, + Description = x.FunctionDescription, + Parameters = JsonSerializer.Deserialize(x.FunctionParameters) }).ToArray(); var realtimeModelSettings = _services.GetRequiredService(); From e8b2bffeb346a59cf184a518b826466dc1a73afa Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Tue, 8 Jul 2025 16:41:25 -0500 Subject: [PATCH 2/5] save states --- .../Controllers/TwilioInboundController.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs index 07c4ad87..31d3db52 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs @@ -68,6 +68,9 @@ public class TwilioInboundController : TwilioController await hook.OnSessionCreated(request); }, request.AgentId); + var conv = _services.GetRequiredService(); + conv.SaveStates(); + if (twilio.MachineDetected(request)) { response = new VoiceResponse(); @@ -204,7 +207,7 @@ public class TwilioInboundController : TwilioController storage.Append(conversation.Id, new RoleDialogModel(AgentRole.User, request.Intent) { - CurrentAgentId = conversation.Id, + CurrentAgentId = agent.Id, CreatedAt = DateTime.UtcNow }); } From 0da486a61b0c9d789ccc4cc023a1956dae76b715 Mon Sep 17 00:00:00 2001 From: Jicheng Lu Date: Tue, 8 Jul 2025 20:30:08 -0500 Subject: [PATCH 3/5] detour rate limit --- .../BotSharp.Logger/Hooks/RateLimitConversationHook.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs b/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs index 97308f69..4752f6d6 100644 --- a/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs +++ b/src/Infrastructure/BotSharp.Logger/Hooks/RateLimitConversationHook.cs @@ -25,7 +25,10 @@ public class RateLimitConversationHook : ConversationHookBase public override async Task OnMessageReceived(RoleDialogModel message) { var settings = _services.GetRequiredService(); + var states = _services.GetRequiredService(); + var rateLimit = settings.RateLimit; + var channel = states.GetState("channel"); // Check max input length var charCount = message.Content.Length; @@ -45,7 +48,7 @@ public class RateLimitConversationHook : ConversationHookBase var userSents = Dialogs.Where(x => x.Role == AgentRole.User) .TakeLast(2).ToList(); - if (userSents.Count > 1) + if (channel != ConversationChannel.Phone && userSents.Count > 1) { var seconds = (DateTime.UtcNow - userSents.First().CreatedAt).TotalSeconds; if (seconds < rateLimit.MinTimeSecondsBetweenMessages) @@ -56,9 +59,6 @@ public class RateLimitConversationHook : ConversationHookBase } } - var states = _services.GetRequiredService(); - var channel = states.GetState("channel"); - // Check the number of conversations if (channel != ConversationChannel.Phone && channel != ConversationChannel.Email && channel != ConversationChannel.Database) { From 81bb38b9b3137c36d1fa3235177a1059de200b30 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 9 Jul 2025 11:14:47 -0500 Subject: [PATCH 4/5] refine side car --- .../SideCar/Attributes/SideCarAttribute.cs | 4 ++-- .../SideCar/IConversationSideCar.cs | 2 +- .../Services/BotSharpConversationSideCar.cs | 14 +++++++------- .../Services/ConversationStateService.cs | 4 ++-- .../Hooks/ChatHubConversationHook.cs | 2 +- .../Controllers/TwilioInboundController.cs | 9 +++++---- 6 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs b/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs index 833cb013..4426d71f 100644 --- a/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs +++ b/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs @@ -79,7 +79,7 @@ public class SideCarAttribute : AsyncMoAttribute object? res = null; var isHandled = false; - var enabled = instance != null && instance.IsEnabled() && method != null; + var enabled = instance != null && instance.IsEnabled && method != null; if (!enabled) { return (isHandled, value); @@ -112,7 +112,7 @@ public class SideCarAttribute : AsyncMoAttribute object? value = null; var isHandled = false; - var enabled = instance != null && instance.IsEnabled() && method != null; + var enabled = instance != null && instance.IsEnabled && method != null; if (!enabled) { return (isHandled, value); diff --git a/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs b/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs index 3c9bcfa4..81b0cf9c 100644 --- a/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs +++ b/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs @@ -5,8 +5,8 @@ namespace BotSharp.Abstraction.SideCar; public interface IConversationSideCar { string Provider { get; } + bool IsEnabled { get; } - bool IsEnabled(); void AppendConversationDialogs(string conversationId, List messages); List GetConversationDialogs(string conversationId); void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint); diff --git a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs index e4996b06..d636fa71 100644 --- a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs +++ b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs @@ -14,7 +14,6 @@ limitations under the License. ******************************************************************************/ -using BotSharp.Abstraction.SideCar.Models; using BotSharp.Core.Infrastructures; namespace BotSharp.Core.SideCar.Services; @@ -31,6 +30,7 @@ public class BotSharpConversationSideCar : IConversationSideCar private string _conversationId = string.Empty; public string Provider => "botsharp"; + public bool IsEnabled => _enabled; public BotSharpConversationSideCar( IServiceProvider services, @@ -40,11 +40,6 @@ public class BotSharpConversationSideCar : IConversationSideCar _logger = logger; } - public bool IsEnabled() - { - return _enabled; - } - public void AppendConversationDialogs(string conversationId, List messages) { if (!IsValid(conversationId)) @@ -99,17 +94,22 @@ public class BotSharpConversationSideCar : IConversationSideCar top.State = new ConversationState(states); } - public async Task SendMessage(string agentId, string text, + public async Task SendMessage( + string agentId, + string text, PostbackMessageModel? postback = null, List? states = null, List? dialogs = null, SideCarOptions? options = null) { _sideCarOptions = options; + _logger.LogInformation($"Entering side car conversation..."); BeforeExecute(dialogs); var response = await InnerExecute(agentId, text, postback, states); AfterExecute(); + + _logger.LogInformation($"Existing side car conversation..."); return response; } diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs index ff61fde2..4c8178d8 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs @@ -159,7 +159,7 @@ public class ConversationStateService : IConversationStateService Reset(); var endNodes = new Dictionary(); - if (_sidecar?.IsEnabled() == true) + if (_sidecar?.IsEnabled == true) { return endNodes; } @@ -234,7 +234,7 @@ public class ConversationStateService : IConversationStateService public void Save() { - if (_conversationId == null || _sidecar?.IsEnabled() == true) + if (_conversationId == null || _sidecar?.IsEnabled == true) { return; } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs index a41abe87..3c7a675d 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Hooks/ChatHubConversationHook.cs @@ -179,7 +179,7 @@ public class ChatHubConversationHook : ConversationHookBase private bool AllowSendingMessage() { var sidecar = _services.GetService(); - return sidecar == null || !sidecar.IsEnabled(); + return sidecar == null || !sidecar.IsEnabled; } private async Task InitClientConversation(string conversationId, ConversationDto conversation) diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs index 31d3db52..de58dca6 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs @@ -53,28 +53,29 @@ public class TwilioInboundController : TwilioController instruction.SpeechPaths.Add(request.InitAudioFile); } + // Before creating session await HookEmitter.Emit(_services, async hook => { await hook.OnSessionCreating(request, instruction); }, request.AgentId); + var (agent, conversationId) = await InitConversation(request); request.ConversationId = conversationId.Id; instruction.AgentId = request.AgentId; instruction.ConversationId = request.ConversationId; + + // After creating session await HookEmitter.Emit(_services, async hook => { await hook.OnSessionCreated(request); }, request.AgentId); - var conv = _services.GetRequiredService(); - conv.SaveStates(); if (twilio.MachineDetected(request)) { response = new VoiceResponse(); - await HookEmitter.Emit(_services, async hook => await hook.OnVoicemailStarting(request), request.AgentId); @@ -122,7 +123,7 @@ public class TwilioInboundController : TwilioController await Task.Delay(1500); await twilio.StartRecording(request.CallSid, request.AgentId, request.ConversationId); }); - } + } return TwiML(response); } From 764bbefbfa36b24394e5d62e31684ed973ac7230 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 9 Jul 2025 15:23:55 -0500 Subject: [PATCH 5/5] refine chat stream with init states --- .../Realtime/IRealtimeHub.cs | 2 +- .../Services/RealtimeHub.cs | 5 ++-- .../ChatStreamMiddleware.cs | 29 ++++++++++++++----- .../Models/Stream/ChatStreamEventResponse.cs | 7 ++--- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Realtime/IRealtimeHub.cs b/src/Infrastructure/BotSharp.Abstraction/Realtime/IRealtimeHub.cs index cad64038..9d64b678 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Realtime/IRealtimeHub.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Realtime/IRealtimeHub.cs @@ -13,5 +13,5 @@ public interface IRealtimeHub IRealTimeCompletion Completer { get; } - Task ConnectToModel(Func? responseToUser = null, Func? init = null); + Task ConnectToModel(Func? responseToUser = null, Func? init = null, List? initStates = null); } diff --git a/src/Infrastructure/BotSharp.Core.Realtime/Services/RealtimeHub.cs b/src/Infrastructure/BotSharp.Core.Realtime/Services/RealtimeHub.cs index baac131c..32b0a112 100644 --- a/src/Infrastructure/BotSharp.Core.Realtime/Services/RealtimeHub.cs +++ b/src/Infrastructure/BotSharp.Core.Realtime/Services/RealtimeHub.cs @@ -1,5 +1,6 @@ using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Hooks; +using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Options; using BotSharp.Core.Infrastructures; @@ -22,10 +23,10 @@ public class RealtimeHub : IRealtimeHub _logger = logger; } - public async Task ConnectToModel(Func? responseToUser = null, Func? init = null) + public async Task ConnectToModel(Func? responseToUser = null, Func? init = null, List? initStates = null) { var convService = _services.GetRequiredService(); - convService.SetConversationId(_conn.ConversationId, []); + convService.SetConversationId(_conn.ConversationId, initStates ?? []); var conversation = await convService.GetConversation(_conn.ConversationId); var routing = _services.GetRequiredService(); diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/ChatStreamMiddleware.cs b/src/Plugins/BotSharp.Plugin.ChatHub/ChatStreamMiddleware.cs index 171bf0f2..7079c642 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/ChatStreamMiddleware.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/ChatStreamMiddleware.cs @@ -1,3 +1,4 @@ +using BotSharp.Abstraction.Models; using BotSharp.Abstraction.Realtime.Models.Session; using BotSharp.Core.Session; using Microsoft.AspNetCore.Http; @@ -66,6 +67,7 @@ public class ChatStreamMiddleware // load conversation and state var convService = services.GetRequiredService(); + var state = services.GetRequiredService(); convService.SetConversationId(conversationId, []); await convService.GetConversationRecordOrCreateNew(agentId); @@ -80,7 +82,8 @@ public class ChatStreamMiddleware var (eventType, data) = MapEvents(conn, receivedText); if (eventType == "start") { - await ConnectToModel(hub, webSocket); + var states = InitStates(data); + await ConnectToModel(hub, webSocket, states); } else if (eventType == "media") { @@ -96,25 +99,26 @@ public class ChatStreamMiddleware } } + convService.SaveStates(); await _session.DisconnectAsync(); _session.Dispose(); } - private async Task ConnectToModel(IRealtimeHub hub, WebSocket webSocket) + private async Task ConnectToModel(IRealtimeHub hub, WebSocket webSocket, List? states = null) { - await hub.ConnectToModel(async data => + await hub.ConnectToModel(responseToUser: async data => { if (_session != null) { await _session.SendEventAsync(data); } - }); + }, initStates: states); } private (string, string) MapEvents(RealtimeHubConnection conn, string receivedText) { var response = JsonSerializer.Deserialize(receivedText); - string data = string.Empty; + var data = response?.Body?.Payload ?? string.Empty; switch (response.Event) { @@ -122,8 +126,6 @@ public class ChatStreamMiddleware conn.ResetStreamState(); break; case "media": - var mediaResponse = JsonSerializer.Deserialize(receivedText); - data = mediaResponse?.Body?.Payload ?? string.Empty; break; case "disconnect": break; @@ -154,4 +156,17 @@ public class ChatStreamMiddleware @event = "clear" }); } + + private List InitStates(string data) + { + try + { + var states = JsonSerializer.Deserialize>(data, BotSharpOptions.defaultJsonOptions); + return states ?? []; + } + catch + { + return []; + } + } } diff --git a/src/Plugins/BotSharp.Plugin.ChatHub/Models/Stream/ChatStreamEventResponse.cs b/src/Plugins/BotSharp.Plugin.ChatHub/Models/Stream/ChatStreamEventResponse.cs index aee742df..022ebdf1 100644 --- a/src/Plugins/BotSharp.Plugin.ChatHub/Models/Stream/ChatStreamEventResponse.cs +++ b/src/Plugins/BotSharp.Plugin.ChatHub/Models/Stream/ChatStreamEventResponse.cs @@ -6,15 +6,12 @@ internal class ChatStreamEventResponse { [JsonPropertyName("event")] public string Event { get; set; } -} -internal class ChatStreamMediaEventResponse : ChatStreamEventResponse -{ [JsonPropertyName("body")] - public MediaEventResponseBody Body { get; set; } + public ChatStreamEventResponseBody Body { get; set; } } -internal class MediaEventResponseBody +internal class ChatStreamEventResponseBody { [JsonPropertyName("payload")] public string Payload { get; set; }