From dccc345bcd828efc665f5a6a9c8c10fd94277269 Mon Sep 17 00:00:00 2001 From: Haiping Chen Date: Thu, 21 Mar 2024 17:04:45 -0500 Subject: [PATCH] Check routing rule after poping agent. --- .../Routing/IRoutingService.cs | 2 + .../Routing/Functions/RouteToAgentFn.cs | 99 +---------------- .../BotSharp.Core/Routing/RoutingContext.cs | 27 ++++- .../RoutingService.HasMissingRequiredField.cs | 100 ++++++++++++++++++ .../Routing/RoutingService.InvokeFunction.cs | 6 ++ 5 files changed, 137 insertions(+), 97 deletions(-) create mode 100644 src/Infrastructure/BotSharp.Core/Routing/RoutingService.HasMissingRequiredField.cs diff --git a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs index 74845e1f..48daad61 100644 --- a/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs +++ b/src/Infrastructure/BotSharp.Abstraction/Routing/IRoutingService.cs @@ -43,4 +43,6 @@ public interface IRoutingService Task InstructDirect(Agent agent, RoleDialogModel message); Task GetConversationContent(List dialogs, int maxDialogCount = 50); + + bool HasMissingRequiredField(RoleDialogModel message, out string agentId); } diff --git a/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs b/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs index 476611d9..d2f478e8 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/Functions/RouteToAgentFn.cs @@ -1,13 +1,12 @@ using BotSharp.Abstraction.Functions; using BotSharp.Abstraction.Routing.Models; -using System.Drawing; namespace BotSharp.Core.Routing; /// /// Router calls this function to set the Active Agent according to the context /// -public class RouteToAgentFn : IFunctionCallback +public partial class RouteToAgentFn : IFunctionCallback { public string Name => "route_to_agent"; private readonly IServiceProvider _services; @@ -82,7 +81,8 @@ public class RouteToAgentFn : IFunctionCallback return false; } - var missingfield = HasMissingRequiredField(message, out var agentId); + var routing = _services.GetRequiredService(); + var missingfield = routing.HasMissingRequiredField(message, out var agentId); if (missingfield && message.CurrentAgentId != agentId) { // Stack redirection agent @@ -94,97 +94,4 @@ public class RouteToAgentFn : IFunctionCallback 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 routing = _services.GetRequiredService(); - - var routingRules = routing.GetRulesByAgentName(args.AgentName); - - if (routingRules == null || !routingRules.Any()) - { - agentId = message.CurrentAgentId; - return false; - } - - agentId = routingRules.First().AgentId; - // Add routed agent - message.FunctionArgs = AppendPropertyToArgs(message.FunctionArgs, "route_to", agentId); - - // Check required fields - var root = JsonSerializer.Deserialize(message.FunctionArgs); - var missingFields = new List(); - foreach (var field in routingRules.Where(x => x.Required).Select(x => x.Field)) - { - if (!root.EnumerateObject().Any(x => x.Name == field)) - { - missingFields.Add(field); - } - else if (root.EnumerateObject().Any(x => x.Name == field) && - string.IsNullOrEmpty(root.EnumerateObject().FirstOrDefault(x => x.Name == field).Value.ToString())) - { - missingFields.Add(field); - } - } - - // Check if states contains the field according conversation context. - var states = _services.GetRequiredService(); - foreach (var field in missingFields.ToList()) - { - if (!string.IsNullOrEmpty(states.GetState(field))) - { - var value = states.GetState(field); - message.FunctionArgs = AppendPropertyToArgs(message.FunctionArgs, field, value); - missingFields.Remove(field); - } - } - - if (missingFields.Any()) - { - // Add field to args - message.FunctionArgs = AppendPropertyToArgs(message.FunctionArgs, "missing_fields", missingFields); - message.Content = $"missing some information: {string.Join(", ", missingFields)}"; - - // Handle redirect - var routingRule = routingRules.FirstOrDefault(x => missingFields.Contains(x.Field)); - if (!string.IsNullOrEmpty(routingRule.RedirectTo)) - { - var db = _services.GetRequiredService(); - var record = db.GetAgent(routingRule.RedirectTo); - - // Add redirected agent - message.FunctionArgs = AppendPropertyToArgs(message.FunctionArgs, "redirect_to", record.Name); - agentId = routingRule.RedirectTo; - var logger = _services.GetRequiredService>(); -#if DEBUG - Console.WriteLine($"*** Routing redirect to {record.Name.ToUpper()} ***", Color.Yellow); -#else - logger.LogInformation($"*** Routing redirect to {record.Name.ToUpper()} ***"); -#endif - } - else - { - // back to router - agentId = message.CurrentAgentId; - } - } - - return missingFields.Any(); - } - - private string AppendPropertyToArgs(string args, string key, string value) - { - return args.Substring(0, args.Length - 1) + $", \"{key}\": \"{value}\"" + "}"; - } - - private string AppendPropertyToArgs(string args, string key, IEnumerable values) - { - string fields = string.Join(",", values.Select(x => $"\"{x}\"")); - return args.Substring(0, args.Length - 1) + $", \"{key}\": [{fields}]" + "}"; - } } diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs index 96cd95e6..e9abdc04 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingContext.cs @@ -105,6 +105,31 @@ public class RoutingContext : IRoutingContext HookEmitter.Emit(_services, async hook => await hook.OnAgentDequeued(agentId, currentAgentId, reason: reason) ).Wait(); + + // Run the routing rule + var agency = _services.GetRequiredService(); + var agent = agency.LoadAgent(currentAgentId).Result; + + var message = new RoleDialogModel(AgentRole.User, $"Try to route to agent {agent.Name}") + { + FunctionName = "route_to_agent", + FunctionArgs = JsonSerializer.Serialize(new FunctionCallFromLlm + { + Function = "route_to_agent", + AgentName = agent.Name, + Reason = $"User manually route to agent {agent.Name}" + }) + }; + + var routing = _services.GetRequiredService(); + var missingfield = routing.HasMissingRequiredField(message, out agentId); + if (missingfield) + { + if (currentAgentId != agentId) + { + _stack.Push(agentId); + } + } } public string PreviousAgentId() @@ -115,7 +140,7 @@ public class RoutingContext : IRoutingContext } else if (_stack.Count > 1) { - return _stack.ToArray()[1]; + return _stack.ToArray()[_stack.Count - 2]; } return string.Empty; diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.HasMissingRequiredField.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.HasMissingRequiredField.cs new file mode 100644 index 00000000..20d75a95 --- /dev/null +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.HasMissingRequiredField.cs @@ -0,0 +1,100 @@ +using BotSharp.Abstraction.Routing.Models; +using System.Drawing; + +namespace BotSharp.Core.Routing; + +public partial class RoutingService +{ + /// + /// If the target agent needs some required fields but the + /// + /// + public bool HasMissingRequiredField(RoleDialogModel message, out string agentId) + { + var args = JsonSerializer.Deserialize(message.FunctionArgs); + var routing = _services.GetRequiredService(); + + var routingRules = routing.GetRulesByAgentName(args.AgentName); + + if (routingRules == null || !routingRules.Any()) + { + agentId = message.CurrentAgentId; + return false; + } + + agentId = routingRules.First().AgentId; + // Add routed agent + message.FunctionArgs = AppendPropertyToArgs(message.FunctionArgs, "route_to", agentId); + + // Check required fields + var root = JsonSerializer.Deserialize(message.FunctionArgs); + var missingFields = new List(); + foreach (var field in routingRules.Where(x => x.Required).Select(x => x.Field)) + { + if (!root.EnumerateObject().Any(x => x.Name == field)) + { + missingFields.Add(field); + } + else if (root.EnumerateObject().Any(x => x.Name == field) && + string.IsNullOrEmpty(root.EnumerateObject().FirstOrDefault(x => x.Name == field).Value.ToString())) + { + missingFields.Add(field); + } + } + + // Check if states contains the field according conversation context. + var states = _services.GetRequiredService(); + foreach (var field in missingFields.ToList()) + { + if (!string.IsNullOrEmpty(states.GetState(field))) + { + var value = states.GetState(field); + message.FunctionArgs = AppendPropertyToArgs(message.FunctionArgs, field, value); + missingFields.Remove(field); + } + } + + if (missingFields.Any()) + { + // Add field to args + message.FunctionArgs = AppendPropertyToArgs(message.FunctionArgs, "missing_fields", missingFields); + message.Content = $"missing some information: {string.Join(", ", missingFields)}"; + + // Handle redirect + var routingRule = routingRules.FirstOrDefault(x => missingFields.Contains(x.Field)); + if (!string.IsNullOrEmpty(routingRule.RedirectTo)) + { + var db = _services.GetRequiredService(); + var record = db.GetAgent(routingRule.RedirectTo); + + // Add redirected agent + message.FunctionArgs = AppendPropertyToArgs(message.FunctionArgs, "redirect_to", record.Name); + agentId = routingRule.RedirectTo; + var logger = _services.GetRequiredService>(); +#if DEBUG + Console.WriteLine($"*** Routing redirect to {record.Name.ToUpper()} ***", Color.Yellow); +#else + logger.LogInformation($"*** Routing redirect to {record.Name.ToUpper()} ***"); +#endif + } + else + { + // back to router + agentId = message.CurrentAgentId; + } + } + + return missingFields.Any(); + } + + private string AppendPropertyToArgs(string args, string key, string value) + { + return args.Substring(0, args.Length - 1) + $", \"{key}\": \"{value}\"" + "}"; + } + + private string AppendPropertyToArgs(string args, string key, IEnumerable values) + { + string fields = string.Join(",", values.Select(x => $"\"{x}\"")); + return args.Substring(0, args.Length - 1) + $", \"{key}\": [{fields}]" + "}"; + } +} diff --git a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs index 77659d69..690308eb 100644 --- a/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs +++ b/src/Infrastructure/BotSharp.Core/Routing/RoutingService.InvokeFunction.cs @@ -36,6 +36,12 @@ public partial class RoutingService { result = await function.Execute(message); } + catch (JsonException ex) + { + _logger.LogError($"The input does not contain any JSON tokens:\r\n{message.Content}"); + message.StopCompletion = true; + message.Content = ex.Message; + } catch (Exception ex) { message.StopCompletion = true;