Added RASA platform emulator.

This commit is contained in:
haiping008 2018-11-19 09:20:59 -06:00
parent bb764cd80c
commit a639e1210e
31 changed files with 1350 additions and 0 deletions

View file

@ -14,6 +14,7 @@
<ItemGroup>
<ProjectReference Include="..\BotSharp.Platform.Dialogflow\BotSharp.Platform.Dialogflow.csproj" />
<ProjectReference Include="..\BotSharp.Platform.Rasa\BotSharp.Platform.Rasa.csproj" />
</ItemGroup>
</Project>

View file

@ -25,6 +25,7 @@
<ItemGroup>
<ProjectReference Include="..\BotSharp.Platform.Dialogflow\BotSharp.Platform.Dialogflow.csproj" />
<ProjectReference Include="..\BotSharp.Platform.Rasa\BotSharp.Platform.Rasa.csproj" />
</ItemGroup>
</Project>

View file

@ -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<TAgent> : IAgentImporter<TAgent> where TAgent : AgentModel, new()
{
public string AgentDir { get; set; }
public async Task<TAgent> 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<EntityEntry>(),
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<EntrySynonym>
{
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<RasaAgentImportModel>(data);
agent.Intents = rasa.Data.Intents;
agent.Entities = rasa.Data.Entities;
}
private void ImportIntentUserSays(RasaIntentExpression intent, List<RasaIntentExpression> sentences)
{
var intents = new List<RasaIntentExpression>();
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<IntentExpressionPart>();
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<TrainingEntity>(),
UserSays = new List<TrainingIntentExpression<TrainingIntentExpressionPart>>()
};
agent.Intents.ForEach(intent =>
{
intent.UserSays.ForEach(say => {
agent.Corpus.UserSays.Add(new TrainingIntentExpression<TrainingIntentExpressionPart>
{
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()
});
});
});
}*/
}
}

View file

@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.1.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\BotSharp\BotSharp.Core\BotSharp.Core.csproj" />
</ItemGroup>
</Project>

View file

@ -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<RasaVersionModel> Get()
{
var status = new RasaStatusModel
{
AvailableProjects = JObject.FromObject(new RasaProjectModel
{
Status = "ready",
AvailableModels = new List<string> { "model_XXXXXX" },
LoadedModels = new List<string> { "model_XXXXXX" }
})
};
return Ok(status);
}
}
}

View file

@ -0,0 +1,98 @@
using BotSharp.Platform.Rasa.Models;
using Microsoft.AspNetCore.Mvc;
namespace BotSharp.Platform.Rasa.Controllers
{
/// <summary>
/// send a text request
/// </summary>
[Route("[controller]")]
public class ParseController : ControllerBase
{
private readonly RasaAi<AgentModel> builder;
/// <summary>
/// Initialize dialog controller and get a platform instance
/// </summary>
/// <param name="platform"></param>
public ParseController(RasaAi<AgentModel> configuration)
{
builder = configuration;
}
/// <summary>
/// parse request
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[HttpPost, HttpGet]
public ActionResult<RasaResponse> 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<RasaRequestModel>(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<RasaResponseIntent>
{
new RasaResponseIntent
{
Name = aIResponse.Result.Metadata.IntentName,
Confidence = aIResponse.Result.Score
}
},
Fullfillment = aIResponse.Result.Fulfillment
};
return rasaResponse;*/
return null;
}
}
}

View file

@ -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
{
/// <summary>
/// This returns all the currently available projects.
/// </summary>
[Route("[controller]")]
public class StatusController : ControllerBase
{
/// <summary>
/// Returns a list of available projects the server can use to fulfill /parse requests.
/// </summary>
/// <returns></returns>
[HttpGet]
public ActionResult<RasaVersionModel> 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<string> availableModels = new List<string>();
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);
}
}
}

View file

@ -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
{
/// <summary>
/// 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.
/// </summary>
[Route("[controller]")]
public class TrainController : ControllerBase
{
private RasaAi<AgentModel> builder;
private readonly IPlatformSettings settings;
public TrainController(RasaAi<AgentModel> configuration, IPlatformSettings settings)
{
builder = configuration;
this.settings = settings;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="model">Model name</param>
/// <param name="project">Agent name or agent id</param>
/// <returns></returns>
[HttpPost]
public async Task<ActionResult<ModelMetaData>> 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<AgentModel> 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<RasaTrainRequestModel>(data);
//rasa_nlu_data.Model = model;
//rasa_nlu_data.Project = project;
var agent = await builder.LoadAgentFromFile<AgentImporterInRasa<AgentModel>>(rawPath);
await builder.SaveAgent(agent);
return agent;
}
private async Task<ActionResult<String>> 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 });
}
}
}

View file

@ -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<RasaVersionModel> Get()
{
return Ok(new RasaVersionModel
{
Version = "0.13.0",
MinimumCompatibleVersion = "0.13.0"
});
}
}
#endif
}

View file

@ -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<RasaIntentExpression> Intents { get; set; }
[JsonProperty("entity_synonyms")]
public List<RasaTrainingEntity> Entities { get; set; }
[JsonProperty("regex_features")]
public List<RasaTrainingRegex> Regex { get; set; }
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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<ConversationContext>()
.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<RasaResponseIntent>
{
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<Intent>().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;
}
/// <summary>
///
/// </summary>
/// <param name="agent"></param>
/// <param name="intentResponse"></param>
/// <param name="response"></param>
/// <param name="request"></param>
/// <returns>Required field is missed</returns>
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<IntentResponseMessage> {
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<IntentResponseParameter> parameters, string text)
{
var reg = new Regex(@"\$\w+");
reg.Matches(text).Cast<Match>().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<ConversationContext>().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<ConversationContext>().Remove(session1);
}
else
{
session1.Lifespan = ctx.Lifespan;
}
}
else
{
dc.Table<ConversationContext>().Add(new ConversationContext
{
ConversationId = aiRequest.SessionId,
Context = ctx.Name,
Lifespan = ctx.Lifespan
});
}
});
});
/*aiResponse.Result.Contexts = dc.Table<ConversationContext>()
.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<ConversationContext>()
.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;
}
}
}

View file

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

View file

@ -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<RasaResponseIntent> IntentRanking { get; set; }
public List<RasaResponseEntity> 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; }
}
}

View file

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

View file

@ -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<string> AvailableModels { get; set; }
[JsonProperty("loaded_models")]
public List<string> LoadedModels { get; set; }
}
}

View file

@ -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<RasaIntentExpression> UserSays { get; set; }
[JsonProperty("entity_synonyms")]
public List<RasaTrainingEntity> Entities { get; set; }
[JsonProperty("regex_features")]
public List<RasaTrainingRegex> Regex { get; set; }
}
}

View file

@ -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<string> Synonyms { get; set; }
}
}

View file

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

View file

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

View file

@ -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<RasaAi<AgentModel>>();
AgentStorageServiceRegister.Register<AgentModel>(services);
PlatformConfigServiceRegister.Register<PlatformSettings>("rasaAi", services, config);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
}
}
}

View file

@ -0,0 +1,11 @@
using BotSharp.Core;
using System;
using System.Collections.Generic;
using System.Text;
namespace BotSharp.Platform.Rasa
{
public class PlatformSettings : PlatformSettingsBase
{
}
}

View file

@ -0,0 +1,2 @@
# botsharp-rasa
BotSharp platform emulator extension which is compatible with RASA NLU.

View file

@ -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
{
/// <summary>
/// Rasa nlu >= 0.12
/// </summary>
public class RasaAi<TAgent> :
PlatformBuilderBase<TAgent>,
IPlatformBuilder<TAgent>
where TAgent : AgentModel
{
public RasaAi(IAgentStorageFactory<TAgent> agentStorageFactory, IPlatformSettings settings)
: base(agentStorageFactory, settings)
{
}
public async Task<AiResponse> 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<TrainingCorpus> ExtractorCorpus(TAgent agent)
{
var corpus = new TrainingCorpus()
{
Entities = new List<TrainingEntity>(),
UserSays = new List<TrainingIntentExpression<TrainingIntentExpressionPart>>()
};
List<string> entities = new List<string>();
// 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<TrainingEntitySynonym>
{
new TrainingEntitySynonym
{
Value = entity.Value,
Synonyms = new List<string>
{
entity.Value
}
}
}
});
entities.Add(entity.Entity);
}
});
});
agent.Intents.ForEach(intent =>
{
var express = new TrainingIntentExpression<TrainingIntentExpressionPart>()
{
Text = intent.Text,
Intent = intent.Intent,
Entities = intent.Entities
};
corpus.UserSays.Add(express);
});
return corpus;
}
}
}

View file

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

View file

@ -10,6 +10,10 @@
"Name": "DialogflowAi",
"Type": "BotSharp.Platform.Dialogflow"
},
{
"Name": "RasaAi",
"Type": "BotSharp.Platform.Rasa"
},
{
"Name": "WeixinChannel",
"Type": "BotSharp.Channel.Weixin"

View file

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