Check routing rule after poping agent.

This commit is contained in:
Haiping Chen 2024-03-21 17:04:45 -05:00
parent 7b26a81a70
commit dccc345bcd
5 changed files with 137 additions and 97 deletions

View file

@ -43,4 +43,6 @@ public interface IRoutingService
Task<RoleDialogModel> InstructDirect(Agent agent, RoleDialogModel message);
Task<string> GetConversationContent(List<RoleDialogModel> dialogs, int maxDialogCount = 50);
bool HasMissingRequiredField(RoleDialogModel message, out string agentId);
}

View file

@ -1,13 +1,12 @@
using BotSharp.Abstraction.Functions;
using BotSharp.Abstraction.Routing.Models;
using System.Drawing;
namespace BotSharp.Core.Routing;
/// <summary>
/// Router calls this function to set the Active Agent according to the context
/// </summary>
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<IRoutingService>();
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;
}
/// <summary>
/// If the target agent needs some required fields but the
/// </summary>
/// <returns></returns>
private bool HasMissingRequiredField(RoleDialogModel message, out string agentId)
{
var args = JsonSerializer.Deserialize<RoutingArgs>(message.FunctionArgs);
var routing = _services.GetRequiredService<IRoutingService>();
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<JsonElement>(message.FunctionArgs);
var missingFields = new List<string>();
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<IConversationStateService>();
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<IBotSharpRepository>();
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<ILogger<RouteToAgentFn>>();
#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<string> values)
{
string fields = string.Join(",", values.Select(x => $"\"{x}\""));
return args.Substring(0, args.Length - 1) + $", \"{key}\": [{fields}]" + "}";
}
}

View file

@ -105,6 +105,31 @@ public class RoutingContext : IRoutingContext
HookEmitter.Emit<IRoutingHook>(_services, async hook =>
await hook.OnAgentDequeued(agentId, currentAgentId, reason: reason)
).Wait();
// Run the routing rule
var agency = _services.GetRequiredService<IAgentService>();
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<IRoutingService>();
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;

View file

@ -0,0 +1,100 @@
using BotSharp.Abstraction.Routing.Models;
using System.Drawing;
namespace BotSharp.Core.Routing;
public partial class RoutingService
{
/// <summary>
/// If the target agent needs some required fields but the
/// </summary>
/// <returns></returns>
public bool HasMissingRequiredField(RoleDialogModel message, out string agentId)
{
var args = JsonSerializer.Deserialize<RoutingArgs>(message.FunctionArgs);
var routing = _services.GetRequiredService<IRoutingService>();
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<JsonElement>(message.FunctionArgs);
var missingFields = new List<string>();
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<IConversationStateService>();
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<IBotSharpRepository>();
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<ILogger<RouteToAgentFn>>();
#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<string> values)
{
string fields = string.Join(",", values.Select(x => $"\"{x}\""));
return args.Substring(0, args.Length - 1) + $", \"{key}\": [{fields}]" + "}";
}
}

View file

@ -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;