Support multi-agent with different profile.

This commit is contained in:
Haiping Chen 2024-01-24 17:02:59 -06:00
parent 4083e76518
commit 9d3cd6b6e9
20 changed files with 58 additions and 56 deletions

View file

@ -2,7 +2,7 @@
An agent helps you process user sentences (unstructure data) into structure data that you can use to return an appropriate response.
Agent is a collection that contains prompt words and function Json Schema definitions, few-shot examples and knowledge base data. You can create multiple different Agents to perform specific operations in specific domains. BotSharp has built-in maintenance for Agents, including creating, updating and deleting, importing and exporting.
Agent is a collection that contains prompt words and function Json Schema definitions, few-shot examples and knowledge base data. You can create multiple different Agents to perform specific operations in specific domains. BotSharp has built-in maintenance for Agents, including creating, updating and deleting, importing and exporting. Agents are divided into task agents and routing (non-task) agents. Business domain agents belong to task agents, and routers belong to non-task agents.
## My Agent
After creating the platform account, you can start to enter the steps of creating the Agent.

View file

@ -15,3 +15,7 @@ For simple questions raised by users, the ordinary routing function can already
![routing with reasoning](./assets/routing-reasoner.png)
For more **Routing** related information, please go to [Agent Routing](../agent/router.md).
## Profile
There is an array field called `Profile` in the Agent data model, which is used to store the current profiles. When this attribute is set in the `Router`, only matching Task Agents can be included in the routing candidate Agents list, which means that the Task Agent also To set the same profile name. Profiles allows you to enter multiple profiles, and the system will automatically combine them for processing.

View file

@ -60,6 +60,9 @@ public class Agent
[JsonIgnore]
public bool IsRouter { get; set; }
[JsonIgnore]
public bool IsHost { get; set; }
[JsonIgnore]
public PluginDef Plugin { get; set; }

View file

@ -5,7 +5,7 @@ namespace BotSharp.Abstraction.Routing;
public interface IRoutingService
{
Agent Router { get; }
RoutingItem[] GetRoutingItems();
RoutableAgent[] GetRoutableAgents(List<string> profiles);
RoutingRule[] GetRulesByName(string name);
RoutingRule[] GetRulesByAgentId(string id);
List<RoutingHandlerDef> GetHandlers();
@ -20,5 +20,5 @@ public interface IRoutingService
/// <param name="agent"></param>
/// <param name="message"></param>
/// <returns></returns>
Task<RoleDialogModel> ExecuteDirectly(Agent agent, RoleDialogModel message);
Task<RoleDialogModel> InstructDirect(Agent agent, RoleDialogModel message);
}

View file

@ -2,7 +2,7 @@ using BotSharp.Abstraction.Functions.Models;
namespace BotSharp.Abstraction.Routing.Models;
public class RoutingItem
public class RoutableAgent
{
[JsonPropertyName("agent_id")]
public string AgentId { get; set; } = string.Empty;
@ -13,6 +13,10 @@ public class RoutingItem
[JsonPropertyName("description")]
public string Description { get; set; } = string.Empty;
[JsonPropertyName("profiles")]
public List<string> Profiles { get; set; }
= new List<string>();
[JsonPropertyName("required_fields")]
public List<ParameterPropertyDef> RequiredFields { get; set; } = new List<ParameterPropertyDef>();

View file

@ -21,6 +21,13 @@ public partial class AgentService
agent.Plugin = GetPlugin(agent.Id);
}
// Set IsHost
var agentSetting = _services.GetRequiredService<AgentSettings>();
foreach (var agent in agents)
{
agent.IsHost = agentSetting.HostAgentId == agent.Id;
}
agents = agents.Where(x => x.Installed).ToList();
var pager = filter?.Pager ?? new Pagination();
return new PagedItems<Agent>

View file

@ -61,7 +61,7 @@ public partial class ConversationService
response = settings.AgentIds.Contains(agentId) ?
await routing.InstructLoop(message) :
await routing.ExecuteDirectly(agent, message);
await routing.InstructDirect(agent, message);
routing.ResetRecursiveCounter();
}

View file

