diff --git a/.gitignore b/.gitignore index 31f175bb..5dba3fab 100644 --- a/.gitignore +++ b/.gitignore @@ -288,3 +288,4 @@ __pycache__/ *.xsd.cs /App_Data /Bot.WebStarter/App_Data/bot-rasa.db +/Bot.WebStarter/App_Data/DbInitializer/Agents/Dialogflow/VirtualAssistant diff --git a/Bot.Rasa.RestApi/AgentController.cs b/Bot.Rasa.RestApi/AgentController.cs index 25524b54..352ed178 100644 --- a/Bot.Rasa.RestApi/AgentController.cs +++ b/Bot.Rasa.RestApi/AgentController.cs @@ -14,17 +14,17 @@ namespace Bot.Rasa.RestApi [HttpGet("id")] public Agent Get([FromRoute] String id) { - var console = new RasaConsole(dc); + var console = new RasaAi(dc); return console.LoadAgent(id); } [HttpPost] public String Create([FromBody] Agent agent) { - var console = new RasaConsole(dc); + var console = new RasaAi(dc); dc.DbTran(() => { - console.CreateAgent(agent); + console.SaveAgent(agent); }); return agent.Id; diff --git a/Bot.Rasa.RestApi/EntityController.cs b/Bot.Rasa.RestApi/EntityController.cs index 7784e954..7aa9332d 100644 --- a/Bot.Rasa.RestApi/EntityController.cs +++ b/Bot.Rasa.RestApi/EntityController.cs @@ -9,7 +9,7 @@ namespace Bot.Rasa.RestApi public class EntityController : EssentialController { [HttpPost] - public string CreateEntity([FromBody] EntityType entity) + public string CreateEntity([FromBody] Entity entity) { return entity.Id; } diff --git a/Bot.Rasa.RestApi/EntityTypeController.cs b/Bot.Rasa.RestApi/EntityTypeController.cs index 8b3f653a..4550aa3a 100644 --- a/Bot.Rasa.RestApi/EntityTypeController.cs +++ b/Bot.Rasa.RestApi/EntityTypeController.cs @@ -11,7 +11,7 @@ namespace Bot.Rasa.RestApi public class EntityTypeController : EssentialController { [HttpPost] - public string CreateType([FromBody] EntityType entityType) + public string CreateType([FromBody] Entity entityType) { var agent = dc.Table().Find(entityType.AgentId); dc.DbTran(() => agent.CreateEntityType(dc, entityType)); diff --git a/Bot.Rasa/Adapters/Dialogflow/DialogflowAgent.cs b/Bot.Rasa/Adapters/Dialogflow/DialogflowAgent.cs new file mode 100644 index 00000000..9e5f441f --- /dev/null +++ b/Bot.Rasa/Adapters/Dialogflow/DialogflowAgent.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Adapters.Dialogflow +{ + public class DialogflowAgent + { + public String Id { get; set; } + public String Name { get; set; } + public String Description { get; set; } + public Boolean Published { get; set; } + + public String Language { get; set; } + } +} diff --git a/Bot.Rasa/Adapters/Dialogflow/DialogflowEntity.cs b/Bot.Rasa/Adapters/Dialogflow/DialogflowEntity.cs new file mode 100644 index 00000000..93d335a4 --- /dev/null +++ b/Bot.Rasa/Adapters/Dialogflow/DialogflowEntity.cs @@ -0,0 +1,42 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Adapters.Dialogflow +{ + [JsonObject] + public class DialogflowEntity + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("entries")] + public List Entries { get; set; } + + public DialogflowEntity() + { + } + + public DialogflowEntity(string name) + { + this.Name = name; + } + + public DialogflowEntity(string name, List entries) + { + this.Name = name; + this.Entries = entries; + } + + public void AddEntry(DialogflowEntityEntry entry) + { + if (Entries == null) + { + Entries = new List(); + } + + Entries.Add(entry); + } + } +} diff --git a/Bot.Rasa/Adapters/Dialogflow/DialogflowEntityEntry.cs b/Bot.Rasa/Adapters/Dialogflow/DialogflowEntityEntry.cs new file mode 100644 index 00000000..bb7b86cb --- /dev/null +++ b/Bot.Rasa/Adapters/Dialogflow/DialogflowEntityEntry.cs @@ -0,0 +1,32 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Adapters.Dialogflow +{ + [JsonObject] + public class DialogflowEntityEntry + { + [JsonProperty("value")] + public string Value { get; set; } + + [JsonProperty("synonyms")] + public List Synonyms { get; set; } + + public DialogflowEntityEntry() + { + } + + public DialogflowEntityEntry(string value, List synonyms) + { + this.Value = value; + this.Synonyms = synonyms; + } + + public DialogflowEntityEntry(string value, string[] synonyms) : this(value, new List(synonyms)) + { + + } + } +} diff --git a/Bot.Rasa/Adapters/Dialogflow/DialogflowIntent.cs b/Bot.Rasa/Adapters/Dialogflow/DialogflowIntent.cs new file mode 100644 index 00000000..2a6e2174 --- /dev/null +++ b/Bot.Rasa/Adapters/Dialogflow/DialogflowIntent.cs @@ -0,0 +1,25 @@ +using Bot.Rasa.Intents; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Adapters.Dialogflow +{ + public class DialogflowIntent + { + public string Id { get; set; } + public string Name { get; set; } + public bool Auto { get; set; } + /// + /// Input Contexts + /// + public List ContextList { get; set; } + + public List UserSays { get; set; } + public List Responses { get; set; } + public int Priority { get; set; } + public bool WebhookUsed { get; set; } + public bool FallbackIntent { get; set; } + public List Events { get; set; } + } +} diff --git a/Bot.Rasa/Adapters/Dialogflow/DialogflowIntentEvent.cs b/Bot.Rasa/Adapters/Dialogflow/DialogflowIntentEvent.cs new file mode 100644 index 00000000..9d01da1a --- /dev/null +++ b/Bot.Rasa/Adapters/Dialogflow/DialogflowIntentEvent.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Adapters.Dialogflow +{ + public class DialogflowIntentEvent + { + public string Name { get; set; } + } +} diff --git a/Bot.Rasa/Adapters/Dialogflow/DialogflowIntentExpression.cs b/Bot.Rasa/Adapters/Dialogflow/DialogflowIntentExpression.cs new file mode 100644 index 00000000..7d240d56 --- /dev/null +++ b/Bot.Rasa/Adapters/Dialogflow/DialogflowIntentExpression.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Adapters.Dialogflow +{ + public class DialogflowIntentExpression + { + public String Id { get; set; } + public List Data { get; set; } + public Boolean IsTemplate { get; set; } + } +} diff --git a/Bot.Rasa/Adapters/Dialogflow/DialogflowIntentExpressionPart.cs b/Bot.Rasa/Adapters/Dialogflow/DialogflowIntentExpressionPart.cs new file mode 100644 index 00000000..af2106dd --- /dev/null +++ b/Bot.Rasa/Adapters/Dialogflow/DialogflowIntentExpressionPart.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Adapters.Dialogflow +{ + public class DialogflowIntentExpressionPart + { + public String Text { get; set; } + public String Alias { get; set; } + public String Meta { get; set; } + public Boolean UserDefined { get; set; } + } +} diff --git a/Bot.Rasa/Adapters/Dialogflow/DialogflowIntentResponse.cs b/Bot.Rasa/Adapters/Dialogflow/DialogflowIntentResponse.cs new file mode 100644 index 00000000..9a30b51a --- /dev/null +++ b/Bot.Rasa/Adapters/Dialogflow/DialogflowIntentResponse.cs @@ -0,0 +1,26 @@ +using Bot.Rasa.Intents; +using Bot.Rasa.Models; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Adapters.Dialogflow +{ + public class DialogflowIntentResponse + { + public string Id { get; set; } + public bool ResetContexts { get; set; } + public string Action { get; set; } + + public List AffectedContexts { get; set; } + + public List Parameters { get; set; } + + public List MessageList { get; set; } + + public DialogflowIntentResponse() + { + Id = Guid.NewGuid().ToString(); + } + } +} diff --git a/Bot.Rasa/Adapters/Dialogflow/DialogflowIntentResponseMessage.cs b/Bot.Rasa/Adapters/Dialogflow/DialogflowIntentResponseMessage.cs new file mode 100644 index 00000000..0c3918c3 --- /dev/null +++ b/Bot.Rasa/Adapters/Dialogflow/DialogflowIntentResponseMessage.cs @@ -0,0 +1,14 @@ +using Bot.Rasa.Models; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Adapters.Dialogflow +{ + public class DialogflowIntentResponseMessage : AIResponseMessageBase + { + public string Lang { get; set; } + public Object Speech { get; set; } + public Object Payload { get; set; } + } +} diff --git a/Bot.Rasa/Adapters/Dialogflow/DialogflowIntentResponseParameter.cs b/Bot.Rasa/Adapters/Dialogflow/DialogflowIntentResponseParameter.cs new file mode 100644 index 00000000..a047f184 --- /dev/null +++ b/Bot.Rasa/Adapters/Dialogflow/DialogflowIntentResponseParameter.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Adapters.Dialogflow +{ + public class DialogflowIntentResponseParameter + { + public string Id { get; set; } + public bool Required { get; set; } + public string DataType { get; set; } + public string Name { get; set; } + public string Value { get; set; } + public bool IsList { get; set; } + } +} diff --git a/Bot.Rasa/Agents/Agent.cs b/Bot.Rasa/Agents/Agent.cs index 696e933c..3affadbd 100644 --- a/Bot.Rasa/Agents/Agent.cs +++ b/Bot.Rasa/Agents/Agent.cs @@ -16,11 +16,17 @@ namespace Bot.Rasa.Agents [MaxLength(64)] public String Name { get; set; } + public String Description { get; set; } + + public Boolean Published { get; set; } + + public String Language { get; set; } + [ForeignKey("AgentId")] public List Intents { get; set; } [ForeignKey("AgentId")] [JsonProperty("entity_types")] - public List EntityTypes { get; set; } + public List Entities { get; set; } } } diff --git a/Bot.Rasa/Agents/AgentExtension.cs b/Bot.Rasa/Agents/AgentExtension.cs index b35dc6ee..7a659b0e 100644 --- a/Bot.Rasa/Agents/AgentExtension.cs +++ b/Bot.Rasa/Agents/AgentExtension.cs @@ -23,29 +23,45 @@ namespace Bot.Rasa.Agents return dc.Table().Find(agentId); } - public static String CreateEntity(this Agent agent, EntityType entity, Database dc) + public static String CreateEntity(this Agent agent, Entity entity, Database dc) { return entity.Id; } - public static RasaTrainingData GrabCorpus(this Agent agent, Database dc) + public static RasaTrainingData GrabCorpus(this Agent agent, Database dc, List ctx) { var trainingData = new RasaTrainingData { UserSays = new List() }; - var intents = dc.Table().Include(x => x.Expressions).ToList(); + var intents = dc.Table() + .Include(x => x.Contexts) + .Include(x => x.UserSays).ThenInclude(say => say.Data).ToList(); - intents.ForEach(intent => { + var contexts = ctx.OrderBy(x => x.Name).Select(x => x.Name.ToLower()).ToList(); - trainingData.UserSays.AddRange(intent.Expressions + // search all potential intents which input context included in contexts + intents = intents.Where(it => + { + if (contexts.Count == 0) + { + return it.Contexts.Count() == 0; + } + else + { + return it.Contexts.Count() > 0 && it.Contexts.Count(x => contexts.Contains(x.Name.ToLower())) == it.Contexts.Count; + } + }).OrderByDescending(x => x.Contexts.Count).ToList(); + + intents.ForEach(intent => + { + trainingData.UserSays.AddRange(intent.UserSays .Select(exp => new UserSay { Intent = intent.Name, - Text = exp.Text + Text = String.Join("", exp.Data.OrderBy(x => x.UpdatedTime).Select(x => x.Text)) })); - }); return trainingData; diff --git a/Bot.Rasa/Agents/AgentResponse.cs b/Bot.Rasa/Agents/AgentResponse.cs deleted file mode 100644 index 48f79116..00000000 --- a/Bot.Rasa/Agents/AgentResponse.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Bot.Rasa.Intents; -using EntityFrameworkCore.BootKit; -using System; -using System.Collections.Generic; -using System.Text; - -namespace Bot.Rasa.Agents -{ - public class AgentResponse - { - public AgentResponseIntent Intent { get; set; } - - public String Text { get; set; } - } - - public class AgentResponseIntent - { - public String Name { get; set; } - - public Decimal Confidence { get; set; } - } -} diff --git a/Bot.Rasa/Bot.Rasa.csproj b/Bot.Rasa/Bot.Rasa.csproj index 6528de66..6f6487ac 100644 --- a/Bot.Rasa/Bot.Rasa.csproj +++ b/Bot.Rasa/Bot.Rasa.csproj @@ -12,4 +12,9 @@ + + + + + diff --git a/Bot.Rasa/Consoles/AgentImporterInDialogflow.cs b/Bot.Rasa/Consoles/AgentImporterInDialogflow.cs new file mode 100644 index 00000000..4a3e9b1b --- /dev/null +++ b/Bot.Rasa/Consoles/AgentImporterInDialogflow.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Bot.Rasa.Adapters.Dialogflow; +using Bot.Rasa.Agents; +using Bot.Rasa.Entities; +using Bot.Rasa.Intents; +using Bot.Rasa.Models; +using DotNetToolkit; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Bot.Rasa.Consoles +{ + public class AgentImporterInDialogflow : IAgentImporter + { + public Agent LoadAgent(string agentId, string agentDir) + { + // load agent profile + string data = File.ReadAllText($"{agentDir}\\Dialogflow\\{agentId}\\agent.json"); + var agent = JsonConvert.DeserializeObject(data); + agent.Id = Guid.NewGuid().ToString(); + agent.Name = agentId; + + return agent.ToObject(); + } + + public void LoadEntities(Agent agent, string agentDir) + { + agent.Entities = new List(); + + Directory.EnumerateFiles($"{agentDir}\\Dialogflow\\{agent.Name}\\entities") + .ToList() + .ForEach(fileName => + { + string entityName = fileName.Split('\\').Last(); + if (!entityName.Contains("_")) + { + string entityJson = File.ReadAllText($"{fileName}"); + var entity = JsonConvert.DeserializeObject(entityJson); + + // load entries + string entriesFileName = fileName.Replace(entity.Name, $"{entity.Name}_entries_{agent.Language}"); + if (File.Exists(entriesFileName)) + { + string entriesJson = File.ReadAllText($"{entriesFileName}"); + entity.Entries = JsonConvert.DeserializeObject>(entriesJson); + } + + agent.Entities.Add(entity.ToObject()); + } + }); + } + + public void LoadIntents(Agent agent, string agentDir) + { + agent.Intents = new List(); + + Directory.EnumerateFiles($"{agentDir}\\Dialogflow\\{agent.Name}\\intents") + .ToList() + .ForEach(fileName => + { + if (!fileName.Contains("_usersays_" + agent.Language)) + { + string intentJson = File.ReadAllText($"{fileName}"); + + // avoid confict data structure + intentJson = intentJson.Replace("\"contexts\":", "\"contextList\":"); + intentJson = intentJson.Replace("\"messages\":", "\"messageList\":"); + + var intent = JsonConvert.DeserializeObject(intentJson); + + // load user expressions + string expressionFileName = fileName.Replace(intent.Name, $"{intent.Name}_usersays_{agent.Language}"); + if (File.Exists(expressionFileName)) + { + string expressionJson = File.ReadAllText($"{expressionFileName}"); + intent.UserSays = JsonConvert.DeserializeObject>(expressionJson); + } + + var newIntent = intent.ToObject(); + intent.Responses.ForEach(res => + { + var newResponse = newIntent.Responses.First(x => x.Id == res.Id); + + newResponse.Contexts = res.AffectedContexts.Select(x => new IntentResponseContext { + Name = x.Name, + Lifespan = x.Lifespan + }).ToList(); + + newResponse.Messages = res.MessageList.Where(x => x.Speech != null || x.Payload != null) + .Select(x => + { + if(x.Type == AIResponseMessageType.Custom) + { + return new IntentResponseMessage + { + Lang = x.Lang, + Payload = JsonConvert.SerializeObject(x.Payload), + Type = x.Type + }; + } else + { + var speech = JsonConvert.SerializeObject(x.Speech.GetType().Equals(typeof(String)) ? + new List { x.Speech.ToString() } : + (x.Speech as JArray).Select(s => s.Value()).ToList()); + + return new IntentResponseMessage + { + Lang = x.Lang, + Speech = speech, + Type = x.Type + }; + } + + }).ToList(); + }); + + newIntent.Contexts = intent.ContextList.Select(x => new IntentInputContext { Name = x }).ToList(); + + agent.Intents.Add(newIntent); + } + }); + } + } +} diff --git a/Bot.Rasa/Consoles/IAgentImporter.cs b/Bot.Rasa/Consoles/IAgentImporter.cs new file mode 100644 index 00000000..947654c8 --- /dev/null +++ b/Bot.Rasa/Consoles/IAgentImporter.cs @@ -0,0 +1,22 @@ +using Bot.Rasa.Agents; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Consoles +{ + public interface IAgentImporter + { + /// + /// Load agent summary + /// + /// agent guid or name + /// + /// + Agent LoadAgent(string agentId, string agentDir); + + void LoadEntities(Agent agent, string agentDir); + + void LoadIntents(Agent agent, string agentDir); + } +} diff --git a/Bot.Rasa/Consoles/RasaAi.cs b/Bot.Rasa/Consoles/RasaAi.cs new file mode 100644 index 00000000..2fb1e00f --- /dev/null +++ b/Bot.Rasa/Consoles/RasaAi.cs @@ -0,0 +1,86 @@ +using Bot.Rasa.Agents; +using Bot.Rasa.Entities; +using EntityFrameworkCore.BootKit; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace Bot.Rasa.Consoles +{ + public class RasaAi + { + private Database dc { get; set; } + public static RasaOptions Options { get; set; } + public static IConfiguration Configuration { get; set; } + + public Agent agent { get; set; } + + public String SessionId { get; set; } + + public RasaAi(Database dc) + { + this.dc = dc; + SessionId = Guid.NewGuid().ToString(); + } + + /// + /// Restore a agent instance from backup json files + /// + /// + /// + /// + public Agent RestoreAgent(IAgentImporter importer, String agentId) + { + string dataDir = $"{Options.ContentRootPath}\\App_Data\\DbInitializer\\Agents\\"; + + // Load agent summary + agent = importer.LoadAgent(agentId, dataDir); + + // Load agent entities + importer.LoadEntities(agent, dataDir); + + // Load agent intents + importer.LoadIntents(agent, dataDir); + + return agent; + } + + /// + /// Dump agent train data to json file + /// + /// + /// + public bool DumpAgent(String agentId) + { + return true; + } + + public Agent LoadAgent(String agentId) + { + return dc.Table() + .Include(x => x.Intents).ThenInclude(x => x.Contexts) + .FirstOrDefault(x => x.Id == agentId); + } + + public String SaveAgent(Agent agent) + { + var existedAgent = dc.Table().FirstOrDefault(x => x.Id == agent.Id || x.Name == agent.Name); + if (existedAgent == null) + { + dc.Table().Add(agent); + return agent.Id; + } + else + { + agent.Id = existedAgent.Id; + return existedAgent.Id; + } + } + } +} diff --git a/Bot.Rasa/Consoles/RasaConsole.cs b/Bot.Rasa/Consoles/RasaConsole.cs deleted file mode 100644 index 41d8115f..00000000 --- a/Bot.Rasa/Consoles/RasaConsole.cs +++ /dev/null @@ -1,81 +0,0 @@ -using Bot.Rasa.Agents; -using Bot.Rasa.Entities; -using EntityFrameworkCore.BootKit; -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Configuration; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; - -namespace Bot.Rasa.Consoles -{ - public class RasaConsole - { - private Database dc { get; set; } - public static RasaOptions Options { get; set; } - public static IConfiguration Configuration { get; set; } - - public RasaConsole(Database dc) - { - this.dc = dc; - } - - /// - /// Restore a agent instance from json file - /// - /// - /// - public Agent RestoreAgent(String agentId) - { - string json = File.ReadAllText($"{Options.ContentRootPath}\\App_Data\\DbInitializer\\Agents\\{agentId}.json"); - var agent = JsonConvert.DeserializeObject(json); - - agent.Id = agentId; - agent.EntityTypes.ForEach(entityType => - { - entityType.Items = entityType.Values.Select(x => new EntityItem - { - Value = x - }).ToList(); - }); - - agent.Intents.ForEach(intent => { - - intent.Expressions.ForEach(expression => - { - }); - - }); - - return agent; - } - - /// - /// Dump agent train data to json file - /// - /// - /// - public bool DumpAgent(String agentId) - { - return true; - } - - public Agent LoadAgent(String agentId) - { - return dc.Table().Include(x => x.Intents).FirstOrDefault(x => x.Id == agentId); - } - - public String CreateAgent(Agent agent) - { - if (dc.Table().Any(x => x.Id == agent.Id)) return String.Empty; - - dc.Table().Add(agent); - - return agent.Id; - } - } -} diff --git a/Bot.Rasa/Consoles/RequestExtension.cs b/Bot.Rasa/Consoles/RequestExtension.cs index 4ea225c7..c0b4317a 100644 --- a/Bot.Rasa/Consoles/RequestExtension.cs +++ b/Bot.Rasa/Consoles/RequestExtension.cs @@ -1,33 +1,167 @@ using Bot.Rasa.Agents; +using Bot.Rasa.Intents; using Bot.Rasa.Models; +using Bot.Rasa.Sessions; +using DotNetToolkit; using EntityFrameworkCore.BootKit; +using Microsoft.EntityFrameworkCore; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using RestSharp; using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text; namespace Bot.Rasa.Consoles { public static class RequestExtension { - public static AgentResponse TextRequest(this RasaConsole console, String agentId, String text) + public static AIResponse TextRequest(this RasaAi rasa, Database dc, AIRequest request) { - var client = new RestClient($"{RasaConsole.Options.HostUrl}"); + AIResponse aiResponse = new AIResponse(); + RasaResponse response = null; - var request = new RestRequest("parse", Method.POST); - string json = JsonConvert.SerializeObject(new { Project = agentId, Q = text }, - new JsonSerializerSettings + // Merge input contexts + var contexts = dc.Table() + .Where(x => x.SessionId == rasa.SessionId && x.Lifespan > 0) + .ToList() + .Select(x => new AIContext { Name = x.Context.ToLower(), Lifespan = x.Lifespan }) + .ToList(); + + contexts.AddRange(request.Contexts.Select(x => new AIContext { Name = x.Name.ToLower(), Lifespan = x.Lifespan })); + contexts = contexts.OrderBy(x => x.Name).ToList(); + + // search all potential intents which input context included in contexts + var intents = rasa.agent.Intents.Where(it => + { + if (contexts.Count == 0) { - ContractResolver = new CamelCasePropertyNamesContractResolver() + return it.Contexts.Count() == 0; + } + else + { + return it.Contexts.Count() > 0 && + it.Contexts.Count(x => contexts.Select(ctx => ctx.Name).Contains(x.Name.ToLower())) == it.Contexts.Count; + } + }).OrderByDescending(x => x.Contexts.Count).ToList(); + + // Max contexts match + foreach(var it in intents) + { + request.Contexts = it.Contexts.Select(x => new AIContext { Name = x.Name.ToLower() }) + .OrderBy(x => x.Name) + .ToList(); + string contextId = $"{String.Join(',', request.Contexts.Select(x => x.Name))}".GetMd5Hash(); + + string modelName = dc.Table().FirstOrDefault(x => x.ContextId == contextId)?.ModelName; + + // need training + if (String.IsNullOrEmpty(modelName)) + { + dc.DbTran(() => + { + modelName = TrainWithContexts(rasa, dc, request, contextId); + }); + } + + var client = new RestClient($"{RasaAi.Options.HostUrl}"); + + var rest = new RestRequest("parse", Method.POST); + string json = JsonConvert.SerializeObject(new { Project = rasa.agent.Id, Q = request.Query.First(), Model = modelName }, + new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }); + rest.AddParameter("application/json", json, ParameterType.RequestBody); + + var result = client.Execute(rest); + + if(result.Data.Intent != null) + { + response = result.Data; + break; + } + }; + + var intent = (dc.Table().Where(x => x.Name == response.Intent.Name) + .Include(x => x.Responses).ThenInclude(x => x.Contexts) + .Include(x => x.Responses).ThenInclude(x => x.Parameters) + .Include(x => x.Responses).ThenInclude(x => x.Messages)).First(); + + var intentResponse = ArrayHelper.GetRandom(intent.Responses); + aiResponse.Id = Guid.NewGuid().ToString(); + aiResponse.Lang = rasa.agent.Language; + aiResponse.Status = new AIResponseStatus { }; + aiResponse.SessionId = rasa.SessionId; + aiResponse.Timestamp = DateTime.UtcNow; + intentResponse.Messages.Where(x => x.Type == AIResponseMessageType.Text) + .ToList() + .ForEach(msg => + { + msg.Speech = ArrayHelper.GetRandom(msg.Speech.Substring(2, msg.Speech.Length - 4).Split("\",\"").ToList()); }); - request.AddParameter("application/json", json, ParameterType.RequestBody); - var response = client.Execute(request); + aiResponse.Result = new AIResponseResult + { + Source = "agent", + ResolvedQuery = request.Query.First(), + Action = intentResponse.Action, + Parameters = new Dictionary(), + Score = response.Intent.Confidence, + Metadata = new AIResponseMetadata { IntentId = intent.Id, IntentName = intent.Name }, + Fulfillment = new AIResponseFulfillment + { + Messages = intentResponse.Messages.Select(x => (object)x).ToList() + } + }; - return response.Data; + // Merge context lifespan + // override if exists, otherwise add, delete if lifespan is zero + dc.DbTran(() => + { + var sessionContexts = dc.Table().Where(x => x.SessionId == rasa.SessionId).ToList(); + + // minus 1 round + sessionContexts.Where(x => !intentResponse.Contexts.Select(ctx => ctx.Name).Contains(x.Context)) + .ToList() + .ForEach(ctx => ctx.Lifespan = ctx.Lifespan - 1); + + intentResponse.Contexts.ForEach(ctx => + { + var session1 = sessionContexts.FirstOrDefault(x => x.Context == ctx.Name); + + if (session1 != null) + { + if (ctx.Lifespan == 0) + { + dc.Table().Remove(session1); + } + else + { + session1.Lifespan = ctx.Lifespan; + } + } + else + { + dc.Table().Add(new SessionContext + { + SessionId = rasa.SessionId, + Context = ctx.Name, + Lifespan = ctx.Lifespan + }); + } + }); + }); + + aiResponse.Result.Contexts = dc.Table() + .Where(x => x.SessionId == rasa.SessionId) + .Select(x => new AIContext { Name = x.Context, Lifespan = x.Lifespan }) + .ToArray(); + + return aiResponse; } /// @@ -35,12 +169,30 @@ namespace Bot.Rasa.Consoles /// /// /// - /// + /// + /// /// - public static bool Train(this RasaConsole console, Database dc, String agentId) + public static string TrainWithContexts(this RasaAi console, Database dc, AIRequest request, String contextId) { - var agent = dc.Table().Find(agentId); - var corpus = agent.GrabCorpus(dc); + var corpus = console.agent.GrabCorpus(dc, request.Contexts); + + corpus.UserSays.Add(new UserSay + { + Intent = "Welcome", + Text = "Hi" + }); + + corpus.UserSays.Add(new UserSay + { + Intent = "Welcome", + Text = "Hey" + }); + + corpus.UserSays.Add(new UserSay + { + Intent = "Welcome", + Text = "Hello" + }); string json = JsonConvert.SerializeObject(new { rasa_nlu_data = corpus }, new JsonSerializerSettings @@ -48,14 +200,33 @@ namespace Bot.Rasa.Consoles ContractResolver = new CamelCasePropertyNamesContractResolver() }); - var client = new RestClient($"{RasaConsole.Options.HostUrl}"); - var request = new RestRequest("train", Method.POST); - request.AddQueryParameter("project", agentId); - request.AddParameter("application/json", json, ParameterType.RequestBody); + var client = new RestClient($"{RasaAi.Options.HostUrl}"); + var rest = new RestRequest("train", Method.POST); + rest.AddQueryParameter("project", console.agent.Id); + rest.AddParameter("application/json", json, ParameterType.RequestBody); - var response = client.Execute(request); + var response = client.Execute(rest); + var result = JObject.Parse(response.Content); - return response.IsSuccessful; + if (response.IsSuccessful) + { + string modelName = result["info"].Value().Split(": ")[1]; + + dc.Table().Add(new ContextModelMapping + { + AgentId = console.agent.Id, + ModelName = modelName, + ContextId = contextId + }); + + return modelName; + } + else + { + Console.WriteLine(result["error"]); + + return String.Empty; + } } } } diff --git a/Bot.Rasa/Entities/EntityType.cs b/Bot.Rasa/Entities/Entity.cs similarity index 74% rename from Bot.Rasa/Entities/EntityType.cs rename to Bot.Rasa/Entities/Entity.cs index ca2838f5..873561fc 100644 --- a/Bot.Rasa/Entities/EntityType.cs +++ b/Bot.Rasa/Entities/Entity.cs @@ -7,8 +7,8 @@ using System.Text; namespace Bot.Rasa.Entities { - [Table("Bot_EntityType")] - public class EntityType : DbRecord, IDbRecord + [Table("Bot_Entity")] + public class Entity : DbRecord, IDbRecord { [Required] [StringLength(36)] @@ -21,7 +21,7 @@ namespace Bot.Rasa.Entities [NotMapped] public List Values { get; set; } - [ForeignKey("EntityTypeId")] - public List Items { get; set; } + [ForeignKey("EntityId")] + public List Entries { get; set; } } } diff --git a/Bot.Rasa/Entities/EntityItem.cs b/Bot.Rasa/Entities/EntityEntry.cs similarity index 77% rename from Bot.Rasa/Entities/EntityItem.cs rename to Bot.Rasa/Entities/EntityEntry.cs index bc721353..cab85188 100644 --- a/Bot.Rasa/Entities/EntityItem.cs +++ b/Bot.Rasa/Entities/EntityEntry.cs @@ -7,12 +7,12 @@ using System.Text; namespace Bot.Rasa.Entities { - [Table("Bot_EntityItem")] - public class EntityItem : DbRecord, IDbRecord + [Table("Bot_EntityEntry")] + public class EntityEntry : DbRecord, IDbRecord { [Required] [StringLength(36)] - public String EntityTypeId { get; set; } + public String EntityId { get; set; } [MaxLength(128)] public String Value { get; set; } diff --git a/Bot.Rasa/Entities/EntityTypeExtension.cs b/Bot.Rasa/Entities/EntityTypeExtension.cs index cefde8b0..d05a26c6 100644 --- a/Bot.Rasa/Entities/EntityTypeExtension.cs +++ b/Bot.Rasa/Entities/EntityTypeExtension.cs @@ -9,21 +9,21 @@ namespace Bot.Rasa.Entities { public static class EntityTypeExtension { - public static string CreateEntityType(this Agent agent, Database dc, EntityType entityType) + public static string CreateEntityType(this Agent agent, Database dc, Entity entityType) { - if (dc.Table().Any(x => x.Name == entityType.Name && x.AgentId == agent.Id)) return agent.Id; + if (dc.Table().Any(x => x.Name == entityType.Name && x.AgentId == agent.Id)) return agent.Id; - dc.Table().Add(entityType); + dc.Table().Add(entityType); return entityType.Id; } public static void DeleteEntityType(this Agent agent, Database dc, String entityTypeId) { - var entityType = dc.Table().FirstOrDefault(x => x.Id == entityTypeId); + var entityType = dc.Table().FirstOrDefault(x => x.Id == entityTypeId); if (entityType == null) return; - dc.Table().Remove(entityType); + dc.Table().Remove(entityType); } } } diff --git a/Bot.Rasa/Expressions/EntitiyOfSpeech.cs b/Bot.Rasa/Expressions/IntentExpressionPart.cs similarity index 58% rename from Bot.Rasa/Expressions/EntitiyOfSpeech.cs rename to Bot.Rasa/Expressions/IntentExpressionPart.cs index 71efad2c..cdb01845 100644 --- a/Bot.Rasa/Expressions/EntitiyOfSpeech.cs +++ b/Bot.Rasa/Expressions/IntentExpressionPart.cs @@ -7,21 +7,23 @@ using System.Text; namespace Bot.Rasa.Expressions { - [Table("Bot_EntityOfSpeech")] - public class EntitiyOfSpeech : DbRecord, IDbRecord + [Table("Bot_IntentExpressionPart")] + public class IntentExpressionPart : DbRecord, IDbRecord { [Required] [StringLength(36)] public String ExpressionId { get; set; } - public int Start { get; set; } - [Required] [MaxLength(128)] - public String Value { get; set; } + public String Text { get; set; } - [Required] [MaxLength(64)] - public String Entity { get; set; } + public String Alias { get; set; } + + [MaxLength(64)] + public String Meta { get; set; } + + public Boolean UserDefined { get; set; } } } diff --git a/Bot.Rasa/Intents/ContextModelMapping.cs b/Bot.Rasa/Intents/ContextModelMapping.cs new file mode 100644 index 00000000..331436e5 --- /dev/null +++ b/Bot.Rasa/Intents/ContextModelMapping.cs @@ -0,0 +1,25 @@ +using EntityFrameworkCore.BootKit; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text; + +namespace Bot.Rasa.Intents +{ + [Table("Bot_ContextModelMapping")] + public class ContextModelMapping : DbRecord, IDbRecord + { + [Required] + [StringLength(36)] + public String AgentId { get; set; } + + [Required] + [StringLength(32)] + public string ContextId { get; set; } + + [Required] + [StringLength(21)] + public string ModelName { get; set; } + } +} diff --git a/Bot.Rasa/Intents/Intent.cs b/Bot.Rasa/Intents/Intent.cs index 0a39a823..7b7df918 100644 --- a/Bot.Rasa/Intents/Intent.cs +++ b/Bot.Rasa/Intents/Intent.cs @@ -24,6 +24,12 @@ namespace Bot.Rasa.Intents public String Description { get; set; } [ForeignKey("IntentId")] - public List Expressions { get; set; } + public List Contexts { get; set; } + + [ForeignKey("IntentId")] + public List UserSays { get; set; } + + [ForeignKey("IntentId")] + public List Responses { get; set; } } } diff --git a/Bot.Rasa/Intents/IntentExpression.cs b/Bot.Rasa/Intents/IntentExpression.cs index 73b5ef84..0099a514 100644 --- a/Bot.Rasa/Intents/IntentExpression.cs +++ b/Bot.Rasa/Intents/IntentExpression.cs @@ -15,23 +15,19 @@ namespace Bot.Rasa.Intents { public IntentExpression() { - Entities = new List(); + Data = new List(); } [Required] [StringLength(36)] public String IntentId { get; set; } - [Required] - [MaxLength(128)] - public String Text { get; set; } - [ForeignKey("ExpressionId")] - public List Entities { get; set; } + public List Data { get; set; } public bool IsExist(Database dc) { - return dc.Table().Any(x => x.IntentId == IntentId && x.Text == Text); + return dc.Table().Any(x => x.IntentId == IntentId); } } } diff --git a/Bot.Rasa/Intents/IntentInputContext.cs b/Bot.Rasa/Intents/IntentInputContext.cs new file mode 100644 index 00000000..41fb966f --- /dev/null +++ b/Bot.Rasa/Intents/IntentInputContext.cs @@ -0,0 +1,21 @@ +using EntityFrameworkCore.BootKit; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text; + +namespace Bot.Rasa.Intents +{ + [Table("Bot_IntentInputContext")] + public class IntentInputContext : DbRecord, IDbRecord + { + [Required] + [StringLength(36)] + public String IntentId { get; set; } + + [Required] + [MaxLength(64)] + public string Name { get; set; } + } +} diff --git a/Bot.Rasa/Intents/IntentResponse.cs b/Bot.Rasa/Intents/IntentResponse.cs new file mode 100644 index 00000000..2e26fb8f --- /dev/null +++ b/Bot.Rasa/Intents/IntentResponse.cs @@ -0,0 +1,31 @@ +using EntityFrameworkCore.BootKit; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text; + +namespace Bot.Rasa.Intents +{ + [Table("Bot_IntentResponse")] + public class IntentResponse : DbRecord, IDbRecord + { + [Required] + [StringLength(36)] + public String IntentId { get; set; } + + [MaxLength(128)] + public String Action { get; set; } + + public Boolean ResetContexts { get; set; } + + [ForeignKey("IntentResponseId")] + public List Contexts { get; set; } + + [ForeignKey("IntentResponseId")] + public List Parameters { get; set; } + + [ForeignKey("IntentResponseId")] + public List Messages { get; set; } + } +} diff --git a/Bot.Rasa/Intents/IntentResponseContext.cs b/Bot.Rasa/Intents/IntentResponseContext.cs new file mode 100644 index 00000000..949d150e --- /dev/null +++ b/Bot.Rasa/Intents/IntentResponseContext.cs @@ -0,0 +1,22 @@ +using EntityFrameworkCore.BootKit; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text; + +namespace Bot.Rasa.Intents +{ + [Table("Bot_IntentResponseContext")] + public class IntentResponseContext : DbRecord, IDbRecord + { + [Required] + [StringLength(36)] + public String IntentResponseId { get; set; } + + [MaxLength(64)] + public string Name { get; set; } + + public int Lifespan { get; set; } + } +} diff --git a/Bot.Rasa/Intents/IntentResponseMessage.cs b/Bot.Rasa/Intents/IntentResponseMessage.cs new file mode 100644 index 00000000..5186d244 --- /dev/null +++ b/Bot.Rasa/Intents/IntentResponseMessage.cs @@ -0,0 +1,36 @@ +using Bot.Rasa.Models; +using EntityFrameworkCore.BootKit; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text; + +namespace Bot.Rasa.Intents +{ + [Table("Bot_IntentResponseMessage")] + public class IntentResponseMessage : DbRecord, IDbRecord + { + [Required] + [StringLength(36)] + public String IntentResponseId { get; set; } + + public AIResponseMessageType Type { get; set; } + + [Required] + [MaxLength(3)] + public String Lang { get; set; } + + /// + /// json list data + /// + [MaxLength(1024)] + public String Speech { get; set; } + + /// + /// custom json payload + /// + [MaxLength(1024)] + public String Payload { get; set; } + } +} diff --git a/Bot.Rasa/Intents/IntentResponseParameter.cs b/Bot.Rasa/Intents/IntentResponseParameter.cs new file mode 100644 index 00000000..34284f09 --- /dev/null +++ b/Bot.Rasa/Intents/IntentResponseParameter.cs @@ -0,0 +1,22 @@ +using EntityFrameworkCore.BootKit; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text; + +namespace Bot.Rasa.Intents +{ + [Table("Bot_IntentResponseParameter")] + public class IntentResponseParameter : DbRecord, IDbRecord + { + [Required] + [StringLength(36)] + public String IntentResponseId { get; set; } + public bool Required { get; set; } + public string DataType { get; set; } + public string Name { get; set; } + public string Value { get; set; } + public bool IsList { get; set; } + } +} diff --git a/Bot.Rasa/Models/AIContext.cs b/Bot.Rasa/Models/AIContext.cs new file mode 100644 index 00000000..997eed1e --- /dev/null +++ b/Bot.Rasa/Models/AIContext.cs @@ -0,0 +1,27 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Models +{ + [JsonObject] + public class AIContext + { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("parameters")] + public Dictionary Parameters { get; set; } + + /// + /// Lifespan of the context measured in requests```` + /// + [JsonProperty("lifespan")] + public int Lifespan { get; set; } + + public AIContext() + { + } + } +} diff --git a/Bot.Rasa/Models/AIRequest.cs b/Bot.Rasa/Models/AIRequest.cs new file mode 100644 index 00000000..064697f1 --- /dev/null +++ b/Bot.Rasa/Models/AIRequest.cs @@ -0,0 +1,46 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Models +{ + [JsonObject] + public class AIRequest : QuestionMetadata + { + [JsonProperty("query")] + public string[] Query { get; set; } + + [JsonProperty("confidence")] + public float[] Confidence { get; set; } + + [JsonProperty("contexts")] + public List Contexts { get; set; } + + [JsonProperty("resetContexts")] + public bool? ResetContexts { get; set; } + + [JsonProperty("originalRequest")] + public OriginalRequest OriginalRequest { get; set; } + + public AIRequest() + { + Contexts = new List(); + } + + public AIRequest(string text) + { + Query = new string[] { text }; + Confidence = new float[] { 1.0f }; + } + + public AIRequest(string text, RequestExtras requestExtras) : this(text) + { + if (requestExtras != null) + { + requestExtras.CopyTo(this); + } + } + + } +} diff --git a/Bot.Rasa/Models/AIResponse.cs b/Bot.Rasa/Models/AIResponse.cs new file mode 100644 index 00000000..0350b45c --- /dev/null +++ b/Bot.Rasa/Models/AIResponse.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Models +{ + public class AIResponse + { + public string Id { get; set; } + + public DateTime Timestamp { get; set; } + + public string Lang { get; set; } + + public AIResponseResult Result { get; set; } + + public AIResponseStatus Status { get; set; } + + public string SessionId { get; set; } + + public bool IsError + { + get + { + if (Status != null && Status.Code >= 400) + { + return true; + } + + return false; + } + } + } +} diff --git a/Bot.Rasa/Models/AIResponseCustomPayload.cs b/Bot.Rasa/Models/AIResponseCustomPayload.cs new file mode 100644 index 00000000..e769b6fe --- /dev/null +++ b/Bot.Rasa/Models/AIResponseCustomPayload.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Models +{ + public class AIResponseCustomPayload : AIResponseMessageBase + { + public string Task { get; set; } + + public Object Body { get; set; } + } +} diff --git a/Bot.Rasa/Models/AIResponseFulfillment.cs b/Bot.Rasa/Models/AIResponseFulfillment.cs new file mode 100644 index 00000000..752573d8 --- /dev/null +++ b/Bot.Rasa/Models/AIResponseFulfillment.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Models +{ + public class AIResponseFulfillment + { + public string Speech { get; set; } + + public List Messages { get; set; } + } +} diff --git a/Bot.Rasa/Models/AIResponseMessageBase.cs b/Bot.Rasa/Models/AIResponseMessageBase.cs new file mode 100644 index 00000000..d7b70842 --- /dev/null +++ b/Bot.Rasa/Models/AIResponseMessageBase.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Models +{ + public class AIResponseMessageBase + { + public AIResponseMessageType Type { get; set; } + } +} diff --git a/Bot.Rasa/Models/AIResponseMessageType.cs b/Bot.Rasa/Models/AIResponseMessageType.cs new file mode 100644 index 00000000..5c7e3f87 --- /dev/null +++ b/Bot.Rasa/Models/AIResponseMessageType.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Models +{ + public enum AIResponseMessageType + { + Text = 0, + Custom = 4 + } +} diff --git a/Bot.Rasa/Models/AIResponseMetadata.cs b/Bot.Rasa/Models/AIResponseMetadata.cs new file mode 100644 index 00000000..23fb1e1b --- /dev/null +++ b/Bot.Rasa/Models/AIResponseMetadata.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Models +{ + public class AIResponseMetadata + { + public string IntentId { get; set; } + public string IntentName { get; set; } + } +} diff --git a/Bot.Rasa/Models/AIResponseResult.cs b/Bot.Rasa/Models/AIResponseResult.cs new file mode 100644 index 00000000..b59f2bd6 --- /dev/null +++ b/Bot.Rasa/Models/AIResponseResult.cs @@ -0,0 +1,148 @@ +using Bot.Rasa.Intents; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace Bot.Rasa.Models +{ + public class AIResponseResult + { + String action; + + public Boolean ActionIncomplete { get; set; } + + public String Action + { + get + { + if (string.IsNullOrEmpty(action)) + { + return string.Empty; + } + return action; + } + set + { + action = value; + } + } + + public Dictionary Parameters { get; set; } + + public AIContext[] Contexts { get; set; } + + public AIResponseMetadata Metadata { get; set; } + + public String ResolvedQuery { get; set; } + + public AIResponseFulfillment Fulfillment { get; set; } + + public string Source { get; set; } + + public decimal Score { get; set; } + + [JsonIgnore] + public bool HasParameters + { + get + { + return Parameters != null && Parameters.Count > 0; + } + } + + public string GetStringParameter(string name, string defaultValue = "") + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentNullException("name"); + } + + if (Parameters.ContainsKey(name)) + { + return Parameters[name].ToString(); + } + + return defaultValue; + } + + public int GetIntParameter(string name, int defaultValue = 0) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentNullException("name"); + } + + if (Parameters.ContainsKey(name)) + { + var parameterValue = Parameters[name].ToString(); + int result; + if (int.TryParse(parameterValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out result)) + { + return result; + } + + float floatResult; + if (float.TryParse(parameterValue, NumberStyles.Float, CultureInfo.InvariantCulture, out floatResult)) + { + result = Convert.ToInt32(floatResult); + return result; + } + } + + return defaultValue; + } + + public float GetFloatParameter(string name, float defaultValue = 0) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentNullException("name"); + } + + if (Parameters.ContainsKey(name)) + { + var parameterValue = Parameters[name].ToString(); + float result; + if (float.TryParse(parameterValue, NumberStyles.Float, CultureInfo.InvariantCulture, out result)) + { + return result; + } + } + + return defaultValue; + } + + public JObject GetJsonParameter(string name, JObject defaultValue = null) + { + if (string.IsNullOrEmpty("name")) + { + throw new ArgumentNullException(nameof(name)); + } + + if (Parameters.ContainsKey(name)) + { + var parameter = Parameters[name] as JObject; + if (parameter != null) + { + return parameter; + } + } + + return defaultValue; + } + + public AIContext GetContext(string name) + { + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException("Name must be not empty", nameof(name)); + } + + return Contexts?.FirstOrDefault(c => string.Equals(c.Name, name, StringComparison.CurrentCultureIgnoreCase)); + } + } +} diff --git a/Bot.Rasa/Models/AIResponseStatus.cs b/Bot.Rasa/Models/AIResponseStatus.cs new file mode 100644 index 00000000..5f14b284 --- /dev/null +++ b/Bot.Rasa/Models/AIResponseStatus.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Models +{ + public class AIResponseStatus + { + public int Code { get; set; } + + public string ErrorType { get; set; } + + public string ErrorDetails { get; set; } + + public string ErrorID { get; set; } + } +} diff --git a/Bot.Rasa/Models/OriginalRequest.cs b/Bot.Rasa/Models/OriginalRequest.cs new file mode 100644 index 00000000..c926edf3 --- /dev/null +++ b/Bot.Rasa/Models/OriginalRequest.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Models +{ + [JsonObject] + public class OriginalRequest + { + [JsonProperty("source")] + public string Source { get; set; } + + [JsonProperty("data")] + public object Data { get; set; } + } +} diff --git a/Bot.Rasa/Models/QuestionMetadata.cs b/Bot.Rasa/Models/QuestionMetadata.cs new file mode 100644 index 00000000..6a3e398d --- /dev/null +++ b/Bot.Rasa/Models/QuestionMetadata.cs @@ -0,0 +1,24 @@ +using Bot.Rasa.Entities; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Models +{ + [JsonObject] + public class QuestionMetadata + { + [JsonProperty("timezone")] + public string Timezone { get; set; } + + [JsonProperty("lang")] + public string Language { get; set; } + + [JsonProperty("sessionId")] + internal string SessionId { get; set; } + + [JsonProperty("entities")] + public List Entities { get; set; } + } +} diff --git a/Bot.Rasa/Models/RasaResponse.cs b/Bot.Rasa/Models/RasaResponse.cs new file mode 100644 index 00000000..441e3594 --- /dev/null +++ b/Bot.Rasa/Models/RasaResponse.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Models +{ + public class RasaResponse + { + public RasaResponseIntent Intent { get; set; } + + public String Text { get; set; } + } + + public class RasaResponseIntent + { + public String Name { get; set; } + + public Decimal Confidence { get; set; } + } +} diff --git a/Bot.Rasa/Models/RequestExtras.cs b/Bot.Rasa/Models/RequestExtras.cs new file mode 100644 index 00000000..f0dc18a3 --- /dev/null +++ b/Bot.Rasa/Models/RequestExtras.cs @@ -0,0 +1,63 @@ +using Bot.Rasa.Entities; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bot.Rasa.Models +{ + public class RequestExtras + { + public List Contexts { get; set; } + + public List Entities { get; set; } + + public bool HasContexts + { + get + { + if (Contexts != null && Contexts.Count > 0) + { + return true; + } + return false; + } + } + + public bool HasEntities + { + get + { + if (Entities != null && Entities.Count > 0) + { + return true; + } + return false; + } + } + + + public RequestExtras() + { + } + + public RequestExtras(List contexts, List entities) + { + this.Contexts = contexts; + this.Entities = entities; + } + + public void CopyTo(AIRequest request) + { + if (HasContexts) + { + request.Contexts = Contexts; + } + + if (HasEntities) + { + request.Entities = Entities; + } + } + + } +} diff --git a/Bot.Rasa/Models/UserSay.cs b/Bot.Rasa/Models/UserSay.cs index fba18a21..8fdc6ed5 100644 --- a/Bot.Rasa/Models/UserSay.cs +++ b/Bot.Rasa/Models/UserSay.cs @@ -9,11 +9,11 @@ namespace Bot.Rasa.Models { public UserSay() { - Entities = new List(); + Entities = new List(); } public String Text { get; set; } public String Intent { get; set; } - public List Entities { get; set; } + public List Entities { get; set; } } } diff --git a/Bot.Rasa/Sessions/SessionContext.cs b/Bot.Rasa/Sessions/SessionContext.cs new file mode 100644 index 00000000..26eb83e0 --- /dev/null +++ b/Bot.Rasa/Sessions/SessionContext.cs @@ -0,0 +1,23 @@ +using EntityFrameworkCore.BootKit; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using System.Text; + +namespace Bot.Rasa.Sessions +{ + [Table("Bot_SessionContext")] + public class SessionContext : DbRecord, IDbRecord + { + [Required] + [StringLength(36)] + public String SessionId { get; set; } + + [Required] + [MaxLength(64)] + public String Context { get; set; } + + public int Lifespan { get; set; } + } +} diff --git a/Bot.UnitTest/AgentTest.cs b/Bot.UnitTest/AgentTest.cs index 3b8319c5..b48e90d2 100644 --- a/Bot.UnitTest/AgentTest.cs +++ b/Bot.UnitTest/AgentTest.cs @@ -1,6 +1,7 @@ using Bot.Rasa; using Bot.Rasa.Agents; using Bot.Rasa.Consoles; +using Bot.Rasa.Models; using EntityFrameworkCore.BootKit; using Microsoft.VisualStudio.TestTools.UnitTesting; using System; @@ -13,43 +14,40 @@ namespace Bot.UnitTest [TestClass] public class AgentTest : TestEssential { - public static String PIZZA_BOT_ID = "2b6a288e-d891-40c6-96ce-6a0cf324545c"; - + public static String BOT_ID = "2b6a288e-d891-40c6-96ce-6a0cf324545c"; + public static String BOT_NAME = "VirtualAssistant"; + [TestMethod] public void CreateAgent() { - var rasa = new RasaConsole(dc); + var rasa = new RasaAi(dc); + var importer = new AgentImporterInDialogflow(); + var agent = rasa.RestoreAgent(importer, BOT_NAME); + agent.Id = BOT_ID; - var agent = rasa.RestoreAgent(PIZZA_BOT_ID); - - int row = dc.DbTran(() => rasa.CreateAgent(agent)); - - if(row > 0) - { - var result = rasa.Train(dc, agent.Id); - } + int row = dc.DbTran(() => rasa.SaveAgent(agent)); var loadedAgent = rasa.LoadAgent(agent.Id); Assert.IsTrue(loadedAgent.Intents.Count == agent.Intents.Count); - - var response = rasa.TextRequest(agent.Id, "weather in Chicago tomorrow"); - Assert.IsTrue(response.Intent.Name == "weather"); } [TestMethod] public void TextRequest() { - var rasa = new RasaConsole(dc); - var response = rasa.TextRequest(PIZZA_BOT_ID, "how old are you"); - response = rasa.TextRequest(PIZZA_BOT_ID, "where are you from"); - response = rasa.TextRequest(PIZZA_BOT_ID, "would you like some cookie"); + var rasa = new RasaAi(dc); + rasa.agent = rasa.LoadAgent(BOT_ID); + + var response = rasa.TextRequest(dc, new AIRequest { Query = new String[] { "Create a work order for PetSmart" } }); + Assert.IsTrue(response.Result.Metadata.IntentName == "Create Work Order"); + + response = rasa.TextRequest(dc, new AIRequest { Query = new String[] { "1010" } }); + Assert.IsTrue(response.Result.Metadata.IntentName == "Telling Store Number"); } [TestMethod] public void Train() { - var rasa = new RasaConsole(dc); - rasa.Train(dc, PIZZA_BOT_ID); + var rasa = new RasaAi(dc); } } } diff --git a/Bot.UnitTest/TestEssential.cs b/Bot.UnitTest/TestEssential.cs index 5d633b31..f5e7974c 100644 --- a/Bot.UnitTest/TestEssential.cs +++ b/Bot.UnitTest/TestEssential.cs @@ -30,20 +30,12 @@ namespace Bot.UnitTest dc = new DefaultDataContextLoader().GetDefaultDc(); - RasaConsole.Options = new RasaOptions + RasaAi.Options = new RasaOptions { - HostUrl = "http://192.168.56.101:5000", + HostUrl = Database.Configuration.GetSection("Rasa:Host").Value, ContentRootPath = contentRoot, Assembles = new String[] { "Bot.Rasa" } }; - - dc = new Database(); - - dc.BindDbContext(new DatabaseBind - { - MasterConnection = new SqliteConnection($"Data Source={RasaConsole.Options.ContentRootPath}\\App_Data\\bot-rasa.db"), - CreateDbIfNotExist = true - }); } } diff --git a/Bot.WebStarter/App_Data/DbInitializer/Agents/2b6a288e-d891-40c6-96ce-6a0cf324545c.json b/Bot.WebStarter/App_Data/DbInitializer/Agents/Rasa/2b6a288e-d891-40c6-96ce-6a0cf324545c.json similarity index 100% rename from Bot.WebStarter/App_Data/DbInitializer/Agents/2b6a288e-d891-40c6-96ce-6a0cf324545c.json rename to Bot.WebStarter/App_Data/DbInitializer/Agents/Rasa/2b6a288e-d891-40c6-96ce-6a0cf324545c.json diff --git a/Bot.WebStarter/settings.auth.json b/Bot.WebStarter/settings.auth.json index e02ee249..875a3f1a 100644 --- a/Bot.WebStarter/settings.auth.json +++ b/Bot.WebStarter/settings.auth.json @@ -1,9 +1,9 @@ { "TokenAuthentication": { - "SecretKey": "tK5aNR0FTEm4htInv+HA3A==", - "Subject": "Voicecoin", - "Issuer": "Voicecoin", - "Audience": "Voicecoin", + "SecretKey": "", + "Subject": "OpenBotKit", + "Issuer": "Haiping Chen", + "Audience": "Haiping Chen", "TokenPath": "/token", "CookieName": "token", "LoginPath": "/login" diff --git a/Bot.WebStarter/settings.aws.json b/Bot.WebStarter/settings.aws.json index 6336e421..88d79907 100644 --- a/Bot.WebStarter/settings.aws.json +++ b/Bot.WebStarter/settings.aws.json @@ -1,10 +1,10 @@ { "AWS": { "AWSRegionEndPoint": "us-east-1", - "AWSSecretKey": "38+UqlSqOVbKfL3LrS6hSmgW/ZSPgZzUa/Hom7ip", - "AWSAccessKey": "AKIAIPXFRDXOTKZWOVXQ", + "AWSSecretKey": "", + "AWSAccessKey": "", "AWSEncoding": "utf-8", - "SESVerifiedEmail": "haiping008@gmail.com", - "AWSBucketPrefix": "voicecoin.ico" + "SESVerifiedEmail": "", + "AWSBucketPrefix": "" } } \ No newline at end of file diff --git a/Bot.WebStarter/settings.db.json b/Bot.WebStarter/settings.db.json index c7db88e6..06db2785 100644 --- a/Bot.WebStarter/settings.db.json +++ b/Bot.WebStarter/settings.db.json @@ -1,10 +1,10 @@ { "Database": { - "Default": "Sqlite", + "Default": "SqlServer", "ConnectionStrings": { "InMemory": "DataSource=:memory:", - "Sqlite": "Data Source=|DataDirectory|\\bot-rasa.db;", - "SqlServer": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=bot-rasa;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False" + "Sqlite": "Data Source=|DataDirectory|\\RasaBot.db;", + "SqlServer": "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=RasaBot;Integrated Security=True;Connect Timeout=15;Encrypt=False;TrustServerCertificate=True;ApplicationIntent=ReadWrite;MultiSubnetFailover=False" } } } \ No newline at end of file diff --git a/Bot.WebStarter/settings.rasa.json b/Bot.WebStarter/settings.rasa.json index f1bb10fb..05127584 100644 --- a/Bot.WebStarter/settings.rasa.json +++ b/Bot.WebStarter/settings.rasa.json @@ -1,5 +1,5 @@ { "Rasa": { - "Host": "http://192.168.56.101:5000" + "Host": "http://81a1f75c.ngrok.io" } } diff --git a/Bot.WebStarter/settings.swagger.json b/Bot.WebStarter/settings.swagger.json index b5a580a8..732f537f 100644 --- a/Bot.WebStarter/settings.swagger.json +++ b/Bot.WebStarter/settings.swagger.json @@ -1,8 +1,8 @@ { "Swagger": { "Version": "v1", - "Title": "RasaBot API", - "Description": "RasaBot API", + "Title": "OpenBotKit API", + "Description": "OpenBotKit API", "TermsOfService": "MIT", "Contact": { "Name": "Haiping Chen",