Merge pull request #270 from hchen2020/master
Allow task agent to fallback to predefined router.
This commit is contained in:
commit
92471faf56
|
|
@ -5,7 +5,7 @@ namespace BotSharp.Abstraction.Functions.Models;
|
|||
public class FunctionParametersDef
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; } = "string";
|
||||
public string Type { get; set; } = "object";
|
||||
|
||||
/// <summary>
|
||||
/// ParameterPropertyDef
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
namespace BotSharp.Abstraction.Routing.Enums;
|
||||
|
||||
public class RuleType
|
||||
{
|
||||
/// <summary>
|
||||
/// Fallback to redirect agent
|
||||
/// </summary>
|
||||
public const string Fallback = "fallback";
|
||||
|
||||
/// <summary>
|
||||
/// Redirect to other agent if data validation failed
|
||||
/// </summary>
|
||||
public const string DataValidation = "data-validation";
|
||||
}
|
||||
|
|
@ -13,8 +13,20 @@ public interface IRoutingService
|
|||
/// <returns></returns>
|
||||
RoutableAgent[] GetRoutableAgents(List<string> profiles);
|
||||
|
||||
RoutingRule[] GetRulesByName(string name);
|
||||
/// <summary>
|
||||
/// Get rules by agent name
|
||||
/// </summary>
|
||||
/// <param name="name">agent name</param>
|
||||
/// <returns></returns>
|
||||
RoutingRule[] GetRulesByAgentName(string name);
|
||||
|
||||
/// <summary>
|
||||
/// Get rules by agent id
|
||||
/// </summary>
|
||||
/// <param name="id">agent id </param>
|
||||
/// <returns></returns>
|
||||
RoutingRule[] GetRulesByAgentId(string id);
|
||||
|
||||
List<RoutingHandlerDef> GetHandlers();
|
||||
void ResetRecursiveCounter();
|
||||
Task<bool> InvokeAgent(string agentId, List<RoleDialogModel> dialogs);
|
||||
|
|
|
|||
|
|
@ -68,6 +68,19 @@ public class RoutingContext
|
|||
_stack.Pop();
|
||||
}
|
||||
|
||||
public void Replace(string agentId)
|
||||
{
|
||||
if (_stack.Count == 0)
|
||||
{
|
||||
_stack.Push(agentId);
|
||||
}
|
||||
else if (_stack.Peek() != agentId)
|
||||
{
|
||||
_stack.Pop();
|
||||
_stack.Push(agentId);
|
||||
}
|
||||
}
|
||||
|
||||
public void Empty()
|
||||
{
|
||||
_stack.Clear();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
using BotSharp.Abstraction.Routing.Enums;
|
||||
|
||||
namespace BotSharp.Abstraction.Routing.Models;
|
||||
|
||||
public class RoutingRule
|
||||
|
|
@ -8,12 +10,15 @@ public class RoutingRule
|
|||
[JsonIgnore]
|
||||
public string AgentName { get; set; }
|
||||
|
||||
public string Type { get; set; } = RuleType.DataValidation;
|
||||
|
||||
public string Field { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Field type: string, number, object
|
||||
/// </summary>
|
||||
public string Type { get; set; } = "string";
|
||||
public string FieldType { get; set; } = "string";
|
||||
|
||||
public bool Required { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
using BotSharp.Abstraction.Functions;
|
||||
using BotSharp.Abstraction.Repositories.Filters;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
|
||||
namespace BotSharp.Core.Routing.Functions;
|
||||
|
||||
public class FallbackToRouterFn : IFunctionCallback
|
||||
{
|
||||
public string Name => "fallback_to_router";
|
||||
private readonly IServiceProvider _services;
|
||||
public FallbackToRouterFn(IServiceProvider services)
|
||||
{
|
||||
_services = services;
|
||||
}
|
||||
|
||||
public async Task<bool> Execute(RoleDialogModel message)
|
||||
{
|
||||
var args = JsonSerializer.Deserialize<RoutingArgs>(message.FunctionArgs);
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var agents = await agentService.GetAgents(new AgentFilter
|
||||
{
|
||||
AgentName = args.AgentName
|
||||
});
|
||||
var targetAgent = agents.Items.FirstOrDefault();
|
||||
if (targetAgent == null)
|
||||
{
|
||||
message.Content = $"Can't find routing agent {args.AgentName}";
|
||||
return false;
|
||||
}
|
||||
|
||||
var routing = _services.GetRequiredService<RoutingContext>();
|
||||
routing.Replace(targetAgent.Id);
|
||||
|
||||
var router = _services.GetRequiredService<IRoutingService>();
|
||||
message.CurrentAgentId = targetAgent.Id;
|
||||
var response = await router.InstructLoop(message);
|
||||
|
||||
message.Content = response.Content;
|
||||
message.StopCompletion = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -91,7 +91,7 @@ public class RouteToAgentFn : IFunctionCallback
|
|||
var args = JsonSerializer.Deserialize<RoutingArgs>(message.FunctionArgs);
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
|
||||
var routingRules = routing.GetRulesByName(args.AgentName);
|
||||
var routingRules = routing.GetRulesByAgentName(args.AgentName);
|
||||
|
||||
if (routingRules == null || !routingRules.Any())
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
using BotSharp.Abstraction.Functions.Models;
|
||||
using BotSharp.Abstraction.Routing;
|
||||
using BotSharp.Abstraction.Routing.Enums;
|
||||
using BotSharp.Abstraction.Routing.Settings;
|
||||
using System.Diagnostics.Metrics;
|
||||
|
||||
namespace BotSharp.Core.Routing.Hooks;
|
||||
|
||||
|
|
@ -33,11 +35,42 @@ public class RoutingAgentHook : AgentHookBase
|
|||
|
||||
public override bool OnFunctionsLoaded(List<FunctionDef> functions)
|
||||
{
|
||||
/*functions.Add(new FunctionDef
|
||||
if (_agent.Type == AgentType.Task)
|
||||
{
|
||||
Name = "fallback_to_router",
|
||||
Description = "If the user's request is beyond your capabilities, you can call this function for help."
|
||||
});*/
|
||||
// check if enabled the routing rule
|
||||
var routing = _services.GetRequiredService<IRoutingService>();
|
||||
var rule = routing.GetRulesByAgentId(_agent.Id)
|
||||
.FirstOrDefault(x => x.Type == RuleType.Fallback);
|
||||
if (rule != null)
|
||||
{
|
||||
var agentService = _services.GetRequiredService<IAgentService>();
|
||||
var redirectAgent = agentService.GetAgent(rule.RedirectTo).Result;
|
||||
|
||||
var json = JsonSerializer.Serialize(new
|
||||
{
|
||||
user_goal_agent = new
|
||||
{
|
||||
type = "string",
|
||||
description = $"the fixed value is: {_agent.Name}"
|
||||
},
|
||||
next_action_agent = new
|
||||
{
|
||||
type = "string",
|
||||
description = $"the fixed value is: {redirectAgent.Name}"
|
||||
}
|
||||
});
|
||||
functions.Add(new FunctionDef
|
||||
{
|
||||
Name = "fallback_to_router",
|
||||
Description = $"If the user's request is beyond your capabilities, you can call this function to handle by other agent ({redirectAgent.Name}).",
|
||||
Parameters =
|
||||
{
|
||||
Properties = JsonSerializer.Deserialize<JsonDocument>(json)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return base.OnFunctionsLoaded(functions);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using BotSharp.Abstraction.Agents.Models;
|
||||
using BotSharp.Abstraction.Routing.Models;
|
||||
using BotSharp.Abstraction.Templating;
|
||||
|
||||
|
|
@ -70,7 +71,7 @@ public partial class RoutingService
|
|||
else if (!message.StopCompletion)
|
||||
{
|
||||
var routing = _services.GetRequiredService<RoutingContext>();
|
||||
|
||||
|
||||
// Find response template
|
||||
var templateService = _services.GetRequiredService<IResponseTemplateService>();
|
||||
var responseTemplate = await templateService.RenderFunctionResponse(message.CurrentAgentId, message);
|
||||
|
|
@ -83,8 +84,8 @@ public partial class RoutingService
|
|||
else
|
||||
{
|
||||
// Save to memory dialogs
|
||||
dialogs.Add(RoleDialogModel.From(message,
|
||||
role: AgentRole.Function,
|
||||
dialogs.Add(RoleDialogModel.From(message,
|
||||
role: AgentRole.Function,
|
||||
content: message.Content));
|
||||
|
||||
// Send to Next LLM
|
||||
|
|
@ -94,8 +95,8 @@ public partial class RoutingService
|
|||
}
|
||||
else
|
||||
{
|
||||
dialogs.Add(RoleDialogModel.From(message,
|
||||
role: AgentRole.Assistant,
|
||||
dialogs.Add(RoleDialogModel.From(message,
|
||||
role: AgentRole.Assistant,
|
||||
content: message.Content));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -172,13 +172,13 @@ public partial class RoutingService : IRoutingService
|
|||
Profiles = x.Profiles,
|
||||
RequiredFields = x.RoutingRules
|
||||
.Where(p => p.Required)
|
||||
.Select(p => new ParameterPropertyDef(p.Field, p.Description, type: p.Type)
|
||||
.Select(p => new ParameterPropertyDef(p.Field, p.Description, type: p.FieldType)
|
||||
{
|
||||
Required = p.Required
|
||||
}).ToList(),
|
||||
OptionalFields = x.RoutingRules
|
||||
.Where(p => !p.Required)
|
||||
.Select(p => new ParameterPropertyDef(p.Field, p.Description, type: p.Type)
|
||||
.Select(p => new ParameterPropertyDef(p.Field, p.Description, type: p.FieldType)
|
||||
{
|
||||
Required = p.Required
|
||||
}).ToList()
|
||||
|
|
@ -202,7 +202,7 @@ public partial class RoutingService : IRoutingService
|
|||
return routableAgents;
|
||||
}
|
||||
|
||||
public RoutingRule[] GetRulesByName(string name)
|
||||
public RoutingRule[] GetRulesByAgentName(string name)
|
||||
{
|
||||
return GetRoutingRecords()
|
||||
.Where(x => x.AgentName.ToLower() == name.ToLower())
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ You're {{router.name}} ({{router.description}}). Follow these steps to handle us
|
|||
3. Determine which agent is suitable to handle this conversation.
|
||||
4. Re-think on whether the function you chose matches the reason.
|
||||
5. For agent required arguments, leave it as blank object if user doesn't provide it.
|
||||
6. Response must be in JSON format.
|
||||
|
||||
[FUNCTIONS]
|
||||
{% for handler in routing_handlers %}
|
||||
|
|
@ -36,4 +37,4 @@ Optional args:
|
|||
{% endfor %}
|
||||
|
||||
[CONVERSATION]
|
||||
{{ conversation }}
|
||||
{{ conversation }}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Break down the user’s most recent needs and figure out the next steps. Response must be in appropriate JSON format.
|
||||
Break down the user’s most recent needs and figure out the next steps.
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
What is the next step based on the CONVERSATION?
|
||||
Response must be in required JSON format without any other contents.
|
||||
Route to the Agent that last handled the conversation if necessary.
|
||||
If user wants to speak to customer service, use function human_intervention_needed.
|
||||
|
|
@ -1,3 +1,2 @@
|
|||
In order to execute the instructions listed by the user in the order specified by the user.
|
||||
What is the next step based on the CONVERSATION?
|
||||
Response must be in required JSON format.
|
||||
What is the next step based on the CONVERSATION?
|
||||
|
|
@ -27,6 +27,7 @@ public class InputUserTextFn : IFunctionCallback
|
|||
await _driver.InputUserText(agent, args, message.MessageId);
|
||||
|
||||
message.Content = $"Input text \"{args.InputText}\" successfully.";
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue