Merge pull request #272 from SciSharp/master

merge last update
This commit is contained in:
geffzhang 2024-01-27 16:27:19 +08:00 committed by GitHub
commit 9a394345b9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
82 changed files with 734 additions and 327 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`, `routing (non-task) agents`, `evaluating agents` and `static agents`. Business domain agents belong to task agents, and routers belong to non-task agents, static agents don't have capabilities to interact with external environment.
## My Agent
After creating the platform account, you can start to enter the steps of creating the Agent.

View file

@ -14,4 +14,8 @@ 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).
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

@ -7,7 +7,7 @@ public enum AgentField
Description,
IsPublic,
Disabled,
AllowRouting,
Type,
Profiles,
RoutingRule,
Instruction,

View file

@ -0,0 +1,22 @@
namespace BotSharp.Abstraction.Agents.Enums;
public class AgentType
{
/// <summary>
/// Routing Agent
/// </summary>
public const string Routing = "routing";
public const string Evaluating = "evaluating";
/// <summary>
/// Routable task agent with capability of interaction with external environment
/// </summary>
public const string Task = "task";
/// <summary>
/// Agent that cannot use external tools
/// </summary>
public const string Static = "static";
}

View file

@ -10,7 +10,7 @@ public interface IAgentService
{
Task<Agent> CreateAgent(Agent agent);
Task RefreshAgents();
Task<List<Agent>> GetAgents(AgentFilter filter);
Task<PagedItems<Agent>> GetAgents(AgentFilter filter);
/// <summary>
/// Load agent configurations and trigger hooks
@ -29,7 +29,7 @@ public interface IAgentService
/// <param name="id"></param>
/// <returns>Original agent information</returns>
Task<Agent> GetAgent(string id);
Task<bool> DeleteAgent(string id);
Task UpdateAgent(Agent agent, AgentField updateField);
Task UpdateAgentFromFile(string id);

View file

@ -9,6 +9,10 @@ public class Agent
public string Id { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
/// <summary>
/// Agent Type
/// </summary>
public string Type { get; set; } = AgentType.Task;
public DateTime CreatedDateTime { get; set; }
public DateTime UpdatedDateTime { get; set; }
@ -16,7 +20,8 @@ public class Agent
/// Default LLM settings
/// </summary>
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public AgentLlmConfig? LlmConfig { get; set; }
public AgentLlmConfig LlmConfig { get; set; }
= new AgentLlmConfig();
/// <summary>
/// Instruction
@ -58,7 +63,7 @@ public class Agent
public bool IsPublic { get; set; }
[JsonIgnore]
public bool IsRouter { get; set; }
public bool IsHost { get; set; }
[JsonIgnore]
public PluginDef Plugin { get; set; }
@ -66,11 +71,6 @@ public class Agent
[JsonIgnore]
public bool Installed => Plugin.Enabled;
/// <summary>
/// Allow to be routed
/// </summary>
public bool AllowRouting { get; set; }
/// <summary>
/// Default is True, user will enable this by installing appropriate plugin.
/// </summary>
@ -104,6 +104,7 @@ public class Agent
Id = agent.Id,
Name = agent.Name,
Description = agent.Description,
Type = agent.Type,
Instruction = agent.Instruction,
Functions = agent.Functions,
Responses = agent.Responses,
@ -111,7 +112,6 @@ public class Agent
Knowledges = agent.Knowledges,
IsPublic = agent.IsPublic,
Disabled = agent.Disabled,
AllowRouting = agent.AllowRouting,
Profiles = agent.Profiles,
RoutingRules = agent.RoutingRules,
LlmConfig = agent.LlmConfig,
@ -180,9 +180,9 @@ public class Agent
return this;
}
public Agent SetAllowRouting(bool allowRouting)
public Agent SetAgentType(string type)
{
AllowRouting = allowRouting;
Type = type;
return this;
}

View file

@ -21,4 +21,7 @@ public class AgentLlmConfig
[JsonPropertyName("model")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Model { get; set; }
[JsonPropertyName("max_recursion_depth")]
public int MaxRecursionDepth { get; set; } = 3;
}

View file

@ -0,0 +1,11 @@
namespace BotSharp.Abstraction.Conversations.Models;
public class ConversationStateLogModel
{
[JsonPropertyName("conversation_id")]
public string ConvsersationId { get; set; }
[JsonPropertyName("states")]
public string States { get; set; }
[JsonPropertyName("created_at")]
public DateTime CreateTime { get; set; }
}

View file

@ -53,12 +53,6 @@ public class RoleDialogModel : ITrackableMessage
[JsonIgnore(Condition = JsonIgnoreCondition.Always)]
public bool StopCompletion { get; set; }
/// <summary>
/// Router routed to a wrong agent.
/// Set this flag as True will force router to re-route current request to a new agent.
/// </summary>
public bool UnmatchedAgent { get; set; }
public FunctionCallFromLlm Instruction { get; set; }
private RoleDialogModel()

View file

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

View file

@ -4,6 +4,8 @@ public class StreamingLogModel
{
[JsonPropertyName("conversation_id")]
public string ConversationId { get; set; }
[JsonPropertyName("name")]
public string? Name { get; set; }
[JsonPropertyName("content")]
public string Content { get; set; }

View file

@ -2,12 +2,11 @@ namespace BotSharp.Abstraction.Repositories.Filters;
public class AgentFilter
{
public Pagination Pager { get; set; } = new Pagination();
public string? AgentName { get; set; }
public bool? Disabled { get; set; }
public bool? Installed { get; set; }
public bool? AllowRouting { get; set; }
public string? Type { get; set; }
public bool? IsPublic { get; set; }
public bool? IsRouter { get; set; }
public bool? IsEvaluator { get; set; }
public List<string>? AgentIds { get; set; }
}

View file

@ -0,0 +1,19 @@
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";
/// <summary>
/// The planning approach name for next step
/// </summary>
public const string Planner = "planner";
}

View file

@ -5,10 +5,29 @@ namespace BotSharp.Abstraction.Routing;
public interface IRoutingService
{
Agent Router { get; }
RoutingItem[] GetRoutingItems();
RoutingRule[] GetRulesByName(string name);
/// <summary>
/// Get routable agents
/// </summary>
/// <param name="profiles">router's profile</param>
/// <returns></returns>
RoutableAgent[] GetRoutableAgents(List<string> profiles);
/// <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();
List<RoutingHandlerDef> GetHandlers(Agent router);
void ResetRecursiveCounter();
Task<bool> InvokeAgent(string agentId, List<RoleDialogModel> dialogs);
Task<bool> InvokeFunction(string name, RoleDialogModel message);
@ -20,5 +39,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

@ -1,12 +1,19 @@
using BotSharp.Abstraction.Agents;
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Routing.Settings;
using Microsoft.Extensions.DependencyInjection;
namespace BotSharp.Abstraction.Routing.Models;
public class RoutingContext
{
private readonly IServiceProvider _services;
private readonly RoutingSettings _setting;
public RoutingContext(RoutingSettings setting)
private string[] _routerAgentIds;
public RoutingContext(IServiceProvider services, RoutingSettings setting)
{
_services = services;
_setting = setting;
}
@ -22,7 +29,22 @@ public class RoutingContext
/// Agent that can handle user original goal.
/// </summary>
public string OriginAgentId
=> _stack.Where(x => !_setting.AgentIds.Contains(x)).Last();
{
get
{
if (_routerAgentIds == null)
{
var agentService = _services.GetRequiredService<IAgentService>();
_routerAgentIds = agentService.GetAgents(new AgentFilter
{
Type = AgentType.Routing
}).Result.Items
.Select(x => x.Id).ToArray();
}
return _stack.Where(x => !_routerAgentIds.Contains(x)).Last();
}
}
public bool IsEmpty => !_stack.Any();
public string GetCurrentAgentId()
@ -46,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();

View file

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

View file

@ -2,10 +2,4 @@ namespace BotSharp.Abstraction.Routing.Settings;
public class RoutingSettings
{
/// <summary>
/// Router Agent Id
/// </summary>
public string[] AgentIds { get; set; } = new string[0];
public string Planner { get; set; } = string.Empty;
}

View file

@ -33,7 +33,7 @@ public partial class AgentService
.SetDescription(foundAgent.Description)
.SetIsPublic(foundAgent.IsPublic)
.SetDisabled(foundAgent.Disabled)
.SetAllowRouting(foundAgent.AllowRouting)
.SetAgentType(foundAgent.Type)
.SetProfiles(foundAgent.Profiles)
.SetRoutingRules(foundAgent.RoutingRules)
.SetInstruction(foundAgent.Instruction)

View file

@ -9,7 +9,7 @@ public partial class AgentService
#if !DEBUG
[MemoryCache(10 * 60)]
#endif
public async Task<List<Agent>> GetAgents(AgentFilter filter)
public async Task<PagedItems<Agent>> GetAgents(AgentFilter filter)
{
var agents = _db.GetAgents(filter);
@ -17,13 +17,23 @@ public partial class AgentService
var routeSetting = _services.GetRequiredService<RoutingSettings>();
foreach (var agent in agents)
{
agent.IsRouter = routeSetting.AgentIds.Contains(agent.Id);
agent.Plugin = GetPlugin(agent.Id);
}
agents = agents.Where(x => x.Installed).ToList();
// Set IsHost
var agentSetting = _services.GetRequiredService<AgentSettings>();
foreach (var agent in agents)
{
agent.IsHost = agentSetting.HostAgentId == agent.Id;
}
return agents;
agents = agents.Where(x => x.Installed).ToList();
var pager = filter?.Pager ?? new Pagination();
return new PagedItems<Agent>
{
Items = agents.Skip(pager.Offset).Take(pager.Size),
Count = agents.Count()
};
}
#if !DEBUG
@ -47,9 +57,6 @@ public partial class AgentService
profile.LlmConfig.IsInherit = true;
}
// Set IsRouter
var routeSetting = _services.GetRequiredService<RoutingSettings>();
profile.IsRouter = routeSetting.AgentIds.Contains(profile.Id);
profile.Plugin = GetPlugin(profile.Id);
return profile;

View file

@ -19,7 +19,7 @@ public partial class AgentService
record.Description = agent.Description ?? string.Empty;
record.IsPublic = agent.IsPublic;
record.Disabled = agent.Disabled;
record.AllowRouting = agent.AllowRouting;
record.Type = agent.Type;
record.Profiles = agent.Profiles ?? new List<string>();
record.RoutingRules = agent.RoutingRules ?? new List<RoutingRule>();
record.Instruction = agent.Instruction ?? string.Empty;
@ -60,7 +60,7 @@ public partial class AgentService
.SetDescription(foundAgent.Description)
.SetIsPublic(foundAgent.IsPublic)
.SetDisabled(foundAgent.Disabled)
.SetAllowRouting(foundAgent.AllowRouting)
.SetAgentType(foundAgent.Type)
.SetProfiles(foundAgent.Profiles)
.SetRoutingRules(foundAgent.RoutingRules)
.SetInstruction(foundAgent.Instruction)

View file

@ -50,8 +50,9 @@
<None Remove="data\agents\01e2fc5c-2c89-4ec7-8470-7688608b496c\instruction.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\agent.json" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\instruction.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\next_step_prompt.hf_planner.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\next_step_prompt.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.hf.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.naive.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.sequential.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\response_with_function.liquid" />
<None Remove="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\welcome.liquid" />
<None Remove="data\plugins\config.json" />
@ -70,10 +71,13 @@
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\instruction.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\next_step_prompt.hf_planner.liquid">
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.sequential.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\next_step_prompt.liquid">
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.hf.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\planner_prompt.naive.liquid">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Include="data\agents\01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a\templates\response_with_function.liquid">

View file

@ -59,9 +59,9 @@ public partial class ConversationService
var routing = _services.GetRequiredService<IRoutingService>();
var settings = _services.GetRequiredService<RoutingSettings>();
response = settings.AgentIds.Contains(agentId) ?
response = agent.Type == AgentType.Routing ?
await routing.InstructLoop(message) :
await routing.ExecuteDirectly(agent, message);
await routing.InstructDirect(agent, message);
routing.ResetRecursiveCounter();
}

View file

@ -4,7 +4,6 @@ using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Repositories;
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Routing.Settings;
using BotSharp.Abstraction.Templating;
namespace BotSharp.Core.Planning;
@ -43,6 +42,7 @@ public class HFPlanner : IPlaner
{
new RoleDialogModel(AgentRole.User, next)
{
FunctionName = nameof(NaivePlanner),
MessageId = messageId
}
};
@ -91,7 +91,7 @@ public class HFPlanner : IPlaner
private string GetNextStepPrompt(Agent router)
{
var template = router.Templates.First(x => x.Name == "next_step_prompt.hf_planner").Content;
var template = router.Templates.First(x => x.Name == "planner_prompt.hf").Content;
var render = _services.GetRequiredService<ITemplateRender>();
var prompt = render.Render(template, router.TemplateDict);
return prompt.Trim();

View file

@ -3,7 +3,6 @@ using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Routing.Settings;
using BotSharp.Abstraction.Templating;
namespace BotSharp.Core.Planning;
@ -49,6 +48,7 @@ public class NaivePlanner : IPlaner
{
new RoleDialogModel(AgentRole.User, next)
{
FunctionName = nameof(NaivePlanner),
MessageId = messageId
}
};
@ -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;
@ -108,7 +108,7 @@ public class NaivePlanner : IPlaner
private string GetNextStepPrompt(Agent router)
{
var template = router.Templates.First(x => x.Name == "next_step_prompt").Content;
var template = router.Templates.First(x => x.Name == "planner_prompt.naive").Content;
var render = _services.GetRequiredService<ITemplateRender>();
return render.Render(template, new Dictionary<string, object>
@ -125,8 +125,8 @@ public class NaivePlanner : IPlaner
var agentService = _services.GetRequiredService<IAgentService>();
var agents = agentService.GetAgents(new AgentFilter
{
AllowRouting = true
}).Result;
Type = AgentType.Task
}).Result.Items.ToList();
var malformed = false;
// Sometimes it populate malformed Function in Agent name

View file

@ -0,0 +1,113 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Routing;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Templating;
namespace BotSharp.Core.Planning;
public class SequentialPlanner : IPlaner
{
private readonly IServiceProvider _services;
private readonly ILogger _logger;
public SequentialPlanner(IServiceProvider services, ILogger<NaivePlanner> logger)
{
_services = services;
_logger = logger;
}
public async Task<FunctionCallFromLlm> GetNextInstruction(Agent router, string messageId)
{
var next = GetNextStepPrompt(router);
var inst = new FunctionCallFromLlm();
// text completion
/*var agentService = _services.GetRequiredService<IAgentService>();
var instruction = agentService.RenderedInstruction(router);
var content = $"{instruction}\r\n###\r\n{next}";
content = content + "\r\nResponse: ";
var completion = CompletionProvider.GetTextCompletion(_services);*/
// chat completion
var completion = CompletionProvider.GetChatCompletion(_services,
provider: router?.LlmConfig?.Provider,
model: router?.LlmConfig?.Model);
int retryCount = 0;
while (retryCount < 3)
{
string text = string.Empty;
try
{
// text completion
// text = await completion.GetCompletion(content, router.Id, messageId);
var dialogs = new List<RoleDialogModel>
{
new RoleDialogModel(AgentRole.User, next)
{
FunctionName = nameof(NaivePlanner),
MessageId = messageId
}
};
var response = await completion.GetChatCompletions(router, dialogs);
inst = response.Content.JsonContent<FunctionCallFromLlm>();
break;
}
catch (Exception ex)
{
_logger.LogError($"{ex.Message}: {text}");
inst.Function = "response_to_user";
inst.Response = ex.Message;
inst.AgentName = "Router";
}
finally
{
retryCount++;
}
}
return inst;
}
public async Task<bool> AgentExecuting(Agent router, FunctionCallFromLlm inst, RoleDialogModel message)
{
// Set user content as Planner's question
message.FunctionName = inst.Function;
message.FunctionArgs = inst.Arguments == null ? "{}" : JsonSerializer.Serialize(inst.Arguments);
return true;
}
public async Task<bool> AgentExecuted(Agent router, FunctionCallFromLlm inst, RoleDialogModel message)
{
var context = _services.GetRequiredService<RoutingContext>();
if (message.StopCompletion)
{
context.Empty();
return false;
}
// Handover to Router;
context.Pop();
var routing = _services.GetRequiredService<IRoutingService>();
routing.ResetRecursiveCounter();
return true;
}
private string GetNextStepPrompt(Agent router)
{
var template = router.Templates.First(x => x.Name == "planner_prompt.sequential").Content;
var render = _services.GetRequiredService<ITemplateRender>();
return render.Render(template, new Dictionary<string, object>
{
});
}
}

View file

@ -28,8 +28,8 @@ namespace BotSharp.Core.Repository
case AgentField.Disabled:
UpdateAgentDisabled(agent.Id, agent.Disabled);
break;
case AgentField.AllowRouting:
UpdateAgentAllowRouting(agent.Id, agent.AllowRouting);
case AgentField.Type:
UpdateAgentType(agent.Id, agent.Type);
break;
case AgentField.Profiles:
UpdateAgentProfiles(agent.Id, agent.Profiles);
@ -112,12 +112,12 @@ namespace BotSharp.Core.Repository
File.WriteAllText(agentFile, json);
}
private void UpdateAgentAllowRouting(string agentId, bool allowRouting)
private void UpdateAgentType(string agentId, string type)
{
var (agent, agentFile) = GetAgentFromFile(agentId);
if (agent == null) return;
agent.AllowRouting = allowRouting;
agent.Type = type;
agent.UpdatedDateTime = DateTime.UtcNow;
var json = JsonSerializer.Serialize(agent, _options);
File.WriteAllText(agentFile, json);
@ -260,7 +260,7 @@ namespace BotSharp.Core.Repository
agent.Description = inputAgent.Description;
agent.IsPublic = inputAgent.IsPublic;
agent.Disabled = inputAgent.Disabled;
agent.AllowRouting = inputAgent.AllowRouting;
agent.Type = inputAgent.Type;
agent.Profiles = inputAgent.Profiles;
agent.RoutingRules = inputAgent.RoutingRules;
agent.LlmConfig = inputAgent.LlmConfig;
@ -336,9 +336,9 @@ namespace BotSharp.Core.Repository
query = query.Where(x => x.Disabled == filter.Disabled);
}
if (filter.AllowRouting.HasValue)
if (filter.Type != null)
{
query = query.Where(x => x.AllowRouting == filter.AllowRouting);
query = query.Where(x => x.Type == filter.Type);
}
if (filter.IsPublic.HasValue)
@ -346,22 +346,6 @@ namespace BotSharp.Core.Repository
query = query.Where(x => x.IsPublic == filter.IsPublic);
}
if (filter.IsRouter.HasValue)
{
var route = _services.GetRequiredService<RoutingSettings>();
query = filter.IsRouter.Value ?
query.Where(x => route.AgentIds.Contains(x.Id)) :
query.Where(x => !route.AgentIds.Contains(x.Id));
}
if (filter.IsEvaluator.HasValue)
{
var evaluate = _services.GetRequiredService<EvaluatorSetting>();
query = filter.IsEvaluator.Value ?
query.Where(x => x.Id == evaluate.AgentId) :
query.Where(x => x.Id != evaluate.AgentId);
}
if (filter.AgentIds != null)
{
query = query.Where(x => filter.AgentIds.Contains(x.Id));

View file

@ -204,10 +204,10 @@ namespace BotSharp.Core.Repository
{
var records = new List<Conversation>();
var dir = Path.Combine(_dbSettings.FileRepository, _conversationSettings.DataDir);
var pager = filter?.Pager ?? new Pagination();
var totalDirs = Directory.GetDirectories(dir);
var dirs = totalDirs.Skip(filter.Pager.Offset).Take(filter.Pager.Size).ToList();
foreach (var d in dirs)
foreach (var d in totalDirs)
{
var path = Path.Combine(d, CONVERSATION_FILE);
if (!File.Exists(path)) continue;
@ -217,20 +217,20 @@ namespace BotSharp.Core.Repository
if (record == null) continue;
var matched = true;
if (filter.Id != null) matched = matched && record.Id == filter.Id;
if (filter.AgentId != null) matched = matched && record.AgentId == filter.AgentId;
if (filter.Status != null) matched = matched && record.Status == filter.Status;
if (filter.Channel != null) matched = matched && record.Channel == filter.Channel;
if (filter.UserId != null) matched = matched && record.UserId == filter.UserId;
if (filter?.Id != null) matched = matched && record.Id == filter.Id;
if (filter?.AgentId != null) matched = matched && record.AgentId == filter.AgentId;
if (filter?.Status != null) matched = matched && record.Status == filter.Status;
if (filter?.Channel != null) matched = matched && record.Channel == filter.Channel;
if (filter?.UserId != null) matched = matched && record.UserId == filter.UserId;
if (!matched) continue;
records.Add(record);
}
return new PagedItems<Conversation>
{
Items = records.OrderByDescending(x => x.CreatedTime),
Count = totalDirs.Count(),
Items = records.OrderByDescending(x => x.CreatedTime).Skip(pager.Offset).Take(pager.Size),
Count = records.Count(),
};
}

View file

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

View file

@ -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())
{

View file

@ -63,7 +63,6 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
var response = _dialogs.Last();
inst.Response = response.Content;
inst.UnmatchedAgent = response.UnmatchedAgent;
return true;
}

View file

@ -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;
@ -17,26 +19,69 @@ public class RoutingAgentHook : AgentHookBase
public override bool OnInstructionLoaded(string template, Dictionary<string, object> dict)
{
if (!_routingSetting.AgentIds.Contains(_agent.Id))
if (_agent.Type != AgentType.Routing)
{
return base.OnInstructionLoaded(template, dict);
}
dict["router"] = _agent;
var routing = _services.GetRequiredService<IRoutingService>();
dict["routing_agents"] = routing.GetRoutingItems();
dict["routing_handlers"] = routing.GetHandlers();
var agents = routing.GetRoutableAgents(_agent.Profiles);
dict["routing_agents"] = agents;
dict["routing_handlers"] = routing.GetHandlers(_agent);
return base.OnInstructionLoaded(template, dict);
}
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 = $"{_agent.Name}"
},
next_action_agent = new
{
type = "string",
description = $"{redirectAgent.Name}"
},
reason = new
{
type = "string",
description = $"the reason why you need to fallback to [{redirectAgent.Name}] from [{_agent.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),
Required = new List<string>
{
"user_goal_agent",
"next_action_agent",
"reason"
}
}
});
}
}
return base.OnFunctionsLoaded(functions);
}
}

View file

@ -37,14 +37,6 @@ public class RoutingPlugin : IBotSharpPlugin
services.AddScoped<NaivePlanner>();
services.AddScoped<HFPlanner>();
services.AddScoped<IPlaner>(provider =>
{
var settingService = provider.GetRequiredService<ISettingService>();
var routingSettings = settingService.Bind<RoutingSettings>("Router");
if (routingSettings.Planner == nameof(HFPlanner))
return provider.GetRequiredService<HFPlanner>();
else
return provider.GetRequiredService<NaivePlanner>();
});
services.AddScoped<SequentialPlanner>();
}
}

View file

@ -0,0 +1,21 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Planning;
using BotSharp.Abstraction.Routing.Enums;
using BotSharp.Core.Planning;
namespace BotSharp.Core.Routing;
public partial class RoutingService
{
public IPlaner GetPlanner(Agent router)
{
var planner = router.RoutingRules.FirstOrDefault(x => x.Type == RuleType.Planner);
if (planner?.Field == nameof(HFPlanner))
return _services.GetRequiredService<HFPlanner>();
else if (planner?.Field == nameof(SequentialPlanner))
return _services.GetRequiredService<SequentialPlanner>();
else
return _services.GetRequiredService<NaivePlanner>();
}
}

View file

@ -1,5 +1,4 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.MLTasks.Settings;
using BotSharp.Abstraction.Routing.Models;
using BotSharp.Abstraction.Templating;
@ -7,20 +6,19 @@ namespace BotSharp.Core.Routing;
public partial class RoutingService
{
const int MAXIMUM_RECURSION_DEPTH = 3;
private int _currentRecursionDepth = 0;
public async Task<bool> InvokeAgent(string agentId, List<RoleDialogModel> dialogs)
{
_currentRecursionDepth++;
if (_currentRecursionDepth > MAXIMUM_RECURSION_DEPTH)
{
_logger.LogWarning($"Current recursive call depth greater than {MAXIMUM_RECURSION_DEPTH}, which will cause unexpected result.");
return false;
}
var agentService = _services.GetRequiredService<IAgentService>();
var agent = await agentService.LoadAgent(agentId);
_currentRecursionDepth++;
if (_currentRecursionDepth > agent.LlmConfig.MaxRecursionDepth)
{
_logger.LogWarning($"Current recursive call depth greater than {agent.LlmConfig.MaxRecursionDepth}, which will cause unexpected result.");
return false;
}
var chatCompletion = CompletionProvider.GetChatCompletion(_services,
agentConfig: agent.LlmConfig);
@ -59,21 +57,11 @@ public partial class RoutingService
// Call functions
await conversationService.CallFunctions(message);
// Router selected the wrong agent, handle this excluding the agent
if (message.UnmatchedAgent)
{
// Save to memory dialogs
var msg = RoleDialogModel.From(message,
role: AgentRole.Function,
content: message.Content);
msg.UnmatchedAgent = true;
dialogs.Add(msg);
}
// Pass execution result to LLM to get response
else if (!message.StopCompletion)
if (!message.StopCompletion)
{
var routing = _services.GetRequiredService<RoutingContext>();
// Find response template
var templateService = _services.GetRequiredService<IResponseTemplateService>();
var responseTemplate = await templateService.RenderFunctionResponse(message.CurrentAgentId, message);
@ -86,8 +74,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
@ -97,8 +85,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));
}

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>();
@ -73,9 +73,10 @@ public partial class RoutingService : IRoutingService
var dialogs = conv.GetDialogHistory();
var context = _services.GetRequiredService<RoutingContext>();
var planner = _services.GetRequiredService<IPlaner>();
var executor = _services.GetRequiredService<IExecutor>();
var planner = GetPlanner(_router);
context.Push(_router.Id);
int loopCount = 0;
@ -85,7 +86,6 @@ public partial class RoutingService : IRoutingService
var conversation = await GetConversationContent(dialogs);
_router.TemplateDict["conversation"] = conversation;
_router.TemplateDict["planner"] = _settings.Planner;
// Get instruction from Planner
var inst = await planner.GetNextInstruction(_router, message.MessageId);
@ -109,9 +109,9 @@ public partial class RoutingService : IRoutingService
return response;
}
public List<RoutingHandlerDef> GetHandlers()
public List<RoutingHandlerDef> GetHandlers(Agent router)
{
var planer = _services.GetRequiredService<IPlaner>();
var planer = GetPlanner(router);
return _services.GetServices<IRoutingHandler>()
.Where(x => x.Planers == null || x.Planers.Contains(planer.GetType().Name))
@ -134,7 +134,7 @@ public partial class RoutingService : IRoutingService
var filter = new AgentFilter
{
Disabled = false,
AllowRouting = true
Type = AgentType.Task
};
var agents = db.GetAgents(filter);
var records = agents.SelectMany(x =>
@ -147,52 +147,62 @@ 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>();
var filter = new AgentFilter
{
Disabled = false,
AllowRouting = true
Type = AgentType.Task
};
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)
.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()
}).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();
}
else if (profiles == null || profiles.Count == 0)
{
routableAgents = routableAgents.Where(x => x.Profiles == null ||
x.Profiles.Count == 0)
.ToArray();
}
return routableAgents;
}
public RoutingRule[] GetRulesByName(string name)
public RoutingRule[] GetRulesByAgentName(string name)
{
return GetRoutingRecords()
.Where(x => x.AgentName.ToLower() == name.ToLower())

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

@ -1,10 +1,12 @@
{
"id": "01e2fc5c-2c89-4ec7-8470-7688608b496c",
"name": "Chatbot",
"description": "AI chatbot that can do variaty of tasks",
"type": "task",
"createdDateTime": "2024-01-15T10:39:32Z",
"updatedDateTime": "2024-01-15T14:39:32Z",
"id": "01e2fc5c-2c89-4ec7-8470-7688608b496c",
"iconUrl": "/images/users/bot.png",
"disabled": false,
"isPublic": true
"isPublic": true,
"profiles": [ "standalone" ]
}

View file

@ -1,10 +1,18 @@
{
"id": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a",
"name": "AI Assistant",
"description": "AI assistant that can complete many different tasks",
"type": "routing",
"createdDateTime": "2023-08-18T10:39:32.2349685Z",
"updatedDateTime": "2023-08-18T14:39:32.2349686Z",
"id": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a",
"iconUrl": "https://cdn.iconscout.com/icon/premium/png-256-thumb/route-1613278-1368497.png",
"disabled": false,
"isPublic": true
"isPublic": true,
"profiles": [ "default" ],
"routingRules": [
{
"type": "planner",
"field": "HFPlanner"
}
]
}

View file

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

View file

@ -1 +1 @@
Break down the users most recent needs and figure out the next steps. Response must be in appropriate JSON format.
Break down the users most recent needs and figure out the next steps.

View file

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

View file

@ -0,0 +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?

View file

@ -1,3 +1,5 @@
using BotSharp.Abstraction.Agents.Models;
namespace BotSharp.OpenAPI.Controllers;
[Authorize]
@ -28,10 +30,20 @@ public class AgentController : ControllerBase
}
[HttpGet("/agents")]
public async Task<List<AgentViewModel>> GetAgents([FromQuery] AgentFilter filter)
public async Task<PagedItems<AgentViewModel>> GetAgents([FromQuery] AgentFilter filter)
{
var agents = await _agentService.GetAgents(filter);
return agents.Select(x => AgentViewModel.FromAgent(x)).ToList();
var pagedAgents = await _agentService.GetAgents(filter);
var items = new List<Agent>();
foreach (var agent in pagedAgents.Items)
{
var renderedAgent = await _agentService.LoadAgent(agent.Id);
items.Add(renderedAgent);
}
return new PagedItems<AgentViewModel>
{
Items = items.Select(x => AgentViewModel.FromAgent(x)).ToList(),
Count = pagedAgents.Count
};
}
[HttpPost("/agent")]

View file

@ -30,8 +30,8 @@ public class ConversationController : ControllerBase
return ConversationViewModel.FromSession(conv);
}
[HttpPost("/conversations")]
public async Task<PagedItems<ConversationViewModel>> GetConversations([FromBody] ConversationFilter filter)
[HttpGet("/conversations")]
public async Task<PagedItems<ConversationViewModel>> GetConversations([FromQuery] ConversationFilter filter)
{
var service = _services.GetRequiredService<IConversationService>();
var conversations = await service.GetConversations(filter);

View file

@ -16,8 +16,8 @@ public class PluginController : ControllerBase
_settings = settings;
}
[HttpPost("/plugins")]
public PagedItems<PluginDef> GetPlugins([FromBody] PluginFilter filter)
[HttpGet("/plugins")]
public PagedItems<PluginDef> GetPlugins([FromQuery] PluginFilter filter)
{
var loader = _services.GetRequiredService<PluginLoader>();
return loader.GetPagedPlugins(_services, filter);

View file

@ -8,6 +8,7 @@ public class AgentCreationModel
{
public string Name { get; set; }
public string Description { get; set; }
public string Type { get; set; } = AgentType.Task;
/// <summary>
/// LLM default system instructions
@ -57,7 +58,7 @@ public class AgentCreationModel
Responses = Responses,
Samples = Samples,
IsPublic = IsPublic,
AllowRouting = AllowRouting,
Type = Type,
Disabled = Disabled,
Profiles = Profiles,
RoutingRules = RoutingRules?

View file

@ -9,7 +9,7 @@ public class AgentUpdateModel
{
public string Name { get; set; } = string.Empty;
public string Description { get; set; } = string.Empty;
public string Type { get; set; } = AgentType.Task;
/// <summary>
/// Instruction
/// </summary>
@ -62,7 +62,7 @@ public class AgentUpdateModel
Description = Description ?? string.Empty,
IsPublic = IsPublic,
Disabled = Disabled,
AllowRouting = AllowRouting,
Type = Type,
Profiles = Profiles ?? new List<string>(),
RoutingRules = RoutingRules?
.Select(x => RoutingRuleUpdateModel.ToDomainElement(x))?

View file

@ -11,23 +11,26 @@ public class AgentViewModel
public string Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Type { get; set; } = AgentType.Task;
public string Instruction { get; set; }
public List<AgentTemplate> Templates { get; set; }
public List<FunctionDef> Functions { get; set; }
public List<AgentResponse> Responses { get; set; }
public List<string> Samples { get; set; }
[JsonPropertyName("is_public")]
public bool IsPublic { get; set; }
[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)]
@ -52,17 +55,17 @@ public class AgentViewModel
Id = agent.Id,
Name = agent.Name,
Description = agent.Description,
Type = agent.Type,
Instruction = agent.Instruction,
Templates = agent.Templates,
Functions = agent.Functions,
Responses = agent.Responses,
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

@ -16,6 +16,12 @@ public class UserViewModel
public string Role { get; set; } = UserRole.Client;
[JsonPropertyName("full_name")]
public string FullName => $"{FirstName} {LastName}";
[JsonPropertyName("external_id")]
public string? ExternalId { get; set; }
[JsonPropertyName("create_date")]
public DateTime CreateDate { get; set; }
[JsonPropertyName("update_date")]
public DateTime UpdateDate { get; set; }
public static UserViewModel FromUser(User user)
{
@ -36,7 +42,10 @@ public class UserViewModel
FirstName = user.FirstName,
LastName = user.LastName,
Email = user.Email,
Role = user.Role
Role = user.Role,
ExternalId = user.ExternalId,
CreateDate = user.CreatedTime,
UpdateDate = user.UpdatedTime
};
}
}

View file

@ -235,7 +235,11 @@ public class ChatCompletionProvider : IChatCompletion
}
else if (message.Role == ChatRole.User)
{
chatCompletionsOptions.Messages.Add(new ChatRequestUserMessage(message.Content));
chatCompletionsOptions.Messages.Add(new ChatRequestUserMessage(message.Content)
{
// To display Planner name in log
Name = message.FunctionName
});
}
else if (message.Role == ChatRole.Assistant)
{
@ -268,6 +272,11 @@ public class ChatCompletionProvider : IChatCompletion
.Where(x => x.Role == AgentRole.System)
.Select(x => x as ChatRequestSystemMessage).Select(x =>
{
if (!string.IsNullOrEmpty(x.Name))
{
// To display Agent name in log
return $"[{x.Name}]: {x.Content}";
}
return $"{x.Role}: {x.Content}";
}));
prompt += $"{verbose}\r\n";

View file

@ -19,5 +19,6 @@ public class ChatHubPlugin : IBotSharpPlugin
// Register hooks
services.AddScoped<IConversationHook, ChatHubConversationHook>();
services.AddScoped<IContentGeneratingHook, StreamingLogHook>();
services.AddScoped<IConversationHook, StreamingLogHook>();
}
}

View file

@ -100,6 +100,7 @@ public class ChatHubConversationHook : ConversationHookBase
public override async Task OnResponseGenerated(RoleDialogModel message)
{
var conv = _services.GetRequiredService<IConversationService>();
var state = _services.GetRequiredService<IConversationStateService>();
var json = JsonSerializer.Serialize(new ChatResponseModel()
{
@ -115,7 +116,20 @@ public class ChatHubConversationHook : ConversationHookBase
}
}, _serializerOptions);
await _chatHub.Clients.User(_user.Id).SendAsync("OnMessageReceivedFromAssistant", json);
await _chatHub.Clients.User(_user.Id).SendAsync("OnConversateStatesGenerated", BuildConversationStates(conv.ConversationId, state.GetStates()));
await base.OnResponseGenerated(message);
}
private string BuildConversationStates(string conversationId, Dictionary<string, string> states)
{
var model = new ConversationStateLogModel
{
ConvsersationId = conversationId,
States = JsonSerializer.Serialize(states, _serializerOptions),
CreateTime = DateTime.UtcNow
};
return JsonSerializer.Serialize(model, _serializerOptions);
}
}

View file

@ -5,21 +5,27 @@ using Microsoft.AspNetCore.SignalR;
namespace BotSharp.Plugin.ChatHub.Hooks;
public class StreamingLogHook : IContentGeneratingHook
public class StreamingLogHook : ConversationHookBase, IContentGeneratingHook
{
private readonly ConversationSetting _convSettings;
private readonly JsonSerializerOptions _serializerOptions;
private readonly IServiceProvider _services;
private readonly IHubContext<SignalRHub> _chatHub;
private readonly JsonSerializerOptions _serializerOptions;
private readonly IConversationStateService _state;
private readonly IUserIdentity _user;
public StreamingLogHook(
ConversationSetting convSettings,
IServiceProvider serivces,
IHubContext<SignalRHub> chatHub)
IHubContext<SignalRHub> chatHub,
IConversationStateService state,
IUserIdentity user)
{
_convSettings = convSettings;
_services = serivces;
_chatHub = chatHub;
_state = state;
_user = user;
_serializerOptions = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true,
@ -27,17 +33,27 @@ public class StreamingLogHook : IContentGeneratingHook
AllowTrailingCommas = true
};
}
public override async Task OnMessageReceived(RoleDialogModel message)
{
var conversationId = _state.GetConversationId();
var log = $"MessageId: {message.MessageId} ==>\r\n{message.Role}: {message.Content}";
await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, _user.UserName, log));
}
public async Task BeforeGenerating(Agent agent, List<RoleDialogModel> conversations)
{
if (!_convSettings.ShowVerboseLog) return;
var user = _services.GetRequiredService<IUserIdentity>();
var states = _services.GetRequiredService<IConversationStateService>();
var conversationId = states.GetConversationId();
/*var _state = _services.GetRequiredService<IConversationStateService>();
var conversationId = _state.GetConversationId();
var dialog = conversations.Last();
var log = $"{dialog.Role}: {dialog.Content} [msg_id: {dialog.MessageId}] ==>";
await _chatHub.Clients.User(user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, log));
await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, log));*/
}
public override async Task OnFunctionExecuted(RoleDialogModel message)
{
}
public async Task AfterGenerated(RoleDialogModel message, TokenStatsModel tokenStats)
@ -45,24 +61,24 @@ public class StreamingLogHook : IContentGeneratingHook
if (!_convSettings.ShowVerboseLog) return;
var agentService = _services.GetRequiredService<IAgentService>();
var states = _services.GetRequiredService<IConversationStateService>();
var conversationId = states.GetConversationId();
var conversationId = _state.GetConversationId();
var agent = await agentService.LoadAgent(message.CurrentAgentId);
await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, agent?.Name, tokenStats.Prompt));
var log = message.Role == AgentRole.Function ?
$"[{agent?.Name}]: {message.FunctionName}({message.FunctionArgs})" :
$"[{agent?.Name}]: {message.Content}" + $" <== [msg_id: {message.MessageId}]";
var user = _services.GetRequiredService<IUserIdentity>();
await _chatHub.Clients.User(user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, tokenStats.Prompt));
await _chatHub.Clients.User(user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, log));
$"[{agent?.Name}]: {message.Content}";
log += $"\r\n<== MessageId: {message.MessageId}";
await _chatHub.Clients.User(_user.Id).SendAsync("OnContentLogGenerated", BuildLog(conversationId, agent?.Name, log));
}
private string BuildLog(string conversationId, string content)
private string BuildLog(string conversationId, string? name, string content)
{
var log = new StreamingLogModel
{
ConversationId = conversationId,
Name = name,
Content = content,
CreateTime = DateTime.UtcNow
};

View file

@ -27,7 +27,6 @@ public class SearchKnowledgesFn : IFunctionCallback
if (string.IsNullOrEmpty(knowledge))
{
message.Content = "Can't find any relevant data in local knowledge base.";
message.UnmatchedAgent = true;
}
return true;

View file

@ -6,6 +6,7 @@ public class AgentDocument : MongoBase
{
public string Name { get; set; }
public string Description { get; set; }
public string Type { get; set; }
public string? IconUrl { get; set; }
public string Instruction { get; set; }
public List<AgentTemplateMongoElement> Templates { get; set; }
@ -13,7 +14,6 @@ public class AgentDocument : MongoBase
public List<AgentResponseMongoElement> Responses { get; set; }
public List<string> Samples { get; set; }
public bool IsPublic { get; set; }
public bool AllowRouting { get; set; }
public bool Disabled { get; set; }
public List<string> Profiles { get; set; }
public List<RoutingRuleMongoElement> RoutingRules { get; set; }

View file

@ -29,8 +29,8 @@ public partial class MongoRepository
case AgentField.Disabled:
UpdateAgentDisabled(agent.Id, agent.Disabled);
break;
case AgentField.AllowRouting:
UpdateAgentAllowRouting(agent.Id, agent.AllowRouting);
case AgentField.Type:
UpdateAgentType(agent.Id, agent.Type);
break;
case AgentField.Profiles:
UpdateAgentProfiles(agent.Id, agent.Profiles);
@ -109,11 +109,11 @@ public partial class MongoRepository
_dc.Agents.UpdateOne(filter, update);
}
private void UpdateAgentAllowRouting(string agentId, bool allowRouting)
private void UpdateAgentType(string agentId, string type)
{
var filter = Builders<AgentDocument>.Filter.Eq(x => x.Id, agentId);
var update = Builders<AgentDocument>.Update
.Set(x => x.AllowRouting, allowRouting)
.Set(x => x.Type, type)
.Set(x => x.UpdatedTime, DateTime.UtcNow);
_dc.Agents.UpdateOne(filter, update);
@ -225,7 +225,7 @@ public partial class MongoRepository
.Set(x => x.Name, agent.Name)
.Set(x => x.Description, agent.Description)
.Set(x => x.Disabled, agent.Disabled)
.Set(x => x.AllowRouting, agent.AllowRouting)
.Set(x => x.Type, agent.Type)
.Set(x => x.Profiles, agent.Profiles)
.Set(x => x.RoutingRules, agent.RoutingRules.Select(r => RoutingRuleMongoElement.ToMongoElement(r)).ToList())
.Set(x => x.Instruction, agent.Instruction)
@ -267,7 +267,7 @@ public partial class MongoRepository
Samples = agent.Samples ?? new List<string>(),
IsPublic = agent.IsPublic,
Disabled = agent.Disabled,
AllowRouting = agent.AllowRouting,
Type = agent.Type,
Profiles = agent.Profiles,
RoutingRules = !agent.RoutingRules.IsNullOrEmpty() ? agent.RoutingRules
.Select(r => RoutingRuleMongoElement.ToDomainElement(agent.Id, agent.Name, r))
@ -292,9 +292,9 @@ public partial class MongoRepository
filters.Add(builder.Eq(x => x.Disabled, filter.Disabled.Value));
}
if (filter.AllowRouting.HasValue)
if (filter.Type != null)
{
filters.Add(builder.Eq(x => x.AllowRouting, filter.AllowRouting.Value));
filters.Add(builder.Eq(x => x.Type, filter.Type));
}
if (filter.IsPublic.HasValue)
@ -302,32 +302,6 @@ public partial class MongoRepository
filters.Add(builder.Eq(x => x.IsPublic, filter.IsPublic.Value));
}
if (filter.IsRouter.HasValue)
{
var route = _services.GetRequiredService<RoutingSettings>();
if (filter.IsRouter.Value)
{
filters.Add(builder.In(x => x.Id, route.AgentIds));
}
else
{
filters.Add(builder.Nin(x => x.Id, route.AgentIds));
}
}
if (filter.IsEvaluator.HasValue)
{
var evaluate = _services.GetRequiredService<EvaluatorSetting>();
if (filter.IsEvaluator.Value)
{
filters.Add(builder.Eq(x => x.Id, evaluate.AgentId));
}
else
{
filters.Add(builder.Ne(x => x.Id, evaluate.AgentId));
}
}
if (filter.AgentIds != null)
{
filters.Add(builder.In(x => x.Id, filter.AgentIds));
@ -354,7 +328,7 @@ public partial class MongoRepository
Samples = x.Samples ?? new List<string>(),
IsPublic = x.IsPublic,
Disabled = x.Disabled,
AllowRouting = x.AllowRouting,
Type = x.Type,
Profiles = x.Profiles,
RoutingRules = !x.RoutingRules.IsNullOrEmpty() ? x.RoutingRules
.Select(r => RoutingRuleMongoElement.ToDomainElement(x.Id, x.Name, r))
@ -418,7 +392,7 @@ public partial class MongoRepository
.ToList() ?? new List<AgentResponseMongoElement>(),
Samples = x.Samples ?? new List<string>(),
IsPublic = x.IsPublic,
AllowRouting = x.AllowRouting,
Type = x.Type,
Disabled = x.Disabled,
Profiles = x.Profiles,
RoutingRules = x.RoutingRules?

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));
@ -217,7 +218,8 @@ public partial class MongoRepository
var filterDef = builder.And(filters);
var sortDefinition = Builders<ConversationDocument>.Sort.Descending(x => x.CreatedTime);
var conversationDocs = _dc.Conversations.Find(filterDef).Sort(sortDefinition).Skip(filter.Pager.Offset).Limit(filter.Pager.Size).ToList();
var pager = filter?.Pager ?? new Pagination();
var conversationDocs = _dc.Conversations.Find(filterDef).Sort(sortDefinition).Skip(pager.Offset).Limit(pager.Size).ToList();
var count = _dc.Conversations.CountDocuments(filterDef);
foreach (var conv in conversationDocs)

View file

@ -54,7 +54,7 @@ public partial class MongoRepository
.ToList() ?? new List<AgentResponseMongoElement>(),
Samples = x.Samples ?? new List<string>(),
IsPublic = x.IsPublic,
AllowRouting = x.AllowRouting,
Type = x.Type,
Disabled = x.Disabled,
Profiles = x.Profiles,
RoutingRules = x.RoutingRules?
@ -77,7 +77,7 @@ public partial class MongoRepository
.Set(x => x.Responses, agent.Responses)
.Set(x => x.Samples, agent.Samples)
.Set(x => x.IsPublic, agent.IsPublic)
.Set(x => x.AllowRouting, agent.AllowRouting)
.Set(x => x.Type, agent.Type)
.Set(x => x.Disabled, agent.Disabled)
.Set(x => x.Profiles, agent.Profiles)
.Set(x => x.RoutingRules, agent.RoutingRules)

View file

@ -53,7 +53,7 @@ public class RoutingConversationHook: ConversationHookBase
public override async Task OnResponseGenerated(RoleDialogModel message)
{
var routerSettings = _services.GetRequiredService<RoutingSettings>();
bool saveFlag = !routerSettings.AgentIds.Contains(message.CurrentAgentId);
bool saveFlag = _agent.Type != AgentType.Routing;
if (saveFlag)
{

View file

@ -1,13 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Nullable>enable</Nullable>
<RootNamespace>$(MSBuildProjectName.Replace(" ", "_"))s</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Infrastructure\BotSharp.Abstraction\BotSharp.Abstraction.csproj" />
</ItemGroup>
</Project>

View file

@ -1,4 +1,5 @@
using BotSharp.Plugin.WebDriver.Services;
using System.Threading;
namespace BotSharp.Plugin.WebDriver.Drivers.PlaywrightDriver;
@ -10,22 +11,49 @@ public partial class PlaywrightWebDriver
var body = await _instance.Page.QuerySelectorAsync("body");
var str = new List<string>();
var inputs = await body.QuerySelectorAllAsync("input");
var inputs = await body.QuerySelectorAllAsync("select");
foreach (var input in inputs)
{
var text = await input.TextContentAsync();
var html = "<select";
var id = await input.GetAttributeAsync("id");
if (!string.IsNullOrEmpty(id))
{
html += $" id='{id}'";
}
var name = await input.GetAttributeAsync("name");
var type = await input.GetAttributeAsync("type");
str.Add($"<input name='{name}' type='{type}'>{text}</input>");
}
if (!string.IsNullOrEmpty(name))
{
html += $" name='{id}'";
}
html += ">";
inputs = await body.QuerySelectorAllAsync("textarea");
foreach (var input in inputs)
{
var text = await input.TextContentAsync();
var name = await input.GetAttributeAsync("name");
var type = await input.GetAttributeAsync("type");
str.Add($"<textarea name='{name}' type='{type}'>{text}</textarea>");
var options = await input.QuerySelectorAllAsync("option");
if (options != null)
{
foreach (var option in options)
{
html += "<option";
var value = await option.GetAttributeAsync("value");
if (!string.IsNullOrEmpty(value))
{
html += $" value='{value}'";
}
html += ">";
var text = await option.TextContentAsync();
if (!string.IsNullOrEmpty(text))
{
html += text;
}
else
{
html += "'<NULL>'";
}
html += "</option>";
}
}
html += "</select>";
str.Add(html);
}
var driverService = _services.GetRequiredService<WebDriverService>();
@ -36,10 +64,52 @@ public partial class PlaywrightWebDriver
throw new Exception($"Can't locate the web element {context.ElementName}.");
}
var element = _instance.Page.Locator(htmlElementContextOut.TagName).Nth(htmlElementContextOut.Index);
ILocator element = default;
if (!string.IsNullOrEmpty(htmlElementContextOut.ElementId))
{
// await _instance.Page.WaitForSelectorAsync($"#{htmlElementContextOut.ElementId}", new PageWaitForSelectorOptions { Timeout = 3 });
element = _instance.Page.Locator($"#{htmlElementContextOut.ElementId}");
}
else
{
element = _instance.Page.Locator(htmlElementContextOut.TagName).Nth(htmlElementContextOut.Index);
}
try
{
await element.FillAsync(context.InputText);
var isVisible = await element.IsVisibleAsync();
if (!isVisible)
{
// Select the element you want to make visible (replace with your own selector)
var control = await _instance.Page.QuerySelectorAsync($"#{htmlElementContextOut.ElementId}");
// Show the element by modifying its CSS styles
await _instance.Page.EvaluateAsync(@"(element) => {
element.style.display = 'block';
element.style.visibility = 'visible';
}", control);
}
await element.FocusAsync();
await element.SelectOptionAsync(new SelectOptionValue
{
Label = context.UpdateValue
});
// Click on the blank area to activate posting
// await body.ClickAsync();
if (!isVisible)
{
// Select the element you want to make visible (replace with your own selector)
var control = await _instance.Page.QuerySelectorAsync($"#{htmlElementContextOut.ElementId}");
// Show the element by modifying its CSS styles
await _instance.Page.EvaluateAsync(@"(element) => {
element.style.display = 'none';
element.style.visibility = 'hidden';
}", control);
}
}
catch (Exception ex)
{

View file

@ -4,6 +4,7 @@ public partial class PlaywrightWebDriver
{
private readonly IServiceProvider _services;
private readonly PlaywrightInstance _instance;
public PlaywrightInstance Instance => _instance;
public PlaywrightWebDriver(IServiceProvider services, PlaywrightInstance instance)
{

View file

@ -23,9 +23,10 @@ public class ChangeListValueFn : IFunctionCallback
var agentService = _services.GetRequiredService<IAgentService>();
var agent = await agentService.LoadAgent(message.CurrentAgentId);
await _driver.Instance.Page.WaitForLoadStateAsync(LoadState.Load);
await _driver.ChangeListValue(agent, args, message.MessageId);
message.Content = "Update successfully.";
message.Content = $"Updat the value of \"${args.ElementName}\" to \"{args.UpdateValue}\" successfully.";
return true;
}
}

View file

@ -23,9 +23,10 @@ public class ClickButtonFn : IFunctionCallback
var agentService = _services.GetRequiredService<IAgentService>();
var agent = await agentService.LoadAgent(message.CurrentAgentId);
await _driver.Instance.Page.WaitForLoadStateAsync(LoadState.Load);
await _driver.ClickElement(agent, args, message.MessageId);
message.Content = "Executed successfully.";
message.Content = $"Click button {args.ElementName} successfully.";
return true;
}

View file

@ -23,6 +23,7 @@ public class ExtractDataFn : IFunctionCallback
var args = JsonSerializer.Deserialize<BrowsingContextIn>(message.FunctionArgs);
var agentService = _services.GetRequiredService<IAgentService>();
var agent = await agentService.LoadAgent(message.CurrentAgentId);
await _driver.Instance.Page.WaitForLoadStateAsync(LoadState.Load);
message.Content = await _driver.ExtractData(agent, args, message.MessageId);
return true;
}

View file

@ -23,6 +23,7 @@ public class InputUserPasswordFn : IFunctionCallback
var agentService = _services.GetRequiredService<IAgentService>();
var agent = await agentService.LoadAgent(message.CurrentAgentId);
await _driver.Instance.Page.WaitForLoadStateAsync(LoadState.Load);
await _driver.InputUserPassword(agent, args, message.MessageId);
message.Content = "Input password successfully";

View file

@ -23,9 +23,11 @@ public class InputUserTextFn : IFunctionCallback
var agentService = _services.GetRequiredService<IAgentService>();
var agent = await agentService.LoadAgent(message.CurrentAgentId);
await _driver.Instance.Page.WaitForLoadStateAsync(LoadState.Load);
await _driver.InputUserText(agent, args, message.MessageId);
message.Content = "Input text successfully.";
message.Content = $"Input text \"{args.InputText}\" successfully.";
return true;
}
}

View file

@ -20,9 +20,7 @@ public class OpenBrowserFn : IFunctionCallback
{
var args = JsonSerializer.Deserialize<BrowsingContextIn>(message.FunctionArgs);
var browser = await _driver.LaunchBrowser(args.Url);
message.Content = string.IsNullOrEmpty(args.Url) ? "Launch browser successfully." : $"Open website successfully.";
message.Content += "\r\nWhat would you like to do next?";
message.StopCompletion = true;
message.Content = string.IsNullOrEmpty(args.Url) ? $"Launch browser with blank page successfully." : $"Open website {args.Url} successfully.";
return true;
}
}

View file

@ -4,6 +4,9 @@ namespace BotSharp.Plugin.WebDriver.LlmContexts;
public class HtmlElementContextOut
{
[JsonPropertyName("element_id")]
public string ElementId { get; set; }
[JsonPropertyName("tag_name")]
public string TagName { get; set; }

View file

@ -1,9 +1,12 @@
{
"name": "Web Driver",
"description": "Perform a specific action on a web browser",
"createdDateTime": "2024-01-02T00:00:00Z",
"updatedDateTime": "2024-01-02T00:00:00Z",
"id": "f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b",
"allowRouting": true,
"isPublic": true
}
"id": "f3ae2a0f-e6ba-4ee1-a0b9-75d7431ff32b",
"name": "Web Driver",
"description": "Perform a specific action on a web browser",
"type": "task",
"createdDateTime": "2024-01-02T00:00:00Z",
"updatedDateTime": "2024-01-02T00:00:00Z",
"isPublic": true,
"llmConfig": {
"max_recursion_depth": 10
}
}

View file

@ -7,7 +7,7 @@
"properties": {
"url": {
"type": "string",
"description": "website url."
"description": "website url starts with https://"
}
},
"required": ["url"]
@ -67,7 +67,7 @@
"properties": {
"element_name": {
"type": "string",
"description": "the html input box element name."
"description": "the html selection element name."
},
"update_value": {
"type": "string",

View file

@ -3,6 +3,8 @@ You are a Web Driver that can manipulate web elements through automation tools.
Follow below steps to response:
1. Analyze user's latest request in the conversation.
2. Call appropriate function to execute the instruction.
3. If user requests execute multiple steps, execute them sequentially.
Additional response requirements:
* Call function input_user_password if user wants to input password.
* Call function input_user_password if user wants to input password.
* Don't do extra steps if user didn't ask.

View file

@ -1,5 +1,6 @@
{{ html_content }}
=== According to above HTML ===
Find the html element tag name of "{{ element_name }}".
Output in JSON format {"tag_name": "", "index": -1} with appropriate values, the "index" starts with 0.
Find the html element in the similar meaning of "{{ element_name }}".
Output in JSON format {"tag_name": "", "element_id": "populated if element has id", "index": -1} with appropriate values.
The index is the position of the element which starts with 0.

View file

@ -60,10 +60,6 @@
],
"Router": {
"AgentIds": [
"01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a"
],
"Planner": "NaivePlanner"
},
"Evaluator": {

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"
}
]