diff --git a/BotSharp.Channel.FacebookMessenger/BotSharp.Channel.FacebookMessenger.csproj b/BotSharp.Channel.FacebookMessenger/BotSharp.Channel.FacebookMessenger.csproj
index 1add5556..56f689c2 100644
--- a/BotSharp.Channel.FacebookMessenger/BotSharp.Channel.FacebookMessenger.csproj
+++ b/BotSharp.Channel.FacebookMessenger/BotSharp.Channel.FacebookMessenger.csproj
@@ -14,6 +14,7 @@
+
diff --git a/BotSharp.Channel.Weixin/BotSharp.Channel.Weixin.csproj b/BotSharp.Channel.Weixin/BotSharp.Channel.Weixin.csproj
index d9bd907c..89f54ed6 100644
--- a/BotSharp.Channel.Weixin/BotSharp.Channel.Weixin.csproj
+++ b/BotSharp.Channel.Weixin/BotSharp.Channel.Weixin.csproj
@@ -25,6 +25,7 @@
+
diff --git a/BotSharp.Platform.Rasa/AgentImporterInRasa.cs b/BotSharp.Platform.Rasa/AgentImporterInRasa.cs
new file mode 100644
index 00000000..13c1739c
--- /dev/null
+++ b/BotSharp.Platform.Rasa/AgentImporterInRasa.cs
@@ -0,0 +1,190 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using BotSharp.Platform.Abstraction;
+using BotSharp.Platform.Models;
+using BotSharp.Platform.Models.Intents;
+using BotSharp.Platform.Rasa.Models;
+using Newtonsoft.Json;
+
+namespace BotSharp.Platform.Rasa
+{
+ public class AgentImporterInRasa : IAgentImporter where TAgent : AgentModel, new()
+ {
+ public string AgentDir { get; set; }
+
+ public async Task LoadAgent(AgentImportHeader agentHeader)
+ {
+ var agent = new TAgent
+ {
+ Id = agentHeader.Id,
+ Name = agentHeader.Name
+ };
+
+ return agent;
+ }
+
+ public async Task LoadBuildinEntities(TAgent agent)
+ {
+ agent.Intents.ForEach(intent =>
+ {
+ /*if (intent.UserSays != null)
+ {
+ intent.UserSays.ForEach(us =>
+ {
+ us.Data.Where(data => data.Meta != null)
+ .ToList()
+ .ForEach(data =>
+ {
+ LoadBuildinEntityTypePerUserSay(agent, data);
+ });
+ });
+ }*/
+ });
+ }
+
+ private void LoadBuildinEntityTypePerUserSay(TAgent agent, IntentExpressionPart data)
+ {
+ /*var existedEntityType = agent.Entities.FirstOrDefault(x => x.Name == data.Meta);
+
+ if (existedEntityType == null)
+ {
+ existedEntityType = new EntityType
+ {
+ Name = data.Meta,
+ Entries = new List(),
+ IsOverridable = true
+ };
+
+ agent.Entities.Add(existedEntityType);
+ }
+
+ var entries = existedEntityType.Entries.Select(x => x.Value.ToLower()).ToList();
+ if (!entries.Contains(data.Text.ToLower()))
+ {
+ existedEntityType.Entries.Add(new EntityEntry
+ {
+ Value = data.Text,
+ Synonyms = new List
+ {
+ new EntrySynonym
+ {
+ Synonym = data.Text
+ }
+ }
+ });
+ }*/
+ }
+
+ public async Task LoadCustomEntities(TAgent agent)
+ {
+ }
+
+ public async Task LoadIntents(TAgent agent)
+ {
+ string data = File.ReadAllText(Path.Combine(AgentDir, "corpus.json"));
+ var rasa = JsonConvert.DeserializeObject(data);
+
+ agent.Intents = rasa.Data.Intents;
+ agent.Entities = rasa.Data.Entities;
+ }
+
+ private void ImportIntentUserSays(RasaIntentExpression intent, List sentences)
+ {
+ var intents = new List();
+
+ var userSays = sentences.Where(x => x.Intent == intent.Intent).ToList();
+
+ userSays.ForEach(say =>
+ {
+ var expression = new IntentExpression();
+
+ say.Entities = say.Entities.OrderBy(x => x.Start).ToList();
+
+ expression.Data = new List();
+
+ int pos = 0;
+ for (int entityIdx = 0; entityIdx < say.Entities.Count; entityIdx++)
+ {
+ var entity = say.Entities[entityIdx];
+
+ // previous
+ if (entity.Start > 0)
+ {
+ expression.Data.Add(new IntentExpressionPart
+ {
+ Text = say.Text.Substring(pos, entity.Start - pos),
+ Start = pos
+ });
+ }
+
+ // self
+ expression.Data.Add(new IntentExpressionPart
+ {
+ Alias = entity.Entity,
+ Meta = entity.Entity,
+ Text = say.Text.Substring(entity.Start, entity.Value.Length),
+ Start = entity.Start
+ });
+
+ pos = entity.End + 1;
+
+ if (pos < say.Text.Length && entityIdx == say.Entities.Count - 1)
+ {
+ // end
+ expression.Data.Add(new IntentExpressionPart
+ {
+ Text = say.Text.Substring(pos),
+ Start = pos
+ });
+ }
+ }
+
+ if (say.Entities.Count == 0)
+ {
+ expression.Data.Add(new IntentExpressionPart
+ {
+ Text = say.Text.Substring(pos)
+ });
+ }
+
+ int second = 0;
+ expression.Data.ForEach(x => x.UpdatedTime = DateTime.UtcNow.AddSeconds(second++));
+
+ intents.Add(say);
+ });
+ }
+
+ /*public void AssembleTrainData(TAgent agent)
+ {
+ // convert agent to training corpus
+ agent.Corpus = new TrainingCorpus
+ {
+ Entities = new List(),
+ UserSays = new List>()
+ };
+
+ agent.Intents.ForEach(intent =>
+ {
+ intent.UserSays.ForEach(say => {
+ agent.Corpus.UserSays.Add(new TrainingIntentExpression
+ {
+ Intent = intent.Name,
+ Text = String.Join("", say.Data.Select(x => x.Text)),
+ Entities = say.Data.Where(x => !String.IsNullOrEmpty(x.Meta))
+ .Select(x => new TrainingIntentExpressionPart
+ {
+ Value = x.Text,
+ Entity = x.Meta,
+ Start = x.Start
+ })
+ .ToList()
+ });
+ });
+ });
+ }*/
+ }
+}
diff --git a/BotSharp.Platform.Rasa/BotSharp.Platform.Rasa.csproj b/BotSharp.Platform.Rasa/BotSharp.Platform.Rasa.csproj
new file mode 100644
index 00000000..cc5394f0
--- /dev/null
+++ b/BotSharp.Platform.Rasa/BotSharp.Platform.Rasa.csproj
@@ -0,0 +1,15 @@
+
+
+
+ netcoreapp2.1
+
+
+
+
+
+
+
+
+
+
+
diff --git a/BotSharp.Platform.Rasa/Controllers/ConfigController.cs b/BotSharp.Platform.Rasa/Controllers/ConfigController.cs
new file mode 100644
index 00000000..b0a05188
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Controllers/ConfigController.cs
@@ -0,0 +1,29 @@
+using BotSharp.Platform.Rasa.Models;
+using Microsoft.AspNetCore.Mvc;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa.Controllers
+{
+ [Route("[controller]")]
+ public class ConfigController : ControllerBase
+ {
+ [HttpGet]
+ public ActionResult Get()
+ {
+ var status = new RasaStatusModel
+ {
+ AvailableProjects = JObject.FromObject(new RasaProjectModel
+ {
+ Status = "ready",
+ AvailableModels = new List { "model_XXXXXX" },
+ LoadedModels = new List { "model_XXXXXX" }
+ })
+ };
+
+ return Ok(status);
+ }
+ }
+}
diff --git a/BotSharp.Platform.Rasa/Controllers/ParseController.cs b/BotSharp.Platform.Rasa/Controllers/ParseController.cs
new file mode 100644
index 00000000..8cb82cca
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Controllers/ParseController.cs
@@ -0,0 +1,98 @@
+using BotSharp.Platform.Rasa.Models;
+using Microsoft.AspNetCore.Mvc;
+
+namespace BotSharp.Platform.Rasa.Controllers
+{
+ ///
+ /// send a text request
+ ///
+ [Route("[controller]")]
+ public class ParseController : ControllerBase
+ {
+ private readonly RasaAi builder;
+
+ ///
+ /// Initialize dialog controller and get a platform instance
+ ///
+ ///
+ public ParseController(RasaAi configuration)
+ {
+ builder = configuration;
+ }
+
+ ///
+ /// parse request
+ ///
+ ///
+ ///
+ [HttpPost, HttpGet]
+ public ActionResult Parse(RasaRequestModel request)
+ {
+ /*var config = new AIConfiguration("", SupportedLanguage.English);
+ config.SessionId = "rasa nlu";
+
+ string body = "";
+ using (var reader = new StreamReader(Request.Body))
+ {
+ body = reader.ReadToEnd();
+ }
+
+ Console.WriteLine($"Got message from {Request.Host}: {body}", Color.Green);
+ if(request.Project ==null && !String.IsNullOrEmpty(body))
+ {
+ request = JsonConvert.DeserializeObject(body);
+ }
+
+ // Load agent
+ var projectPath = Path.Combine(AppDomain.CurrentDomain.GetData("DataPath").ToString(), "Projects", request.Project);
+
+ if (String.IsNullOrEmpty(request.Model))
+ {
+ request.Model = Directory.GetDirectories(projectPath).Where(x => x.Contains("model_")).Last().Split(Path.DirectorySeparatorChar).Last();
+ }
+
+ var modelPath = Path.Combine(projectPath, request.Model);
+
+ var agent = _platform.LoadAgentFromFile(modelPath);
+
+ var aIResponse = _platform.TextRequest(new AIRequest
+ {
+ AgentDir = projectPath,
+ Model = request.Model,
+ Query = new String[] { request.Text }
+ });
+
+ var rasaResponse = new RasaResponse
+ {
+ Intent = new RasaResponseIntent
+ {
+ Name = aIResponse.Result.Metadata.IntentName,
+ Confidence = aIResponse.Result.Score
+ },
+ Entities = aIResponse.Result.Entities.Select(x => new RasaResponseEntity
+ {
+ Extractor = x.Extrator,
+ Start = x.Start,
+ Entity = x.Entity,
+ Value = x.Value
+ }).ToList(),
+ Text = request.Text,
+ Model = request.Model,
+ Project = agent.Name,
+ IntentRanking = new List
+ {
+ new RasaResponseIntent
+ {
+ Name = aIResponse.Result.Metadata.IntentName,
+ Confidence = aIResponse.Result.Score
+ }
+ },
+ Fullfillment = aIResponse.Result.Fulfillment
+ };
+
+ return rasaResponse;*/
+
+ return null;
+ }
+ }
+}
diff --git a/BotSharp.Platform.Rasa/Controllers/StatusController.cs b/BotSharp.Platform.Rasa/Controllers/StatusController.cs
new file mode 100644
index 00000000..134b4c20
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Controllers/StatusController.cs
@@ -0,0 +1,65 @@
+using BotSharp.Core.Engines;
+using BotSharp.Platform.Rasa.Models;
+using Microsoft.AspNetCore.Mvc;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa.Controllers
+{
+ ///
+ /// This returns all the currently available projects.
+ ///
+ [Route("[controller]")]
+ public class StatusController : ControllerBase
+ {
+ ///
+ /// Returns a list of available projects the server can use to fulfill /parse requests.
+ ///
+ ///
+ [HttpGet]
+ public ActionResult Get()
+ {
+ var status = new RasaStatusModel();
+ status.AvailableProjects = JObject.FromObject(new { });
+ status.MaxTrainingProcesses = 1;
+
+ // scan dir, get all models
+ var projectPath = Path.Combine(AppDomain.CurrentDomain.GetData("DataPath").ToString(), "Projects");
+
+ if (!Directory.Exists(projectPath))
+ {
+ Directory.CreateDirectory(projectPath);
+ }
+
+ var projectDirs = Directory.GetDirectories(projectPath);
+ for(int idx = 0; idx < projectDirs.Length; idx++)
+ {
+ string project = projectDirs[idx].Split('\\').Last();
+ var modelDirs = Directory.GetDirectories(projectDirs[idx]);
+
+ List availableModels = new List();
+
+ for (int mIdx = 0; mIdx < modelDirs.Length; mIdx++)
+ {
+ string model = modelDirs[mIdx].Split('\\').Last();
+ if (model.StartsWith(project + "_"))
+ {
+ availableModels.Add(model);
+ }
+ }
+
+ status.AvailableProjects.Add(project, JObject.FromObject(new RasaProjectModel
+ {
+ Status = "ready",
+ AvailableModels = availableModels
+ }));
+ }
+
+ return Ok(status);
+ }
+ }
+}
diff --git a/BotSharp.Platform.Rasa/Controllers/TrainController.cs b/BotSharp.Platform.Rasa/Controllers/TrainController.cs
new file mode 100644
index 00000000..7e49b8d4
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Controllers/TrainController.cs
@@ -0,0 +1,184 @@
+using BotSharp.Core.Engines;
+using BotSharp.Platform.Abstraction;
+using BotSharp.Platform.Models;
+using BotSharp.Platform.Models.MachineLearning;
+using BotSharp.Platform.Rasa.Models;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+
+namespace BotSharp.Platform.Rasa.Controllers
+{
+ ///
+ /// You can post your training data to this endpoint to train a new model for a project.
+ /// This request will wait for the server answer: either the model was trained successfully or the training exited with an error.
+ ///
+ [Route("[controller]")]
+ public class TrainController : ControllerBase
+ {
+ private RasaAi builder;
+ private readonly IPlatformSettings settings;
+
+ public TrainController(RasaAi configuration, IPlatformSettings settings)
+ {
+ builder = configuration;
+ this.settings = settings;
+ }
+
+ ///
+ /// Using the HTTP server, you must specify the project you want to train a new model for to be able to use it during parse requests later on : /train?project=my_project.
+ ///
+ /// Model name
+ /// Agent name or agent id
+ ///
+ [HttpPost]
+ public async Task> Train([FromQuery] string project, [FromQuery] string model)
+ {
+ string agentDir = Path.Combine(AppDomain.CurrentDomain.GetData("DataPath").ToString(), "Projects", project);
+ if (!Directory.Exists(agentDir))
+ {
+ Directory.CreateDirectory(agentDir);
+ }
+
+ string body = "";
+ using (var reader = new StreamReader(Request.Body))
+ {
+ body = reader.ReadToEnd();
+ }
+
+ var agent = await ImportAgent(project, body);
+
+ var corpus = await builder.ExtractorCorpus(agent);
+
+ var meta = await builder.Train(agent, corpus, new BotTrainOptions { Model = model });
+
+ return meta;
+
+ }
+
+ private async Task ImportAgent(string project, string body)
+ {
+ Console.WriteLine($"Update agent from http post, data length: {body.Length}");
+
+ // save to file
+ // save corpus to agent dir
+ var projectPath = Path.Combine(AppDomain.CurrentDomain.GetData("DataPath").ToString(), "Projects", project);
+ var rawPath = Path.Combine(projectPath, "tmp");
+
+ // clear tmp dir
+ if (Directory.Exists(rawPath))
+ {
+ Directory.Delete(rawPath, true);
+ }
+
+ Directory.CreateDirectory(rawPath);
+
+ // Save raw data to file, then parse it to Agent instance.
+ var metaFileName = Path.Combine(rawPath, "meta.json");
+ System.IO.File.WriteAllText(metaFileName, JsonConvert.SerializeObject(new AgentImportHeader
+ {
+ Name = project,
+ Platform = PlatformType.Rasa,
+ Id = Guid.NewGuid().ToString()
+ }, new JsonSerializerSettings
+ {
+ Formatting = Formatting.Indented,
+ NullValueHandling = NullValueHandling.Ignore,
+ ContractResolver = new CamelCasePropertyNamesContractResolver()
+ }));
+
+ // in order to unify the process.
+ var fileName = Path.Combine(rawPath, "corpus.json");
+
+ System.IO.File.WriteAllText(fileName, body);
+
+ /*string lang = Regex.Match(body, @"language:.+")?.Value;
+ if (!String.IsNullOrEmpty(lang))
+ {
+ lang = lang.Substring(11, 2);
+ }
+ string data = Regex.Match(body, @"data:([\s\S]*)")?.Value;
+ if (String.IsNullOrEmpty(data))
+ {
+ data = body;
+ }
+ else
+ {
+ data = data.Substring(6);
+ }*/
+
+ /*var agent = builder.GetAgentById(project);
+
+ if (agent == null)
+ {
+ agent = builder.GetAgentByName(project);
+ }
+
+ var corpus = builder.ExtractorCorpus(agent);
+
+ var meta = await builder.Train(agent, corpus);*/
+
+ // var rasa_nlu_data = JsonConvert.DeserializeObject(data);
+ //rasa_nlu_data.Model = model;
+ //rasa_nlu_data.Project = project;
+
+ var agent = await builder.LoadAgentFromFile>(rawPath);
+ await builder.SaveAgent(agent);
+
+ return agent;
+ }
+
+ private async Task> Train([FromBody] RasaTrainRequestViewModel request, [FromQuery] string project)
+ {
+ var trainer = new BotTrainer(settings);
+ if (String.IsNullOrEmpty(request.Project))
+ {
+ request.Project = project;
+ }
+
+ // save corpus to agent dir
+ var projectPath = Path.Combine(AppDomain.CurrentDomain.GetData("DataPath").ToString(), "Projects", project);
+ var modelPath = Path.Combine(projectPath, request.Model);
+
+ if (!Directory.Exists(modelPath))
+ {
+ Directory.CreateDirectory(modelPath);
+ }
+
+ // Save raw data to file, then parse it to Agent instance.
+ var metaFileName = Path.Combine(modelPath, "meta.json");
+ System.IO.File.WriteAllText(metaFileName, JsonConvert.SerializeObject(new AgentImportHeader
+ {
+ Name = project
+ }));
+ // in order to unify the process.
+ var fileName = Path.Combine(modelPath, "corpus.json");
+
+ System.IO.File.WriteAllText(fileName, JsonConvert.SerializeObject(request, new JsonSerializerSettings
+ {
+ Formatting = Formatting.Indented,
+ NullValueHandling = NullValueHandling.Ignore,
+ ContractResolver = new CamelCasePropertyNamesContractResolver()
+ }));
+
+ var agent = await builder.GetAgentByName(project);
+
+ var info = await trainer.Train(agent, new BotTrainOptions
+ {
+ AgentDir = projectPath,
+ Model = request.Model
+ });
+
+ return Ok(new { info = info.Model });
+ }
+ }
+}
diff --git a/BotSharp.Platform.Rasa/Controllers/VersionController.cs b/BotSharp.Platform.Rasa/Controllers/VersionController.cs
new file mode 100644
index 00000000..fc356271
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Controllers/VersionController.cs
@@ -0,0 +1,23 @@
+using Microsoft.AspNetCore.Mvc;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa.Controllers
+{
+#if RASA
+ [Route("[controller]")]
+ public class VersionController : ControllerBase
+ {
+ [HttpGet]
+ public ActionResult Get()
+ {
+ return Ok(new RasaVersionModel
+ {
+ Version = "0.13.0",
+ MinimumCompatibleVersion = "0.13.0"
+ });
+ }
+ }
+#endif
+}
diff --git a/BotSharp.Platform.Rasa/Models/AgentModel.cs b/BotSharp.Platform.Rasa/Models/AgentModel.cs
new file mode 100644
index 00000000..9f7652e7
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Models/AgentModel.cs
@@ -0,0 +1,20 @@
+using BotSharp.Platform.Models;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa.Models
+{
+ public class AgentModel : AgentBase
+ {
+ [JsonProperty("common_examples")]
+ public List Intents { get; set; }
+
+ [JsonProperty("entity_synonyms")]
+ public List Entities { get; set; }
+
+ [JsonProperty("regex_features")]
+ public List Regex { get; set; }
+ }
+}
diff --git a/BotSharp.Platform.Rasa/Models/RasaAgentImportModel.cs b/BotSharp.Platform.Rasa/Models/RasaAgentImportModel.cs
new file mode 100644
index 00000000..1cc5f345
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Models/RasaAgentImportModel.cs
@@ -0,0 +1,13 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa.Models
+{
+ public class RasaAgentImportModel
+ {
+ [JsonProperty("rasa_nlu_data")]
+ public AgentModel Data { get; set; }
+ }
+}
diff --git a/BotSharp.Platform.Rasa/Models/RasaConfigModel.cs b/BotSharp.Platform.Rasa/Models/RasaConfigModel.cs
new file mode 100644
index 00000000..d40f3e34
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Models/RasaConfigModel.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa.Models
+{
+ public class RasaConfigModel
+ {
+ public string Config { get; set; }
+
+ public string Data { get; set; }
+ }
+}
diff --git a/BotSharp.Platform.Rasa/Models/RasaIntentExpression.cs b/BotSharp.Platform.Rasa/Models/RasaIntentExpression.cs
new file mode 100644
index 00000000..63b9a06f
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Models/RasaIntentExpression.cs
@@ -0,0 +1,13 @@
+using BotSharp.Platform.Models;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa.Models
+{
+ public class RasaIntentExpression : TrainingIntentExpression
+ {
+
+ }
+}
diff --git a/BotSharp.Platform.Rasa/Models/RasaIntentExpressionPart.cs b/BotSharp.Platform.Rasa/Models/RasaIntentExpressionPart.cs
new file mode 100644
index 00000000..6adcd39c
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Models/RasaIntentExpressionPart.cs
@@ -0,0 +1,12 @@
+using BotSharp.Core.Engines;
+using BotSharp.Platform.Models;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa.Models
+{
+ public class RasaIntentExpressionPart : TrainingIntentExpressionPart
+ {
+ }
+}
diff --git a/BotSharp.Platform.Rasa/Models/RasaOptions.cs b/BotSharp.Platform.Rasa/Models/RasaOptions.cs
new file mode 100644
index 00000000..9e12933e
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Models/RasaOptions.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa.Models
+{
+ public class RasaOptions
+ {
+ public string HostUrl { get; set; }
+ public String[] Assembles { get; set; }
+ public string ContentRootPath { get; set; }
+ public String DbName { get; set; }
+ public String DbConnectionString { get; set; }
+ }
+}
diff --git a/BotSharp.Platform.Rasa/Models/RasaRequestExtension.cs b/BotSharp.Platform.Rasa/Models/RasaRequestExtension.cs
new file mode 100644
index 00000000..9499a3e0
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Models/RasaRequestExtension.cs
@@ -0,0 +1,272 @@
+using BotSharp.Core.Conversations;
+using DotNetToolkit;
+using EntityFrameworkCore.BootKit;
+using Microsoft.EntityFrameworkCore;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using BotSharp.Platform.Models.AiRequest;
+using BotSharp.Platform.Models.Intents;
+using BotSharp.Platform.Models.AiResponse;
+using BotSharp.Platform.Models;
+
+namespace BotSharp.Platform.Rasa.Models
+{
+ public static class RasaRequestExtension
+ {
+ public static IntentResponse HandleIntentPerContextIn(AgentModel agent, AiRequest request, RasaResponse response, Database dc)
+ {
+ // Merge input contexts
+ /*var contexts = dc.Table()
+ .Where(x => x.ConversationId == request.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 = agent.Intents.Where(it =>
+ {
+ if (contexts.Count == 0)
+ {
+ 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();*/
+
+ /*if (response.IntentRanking == null)
+ {
+ response.IntentRanking = new List
+ {
+ response.Intent
+ };
+ }
+
+ response.IntentRanking = response.IntentRanking.Where(x => x.Confidence > agent.MlConfig.MinConfidence).ToList();
+ response.IntentRanking = response.IntentRanking.Where(x => intents.Select(i => i.Name).Contains(x.Name)).ToList();*/
+
+ // add Default Fallback Intent
+ /*if (response.IntentRanking.Count == 0)
+ {
+ var defaultFallbackIntent = agent.Intents.FirstOrDefault(x => x.Name == "Default Fallback Intent");
+ response.IntentRanking.Add(new RasaResponseIntent
+ {
+ Name = defaultFallbackIntent.Name,
+ Confidence = decimal.Parse("0.8")
+ });
+ }*/
+
+ response.Intent = response.IntentRanking.First();
+
+ var intent = (dc.Table().Where(x => x.AgentId == agent.Id && x.Name == response.Intent.Name)
+ .Include(x => x.Responses).ThenInclude(x => x.Contexts)
+ .Include(x => x.Responses).ThenInclude(x => x.Parameters).ThenInclude(x => x.Prompts)
+ .Include(x => x.Responses).ThenInclude(x => x.Messages)).First();
+
+ var intentResponse = ArrayHelper.GetRandom(intent.Responses);
+ intentResponse.IntentName = intent.Name;
+
+ return intentResponse;
+
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ /// Required field is missed
+ public static void HandleParameter(AgentModel agent, IntentResponse intentResponse, RasaResponse response, AiRequest aiRequest)
+ {
+ if (intentResponse == null) return;
+
+ intentResponse.Parameters.ForEach(p => {
+ string query = aiRequest.Text;
+ var entity = response.Entities.FirstOrDefault(x => x.Entity == p.Name || x.Entity.Split(':').Contains(p.Name));
+ if (entity != null)
+ {
+ p.Value = query.Substring(entity.Start, entity.End - entity.Start);
+ }
+
+ // convert to Standard entity value
+ /*if (!String.IsNullOrEmpty(p.Value) && !p.DataType.StartsWith("sys."))
+ {
+ p.Value = agent.Entities
+ .FirstOrDefault(x => x.Entity == p.DataType)
+ .Entries
+ .FirstOrDefault((entry) =>
+ {
+ return entry.Value.ToLower() == p.Value.ToLower() ||
+ entry.Synonyms.Select(synonym => synonym.Synonym.ToLower()).Contains(p.Value.ToLower());
+ })?.Value;
+ }*/
+
+ // fixed entity per request
+ /*if (aiRequest.Entities != null)
+ {
+ var fixedEntity = request.Entities.FirstOrDefault(x => x.Name == p.Name);
+ if (fixedEntity != null)
+ {
+ if (query.ToLower().Contains(fixedEntity.Entries.First().Value.ToLower()))
+ {
+ p.Value = fixedEntity.Entries.First().Value;
+ }
+ }
+ }*/
+ });
+ }
+
+ public static void HandleMessage(IntentResponse intentResponse)
+ {
+ if (intentResponse == null) return;
+
+ var missingRequiredParameter = intentResponse.Parameters.FirstOrDefault(x => x.Required && String.IsNullOrEmpty(x.Value));
+ if (missingRequiredParameter != null)
+ {
+ intentResponse.Messages = new List {
+ new IntentResponseMessage {
+ Type = AIResponseMessageType.Text,
+ Speech = ArrayHelper.GetRandom(missingRequiredParameter.Prompts).Prompt,
+ IntentResponseId = intentResponse.Id,
+ UpdatedTime = DateTime.UtcNow
+ }
+ };
+ }
+ else
+ {
+ intentResponse.Messages = intentResponse.Messages.OrderBy(x => x.UpdatedTime).ToList();
+ }
+
+ intentResponse.Messages.ToList()
+ .ForEach(msg =>
+ {
+ if (msg.Type == AIResponseMessageType.Custom)
+ {
+
+ }
+ else
+ {
+ if (msg.Speech != "[]")
+ {
+ msg.Speech = msg.Speech.StartsWith("[") ?
+ ArrayHelper.GetRandom(msg.Speech.Substring(2, msg.Speech.Length - 4).Split(new string[] { "\",\"" }, StringSplitOptions.None).ToList()) :
+ msg.Speech;
+
+ msg.Speech = ReplaceParameters4Response(intentResponse.Parameters, msg.Speech);
+ }
+ }
+ });
+ }
+
+ private static string ReplaceParameters4Response(List parameters, string text)
+ {
+ var reg = new Regex(@"\$\w+");
+
+ reg.Matches(text).Cast().ToList().ForEach(token => {
+ var parameter = parameters.FirstOrDefault(x => x.Name == token.Value.Substring(1));
+ if(parameter != null)
+ {
+ text = text.Replace(token.Value, parameter?.Value?.ToString());
+ }
+ });
+
+ return text;
+ }
+
+ public static void HandleContext(Database dc, AiRequest aiRequest, IntentResponse intentResponse, AiResponse aiResponse)
+ {
+ if (intentResponse == null) return;
+
+ // Merge context lifespan
+ // override if exists, otherwise add, delete if lifespan is zero
+ dc.DbTran(() =>
+ {
+ var sessionContexts = dc.Table().Where(x => x.ConversationId == aiRequest.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 ConversationContext
+ {
+ ConversationId = aiRequest.SessionId,
+ Context = ctx.Name,
+ Lifespan = ctx.Lifespan
+ });
+ }
+ });
+ });
+
+ /*aiResponse.Result.Contexts = dc.Table()
+ .Where(x => x.Lifespan > 0 && x.ConversationId == AiConfig.SessionId)
+ .Select(x => new AIContext { Name = x.Context.ToLower(), Lifespan = x.Lifespan })
+ .ToArray();*/
+ }
+
+ public static string GetModelPerContexts(AgentModel agent, AiRequest aiRequest, AiRequest request, Database dc)
+ {
+ // Merge input contexts
+ /*var contexts = dc.Table()
+ .Where(x => x.ConversationId == aiConfig.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 = agent.Intents.Where(it =>
+ {
+ if (contexts.Count == 0)
+ {
+ 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();
+
+ // query per request contexts
+ var contextHashs = intents.Select(x => x.ContextHash).Distinct().ToList();
+
+ return contextHashs.FirstOrDefault();*/
+
+ return string.Empty;
+ }
+ }
+}
diff --git a/BotSharp.Platform.Rasa/Models/RasaRequestModel.cs b/BotSharp.Platform.Rasa/Models/RasaRequestModel.cs
new file mode 100644
index 00000000..4f7b42ad
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Models/RasaRequestModel.cs
@@ -0,0 +1,17 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa.Models
+{
+ public class RasaRequestModel
+ {
+ [JsonProperty("q")]
+ public string Text { get; set; }
+
+ public string Project { get; set; }
+
+ public string Model { get; set; }
+ }
+}
diff --git a/BotSharp.Platform.Rasa/Models/RasaResponse.cs b/BotSharp.Platform.Rasa/Models/RasaResponse.cs
new file mode 100644
index 00000000..8461ea7e
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Models/RasaResponse.cs
@@ -0,0 +1,32 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa.Models
+{
+ public class RasaResponse
+ {
+ public RasaResponseIntent Intent { get; set; }
+
+ //public AIResponseFulfillment Fullfillment { get; set; }
+
+ [JsonProperty("intent_ranking")]
+ public List IntentRanking { get; set; }
+
+ public List Entities { get; set; }
+
+ public String Text { get; set; }
+
+ public String Project { get; set; }
+
+ public String Model { get; set; }
+ }
+
+ public class RasaResponseIntent
+ {
+ public String Name { get; set; }
+
+ public Decimal Confidence { get; set; }
+ }
+}
diff --git a/BotSharp.Platform.Rasa/Models/RasaResponseEntity.cs b/BotSharp.Platform.Rasa/Models/RasaResponseEntity.cs
new file mode 100644
index 00000000..6a0ae753
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Models/RasaResponseEntity.cs
@@ -0,0 +1,11 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa.Models
+{
+ public class RasaResponseEntity : RasaIntentExpressionPart
+ {
+ public string Extractor { get; set; }
+ }
+}
diff --git a/BotSharp.Platform.Rasa/Models/RasaStatusModel.cs b/BotSharp.Platform.Rasa/Models/RasaStatusModel.cs
new file mode 100644
index 00000000..112f1727
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Models/RasaStatusModel.cs
@@ -0,0 +1,35 @@
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa.Models
+{
+ public class RasaStatusModel
+ {
+ [JsonProperty("available_projects")]
+ public JObject AvailableProjects { get; set; }
+
+ [JsonProperty("current_training_processes")]
+ public int CurrentTrainingProcesses { get; set; }
+
+ [JsonProperty("max_training_processes")]
+ public int MaxTrainingProcesses { get; set; }
+ }
+
+ public class RasaProjectModel
+ {
+ [JsonProperty("status")]
+ public string Status { get; set; }
+
+ [JsonProperty("current_training_processes")]
+ public int CurrentTrainingProcesses { get; set; }
+
+ [JsonProperty("available_models")]
+ public List AvailableModels { get; set; }
+
+ [JsonProperty("loaded_models")]
+ public List LoadedModels { get; set; }
+ }
+}
diff --git a/BotSharp.Platform.Rasa/Models/RasaTrainingData.cs b/BotSharp.Platform.Rasa/Models/RasaTrainingData.cs
new file mode 100644
index 00000000..f44b61aa
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Models/RasaTrainingData.cs
@@ -0,0 +1,19 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa.Models
+{
+ public class RasaTrainingData
+ {
+ [JsonProperty("common_examples")]
+ public List UserSays { get; set; }
+
+ [JsonProperty("entity_synonyms")]
+ public List Entities { get; set; }
+
+ [JsonProperty("regex_features")]
+ public List Regex { get; set; }
+ }
+}
diff --git a/BotSharp.Platform.Rasa/Models/RasaTrainingEntity.cs b/BotSharp.Platform.Rasa/Models/RasaTrainingEntity.cs
new file mode 100644
index 00000000..7df3081c
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Models/RasaTrainingEntity.cs
@@ -0,0 +1,17 @@
+using BotSharp.Core.Engines;
+using BotSharp.Platform.Models;
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa.Models
+{
+ public sealed class RasaTrainingEntity
+ {
+ [JsonProperty("value")]
+ public String Entity { get; set; }
+
+ public List Synonyms { get; set; }
+ }
+}
diff --git a/BotSharp.Platform.Rasa/Models/RasaTrainingRegex.cs b/BotSharp.Platform.Rasa/Models/RasaTrainingRegex.cs
new file mode 100644
index 00000000..3c173afd
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Models/RasaTrainingRegex.cs
@@ -0,0 +1,13 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa.Models
+{
+ public class RasaTrainingRegex
+ {
+ public String Name { get; set; }
+
+ public String Pattern { get; set; }
+ }
+}
diff --git a/BotSharp.Platform.Rasa/Models/RasaVersionModel.cs b/BotSharp.Platform.Rasa/Models/RasaVersionModel.cs
new file mode 100644
index 00000000..b93b1283
--- /dev/null
+++ b/BotSharp.Platform.Rasa/Models/RasaVersionModel.cs
@@ -0,0 +1,15 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa.Models
+{
+ public class RasaVersionModel
+ {
+ public string Version { get; set; }
+
+ [JsonProperty("minimum_compatible_version")]
+ public string MinimumCompatibleVersion { get; set; }
+ }
+}
diff --git a/BotSharp.Platform.Rasa/ModuleInjector.cs b/BotSharp.Platform.Rasa/ModuleInjector.cs
new file mode 100644
index 00000000..c430792c
--- /dev/null
+++ b/BotSharp.Platform.Rasa/ModuleInjector.cs
@@ -0,0 +1,29 @@
+using BotSharp.Core;
+using BotSharp.Core.AgentStorage;
+using BotSharp.Core.Modules;
+using BotSharp.Platform.Rasa.Models;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa
+{
+ public class ModuleInjector : IModule
+ {
+ public void ConfigureServices(IServiceCollection services, IConfiguration config)
+ {
+ services.AddSingleton>();
+ AgentStorageServiceRegister.Register(services);
+ PlatformConfigServiceRegister.Register("rasaAi", services, config);
+ }
+
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ {
+
+ }
+ }
+}
diff --git a/BotSharp.Platform.Rasa/PlatformSettings.cs b/BotSharp.Platform.Rasa/PlatformSettings.cs
new file mode 100644
index 00000000..9bec1404
--- /dev/null
+++ b/BotSharp.Platform.Rasa/PlatformSettings.cs
@@ -0,0 +1,11 @@
+using BotSharp.Core;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa
+{
+ public class PlatformSettings : PlatformSettingsBase
+ {
+ }
+}
diff --git a/BotSharp.Platform.Rasa/README.md b/BotSharp.Platform.Rasa/README.md
new file mode 100644
index 00000000..3c1941f0
--- /dev/null
+++ b/BotSharp.Platform.Rasa/README.md
@@ -0,0 +1,2 @@
+# botsharp-rasa
+BotSharp platform emulator extension which is compatible with RASA NLU.
diff --git a/BotSharp.Platform.Rasa/RasaAi.cs b/BotSharp.Platform.Rasa/RasaAi.cs
new file mode 100644
index 00000000..f1080949
--- /dev/null
+++ b/BotSharp.Platform.Rasa/RasaAi.cs
@@ -0,0 +1,138 @@
+using BotSharp.Core;
+using BotSharp.Core.Engines;
+using BotSharp.Platform.Abstraction;
+using BotSharp.Platform.Models;
+using BotSharp.Platform.Models.AiRequest;
+using BotSharp.Platform.Models.AiResponse;
+using BotSharp.Platform.Rasa.Models;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+
+namespace BotSharp.Platform.Rasa
+{
+ ///
+ /// Rasa nlu >= 0.12
+ ///
+ public class RasaAi :
+ PlatformBuilderBase,
+ IPlatformBuilder
+ where TAgent : AgentModel
+ {
+
+ public RasaAi(IAgentStorageFactory agentStorageFactory, IPlatformSettings settings)
+ : base(agentStorageFactory, settings)
+ {
+
+ }
+
+
+ public async Task TextRequest(AiRequest request)
+ {
+ AiResponse aiResponse = new AiResponse();
+
+ /*string model = RasaRequestExtension.GetModelPerContexts(agent, AiConfig, request, dc);
+ var result = CallRasa(agent.Id, request.Query.First(), model);
+
+ result.Content.Log();
+
+ RasaResponse response = result.Data;
+ aiResponse.Id = Guid.NewGuid().ToString();
+ aiResponse.Lang = agent.Language;
+ aiResponse.Status = new AIResponseStatus { };
+ aiResponse.SessionId = AiConfig.SessionId;
+ aiResponse.Timestamp = DateTime.UtcNow;
+
+ var intentResponse = RasaRequestExtension.HandleIntentPerContextIn(agent, AiConfig, request, result.Data, dc);
+
+ RasaRequestExtension.HandleParameter(agent, intentResponse, response, request);
+
+ RasaRequestExtension.HandleMessage(intentResponse);
+
+ aiResponse.Result = new AIResponseResult
+ {
+ Source = "agent",
+ ResolvedQuery = request.Query.First(),
+ Action = intentResponse?.Action,
+ Parameters = intentResponse?.Parameters?.ToDictionary(x => x.Name, x => (object)x.Value),
+ Score = response.Intent.Confidence,
+ Metadata = new AIResponseMetadata { IntentId = intentResponse?.IntentId, IntentName = intentResponse?.IntentName },
+ Fulfillment = new AIResponseFulfillment
+ {
+ Messages = intentResponse?.Messages?.Select(x => {
+ if (x.Type == AIResponseMessageType.Custom)
+ {
+ return (new
+ {
+ x.Type,
+ Payload = JsonConvert.DeserializeObject(x.PayloadJson)
+ }) as Object;
+ }
+ else
+ {
+ return (new { x.Type, x.Speech }) as Object;
+ }
+
+ }).ToList()
+ }
+ };
+
+ RasaRequestExtension.HandleContext(dc, AiConfig, intentResponse, aiResponse);
+
+ Console.WriteLine(JsonConvert.SerializeObject(aiResponse.Result));*/
+
+ return aiResponse;
+ }
+
+ public async Task ExtractorCorpus(TAgent agent)
+ {
+ var corpus = new TrainingCorpus()
+ {
+ Entities = new List(),
+ UserSays = new List>()
+ };
+
+ List entities = new List();
+
+ // generate entity list
+ agent.Intents.ForEach(intent =>
+ {
+ intent.Entities.ForEach(entity =>
+ {
+ if (!entities.Contains(entity.Entity))
+ {
+ corpus.Entities.Add(new TrainingEntity
+ {
+ Entity = entity.Entity,
+ Values = new List
+ {
+ new TrainingEntitySynonym
+ {
+ Value = entity.Value,
+ Synonyms = new List
+ {
+ entity.Value
+ }
+ }
+ }
+ });
+ entities.Add(entity.Entity);
+ }
+ });
+ });
+
+ agent.Intents.ForEach(intent =>
+ {
+ var express = new TrainingIntentExpression()
+ {
+ Text = intent.Text,
+ Intent = intent.Intent,
+ Entities = intent.Entities
+ };
+
+ corpus.UserSays.Add(express);
+ });
+
+ return corpus;
+ }
+ }
+}
diff --git a/BotSharp.Platform.Rasa/ViewModels/RasaTrainRequestViewModel.cs b/BotSharp.Platform.Rasa/ViewModels/RasaTrainRequestViewModel.cs
new file mode 100644
index 00000000..e6950a7f
--- /dev/null
+++ b/BotSharp.Platform.Rasa/ViewModels/RasaTrainRequestViewModel.cs
@@ -0,0 +1,17 @@
+using Newtonsoft.Json;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Platform.Rasa.Models
+{
+ public class RasaTrainRequestViewModel
+ {
+ public string Project { get; set; }
+
+ public string Model { get; set; }
+
+ [JsonProperty("rasa_nlu_data")]
+ public RasaTrainingData Corpus { get; set; }
+ }
+}
diff --git a/BotSharp.WebHost/Settings/app.json b/BotSharp.WebHost/Settings/app.json
index 486cfadc..80e7e1a3 100644
--- a/BotSharp.WebHost/Settings/app.json
+++ b/BotSharp.WebHost/Settings/app.json
@@ -10,6 +10,10 @@
"Name": "DialogflowAi",
"Type": "BotSharp.Platform.Dialogflow"
},
+ {
+ "Name": "RasaAi",
+ "Type": "BotSharp.Platform.Rasa"
+ },
{
"Name": "WeixinChannel",
"Type": "BotSharp.Channel.Weixin"
diff --git a/BotSharp.sln b/BotSharp.sln
index c803e238..a4aeaff0 100644
--- a/BotSharp.sln
+++ b/BotSharp.sln
@@ -23,6 +23,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Channel.Weixin", "
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Channel.FacebookMessenger", "BotSharp.Channel.FacebookMessenger\BotSharp.Channel.FacebookMessenger.csproj", "{22C52A04-581B-4186-8C04-0CD359FA568A}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BotSharp.Platform.Rasa", "BotSharp.Platform.Rasa\BotSharp.Platform.Rasa.csproj", "{2EDE5F82-9219-4827-B636-13717DCDDF01}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
ARTICULATE|Any CPU = ARTICULATE|Any CPU
@@ -231,6 +233,30 @@ Global
{22C52A04-581B-4186-8C04-0CD359FA568A}.Test|Any CPU.Build.0 = Debug|Any CPU
{22C52A04-581B-4186-8C04-0CD359FA568A}.Test|x64.ActiveCfg = Debug|Any CPU
{22C52A04-581B-4186-8C04-0CD359FA568A}.Test|x64.Build.0 = Debug|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.ARTICULATE|Any CPU.ActiveCfg = Debug|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.ARTICULATE|Any CPU.Build.0 = Debug|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.ARTICULATE|x64.ActiveCfg = Debug|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.ARTICULATE|x64.Build.0 = Debug|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.Debug|x64.Build.0 = Debug|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.DIALOGFLOW|Any CPU.ActiveCfg = Debug|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.DIALOGFLOW|Any CPU.Build.0 = Debug|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.DIALOGFLOW|x64.ActiveCfg = Debug|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.DIALOGFLOW|x64.Build.0 = Debug|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.RASA|Any CPU.ActiveCfg = Release|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.RASA|Any CPU.Build.0 = Release|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.RASA|x64.ActiveCfg = Release|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.RASA|x64.Build.0 = Release|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.Release|Any CPU.Build.0 = Release|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.Release|x64.ActiveCfg = Release|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.Release|x64.Build.0 = Release|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.Test|Any CPU.ActiveCfg = Debug|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.Test|Any CPU.Build.0 = Debug|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.Test|x64.ActiveCfg = Debug|Any CPU
+ {2EDE5F82-9219-4827-B636-13717DCDDF01}.Test|x64.Build.0 = Debug|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE