Support to set multiple routers.

This commit is contained in:
Haiping Chen 2024-01-13 15:17:40 -06:00
parent 0bacd32ed5
commit 3022cf50cb
32 changed files with 423 additions and 25 deletions

View file

@ -4,5 +4,6 @@
<LangVersion>10.0</LangVersion> <LangVersion>10.0</LangVersion>
<BotSharpVersion>0.21.0</BotSharpVersion> <BotSharpVersion>0.21.0</BotSharpVersion>
<GeneratePackageOnBuild>false</GeneratePackageOnBuild> <GeneratePackageOnBuild>false</GeneratePackageOnBuild>
<GenerateDocumentationFile>false</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
</Project> </Project>

View file

@ -1,3 +1,4 @@
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Repositories.Filters;
namespace BotSharp.Abstraction.Agents; namespace BotSharp.Abstraction.Agents;
@ -34,4 +35,6 @@ public interface IAgentService
Task UpdateAgentFromFile(string id); Task UpdateAgentFromFile(string id);
string GetDataDir(); string GetDataDir();
string GetAgentDataDir(string agentId); string GetAgentDataDir(string agentId);
PluginDef GetPlugin(string agentId);
} }

View file

@ -1,4 +1,5 @@
using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Routing.Models; using BotSharp.Abstraction.Routing.Models;
namespace BotSharp.Abstraction.Agents.Models; namespace BotSharp.Abstraction.Agents.Models;
@ -56,6 +57,12 @@ public class Agent
public bool IsPublic { get; set; } public bool IsPublic { get; set; }
[JsonIgnore]
public bool IsRouter { get; set; }
[JsonIgnore]
public PluginDef Plugin { get; set; }
/// <summary> /// <summary>
/// Allow to be routed /// Allow to be routed
/// </summary> /// </summary>

View file

@ -19,18 +19,14 @@ public class RoutingContext
public string IntentName { get; set; } public string IntentName { get; set; }
/// <summary> /// <summary>
/// Agent that can handl user original goal. /// Agent that can handle user original goal.
/// </summary> /// </summary>
public string OriginAgentId public string OriginAgentId
=> _stack.Where(x => x != _setting.AgentId).Last(); => _stack.Where(x => !_setting.AgentIds.Contains(x)).Last();
public bool IsEmpty => !_stack.Any(); public bool IsEmpty => !_stack.Any();
public string GetCurrentAgentId() public string GetCurrentAgentId()
{ {
if (_stack.Count == 0)
{
_stack.Push(_setting.AgentId);
}
return _stack.Peek(); return _stack.Peek();
} }

View file

@ -5,7 +5,7 @@ public class RoutingSettings
/// <summary> /// <summary>
/// Router Agent Id /// Router Agent Id
/// </summary> /// </summary>
public string AgentId { get; set; } = string.Empty; public string[] AgentIds { get; set; } = new string[0];
public string Planner { get; set; } = string.Empty; public string Planner { get; set; } = string.Empty;
} }

View file

@ -1,5 +1,6 @@
using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Repositories.Filters; using BotSharp.Abstraction.Repositories.Filters;
using BotSharp.Abstraction.Routing.Settings;
namespace BotSharp.Core.Agents.Services; namespace BotSharp.Core.Agents.Services;
@ -11,6 +12,15 @@ public partial class AgentService
public async Task<List<Agent>> GetAgents(AgentFilter filter) public async Task<List<Agent>> GetAgents(AgentFilter filter)
{ {
var agents = _db.GetAgents(filter); var agents = _db.GetAgents(filter);
// Set IsRouter
var routeSetting = _services.GetRequiredService<RoutingSettings>();
foreach (var agent in agents)
{
agent.IsRouter = routeSetting.AgentIds.Contains(agent.Id);
agent.Plugin = GetPlugin(agent.Id);
}
return await Task.FromResult(agents); return await Task.FromResult(agents);
} }
@ -35,6 +45,11 @@ public partial class AgentService
profile.LlmConfig.IsInherit = true; 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; return profile;
} }
} }

View file

@ -0,0 +1,24 @@
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Core.Plugins;
namespace BotSharp.Core.Agents.Services;
public partial class AgentService
{
public PluginDef GetPlugin(string agentId)
{
var loader = _services.GetRequiredService<PluginLoader>();
var plugins = loader.GetPlugins(_services);
return plugins.FirstOrDefault(x => x.AgentIds.Contains(agentId)) ??
new PluginDef
{
Id = Guid.Empty.ToString(),
AgentIds = new[]
{
agentId
},
Assembly = typeof(AgentService).Assembly.FullName.Split(',').First(),
Name = "BotSharp.Core"
};
}
}

View file

@ -1,4 +1,3 @@
using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Repositories; using BotSharp.Abstraction.Repositories;
using System.IO; using System.IO;

View file

@ -59,7 +59,7 @@ public partial class ConversationService
var routing = _services.GetRequiredService<IRoutingService>(); var routing = _services.GetRequiredService<IRoutingService>();
var settings = _services.GetRequiredService<RoutingSettings>(); var settings = _services.GetRequiredService<RoutingSettings>();
response = agentId == settings.AgentId ? response = settings.AgentIds.Contains(agentId) ?
await routing.InstructLoop(message) : await routing.InstructLoop(message) :
await routing.ExecuteDirectly(agent, message); await routing.ExecuteDirectly(agent, message);

View file

@ -59,6 +59,14 @@ public class TokenStatistics : ITokenStatistics
public void PrintStatistics() public void PrintStatistics()
{ {
if (_timer == null)
{
_timer = Stopwatch.StartNew();
}
else
{
_timer.Start();
}
var stats = $"Token Usage: {_promptTokenCount} prompt + {_completionTokenCount} completion = {Total} total tokens ({_timer.ElapsedMilliseconds / 1000f:f2}s). One-Way cost: {Cost:C4}, accumulated cost: {AccumulatedCost:C4}. [{_model}]"; var stats = $"Token Usage: {_promptTokenCount} prompt + {_completionTokenCount} completion = {Total} total tokens ({_timer.ElapsedMilliseconds / 1000f:f2}s). One-Way cost: {Cost:C4}, accumulated cost: {AccumulatedCost:C4}. [{_model}]";
#if DEBUG #if DEBUG
Console.WriteLine(stats, Color.DarkGray); Console.WriteLine(stats, Color.DarkGray);

View file

@ -21,6 +21,16 @@ public partial class InstructService : IInstructService
var agentService = _services.GetRequiredService<IAgentService>(); var agentService = _services.GetRequiredService<IAgentService>();
Agent agent = await agentService.LoadAgent(agentId); Agent agent = await agentService.LoadAgent(agentId);
if (agent.Disabled)
{
var content = $"This agent ({agent.Name}) is disabled, please install the corresponding plugin ({agent.Plugin.Name}) to activate this agent.";
return new InstructResult
{
MessageId = message.MessageId,
Text = content
};
}
// Trigger before completion hooks // Trigger before completion hooks
var hooks = _services.GetServices<IInstructHook>(); var hooks = _services.GetServices<IInstructHook>();
foreach (var hook in hooks) foreach (var hook in hooks)

View file

@ -349,8 +349,8 @@ namespace BotSharp.Core.Repository
{ {
var route = _services.GetRequiredService<RoutingSettings>(); var route = _services.GetRequiredService<RoutingSettings>();
query = filter.IsRouter.Value ? query = filter.IsRouter.Value ?
query.Where(x => x.Id == route.AgentId) : query.Where(x => route.AgentIds.Contains(x.Id)) :
query.Where(x => x.Id != route.AgentId); query.Where(x => !route.AgentIds.Contains(x.Id));
} }
if (filter.IsEvaluator.HasValue) if (filter.IsEvaluator.HasValue)

View file

@ -58,6 +58,11 @@ public class RouteToAgentFn : IFunctionCallback
return false; return false;
} }
if (targetAgent.Disabled)
{
return false;
}
var missingfield = HasMissingRequiredField(message, out var agentId); var missingfield = HasMissingRequiredField(message, out var agentId);
if (missingfield && message.CurrentAgentId != agentId) if (missingfield && message.CurrentAgentId != agentId)
{ {

View file

@ -46,7 +46,20 @@ public class RouteToAgentRoutingHandler : RoutingHandlerBase, IRoutingHandler
{ {
message.Content = inst.Question; message.Content = inst.Question;
} }
ret = await routing.InvokeAgent(agentId, _dialogs);
if (agent.Disabled)
{
var content = $"This agent ({agent.Name}) is disabled, please install the corresponding plugin ({agent.Plugin.Name}) to activate this agent.";
message = RoleDialogModel.From(message,
role: AgentRole.Assistant,
content: content);
_dialogs.Add(message);
}
else
{
ret = await routing.InvokeAgent(agentId, _dialogs);
}
var response = _dialogs.Last(); var response = _dialogs.Last();
inst.Response = response.Content; inst.Response = response.Content;

View file

@ -7,7 +7,7 @@ namespace BotSharp.Core.Routing.Hooks;
public class RoutingAgentHook : AgentHookBase public class RoutingAgentHook : AgentHookBase
{ {
private readonly RoutingSettings _routingSetting; private readonly RoutingSettings _routingSetting;
public override string SelfId => _routingSetting.AgentId; public override string SelfId => string.Empty;
public RoutingAgentHook(IServiceProvider services, AgentSettings settings, RoutingSettings routingSetting) public RoutingAgentHook(IServiceProvider services, AgentSettings settings, RoutingSettings routingSetting)
: base(services, settings) : base(services, settings)
@ -17,6 +17,10 @@ public class RoutingAgentHook : AgentHookBase
public override bool OnInstructionLoaded(string template, Dictionary<string, object> dict) public override bool OnInstructionLoaded(string template, Dictionary<string, object> dict)
{ {
if (!_routingSetting.AgentIds.Contains(_agent.Id))
{
return base.OnInstructionLoaded(template, dict);
}
dict["router"] = _agent; dict["router"] = _agent;
var routing = _services.GetRequiredService<IRoutingService>(); var routing = _services.GetRequiredService<IRoutingService>();

View file

@ -64,7 +64,7 @@ public partial class RoutingService : IRoutingService
public async Task<RoleDialogModel> InstructLoop(RoleDialogModel message) public async Task<RoleDialogModel> InstructLoop(RoleDialogModel message)
{ {
var agentService = _services.GetRequiredService<IAgentService>(); var agentService = _services.GetRequiredService<IAgentService>();
_router = await agentService.LoadAgent(_settings.AgentId); _router = await agentService.LoadAgent(message.CurrentAgentId);
RoleDialogModel response = default; RoleDialogModel response = default;

View file

@ -115,6 +115,8 @@ public class ConversationController : ControllerBase
[FromRoute] string conversationId, [FromRoute] string conversationId,
[FromBody] NewMessageModel input) [FromBody] NewMessageModel input)
{ {
var inputMsg = new RoleDialogModel(AgentRole.User, input.Text);
var conv = _services.GetRequiredService<IConversationService>(); var conv = _services.GetRequiredService<IConversationService>();
conv.SetConversationId(conversationId, input.States); conv.SetConversationId(conversationId, input.States);
conv.States.SetState("channel", input.Channel) conv.States.SetState("channel", input.Channel)
@ -124,7 +126,7 @@ public class ConversationController : ControllerBase
.SetState("sampling_factor", input.SamplingFactor); .SetState("sampling_factor", input.SamplingFactor);
var response = new ChatResponseModel(); var response = new ChatResponseModel();
var inputMsg = new RoleDialogModel(AgentRole.User, input.Text);
await conv.SendMessage(agentId, inputMsg, await conv.SendMessage(agentId, inputMsg,
async msg => async msg =>
{ {

View file

@ -1,5 +1,6 @@
using BotSharp.Abstraction.Agents.Models; using BotSharp.Abstraction.Agents.Models;
using BotSharp.Abstraction.Functions.Models; using BotSharp.Abstraction.Functions.Models;
using BotSharp.Abstraction.Plugins.Models;
using BotSharp.Abstraction.Routing.Models; using BotSharp.Abstraction.Routing.Models;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
@ -18,6 +19,9 @@ public class AgentViewModel
[JsonPropertyName("is_public")] [JsonPropertyName("is_public")]
public bool IsPublic { get; set; } public bool IsPublic { get; set; }
[JsonPropertyName("is_router")]
public bool IsRouter { get; set; }
[JsonPropertyName("allow_routing")] [JsonPropertyName("allow_routing")]
public bool AllowRouting { get; set; } public bool AllowRouting { get; set; }
public bool Disabled { get; set; } public bool Disabled { get; set; }
@ -33,6 +37,8 @@ public class AgentViewModel
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public AgentLlmConfig? LlmConfig { get; set; } public AgentLlmConfig? LlmConfig { get; set; }
public PluginDef Plugin { get; set; }
[JsonPropertyName("created_datetime")] [JsonPropertyName("created_datetime")]
public DateTime CreatedDateTime { get; set; } public DateTime CreatedDateTime { get; set; }
@ -52,12 +58,14 @@ public class AgentViewModel
Responses = agent.Responses, Responses = agent.Responses,
Samples = agent.Samples, Samples = agent.Samples,
IsPublic= agent.IsPublic, IsPublic= agent.IsPublic,
IsRouter = agent.IsRouter,
Disabled = agent.Disabled, Disabled = agent.Disabled,
IconUrl = agent.IconUrl, IconUrl = agent.IconUrl,
AllowRouting = agent.AllowRouting, AllowRouting = agent.AllowRouting,
Profiles = agent.Profiles, Profiles = agent.Profiles,
RoutingRules = agent.RoutingRules, RoutingRules = agent.RoutingRules,
LlmConfig = agent.LlmConfig, LlmConfig = agent.LlmConfig,
Plugin = agent.Plugin,
CreatedDateTime = agent.CreatedDateTime, CreatedDateTime = agent.CreatedDateTime,
UpdatedDateTime = agent.UpdatedDateTime UpdatedDateTime = agent.UpdatedDateTime
}; };

View file

@ -6,7 +6,7 @@
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<VersionPrefix>$(BotSharpVersion)</VersionPrefix> <VersionPrefix>$(BotSharpVersion)</VersionPrefix>
<GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild> <GeneratePackageOnBuild>$(GeneratePackageOnBuild)</GeneratePackageOnBuild>
<GenerateDocumentationFile>True</GenerateDocumentationFile> <GenerateDocumentationFile>$(GenerateDocumentationFile)</GenerateDocumentationFile>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View file

@ -304,8 +304,8 @@ public partial class MongoRepository
{ {
var route = _services.GetRequiredService<RoutingSettings>(); var route = _services.GetRequiredService<RoutingSettings>();
query = filter.IsRouter.Value ? query = filter.IsRouter.Value ?
query.Where(x => x.Id == route.AgentId) : query.Where(x => route.AgentIds.Contains(x.Id)) :
query.Where(x => x.Id != route.AgentId); query.Where(x => !route.AgentIds.Contains(x.Id));
} }
if (filter.IsEvaluator.HasValue) if (filter.IsEvaluator.HasValue)

View file

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

View file

@ -0,0 +1,35 @@
using BotSharp.Abstraction.Conversations.Models;
using BotSharp.Abstraction.Functions;
using BotSharp.Plugin.SqlHero.Models;
using BotSharp.Plugin.SqlHero.Settings;
using Dapper;
using MySqlConnector;
using System.Text.Json;
using System.Threading.Tasks;
namespace BotSharp.Plugin.SqlHero.Actions;
public class ExecuteQueryAction : IFunctionCallback
{
public string Name => "execute_sql";
private readonly SqlHeroSetting _setting;
public ExecuteQueryAction(SqlHeroSetting setting)
{
_setting = setting;
}
public async Task<bool> Execute(RoleDialogModel message)
{
var args = JsonSerializer.Deserialize<LlmInputArgs>(message.FunctionArgs);
message.Content = "executed successully";
/*using var connection = new MySqlConnection(_setting.MySqlConnectionString);
message.Content = JsonSerializer.Serialize(connection.Query(args.SqlStatement), new JsonSerializerOptions
{
WriteIndented = true,
});*/
// message.StopCompletion = true;
return true;
}
}

View file

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

View file

@ -0,0 +1,9 @@
using System.Text.Json.Serialization;
namespace BotSharp.Plugin.SqlHero.Models;
public class LlmInputArgs
{
[JsonPropertyName("sql_statement")]
public string SqlStatement { get; set; }
}

View file

@ -0,0 +1,6 @@
namespace BotSharp.Plugin.SqlHero.Settings;
public class SqlHeroSetting
{
public string MySqlConnectionString { get; set; }
}

View file

@ -0,0 +1,26 @@
using BotSharp.Abstraction.Plugins;
using BotSharp.Plugin.SqlHero.Settings;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Drawing;
using System.Text.RegularExpressions;
namespace BotSharp.Plugin.SqlHero;
public class SqlHeroPlugin : IBotSharpPlugin
{
public string Name => "SQL Hero";
public string Description => "Convert the requirements into corresponding SQL statements and execute if needed";
public void RegisterDI(IServiceCollection services, IConfiguration config)
{
var settings = new SqlHeroSetting();
config.Bind("SqlHero", settings);
services.AddSingleton(x =>
{
Console.WriteLine($"Loaded SqlHero settings:: {Regex.Replace(settings.MySqlConnectionString, "password=.*?;", "password=******;")}", Color.Yellow);
return settings;
});
}
}

View file

@ -0,0 +1,9 @@
{
"id": "beda4c12-e1ec-4b4b-b328-3df4a6687c4f",
"name": "SQL Expert",
"description": "Convert the requirements into corresponding SQL statements according to the table structure and execute them if needed.",
"createdDateTime": "2023-11-15T13:49:00Z",
"updatedDateTime": "2023-11-15T13:49:00Z",
"isPublic": false,
"allowRouting": false
}

View file

@ -0,0 +1,14 @@
[{
"name": "execute_sql",
"description": "generate sql statement and execute the query.",
"parameters": {
"type": "object",
"properties": {
"sql_statement": {
"type": "string",
"description": "SQL statement"
}
},
"required": ["sql_statement"]
}
}]

View file

@ -0,0 +1,181 @@
You are a SQL Expert who knows how to convert business requirements to SQL expressions.
Follow these steps:
1: Look at the table DDL defintions especially for the CONSTRAINT and FOREIGN KEY REFERENCES.
2: Translate user requirements into SQL statements step by step.
3: Double check, don't miss any requirements, all the parameters must have values.
4: Confirm with the user whether to execute the sql statement.
If user confirms to run the query, call function execute_sql to execute it.
Table structure guideline:
Table with prefix "data_" represents system enum item.
Table with prefix "client_" represents client specific configuration or client dataset.
Below are the table DDL information in JSON format:
CREATE TABLE `data_ServiceCategory` (
`Id` smallint(6) NOT NULL,
`Name` varchar(50) NOT NULL,
`IsClientVisible` tinyint(1) NOT NULL DEFAULT '1',
`IsTurnService` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`Id`),
KEY `ID_data_ServiceCategory_Name` (`Name`)
)
CREATE TABLE `data_ServiceType` (
`Id` smallint(6) NOT NULL,
`Name` varchar(50) NOT NULL,
`ServcieCategoryId` smallint(6) NOT NULL,
PRIMARY KEY (`Id`),
KEY `FK_data_ServiceType_ServcieCategoryId` (`ServcieCategoryId`),
CONSTRAINT `FK_data_ServiceType_ServcieCategoryId` FOREIGN KEY (`ServcieCategoryId`) REFERENCES `data_ServiceCategory` (`Id`)
)
CREATE TABLE `data_ServiceCode` (
`Id` smallint(6) NOT NULL,
`Name` varchar(100) NOT NULL,
`ServiceTypeId` smallint(6) NOT NULL,
`AbbrName` varchar(100) DEFAULT NULL,
`IsClientVisible` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`Id`),
KEY `FK_data_ServiceCode_ServiceCodeId` (`ServiceTypeId`),
KEY `FK_data_ServiceCode_SkillLevelId` (`SkillLevelId`),
CONSTRAINT `FK_data_ServiceCode_ServiceCodeId` FOREIGN KEY (`ServiceTypeId`) REFERENCES `data_ServiceType` (`Id`),
CONSTRAINT `FK_data_ServiceCode_SkillLevelId` FOREIGN KEY (`SkillLevelId`) REFERENCES `data_SkillLevel` (`Id`)
)
CREATE TABLE `client_ServiceCode` (
`Id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ClientServiceTypeId` int(10) unsigned NOT NULL,
`ServiceCodeId` smallint(6) NOT NULL,
`IsContract` tinyint(1) DEFAULT '0',
`IsHidden` tinyint(1) DEFAULT '0',
`IsPersonal` tinyint(1) NOT NULL DEFAULT '0',
`IsHiddenForClient` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`Id`),
UNIQUE KEY `UK_client_ServiceCode` (`ClientServiceTypeId`,`ServiceCodeId`,`IsPersonal`),
KEY `FK_client_ServiceCode_ServiceCodeId_idx` (`ServiceCodeId`),
CONSTRAINT `FK_client_ServiceCode_ClientServiceTypeId` FOREIGN KEY (`ClientServiceTypeId`) REFERENCES `client_ServiceType` (`Id`),
CONSTRAINT `FK_client_ServiceCode_ServiceCodeId` FOREIGN KEY (`ServiceCodeId`) REFERENCES `data_ServiceCode` (`Id`)
)
CREATE TABLE `client_ServiceType` (
`Id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ClientServiceCategoryId` int(10) unsigned NOT NULL,
`ServiceTypeId` smallint(6) NOT NULL,
`IsHidden` tinyint(1) DEFAULT '0',
PRIMARY KEY (`Id`),
UNIQUE KEY `UK_client_ServiceType` (`ClientServiceCategoryId`,`ServiceTypeId`),
KEY `FK_client_ServiceType_ServiceTypeId` (`ServiceTypeId`),
CONSTRAINT `FK_client_ServiceType_ClientServiceCategoryId` FOREIGN KEY (`ClientServiceCategoryId`) REFERENCES `client_ServiceCategory` (`Id`),
CONSTRAINT `FK_client_ServiceType_ServiceTypeId` FOREIGN KEY (`ServiceTypeId`) REFERENCES `data_ServiceType` (`Id`)
)
CREATE TABLE `client_ServiceCategory` (
`Id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ClientId` int(10) unsigned NOT NULL,
`ServiceCategoryId` smallint(6) NOT NULL,
`IsHidden` tinyint(1) DEFAULT '0',
PRIMARY KEY (`Id`),
UNIQUE KEY `UK_client_ServiceCategory` (`ClientId`,`ServiceCategoryId`),
KEY `FK_client_ServiceCategory_ServiceCategoryId` (`ServiceCategoryId`),
CONSTRAINT `FK_client_ServiceCategory_ClientId` FOREIGN KEY (`ClientId`) REFERENCES `client_Profile` (`Id`),
CONSTRAINT `FK_client_ServiceCategory_ServiceCategoryId` FOREIGN KEY (`ServiceCategoryId`) REFERENCES `data_ServiceCategory` (`Id`)
)
CREATE TABLE `client_Profile` (
`Id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`Name` varchar(100) NOT NULL,
`Active` tinyint(1) NOT NULL DEFAULT '1',
`ClientCode` varchar(20) DEFAULT NULL,
PRIMARY KEY (`Id`),
UNIQUE KEY `UK_client_Profile` (`ClientCode`),
KEY `FK_client_Location_CustomerTypeId` (`CustomerTypeId`),
KEY `IDX_client_Profile_BusinessTypeId` (`BusinessTypeId`),
CONSTRAINT `FK_client_Location_CustomerTypeId` FOREIGN KEY (`CustomerTypeId`) REFERENCES `data_CustomerType` (`Id`)
)
CREATE TABLE `client_WOReactive` (
`Id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`WONum` varchar(40) NOT NULL,
`LocationId` int(10) unsigned NOT NULL,
`AffiliateId` int(10) unsigned DEFAULT NULL,
`ServiceCodeId` smallint(6) NOT NULL,
`StatusId` smallint(6) NOT NULL,
`ClientNTE` decimal(18,2) DEFAULT NULL,
`ReferWONum` varchar(512) DEFAULT NULL,
`WOCategoryId` smallint(6) DEFAULT NULL,
`WOTypeId` smallint(6) DEFAULT NULL,
`WOServiceCategoryId` smallint(6) DEFAULT NULL,
`WOServiceTypeId` smallint(6) DEFAULT NULL,
`WOClientId` int(10) unsigned DEFAULT NULL
PRIMARY KEY (`Id`),
CONSTRAINT `FK_client_WOReactive_ServiceCodeId` FOREIGN KEY (`ServiceCodeId`) REFERENCES `data_ServiceCode` (`Id`),
CONSTRAINT `FK_client_WOReactive_StatusId` FOREIGN KEY (`StatusId`) REFERENCES `data_WOStatus` (`Id`),
CONSTRAINT `FK_client_WOReactive_WOCategoryId` FOREIGN KEY (`WOCategoryId`) REFERENCES `data_WOCategory` (`Id`),
CONSTRAINT `FK_client_WOReactive_WOTypeId` FOREIGN KEY (`WOTypeId`) REFERENCES `data_WOType` (`Id`)
)
CREATE TABLE `client_ServiceCodeNTE` (
`Id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ClientServiceCodeId` int(10) unsigned NOT NULL,
`ClientNTE` decimal(18,2) DEFAULT NULL,
`AffiliateNTE` decimal(18,2) DEFAULT NULL,
PRIMARY KEY (`Id`),
CONSTRAINT `FK_client_ServiceCodeNTE_ClientServiceCodeId` FOREIGN KEY (`ClientServiceCodeId`) REFERENCES `client_ServiceCode` (`Id`)
)
CREATE TABLE `data_Priority` (
`Id` smallint(6) NOT NULL,
`Name` varchar(50) NOT NULL,
`AbbrName` varchar(50) NOT NULL,
PRIMARY KEY (`Id`)
)
CREATE TABLE `data_Trade` (
`Id` smallint(6) NOT NULL,
`Name` varchar(50) NOT NULL,
PRIMARY KEY (`Id`),
KEY `ID_data_Trade_Name` (`Name`)
)
CREATE TABLE `data_TradeServiceCode` (
`Id` int(10) NOT NULL AUTO_INCREMENT,
`TradeId` smallint(6) NOT NULL,
`ServiceCodeId` smallint(6) NOT NULL,
PRIMARY KEY (`Id`),
UNIQUE KEY `UK_data_TradeServiceCode` (`ServiceCodeId`,`CustomerTypeId`),
KEY `FK_data_TradeServiceCode_TradeId` (`TradeId`),
KEY `FK_data_TradeServiceCode_ServiceCodeId` (`ServiceCodeId`),
CONSTRAINT `FK_data_TradeServiceCode_ServiceCodeId` FOREIGN KEY (`ServiceCodeId`) REFERENCES `data_ServiceCode` (`Id`),
CONSTRAINT `FK_data_TradeServiceCode_TradeId` FOREIGN KEY (`TradeId`) REFERENCES `data_Trade` (`Id`)
)
CREATE TABLE `Client_ServiceCodePriority` (
`Id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`ClientServiceCodeId` int(10) unsigned NOT NULL,
`PriorityId` smallint(6) NOT NULL,
PRIMARY KEY (`Id`),
CONSTRAINT `FK_Client_ServiceCodePriority_ClientServiceCodeId` FOREIGN KEY (`ClientServiceCodeId`) REFERENCES `client_ServiceCode` (`Id`),
CONSTRAINT `FK_Client_ServiceCodePriority_PriorityId` FOREIGN KEY (`PriorityId`) REFERENCES `data_Priority` (`Id`)
)
====
User Task:
Find all the NTE for all the service combination of client 'IH'.
Out put the service combination and NTE and priority.
====
Examples:
user: Create a new service category named 'DSC Equipment' for client 'Signet'.
assistant: Steps: 1. Check if the service category is in data_ServiceCategory.
2. If exists, get the service category id from data_ServiceCategory.
3. If not exists, insert a new record and get the id.
4. Insert a new record to client_ServiceCategory based on the FOREIGN KEY and REFERENCES.
user: Create a new service type and service code.
assistant: You can follow the process same as service category creation.

View file

@ -1,3 +1,4 @@
using BotSharp.Plugin.Twilio.Settings;
using Twilio.Jwt.AccessToken; using Twilio.Jwt.AccessToken;
using Token = Twilio.Jwt.AccessToken.Token; using Token = Twilio.Jwt.AccessToken.Token;
@ -47,7 +48,7 @@ public class TwilioService
public VoiceResponse ReturnInstructions(string message) public VoiceResponse ReturnInstructions(string message)
{ {
var routingSetting = _services.GetRequiredService<RoutingSettings>(); var twilioSetting = _services.GetRequiredService<TwilioSetting>();
var response = new VoiceResponse(); var response = new VoiceResponse();
var gather = new Gather() var gather = new Gather()
@ -56,7 +57,7 @@ public class TwilioService
{ {
Gather.InputEnum.Speech Gather.InputEnum.Speech
}, },
Action = new Uri($"{_settings.CallbackHost}/twilio/voice/{routingSetting.AgentId}") Action = new Uri($"{_settings.CallbackHost}/twilio/voice/{twilioSetting.AgentId}")
}; };
gather.Say(message); gather.Say(message);
response.Append(gather); response.Append(gather);
@ -76,13 +77,13 @@ public class TwilioService
public VoiceResponse HoldOn(int interval, string message = null) public VoiceResponse HoldOn(int interval, string message = null)
{ {
var routingSetting = _services.GetRequiredService<RoutingSettings>(); var twilioSetting = _services.GetRequiredService<TwilioSetting>();
var response = new VoiceResponse(); var response = new VoiceResponse();
var gather = new Gather() var gather = new Gather()
{ {
Input = new List<Gather.InputEnum>() { Gather.InputEnum.Speech }, Input = new List<Gather.InputEnum>() { Gather.InputEnum.Speech },
Action = new Uri($"{_settings.CallbackHost}/twilio/voice/{routingSetting.AgentId}"), Action = new Uri($"{_settings.CallbackHost}/twilio/voice/{twilioSetting.AgentId}"),
ActionOnEmptyResult = true ActionOnEmptyResult = true
}; };
if (!string.IsNullOrEmpty(message)) if (!string.IsNullOrEmpty(message))

View file

@ -9,4 +9,5 @@ public class TwilioSetting
public string ApiKeySID { get; set; } public string ApiKeySID { get; set; }
public string ApiSecret { get; set; } public string ApiSecret { get; set; }
public string CallbackHost { get; set; } public string CallbackHost { get; set; }
public string AgentId { get; set; }
} }

View file

@ -38,7 +38,9 @@
], ],
"Router": { "Router": {
"AgentId": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a", "AgentIds": [
"01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a"
],
"Planner": "NaivePlanner" "Planner": "NaivePlanner"
}, },
@ -107,7 +109,8 @@
"PhoneNumber": "+1", "PhoneNumber": "+1",
"AccountSID": "", "AccountSID": "",
"AuthToken": "", "AuthToken": "",
"CallbackHost": "https://" "CallbackHost": "https://",
"AgentId": "01fcc3e5-9af7-49e6-ad7a-a760bd12dc4a"
}, },
"Database": { "Database": {