From 333d8a7302b443af4bde03d527027d2e9e0cfecc Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 18 Jun 2025 14:29:25 -0500 Subject: [PATCH 01/17] fix --- .../Models/MessageState.cs | 5 ++ .../Functions/OutboundPhoneCallFn.cs | 61 +++++++++++++++---- 2 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/Models/MessageState.cs b/src/Infrastructure/BotSharp.Abstraction/Models/MessageState.cs index ff26e981..a8709810 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Models/MessageState.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Models/MessageState.cs @@ -19,4 +19,9 @@ public class MessageState Value = value; ActiveRounds = activeRounds; } + + public override string ToString() + { + return $"Key: {Key} => Value: {Value}, ActiveRounds: {ActiveRounds}"; + } } diff --git a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/OutboundPhoneCallFn.cs b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/OutboundPhoneCallFn.cs index 5ffd692e..8d34dd22 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/OutboundPhoneCallFn.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/OutboundPhoneCallHandler/Functions/OutboundPhoneCallFn.cs @@ -1,7 +1,9 @@ using BotSharp.Abstraction.Files; using BotSharp.Abstraction.Infrastructures.Enums; using BotSharp.Abstraction.Options; +using BotSharp.Abstraction.Repositories; using BotSharp.Abstraction.Routing; +using BotSharp.Abstraction.Utilities; using BotSharp.Core.Infrastructures; using BotSharp.Plugin.Twilio.Interfaces; using BotSharp.Plugin.Twilio.Models; @@ -134,9 +136,10 @@ public class OutboundPhoneCallFn : IFunctionCallback } } - private async Task ForkConversation(LlmContextIn args, - string entryAgentId, - string originConversationId, + private async Task ForkConversation( + LlmContextIn args, + string entryAgentId, + string originConversationId, string newConversationId, CallResource call) { @@ -145,6 +148,8 @@ public class OutboundPhoneCallFn : IFunctionCallback var services = scope.ServiceProvider; var convService = services.GetRequiredService(); var convStorage = services.GetRequiredService(); + var state = _services.GetRequiredService(); + var db = _services.GetRequiredService(); var newConv = await convService.NewConversation(new Conversation { @@ -170,15 +175,45 @@ public class OutboundPhoneCallFn : IFunctionCallback } }); - convService.SetConversationId(newConversationId, - [ - new MessageState(StateConst.ORIGIN_CONVERSATION_ID, originConversationId), - new MessageState("channel", "phone"), - new MessageState("phone_from", call.From), - new MessageState("phone_direction", call.Direction), - new MessageState("phone_number", call.To), - new MessageState("twilio_call_sid", call.Sid) - ]); - convService.SaveStates(); + var utcNow = DateTime.UtcNow; + var excludStates = new List + { + "provider", + "model", + "prompt_total", + "completion_total", + "llm_total_cost" + }; + + var curStates = state.GetStates().Select(x => new MessageState(x.Key, x.Value)).ToList(); + var subConvStates = new List + { + new(StateConst.ORIGIN_CONVERSATION_ID, originConversationId), + new("channel", "phone"), + new("phone_from", call.From), + new("phone_direction", call.Direction), + new("phone_number", call.To), + new("twilio_call_sid", call.Sid) + }; + var subStateKeys = subConvStates.Select(x => x.Key).ToList(); + var included = curStates.Where(x => !subStateKeys.Contains(x.Key) && !excludStates.Contains(x.Key)); + var newStates = subConvStates.Concat(included).Select(x => new StateKeyValue + { + Key = x.Key, + Versioning = true, + Values = [ + new StateValue + { + Data = x.Value.ConvertToString(_options.JsonSerializerOptions), + MessageId = messageId, + Active = true, + ActiveRounds = x.ActiveRounds, + Source = StateSource.Application, + UpdateTime = utcNow + } + ] + }).ToList(); + + db.UpdateConversationStates(newConversationId, newStates); } } From ef95e640aceb535a98384fb14522393877ba40db Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 18 Jun 2025 14:52:42 -0500 Subject: [PATCH 02/17] refine sidecar --- .../Services/BotSharpConversationSideCar.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs index 34a98017..8aa0c231 100644 --- a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs +++ b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs @@ -25,7 +25,8 @@ public class BotSharpConversationSideCar : IConversationSideCar private Stack contextStack = new(); - private bool enabled = false; + private bool _enabled = false; + private string _conversationId = string.Empty; public string Provider => "botsharp"; @@ -39,12 +40,15 @@ public class BotSharpConversationSideCar : IConversationSideCar public bool IsEnabled() { - return enabled; + return _enabled; } public void AppendConversationDialogs(string conversationId, List messages) { - if (contextStack.IsNullOrEmpty()) return; + if (contextStack.IsNullOrEmpty() || _conversationId != conversationId) + { + return; + } var top = contextStack.Peek(); top.Dialogs.AddRange(messages); @@ -52,7 +56,7 @@ public class BotSharpConversationSideCar : IConversationSideCar public List GetConversationDialogs(string conversationId) { - if (contextStack.IsNullOrEmpty()) + if (contextStack.IsNullOrEmpty() || _conversationId != conversationId) { return new List(); } @@ -62,7 +66,10 @@ public class BotSharpConversationSideCar : IConversationSideCar public void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint) { - if (contextStack.IsNullOrEmpty()) return; + if (contextStack.IsNullOrEmpty() || _conversationId != conversationId) + { + return; + } var top = contextStack.Peek().Breakpoints; top.Add(breakpoint); @@ -70,7 +77,7 @@ public class BotSharpConversationSideCar : IConversationSideCar public ConversationBreakpoint? GetConversationBreakpoint(string conversationId) { - if (contextStack.IsNullOrEmpty()) + if (contextStack.IsNullOrEmpty() || _conversationId != conversationId) { return null; } @@ -94,6 +101,7 @@ public class BotSharpConversationSideCar : IConversationSideCar var conv = _services.GetRequiredService(); var routing = _services.GetRequiredService(); var state = _services.GetRequiredService(); + _conversationId = conv.ConversationId; var inputMsg = new RoleDialogModel(AgentRole.User, text); routing.Context.SetMessageId(conv.ConversationId, inputMsg.MessageId); @@ -116,7 +124,7 @@ public class BotSharpConversationSideCar : IConversationSideCar private void BeforeExecute(List? dialogs) { - enabled = true; + _enabled = true; var state = _services.GetRequiredService(); var routing = _services.GetRequiredService(); @@ -152,6 +160,6 @@ public class BotSharpConversationSideCar : IConversationSideCar routing.Context.SetAgentStack(node.RoutingStack); routing.Context.SetDialogs(node.RoutingDialogs); Utilities.ClearCache(); - enabled = false; + _enabled = false; } } \ No newline at end of file From 796388a8fcd06a48f925a2a487117c382603d22d Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 18 Jun 2025 15:03:16 -0500 Subject: [PATCH 03/17] refine side car --- .../SideCar/IConversationSideCar.cs | 1 + .../Services/BotSharpConversationSideCar.cs | 11 +++++++++++ .../FileRepository/FileRepository.Conversation.cs | 1 + .../Repository/MongoRepository.Conversation.cs | 1 + 4 files changed, 14 insertions(+) diff --git a/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs b/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs index 84b65573..28162a7e 100644 --- a/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs +++ b/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs @@ -9,6 +9,7 @@ public interface IConversationSideCar List GetConversationDialogs(string conversationId); void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint); ConversationBreakpoint? GetConversationBreakpoint(string conversationId); + void UpdateConversationStates(string conversationId, List states); Task SendMessage(string agentId, string text, PostbackMessageModel? postback = null, List? states = null, List? dialogs = null); } diff --git a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs index 8aa0c231..7988171a 100644 --- a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs +++ b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs @@ -86,6 +86,17 @@ public class BotSharpConversationSideCar : IConversationSideCar return top.LastOrDefault(); } + public void UpdateConversationStates(string conversationId, List states) + { + if (contextStack.IsNullOrEmpty() || _conversationId != conversationId) + { + return; + } + + var top = contextStack.Peek(); + top.State = new ConversationState(states); + } + public async Task SendMessage(string agentId, string text, PostbackMessageModel? postback = null, List? states = null, List? dialogs = null) { diff --git a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs index 3b60a61a..3d03e816 100644 --- a/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs +++ b/src/Infrastructure/BotSharp.Core/Repository/FileRepository/FileRepository.Conversation.cs @@ -307,6 +307,7 @@ public partial class FileRepository return new ConversationState(states); } + [SideCar] public void UpdateConversationStates(string conversationId, List states) { if (states.IsNullOrEmpty()) return; diff --git a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs index 02f5a695..edcf3a5f 100644 --- a/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs +++ b/src/Plugins/BotSharp.Plugin.MongoStorage/Repository/MongoRepository.Conversation.cs @@ -266,6 +266,7 @@ public partial class MongoRepository return new ConversationState(savedStates); } + [SideCar] public void UpdateConversationStates(string conversationId, List states) { if (string.IsNullOrEmpty(conversationId) || states == null) return; From 3458e9f70fc98552fbe3cb8567081527f387e0b1 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Wed, 18 Jun 2025 15:17:57 -0500 Subject: [PATCH 04/17] refine valid condition --- .../Services/BotSharpConversationSideCar.cs | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs index 7988171a..a948e5e2 100644 --- a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs +++ b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs @@ -23,7 +23,7 @@ public class BotSharpConversationSideCar : IConversationSideCar private readonly IServiceProvider _services; private readonly ILogger _logger; - private Stack contextStack = new(); + private Stack _contextStack = new(); private bool _enabled = false; private string _conversationId = string.Empty; @@ -45,55 +45,55 @@ public class BotSharpConversationSideCar : IConversationSideCar public void AppendConversationDialogs(string conversationId, List messages) { - if (contextStack.IsNullOrEmpty() || _conversationId != conversationId) + if (!IsValid(conversationId)) { return; } - var top = contextStack.Peek(); + var top = _contextStack.Peek(); top.Dialogs.AddRange(messages); } public List GetConversationDialogs(string conversationId) { - if (contextStack.IsNullOrEmpty() || _conversationId != conversationId) + if (!IsValid(conversationId)) { return new List(); } - return contextStack.Peek().Dialogs; + return _contextStack.Peek().Dialogs; } public void UpdateConversationBreakpoint(string conversationId, ConversationBreakpoint breakpoint) { - if (contextStack.IsNullOrEmpty() || _conversationId != conversationId) + if (!IsValid(conversationId)) { return; } - var top = contextStack.Peek().Breakpoints; + var top = _contextStack.Peek().Breakpoints; top.Add(breakpoint); } public ConversationBreakpoint? GetConversationBreakpoint(string conversationId) { - if (contextStack.IsNullOrEmpty() || _conversationId != conversationId) + if (!IsValid(conversationId)) { return null; } - var top = contextStack.Peek().Breakpoints; + var top = _contextStack.Peek().Breakpoints; return top.LastOrDefault(); } public void UpdateConversationStates(string conversationId, List states) { - if (contextStack.IsNullOrEmpty() || _conversationId != conversationId) + if (!IsValid(conversationId)) { return; } - var top = contextStack.Peek(); + var top = _contextStack.Peek(); top.State = new ConversationState(states); } @@ -148,7 +148,7 @@ public class BotSharpConversationSideCar : IConversationSideCar RecursiveCounter = routing.Context.GetRecursiveCounter(), RoutingStack = routing.Context.GetAgentStack() }; - contextStack.Push(node); + _contextStack.Push(node); // Reset state.ResetCurrentState(); @@ -163,7 +163,7 @@ public class BotSharpConversationSideCar : IConversationSideCar var state = _services.GetRequiredService(); var routing = _services.GetRequiredService(); - var node = contextStack.Pop(); + var node = _contextStack.Pop(); // Recover state.SetCurrentState(node.State); @@ -173,4 +173,12 @@ public class BotSharpConversationSideCar : IConversationSideCar Utilities.ClearCache(); _enabled = false; } + + private bool IsValid(string conversationId) + { + return !_contextStack.IsNullOrEmpty() + && _conversationId == conversationId + && !string.IsNullOrEmpty(conversationId) + && !string.IsNullOrEmpty(_conversationId); + } } \ No newline at end of file From 8d33c3619c785c0cd99bed3ff2cde97e6fc1b62f Mon Sep 17 00:00:00 2001 From: Jicheng Lu Date: Wed, 18 Jun 2025 20:55:23 -0500 Subject: [PATCH 05/17] prevent disposed service provider --- .../SideCar/Attributes/SideCarAttribute.cs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs b/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs index c10ffd8d..833cb013 100644 --- a/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs +++ b/src/Infrastructure/BotSharp.Abstraction/SideCar/Attributes/SideCarAttribute.cs @@ -54,16 +54,23 @@ public class SideCarAttribute : AsyncMoAttribute private (IConversationSideCar?, MethodInfo?) GetSideCarMethod(IServiceProvider serviceProvider, string methodName, Type retType, object[] args) { - var sidecar = serviceProvider.GetService(); - var argTypes = args.Select(x => x.GetType()).ToArray(); - var sidecarMethod = sidecar?.GetType()?.GetMethods(BindingFlags.Public | BindingFlags.Instance) - .FirstOrDefault(x => x.Name == methodName - && x.ReturnType == retType - && x.GetParameters().Length == argTypes.Length - && x.GetParameters().Select(p => p.ParameterType) - .Zip(argTypes, (paramType, argType) => paramType.IsAssignableFrom(argType)).All(y => y)); + try + { + var sidecar = serviceProvider.GetService(); + var argTypes = args.Select(x => x.GetType()).ToArray(); + var sidecarMethod = sidecar?.GetType()?.GetMethods(BindingFlags.Public | BindingFlags.Instance) + .FirstOrDefault(x => x.Name == methodName + && x.ReturnType == retType + && x.GetParameters().Length == argTypes.Length + && x.GetParameters().Select(p => p.ParameterType) + .Zip(argTypes, (paramType, argType) => paramType.IsAssignableFrom(argType)).All(y => y)); - return (sidecar, sidecarMethod); + return (sidecar, sidecarMethod); + } + catch + { + return (null, null); + } } private async Task<(bool, object?)> CallAsyncMethod(IConversationSideCar instance, MethodInfo method, Type retType, object[] args) From 8d5b1402d014f03a8e08c4d5b476f3e582b33707 Mon Sep 17 00:00:00 2001 From: "nick.yi" Date: Fri, 27 Jun 2025 14:13:53 +0800 Subject: [PATCH 06/17] Fixed an issue where response_to_user is called when the user wants to end the conversation. It should call route_to_agent with converation_end as true --- .../instructions/instruction.liquid | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instructions/instruction.liquid b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instructions/instruction.liquid index 775de66c..8d2da12c 100644 --- a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instructions/instruction.liquid +++ b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instructions/instruction.liquid @@ -5,7 +5,8 @@ Follow these steps to handle user request: 2. Determine which agent is suitable to handle this conversation. Try to minimize the routing of human service. 3. Extract and populate agent required arguments, think carefully, leave it as blank object if user didn't provide the specific arguments. 4. You must include all required args for the selected agent, but you must not make up any parameters when there is no exact value provided, those parameters must set value as null if not declared. -5. If user is greeting, you can call function response_to_user with a greeting message. +5. If user wants to end the conversation, call route_to_agent function with conversation_end as true. +6. If user is greeting, you can call function response_to_user with a greeting message. {% if routing_requirements and routing_requirements != empty %} [REQUIREMENTS] From f11790ed104ca40cd1cc75d4ffd0e0ca093c225c Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Fri, 27 Jun 2025 08:28:22 -0500 Subject: [PATCH 07/17] Added conversation_end to response_to_user --- .../BotSharp.Core/Routing/Reasoning/ReasonerHelper.cs | 6 ++++++ .../functions/response_to_user.json | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/src/Infrastructure/BotSharp.Core/Routing/Reasoning/ReasonerHelper.cs b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/ReasonerHelper.cs index 59ff3b9a..a37e2407 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Reasoning/ReasonerHelper.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Reasoning/ReasonerHelper.cs @@ -65,6 +65,12 @@ public static class ReasonerHelper } } + if (args.AgentName == "response_to_user") + { + args.AgentName = ""; + malformed = true; + } + if (malformed) { Console.WriteLine($"Captured LLM malformed response"); diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/functions/response_to_user.json b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/functions/response_to_user.json index 9e7f8929..6f6c995a 100644 --- a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/functions/response_to_user.json +++ b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/functions/response_to_user.json @@ -7,6 +7,10 @@ "content": { "type": "string", "description": "Response content" + }, + "conversation_end": { + "type": "boolean", + "description": "User is ending the conversation." } }, "required": [ "content" ] From c6cc057c461659787551ba114d76f4be0fd85d48 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Fri, 27 Jun 2025 09:03:17 -0500 Subject: [PATCH 08/17] BotSharpVersion v5.1 --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 5ca4cb96..0879abfc 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -2,7 +2,7 @@ net8.0 12.0 - 5.0.0 + 5.1.0 true false From 3bef027b2274a9b350d36d747364390b4fc6cc7c Mon Sep 17 00:00:00 2001 From: Haiping Date: Mon, 30 Jun 2025 10:10:40 -0500 Subject: [PATCH 09/17] Allow engineer role to edit knowledgebase --- .../BotSharp.Plugin.KnowledgeBase/KnowledgeBasePlugin.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugins/BotSharp.Plugin.KnowledgeBase/KnowledgeBasePlugin.cs b/src/Plugins/BotSharp.Plugin.KnowledgeBase/KnowledgeBasePlugin.cs index 31fa964c..2bbc950a 100644 --- a/src/Plugins/BotSharp.Plugin.KnowledgeBase/KnowledgeBasePlugin.cs +++ b/src/Plugins/BotSharp.Plugin.KnowledgeBase/KnowledgeBasePlugin.cs @@ -35,7 +35,7 @@ public class KnowledgeBasePlugin : IBotSharpPlugin var section = menu.First(x => x.Label == "Apps"); menu.Add(new PluginMenuDef("Knowledge Base", icon: "bx bx-book-open", weight: section.Weight + 1) { - Roles = new List { UserRole.Root, UserRole.Admin }, + Roles = new List { UserRole.Root, UserRole.Admin, UserRole.Engineer }, SubMenu = new List { new PluginMenuDef("Q & A", link: "page/knowledge-base/question-answer"), From 7b64416c9b2c7bf12fe16044fa6c9bfc25572c38 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Thu, 3 Jul 2025 12:09:10 -0500 Subject: [PATCH 10/17] fix realtime toolcall --- .../Providers/Chat/ChatCompletionProvider.cs | 2 +- .../Providers/Realtime/RealTimeCompletionProvider.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.DeepSeekAI/Providers/Chat/ChatCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.DeepSeekAI/Providers/Chat/ChatCompletionProvider.cs index 42ce1ac9..eb14ac99 100644 --- a/src/Plugins/BotSharp.Plugin.DeepSeekAI/Providers/Chat/ChatCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.DeepSeekAI/Providers/Chat/ChatCompletionProvider.cs @@ -270,7 +270,7 @@ public class ChatCompletionProvider : IChatCompletion { messages.Add(new AssistantChatMessage(new List { - ChatToolCall.CreateFunctionToolCall(message.ToolCallId.IfNullOrEmptyAs(message.FunctionName), message.FunctionName, BinaryData.FromString(message.FunctionArgs ?? string.Empty)) + ChatToolCall.CreateFunctionToolCall(message.ToolCallId.IfNullOrEmptyAs(message.FunctionName), message.FunctionName, BinaryData.FromString(message.FunctionArgs ?? "{}")) })); messages.Add(new ToolChatMessage(message.ToolCallId.IfNullOrEmptyAs(message.FunctionName), message.Content)); diff --git a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs index 8219bf1c..92fa6f84 100644 --- a/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs +++ b/src/Plugins/BotSharp.Plugin.OpenAI/Providers/Realtime/RealTimeCompletionProvider.cs @@ -615,10 +615,10 @@ public class RealTimeCompletionProvider : IRealTimeCompletion { messages.Add(new AssistantChatMessage(new List { - ChatToolCall.CreateFunctionToolCall(message.ToolCallId, message.FunctionName, BinaryData.FromString(message.FunctionArgs ?? string.Empty)) + ChatToolCall.CreateFunctionToolCall(message.ToolCallId.IfNullOrEmptyAs(message.FunctionName), message.FunctionName, BinaryData.FromString(message.FunctionArgs ?? "{}")) })); - messages.Add(new ToolChatMessage(message.ToolCallId, message.Content)); + messages.Add(new ToolChatMessage(message.ToolCallId.IfNullOrEmptyAs(message.FunctionName), message.Content)); } else if (message.Role == AgentRole.User) { From 3968463a7e9fbc087892e31f6ca0453fe45f39aa Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 7 Jul 2025 12:24:44 -0500 Subject: [PATCH 11/17] refine side car states --- .../SideCar/IConversationSideCar.cs | 7 ++- .../SideCar/Models/SideCarOptions.cs | 12 +++++ .../Services/BotSharpConversationSideCar.cs | 50 ++++++++++++++++++- .../BotSharp.Core.SideCar/Using.cs | 3 +- .../Services/ConversationStateService.cs | 2 +- 5 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Abstraction/SideCar/Models/SideCarOptions.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs b/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs index 28162a7e..3c9bcfa4 100644 --- a/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs +++ b/src/Infrastructure/BotSharp.Abstraction/SideCar/IConversationSideCar.cs @@ -1,3 +1,5 @@ +using BotSharp.Abstraction.SideCar.Models; + namespace BotSharp.Abstraction.SideCar; public interface IConversationSideCar @@ -11,5 +13,8 @@ public interface IConversationSideCar ConversationBreakpoint? GetConversationBreakpoint(string conversationId); void UpdateConversationStates(string conversationId, List states); Task SendMessage(string agentId, string text, - PostbackMessageModel? postback = null, List? states = null, List? dialogs = null); + PostbackMessageModel? postback = null, + List? states = null, + List? dialogs = null, + SideCarOptions? options = null); } diff --git a/src/Infrastructure/BotSharp.Abstraction/SideCar/Models/SideCarOptions.cs b/src/Infrastructure/BotSharp.Abstraction/SideCar/Models/SideCarOptions.cs new file mode 100644 index 00000000..a4858c5c --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/SideCar/Models/SideCarOptions.cs @@ -0,0 +1,12 @@ +namespace BotSharp.Abstraction.SideCar.Models; + +public class SideCarOptions +{ + public bool IsInheritStates { get; set; } + public IEnumerable? InheritStateKeys { get; set; } + + public static SideCarOptions Empty() + { + return new SideCarOptions(); + } +} diff --git a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs index a948e5e2..9689d367 100644 --- a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs +++ b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs @@ -14,6 +14,7 @@ limitations under the License. ******************************************************************************/ +using BotSharp.Abstraction.SideCar.Models; using BotSharp.Core.Infrastructures; namespace BotSharp.Core.SideCar.Services; @@ -24,6 +25,7 @@ public class BotSharpConversationSideCar : IConversationSideCar private readonly ILogger _logger; private Stack _contextStack = new(); + private SideCarOptions? _sideCarOptions; private bool _enabled = false; private string _conversationId = string.Empty; @@ -98,8 +100,13 @@ public class BotSharpConversationSideCar : IConversationSideCar } public async Task SendMessage(string agentId, string text, - PostbackMessageModel? postback = null, List? states = null, List? dialogs = null) + PostbackMessageModel? postback = null, + List? states = null, + List? dialogs = null, + SideCarOptions? options = null) { + _sideCarOptions = options; + BeforeExecute(dialogs); var response = await InnerExecute(agentId, text, postback, states); AfterExecute(); @@ -166,7 +173,7 @@ public class BotSharpConversationSideCar : IConversationSideCar var node = _contextStack.Pop(); // Recover - state.SetCurrentState(node.State); + RestoreStates(node.State); routing.Context.SetRecursiveCounter(node.RecursiveCounter); routing.Context.SetAgentStack(node.RoutingStack); routing.Context.SetDialogs(node.RoutingDialogs); @@ -181,4 +188,43 @@ public class BotSharpConversationSideCar : IConversationSideCar && !string.IsNullOrEmpty(conversationId) && !string.IsNullOrEmpty(_conversationId); } + + private void RestoreStates(ConversationState prevStates) + { + var innerStates = prevStates; + var state = _services.GetRequiredService(); + + if (_sideCarOptions?.IsInheritStates == true) + { + var curStates = state.GetCurrentState(); + foreach (var pair in curStates) + { + var endNode = pair.Value.Values.LastOrDefault(); + if (endNode == null) continue; + + if (_sideCarOptions?.InheritStateKeys?.Any() == true + && !_sideCarOptions.InheritStateKeys.Contains(pair.Key)) + { + continue; + } + + if (innerStates.ContainsKey(pair.Key)) + { + innerStates[pair.Key].Values.Add(endNode); + } + else + { + innerStates[pair.Key] = new StateKeyValue + { + Key = pair.Key, + Versioning = pair.Value.Versioning, + Readonly = pair.Value.Readonly, + Values = [endNode] + }; + } + } + } + + state.SetCurrentState(innerStates); + } } \ No newline at end of file diff --git a/src/Infrastructure/BotSharp.Core.SideCar/Using.cs b/src/Infrastructure/BotSharp.Core.SideCar/Using.cs index d047ee15..e391d790 100644 --- a/src/Infrastructure/BotSharp.Core.SideCar/Using.cs +++ b/src/Infrastructure/BotSharp.Core.SideCar/Using.cs @@ -16,5 +16,6 @@ global using BotSharp.Abstraction.Conversations.Models; global using BotSharp.Abstraction.Models; global using BotSharp.Abstraction.Routing; global using BotSharp.Abstraction.SideCar; +global using BotSharp.Abstraction.SideCar.Models; global using BotSharp.Abstraction.Utilities; -global using BotSharp.Core.SideCar.Settings; \ No newline at end of file +global using BotSharp.Core.SideCar.Settings; diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs index c8665179..ff61fde2 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs @@ -443,7 +443,7 @@ public class ConversationStateService : IConversationStateService public void SetCurrentState(ConversationState state) { - var values = _curStates.Values.ToList(); + var values = state.Values.ToList(); var copy = JsonSerializer.Deserialize>(JsonSerializer.Serialize(values)); _curStates = new ConversationState(copy ?? []); } From df7b7bb61d2db7ec4422930fbf4c389242d7240a Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 7 Jul 2025 17:11:18 -0500 Subject: [PATCH 12/17] temp revert --- .../Services/BotSharpConversationSideCar.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs index 9689d367..2d1fec73 100644 --- a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs +++ b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs @@ -173,7 +173,7 @@ public class BotSharpConversationSideCar : IConversationSideCar var node = _contextStack.Pop(); // Recover - RestoreStates(node.State); + state.SetCurrentState(node.State); routing.Context.SetRecursiveCounter(node.RecursiveCounter); routing.Context.SetAgentStack(node.RoutingStack); routing.Context.SetDialogs(node.RoutingDialogs); From f43c32b8662baf16b15690ea35ee3a026ceb6a62 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 7 Jul 2025 17:29:25 -0500 Subject: [PATCH 13/17] revert to init --- .../Conversations/Services/ConversationStateService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs index ff61fde2..c8665179 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs @@ -443,7 +443,7 @@ public class ConversationStateService : IConversationStateService public void SetCurrentState(ConversationState state) { - var values = state.Values.ToList(); + var values = _curStates.Values.ToList(); var copy = JsonSerializer.Deserialize>(JsonSerializer.Serialize(values)); _curStates = new ConversationState(copy ?? []); } From ef1142f62ac11a886860b4284bf4c6066b9a7d48 Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 7 Jul 2025 17:41:39 -0500 Subject: [PATCH 14/17] refine --- .../Services/BotSharpConversationSideCar.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs index 2d1fec73..e4996b06 100644 --- a/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs +++ b/src/Infrastructure/BotSharp.Core.SideCar/Services/BotSharpConversationSideCar.cs @@ -167,13 +167,11 @@ public class BotSharpConversationSideCar : IConversationSideCar private void AfterExecute() { - var state = _services.GetRequiredService(); var routing = _services.GetRequiredService(); - var node = _contextStack.Pop(); // Recover - state.SetCurrentState(node.State); + RestoreStates(node.State); routing.Context.SetRecursiveCounter(node.RecursiveCounter); routing.Context.SetAgentStack(node.RoutingStack); routing.Context.SetDialogs(node.RoutingDialogs); From 9f7aa8f4d9e4674c052b93199398179d13d2862f Mon Sep 17 00:00:00 2001 From: Jicheng Lu <103353@smsassist.com> Date: Mon, 7 Jul 2025 17:43:17 -0500 Subject: [PATCH 15/17] revert --- .../Conversations/Services/ConversationStateService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs index c8665179..ff61fde2 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationStateService.cs @@ -443,7 +443,7 @@ public class ConversationStateService : IConversationStateService public void SetCurrentState(ConversationState state) { - var values = _curStates.Values.ToList(); + var values = state.Values.ToList(); var copy = JsonSerializer.Deserialize>(JsonSerializer.Serialize(values)); _curStates = new ConversationState(copy ?? []); } From 94c3a86635b8e00ce040e0cdaec2615110164fee Mon Sep 17 00:00:00 2001 From: "nick.yi" Date: Tue, 8 Jul 2025 23:03:54 +0800 Subject: [PATCH 16/17] optimize twilio --- .../Controllers/TwilioInboundController.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs index 511abb45..07c4ad87 100644 --- a/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs +++ b/src/Plugins/BotSharp.Plugin.Twilio/Controllers/TwilioInboundController.cs @@ -63,6 +63,11 @@ public class TwilioInboundController : TwilioController instruction.AgentId = request.AgentId; instruction.ConversationId = request.ConversationId; + await HookEmitter.Emit(_services, async hook => + { + await hook.OnSessionCreated(request); + }, request.AgentId); + if (twilio.MachineDetected(request)) { response = new VoiceResponse(); @@ -114,12 +119,7 @@ public class TwilioInboundController : TwilioController await Task.Delay(1500); await twilio.StartRecording(request.CallSid, request.AgentId, request.ConversationId); }); - } - - await HookEmitter.Emit(_services, async hook => - { - await hook.OnSessionCreated(request); - }, request.AgentId); + } return TwiML(response); } From a5d9da92bc615e802a84ce45a2d7871aa1664607 Mon Sep 17 00:00:00 2001 From: "nick.yi" Date: Tue, 8 Jul 2025 23:09:22 +0800 Subject: [PATCH 17/17] Revert "Fixed an issue where response_to_user is called when the user wants to end the conversation. It should call route_to_agent with converation_end as true" This reverts commit 8d5b1402d014f03a8e08c4d5b476f3e582b33707. --- .../instructions/instruction.liquid | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instructions/instruction.liquid b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instructions/instruction.liquid index 8d2da12c..775de66c 100644 --- a/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instructions/instruction.liquid +++ b/src/Infrastructure/BotSharp.Core/data/agents/01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a/instructions/instruction.liquid @@ -5,8 +5,7 @@ Follow these steps to handle user request: 2. Determine which agent is suitable to handle this conversation. Try to minimize the routing of human service. 3. Extract and populate agent required arguments, think carefully, leave it as blank object if user didn't provide the specific arguments. 4. You must include all required args for the selected agent, but you must not make up any parameters when there is no exact value provided, those parameters must set value as null if not declared. -5. If user wants to end the conversation, call route_to_agent function with conversation_end as true. -6. If user is greeting, you can call function response_to_user with a greeting message. +5. If user is greeting, you can call function response_to_user with a greeting message. {% if routing_requirements and routing_requirements != empty %} [REQUIREMENTS]