From 2d04226a14bc31224fb82bc3b34d9498e2b20227 Mon Sep 17 00:00:00 2001 From: hchen Date: Mon, 21 Aug 2023 15:43:03 -0500 Subject: [PATCH 1/6] Fix QuickReplies. --- .../Controllers/WebhookController.cs | 5 ++++- .../MessagingModels/QuickReplyMessage.cs | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Plugins/BotSharp.Plugin.MetaMessenger/Controllers/WebhookController.cs b/src/Plugins/BotSharp.Plugin.MetaMessenger/Controllers/WebhookController.cs index 47e87dc3..a83e424a 100644 --- a/src/Plugins/BotSharp.Plugin.MetaMessenger/Controllers/WebhookController.cs +++ b/src/Plugins/BotSharp.Plugin.MetaMessenger/Controllers/WebhookController.cs @@ -124,7 +124,10 @@ public class WebhookController : ControllerBase try { var parsed = JsonSerializer.Deserialize(json, jsonOpt); - reply.QuickReplies = parsed; + if (parsed.Length > 0) + { + reply.QuickReplies = parsed; + } } catch(Exception ex) { diff --git a/src/Plugins/BotSharp.Plugin.MetaMessenger/MessagingModels/QuickReplyMessage.cs b/src/Plugins/BotSharp.Plugin.MetaMessenger/MessagingModels/QuickReplyMessage.cs index e0db6925..9d577143 100644 --- a/src/Plugins/BotSharp.Plugin.MetaMessenger/MessagingModels/QuickReplyMessage.cs +++ b/src/Plugins/BotSharp.Plugin.MetaMessenger/MessagingModels/QuickReplyMessage.cs @@ -13,5 +13,6 @@ public class QuickReplyMessage : IResponseMessage public string Text { get; set; } [JsonPropertyName("quick_replies")] - public QuickReplyMessageItem[] QuickReplies { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public QuickReplyMessageItem[]? QuickReplies { get; set; } } From 36f0f2771b1cc71cc5fd5144ef8ea767d60093b2 Mon Sep 17 00:00:00 2001 From: hchen2020 <101423@smsassist.com> Date: Mon, 21 Aug 2023 23:48:17 -0500 Subject: [PATCH 2/6] Ignore state if the value is empty. --- .../Services/ConversationService.SendMessage.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs index 521f3054..45ede9dc 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.SendMessage.cs @@ -85,7 +85,10 @@ public partial class ConversationService { foreach (JsonProperty property in root.EnumerateObject()) { - stateService.SetState(property.Name, property.Value.ToString()); + if (!string.IsNullOrEmpty(property.Value.ToString())) + { + stateService.SetState(property.Name, property.Value.ToString()); + } } } } From 78b33bd4de746e770dcd0ec6b5cb61e458d845cf Mon Sep 17 00:00:00 2001 From: hchen2020 <101423@smsassist.com> Date: Mon, 21 Aug 2023 23:49:42 -0500 Subject: [PATCH 3/6] Apply centralized routing by routing table. --- .../Agents/Models/AgentRoutingArgs.cs | 9 ---- .../Agents/Models/RoutingArgs.cs | 14 ++++++ .../Agents/Models/RoutingResult.cs | 11 +++++ .../Agents/Models/RoutingTable.cs | 20 ++++++++ ...vice.GetChatCompletionsAsyncRecursively.cs | 7 +-- .../BotSharp.Core/Functions/RouteToAgentFn.cs | 47 ++++++++++++++++--- 6 files changed, 89 insertions(+), 19 deletions(-) delete mode 100644 src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRoutingArgs.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingArgs.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingResult.cs create mode 100644 src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingTable.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRoutingArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRoutingArgs.cs deleted file mode 100644 index 4046cda4..00000000 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/AgentRoutingArgs.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Text.Json.Serialization; - -namespace BotSharp.Abstraction.Agents.Models; - -public class AgentRoutingArgs -{ - [JsonPropertyName("agent_id")] - public string AgentId { get; set; } -} diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingArgs.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingArgs.cs new file mode 100644 index 00000000..e02b1c7b --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingArgs.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Abstraction.Agents.Models; + +public class RoutingArgs +{ + [JsonPropertyName("agent_name")] + public string AgentName { get; set; } + + public override string ToString() + { + return AgentName; + } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingResult.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingResult.cs new file mode 100644 index 00000000..b8ecdf6b --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingResult.cs @@ -0,0 +1,11 @@ +namespace BotSharp.Abstraction.Agents.Models; + +public class RoutingResult +{ + public string Result { get; set; } + + public RoutingResult(string result) + { + Result = result; + } +} diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingTable.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingTable.cs new file mode 100644 index 00000000..2feedcf8 --- /dev/null +++ b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingTable.cs @@ -0,0 +1,20 @@ +using System.Text.Json.Serialization; + +namespace BotSharp.Abstraction.Agents.Models; + +public class RoutingTable +{ + [JsonPropertyName("agent_id")] + public string AgentId { get; set; } + + [JsonPropertyName("name")] + public string AgentName { get; set; } + + [JsonPropertyName("required")] + public List RequiredFields { get; set; } + + public override string ToString() + { + return AgentName; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs index 71698ebd..c1fd2e30 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs @@ -53,18 +53,19 @@ public partial class ConversationService return; } - fn.Content = fn.ExecutionResult; + fn.Content = fn.FunctionArgs.Replace("\r", " ").Replace("\n", " ").Trim() + " => " + fn.ExecutionResult; // Agent has been transferred + var agentSettings = _services.GetRequiredService(); if (fn.CurrentAgentId != preAgentId) { - var agentSettings = _services.GetRequiredService(); var agentService = _services.GetRequiredService(); agent = await agentService.LoadAgent(fn.CurrentAgentId); } // Add to dialog history - _storage.Append(conversationId, preAgentId, fn); + // The server had an error processing your request. Sorry about that! + // _storage.Append(conversationId, preAgentId, fn); // After function is executed, pass the result to LLM to get a natural response wholeDialogs.Add(fn); diff --git a/src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs b/src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs index c8cd625d..bae33c7e 100644 --- a/src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs +++ b/src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs @@ -2,6 +2,8 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.Functions; using BotSharp.Abstraction.Functions.Models; +using Microsoft.Extensions.Logging; +using System.IO; namespace BotSharp.Core.Functions; @@ -17,20 +19,51 @@ public class RouteToAgentFn : IFunctionCallback public async Task Execute(RoleDialogModel message) { - var args = JsonSerializer.Deserialize(message.FunctionArgs); + var args = JsonSerializer.Deserialize(message.FunctionArgs); + var result = new RoutingResult($"Routed to {args.AgentName}"); - if (string.IsNullOrEmpty(args.AgentId)) + if (string.IsNullOrEmpty(args.AgentName)) { - var result = new FunctionExecutionValidationResult("false", "agent_id can't be parsed."); - message.ExecutionResult = JsonSerializer.Serialize(result); + result = new RoutingResult($"Can't find {args.AgentName}"); } else { - var result = new FunctionExecutionValidationResult("true"); - message.ExecutionResult = JsonSerializer.Serialize(result); - message.CurrentAgentId = args.AgentId; + var agentSettings = _services.GetRequiredService(); + var dbSettings = _services.GetRequiredService(); + var filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir, agentSettings.RouterId, "route.json"); + var routes = JsonSerializer.Deserialize(File.ReadAllText(filePath)); + + var agent = routes.FirstOrDefault(x => x.AgentName.ToLower() == args.AgentName.ToLower()); + if (agent == null) + { + result = new RoutingResult($"Can't find agent {args.AgentName}."); + } + else + { + // Check required fields + var jo = JsonSerializer.Deserialize(message.FunctionArgs); + bool hasMissingField = false; + foreach (var field in agent.RequiredFields) + { + if (jo is JsonElement root) + { + if (!root.EnumerateObject().Any(x => x.Name == field)) + { + result = new RoutingResult($"Please provide {field}."); + hasMissingField = true; + break; + } + } + } + + if (!hasMissingField) + { + message.CurrentAgentId = agent.AgentId; + } + } } + message.ExecutionResult = JsonSerializer.Serialize(result); return true; } } From e3a00c54e8a7d1ccabce1a1dfd89a221a0ce6f37 Mon Sep 17 00:00:00 2001 From: hchen2020 <101423@smsassist.com> Date: Tue, 22 Aug 2023 15:02:50 -0500 Subject: [PATCH 4/6] Remove GoToRouterFn. --- .../BotSharpServiceCollectionExtensions.cs | 1 - ...vice.GetChatCompletionsAsyncRecursively.cs | 4 +-- .../BotSharp.Core/Functions/GoToRouterFn.cs | 27 ------------------- 3 files changed, 2 insertions(+), 30 deletions(-) delete mode 100644 src/Infrastructure/BotSharp.Core/Functions/GoToRouterFn.cs diff --git a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs index ae315146..9081ca05 100644 --- a/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs +++ b/src/Infrastructure/BotSharp.Core/BotSharpServiceCollectionExtensions.cs @@ -37,7 +37,6 @@ public static class BotSharpServiceCollectionExtensions services.AddScoped(); - services.AddScoped(); services.AddScoped(); return services; diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs index c1fd2e30..3f56f97b 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs @@ -21,8 +21,8 @@ public partial class ConversationService currentRecursiveDepth++; if (currentRecursiveDepth > maxRecursiveDepth) { - _logger.LogError($"Exceed max current recursive depth."); - await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, "System has exception, please try later.") + _logger.LogError($"Exceeded max recursive depth."); + await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, "I'm sorry, can you see it again?") { CurrentAgentId = agent.Id, Channel = wholeDialogs.Last().Channel diff --git a/src/Infrastructure/BotSharp.Core/Functions/GoToRouterFn.cs b/src/Infrastructure/BotSharp.Core/Functions/GoToRouterFn.cs deleted file mode 100644 index e7a3455a..00000000 --- a/src/Infrastructure/BotSharp.Core/Functions/GoToRouterFn.cs +++ /dev/null @@ -1,27 +0,0 @@ -using BotSharp.Abstraction.Conversations.Models; -using BotSharp.Abstraction.Functions; -using BotSharp.Abstraction.Functions.Models; - -namespace BotSharp.Core.Functions; - -public class GoToRouterFn : IFunctionCallback -{ - public string Name => "go_to_router"; - private readonly IServiceProvider _services; - - public GoToRouterFn(IServiceProvider services) - { - _services = services; - } - - public async Task Execute(RoleDialogModel message) - { - var settings = _services.GetRequiredService(); - message.CurrentAgentId = settings.RouterId; - - var result = new FunctionExecutionValidationResult("true"); - message.ExecutionResult = JsonSerializer.Serialize(result); - - return true; - } -} From 7570108be5d3a700e8ca305393bc67b794dfe30c Mon Sep 17 00:00:00 2001 From: hchen2020 <101423@smsassist.com> Date: Tue, 22 Aug 2023 22:08:14 -0500 Subject: [PATCH 5/6] Remove RoutingResult. --- .../Agents/Models/RoutingResult.cs | 11 --- .../Conversations/Models/RoleDialogModel.cs | 5 -- .../BotSharp.Core/Functions/RouteToAgentFn.cs | 84 +++++++++++-------- 3 files changed, 51 insertions(+), 49 deletions(-) delete mode 100644 src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingResult.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingResult.cs b/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingResult.cs deleted file mode 100644 index b8ecdf6b..00000000 --- a/src/Infrastructure/BotSharp.Abstraction/Agents/Models/RoutingResult.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace BotSharp.Abstraction.Agents.Models; - -public class RoutingResult -{ - public string Result { get; set; } - - public RoutingResult(string result) - { - Result = result; - } -} diff --git a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs index bc460159..b681c2c3 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Conversations/Models/RoleDialogModel.cs @@ -30,11 +30,6 @@ public class RoleDialogModel /// public object ExecutionData { get; set; } - public bool IsConversationEnd { get; set; } - - public bool NeedReloadAgent { get; set; } - public bool StopPropagate { get; set; } - /// /// Channel name /// diff --git a/src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs b/src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs index bae33c7e..4c6d1967 100644 --- a/src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs +++ b/src/Infrastructure/BotSharp.Core/Functions/RouteToAgentFn.cs @@ -1,12 +1,13 @@ using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Conversations.Models; using BotSharp.Abstraction.Functions; -using BotSharp.Abstraction.Functions.Models; -using Microsoft.Extensions.Logging; using System.IO; namespace BotSharp.Core.Functions; +/// +/// Router calls this function to set the Active Agent according to the context +/// public class RouteToAgentFn : IFunctionCallback { public string Name => "route_to_agent"; @@ -20,50 +21,67 @@ public class RouteToAgentFn : IFunctionCallback public async Task Execute(RoleDialogModel message) { var args = JsonSerializer.Deserialize(message.FunctionArgs); - var result = new RoutingResult($"Routed to {args.AgentName}"); if (string.IsNullOrEmpty(args.AgentName)) { - result = new RoutingResult($"Can't find {args.AgentName}"); + message.ExecutionResult = $"missing agent name"; } else { - var agentSettings = _services.GetRequiredService(); - var dbSettings = _services.GetRequiredService(); - var filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir, agentSettings.RouterId, "route.json"); - var routes = JsonSerializer.Deserialize(File.ReadAllText(filePath)); - - var agent = routes.FirstOrDefault(x => x.AgentName.ToLower() == args.AgentName.ToLower()); - if (agent == null) + if (!HasMissingRequiredField(message, out var agentId)) { - result = new RoutingResult($"Can't find agent {args.AgentName}."); + message.CurrentAgentId = agentId; + message.ExecutionResult = $"Routed to {args.AgentName}"; } - else - { - // Check required fields - var jo = JsonSerializer.Deserialize(message.FunctionArgs); - bool hasMissingField = false; - foreach (var field in agent.RequiredFields) - { - if (jo is JsonElement root) - { - if (!root.EnumerateObject().Any(x => x.Name == field)) - { - result = new RoutingResult($"Please provide {field}."); - hasMissingField = true; - break; - } - } - } + } - if (!hasMissingField) + return true; + } + + /// + /// If the target agent needs some required fields but the + /// + /// + private bool HasMissingRequiredField(RoleDialogModel message, out string agentId) + { + var args = JsonSerializer.Deserialize(message.FunctionArgs); + + var routes = GetRoutingTable(); + var agent = routes.FirstOrDefault(x => x.AgentName.ToLower() == args.AgentName.ToLower()); + + if (agent == null) + { + agentId = message.CurrentAgentId; + message.ExecutionResult = $"Can't find agent {args.AgentName}"; + return true; + } + + agentId = agent.AgentId; + + // Check required fields + var jo = JsonSerializer.Deserialize(message.FunctionArgs); + bool hasMissingField = false; + foreach (var field in agent.RequiredFields) + { + if (jo is JsonElement root) + { + if (!root.EnumerateObject().Any(x => x.Name == field)) { - message.CurrentAgentId = agent.AgentId; + message.ExecutionResult = $"missing {field}."; + hasMissingField = true; + break; } } } - message.ExecutionResult = JsonSerializer.Serialize(result); - return true; + return hasMissingField; + } + + private RoutingTable[] GetRoutingTable() + { + var agentSettings = _services.GetRequiredService(); + var dbSettings = _services.GetRequiredService(); + var filePath = Path.Combine(dbSettings.FileRepository, agentSettings.DataDir, agentSettings.RouterId, "route.json"); + return JsonSerializer.Deserialize(File.ReadAllText(filePath)); } } From c98c79d2dbb1b41cf3a5de4606a7fe6e93be2375 Mon Sep 17 00:00:00 2001 From: hchen2020 <101423@smsassist.com> Date: Wed, 23 Aug 2023 10:40:46 -0500 Subject: [PATCH 6/6] Fix dead loop when depth exceeds the limit. --- ...ionService.GetChatCompletionsAsyncRecursively.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs index 3f56f97b..d4f058f7 100644 --- a/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs +++ b/src/Infrastructure/BotSharp.Core/Conversations/Services/ConversationService.GetChatCompletionsAsyncRecursively.cs @@ -21,12 +21,21 @@ public partial class ConversationService currentRecursiveDepth++; if (currentRecursiveDepth > maxRecursiveDepth) { - _logger.LogError($"Exceeded max recursive depth."); - await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, "I'm sorry, can you see it again?") + _logger.LogWarning($"Exceeded max recursive depth."); + + var latestResponse = wholeDialogs.Last(); + var text = latestResponse.Content; + if (latestResponse.Role == AgentRole.Function) + { + text = latestResponse.Content.Split("=>").Last(); + } + + await HandleAssistantMessage(new RoleDialogModel(AgentRole.Assistant, text) { CurrentAgentId = agent.Id, Channel = wholeDialogs.Last().Channel }, onMessageReceived); + return false; }