@ -93,7 +93,7 @@ public class NaivePlanner : IPlaner
var unmatchedAgentId = context.GetCurrentAgentId();
// Exclude the wrong routed agent
var agents = router.TemplateDict["routing_agents"] as RoutingItem[];
var agents = router.TemplateDict["routing_agents"] as RoutableAgent[];
router.TemplateDict["routing_agents"] = agents.Where(x => x.AgentId != unmatchedAgentId).ToArray();
// Handover to Router;

View file

@ -24,7 +24,8 @@ public class RoutingAgentHook : AgentHookBase
dict["router"] = _agent;
var routing = _services.GetRequiredService<IRoutingService>();
dict["routing_agents"] = routing.GetRoutingItems();
var agents = routing.GetRoutableAgents(_agent.Profiles);
dict["routing_agents"] = agents;
dict["routing_handlers"] = routing.GetHandlers();
return base.OnInstructionLoaded(template, dict);

View file

@ -32,7 +32,7 @@ public partial class RoutingService : IRoutingService
_logger = logger;
}
public async Task<RoleDialogModel> ExecuteDirectly(Agent agent, RoleDialogModel message)
public async Task<RoleDialogModel> InstructDirect(Agent agent, RoleDialogModel message)
{
var handlers = _services.GetServices<IRoutingHandler>();
@ -147,22 +147,13 @@ public partial class RoutingService : IRoutingService
return x.RoutingRules;
}).ToArray();
// Filter agents by profile
var state = _services.GetRequiredService<IConversationStateService>();
var channel = state.GetState("channel");
var specifiedProfile = agents.FirstOrDefault(x => x.Profiles.Contains(channel));
if (specifiedProfile != null)
{
records = records.Where(x => specifiedProfile.Profiles.Contains(channel)).ToArray();
}
return records;
}
#if !DEBUG
[MemoryCache(10 * 60)]
#endif
public RoutingItem[] GetRoutingItems()
public RoutableAgent[] GetRoutableAgents(List<string> profiles)
{
var db = _services.GetRequiredService<IBotSharpRepository>();
@ -171,12 +162,14 @@ public partial class RoutingService : IRoutingService
Disabled = false,
AllowRouting = true
};
var agents = db.GetAgents(filter);
return agents.Select(x => new RoutingItem
var routableAgents = agents.Select(x => new RoutableAgent
{
AgentId = x.Id,
Description = x.Description,
Name = x.Name,
Profiles = x.Profiles,
RequiredFields = x.RoutingRules
.Where(p => p.Required)
.Select(p => new ParameterPropertyDef(p.Field, p.Description, type: p.Type)
@ -190,6 +183,17 @@ public partial class RoutingService : IRoutingService
Required = p.Required
}).ToList()
}).ToArray();
// Handle profile.
// Router profile must match the agent profile
if (routableAgents.Length > 0 && profiles.Count > 0)
{
routableAgents = routableAgents.Where(x => x.Profiles != null &&
x.Profiles.Exists(x1 => profiles.Exists(y => x1 == y)))
.ToArray();
}
return routableAgents;
}
public RoutingRule[] GetRulesByName(string name)

View file

@ -25,7 +25,7 @@ public class TemplateRender : ITemplateRender
_options.MemberAccessStrategy.Register<ParameterPropertyDef>();
_options.MemberAccessStrategy.Register<RoleDialogModel>();
_options.MemberAccessStrategy.Register<Agent>();
_options.MemberAccessStrategy.Register<RoutingItem>();
_options.MemberAccessStrategy.Register<RoutableAgent>();
_options.MemberAccessStrategy.Register<RoutingHandlerDef>();
}

View file

@ -22,12 +22,19 @@ public class AgentViewModel
[JsonPropertyName("is_router")]
public bool IsRouter { get; set; }
[JsonPropertyName("is_host")]
public bool IsHost { get; set; }
[JsonPropertyName("allow_routing")]
public bool AllowRouting { get; set; }
public bool Disabled { get; set; }
[JsonPropertyName("icon_url")]
public string IconUrl { get; set; }
public List<string> Profiles { get; set; }
= new List<string>();
[JsonPropertyName("routing_rules")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
@ -59,10 +66,11 @@ public class AgentViewModel
Samples = agent.Samples,
IsPublic= agent.IsPublic,
IsRouter = agent.IsRouter,
IsHost = agent.IsHost,
Disabled = agent.Disabled,
IconUrl = agent.IconUrl,
AllowRouting = agent.AllowRouting,
Profiles = agent.Profiles,
Profiles = agent.Profiles ?? new List<string>(),
RoutingRules = agent.RoutingRules,
LlmConfig = agent.LlmConfig,
Plugin = agent.Plugin,

View file

@ -210,6 +210,7 @@ public partial class MongoRepository
var builder = Builders<ConversationDocument>.Filter;
var filters = new List<FilterDefinition<ConversationDocument>>() { builder.Empty };
if (!string.IsNullOrEmpty(filter.Id)) filters.Add(builder.Eq(x => x.Id, filter.Id));
if (!string.IsNullOrEmpty(filter.AgentId)) filters.Add(builder.Eq(x => x.AgentId, filter.AgentId));
if (!string.IsNullOrEmpty(filter.Status)) filters.Add(builder.Eq(x => x.Status, filter.Status));
if (!string.IsNullOrEmpty(filter.Channel)) filters.Add(builder.Eq(x => x.Channel, filter.Channel));

View file

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
@ -35,13 +35,9 @@
<None Remove="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\agent.json" />
<None Remove="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\functions.json" />
<None Remove="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\instruction.liquid" />
<None Remove="data\users\10d12798-08fb-4aa6-977b-5dd94d82dbfe\agents.json" />
<None Remove="data\users\10d12798-08fb-4aa6-977b-5dd94d82dbfe\user.json" />
<None Remove="data\users\456e35c5-caf0-4d45-9084-b44a8ca717e4\agents.json" />
<None Remove="data\users\456e35c5-caf0-4d45-9084-b44a8ca717e4\user.json" />
<None Remove="data\users\d0e6680d-03d5-4ed8-bdcd-aa7d86f2a1bc\agents.json" />
<None Remove="data\users\d0e6680d-03d5-4ed8-bdcd-aa7d86f2a1bc\user.json" />
<None Remove="data\users\e465af5f-044f-414b-b670-92834929b96c\agents.json" />
<None Remove="data\users\e465af5f-044f-414b-b670-92834929b96c\user.json" />
</ItemGroup>
@ -91,27 +87,15 @@
<Content Include="data\agents\fe8c60aa-b114-4ef3-93cb-a8efeac80f75\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\users\10d12798-08fb-4aa6-977b-5dd94d82dbfe\agents.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\users\10d12798-08fb-4aa6-977b-5dd94d82dbfe\user.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\users\456e35c5-caf0-4d45-9084-b44a8ca717e4\agents.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\users\456e35c5-caf0-4d45-9084-b44a8ca717e4\user.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\users\d0e6680d-03d5-4ed8-bdcd-aa7d86f2a1bc\agents.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\users\d0e6680d-03d5-4ed8-bdcd-aa7d86f2a1bc\user.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\users\e465af5f-044f-414b-b670-92834929b96c\agents.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\users\e465af5f-044f-414b-b670-92834929b96c\user.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>

View file

@ -6,6 +6,7 @@
"id": "b284db86-e9c2-4c25-a59e-4649797dd130",
"allowRouting": true,
"isPublic": true,
"profiles": [ "pizza" ],
"routingRules": [
{
"field": "order_number",

View file

@ -5,5 +5,6 @@
"updatedDateTime": "2023-07-26T02:29:25.123274Z",
"id": "c2b57a74-ae4e-4c81-b3ad-9ac5bff982bd",
"allowRouting": true,
"isPublic": true
"isPublic": true,
"profiles": [ "pizza" ]
}

View file

@ -1,10 +0,0 @@
[
{
"userId": "456e35c5-caf0-4d45-9084-b44a8ca717e4",
"agentId": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a",
"updatedTime": "2023-08-14T18:14:11.6833783Z",
"createdTime": "2023-08-14T18:14:11.6829767Z",
"editable": true,
"id": "1273379c-4419-460a-b0a2-5695afd097f5"
}
]