From b72717cedfb89a19a41732c9e337e4185a6081f0 Mon Sep 17 00:00:00 2001
From: Bolo <36342729+PppBr@users.noreply.github.com>
Date: Thu, 9 Aug 2018 16:06:40 -0500
Subject: [PATCH] update code with Oceania
---
.gitignore | 9 +
BotSharp.Core/Abstractions/IBotPlatform.cs | 3 +-
BotSharp.Core/Abstractions/INlpPipeline.cs | 12 +-
BotSharp.Core/BotSharp.Core.csproj | 6 +-
BotSharp.Core/Engines/BotEngineBase.cs | 13 +-
BotSharp.Core/Engines/BotPreditor.cs | 53 +
BotSharp.Core/Engines/BotSharp/BotSharpAi.cs | 9 +-
BotSharp.Core/Engines/BotTrainer.cs | 63 +-
.../CRFsuite/CRFsuiteEntityRecognizer.cs | 141 +-
.../Engines/Classifiers/FasttextClassifier.cs | 49 +
BotSharp.Core/Engines/ModelMetaData.cs | 17 +
BotSharp.Core/Engines/PipeModel.cs | 29 +
.../Engines/Sebis/AgentImporterInSebis.cs | 2 +-
BotSharp.Core/Engines/SpaCy/SpaCyEntitizer.cs | 8 +-
.../Engines/SpaCy/SpaCyEntityRecognizer.cs | 8 +-
BotSharp.Core/Engines/SpaCy/SpaCyProvider.cs | 26 +-
BotSharp.Core/Engines/SpaCy/SpaCyTagger.cs | 11 +-
.../Engines/SpaCy/SpaCyTextCategorizer.cs | 8 +-
BotSharp.Core/Engines/SpaCy/SpaCyTokenizer.cs | 20 +-
.../Engines/SpaCy/SpacyFeaturizer.cs | 8 +-
BotSharp.MachineLearning/CRFsuite/Crfutils.cs | 11 +-
BotSharp.MachineLearning/CRFsuite/Ner.cs | 22 +-
BotSharp.MachineLearning/NLP/NlpToken.cs | 3 +
BotSharp.MachineLearning/SpaCy/server.py | 148 +-
BotSharp.RestApi/Dialogs/DialogController.cs | 9 +-
BotSharp.WebHost/Algorithms/crfsuite.exe | Bin 0 -> 215040 bytes
BotSharp.WebHost/Algorithms/fasttext.exe | Bin 0 -> 495855 bytes
.../DbInitializer/Accounts/users.json | 1 +
.../Sebis/{Airport => Chatbot}/agent.json | 0
.../Sebis/{Airport => Chatbot}/corpus.json | 8444 ++++++++---------
.../App_Data/DbInitializer/Agents/agents.json | 2 +-
BotSharp.WebHost/BotSharp.WebHost.csproj | 16 +-
BotSharp.WebHost/Settings/app.json | 3 +-
BotSharp.WebHost/Settings/bot.json | 7 +-
BotSharp.WebHost/Startup.cs | 74 +-
README.md | 2 +-
36 files changed, 4782 insertions(+), 4455 deletions(-)
create mode 100644 BotSharp.Core/Engines/BotPreditor.cs
create mode 100644 BotSharp.Core/Engines/Classifiers/FasttextClassifier.cs
create mode 100644 BotSharp.Core/Engines/ModelMetaData.cs
create mode 100644 BotSharp.Core/Engines/PipeModel.cs
create mode 100644 BotSharp.WebHost/Algorithms/crfsuite.exe
create mode 100644 BotSharp.WebHost/Algorithms/fasttext.exe
rename BotSharp.WebHost/App_Data/DbInitializer/Agents/Sebis/{Airport => Chatbot}/agent.json (100%)
rename BotSharp.WebHost/App_Data/DbInitializer/Agents/Sebis/{Airport => Chatbot}/corpus.json (94%)
diff --git a/.gitignore b/.gitignore
index cae01f15..4298cb56 100644
--- a/.gitignore
+++ b/.gitignore
@@ -294,3 +294,12 @@ __pycache__/
/BotSharp.UnitTest/App_Data/BotSharp.db
/BotSharp.WebHost/App_Data/BotSharp.db
/BotSharp.UI
+/BotSharp.WebHost/App_Data/TrainingFiles/bff7605c-3db5-44dc-9ba7-1c9be2832318.parsed.txt
+/BotSharp.WebHost/App_Data/TrainingFiles/bff7605c-3db5-44dc-9ba7-1c9be2832318.model
+/BotSharp.WebHost/App_Data/TrainingFiles/bff7605c-3db5-44dc-9ba7-1c9be2832318.corpus.txt
+/BotSharp.WebHost/App_Data/ModelFiles/bff7605c-3db5-44dc-9ba7-1c9be2832318/ner-crf.model
+/BotSharp.WebHost/App_Data/ModelFiles/bff7605c-3db5-44dc-9ba7-1c9be2832318/metadata.json
+/BotSharp.WebHost/App_Data/TrainingFiles/bff7605c-3db5-44dc-9ba7-1c9be2832318/ner-crf.parsed.txt
+/BotSharp.WebHost/App_Data/TrainingFiles/bff7605c-3db5-44dc-9ba7-1c9be2832318/ner-crf.corpus.txt
+/BotSharp.WebHost/App_Data/ModelFiles/bff7605c-3db5-44dc-9ba7-1c9be2832318
+/BotSharp.WebHost/App_Data/TrainingFiles/bff7605c-3db5-44dc-9ba7-1c9be2832318
diff --git a/BotSharp.Core/Abstractions/IBotPlatform.cs b/BotSharp.Core/Abstractions/IBotPlatform.cs
index a07c4198..546c6b11 100644
--- a/BotSharp.Core/Abstractions/IBotPlatform.cs
+++ b/BotSharp.Core/Abstractions/IBotPlatform.cs
@@ -4,6 +4,7 @@ using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Text;
+using System.Threading.Tasks;
namespace BotSharp.Core.Engines
{
@@ -18,6 +19,6 @@ namespace BotSharp.Core.Engines
AIResponse TextRequest(AIRequest request);
- void Train();
+ Task Train();
}
}
diff --git a/BotSharp.Core/Abstractions/INlpPipeline.cs b/BotSharp.Core/Abstractions/INlpPipeline.cs
index 774be45e..eb16b353 100644
--- a/BotSharp.Core/Abstractions/INlpPipeline.cs
+++ b/BotSharp.Core/Abstractions/INlpPipeline.cs
@@ -1,9 +1,11 @@
using BotSharp.Core.Agents;
+using BotSharp.Core.Engines;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
using System.Text;
+using System.Threading.Tasks;
namespace BotSharp.Core.Abstractions
{
@@ -14,6 +16,14 @@ namespace BotSharp.Core.Abstractions
{
IConfiguration Configuration { get; set; }
- bool Process(Agent agent, JObject data);
+ ///
+ /// Process
+ ///
+ ///
+ /// Intermediate result
+ /// Meta data which is packed to model
+ ///
+ Task Train(Agent agent, JObject data, PipeModel meta);
+ Task Predict(Agent agent, JObject data, PipeModel meta);
}
}
diff --git a/BotSharp.Core/BotSharp.Core.csproj b/BotSharp.Core/BotSharp.Core.csproj
index 6378b2af..fec84743 100644
--- a/BotSharp.Core/BotSharp.Core.csproj
+++ b/BotSharp.Core/BotSharp.Core.csproj
@@ -35,7 +35,7 @@
-
+
@@ -46,4 +46,8 @@
+
+
+
+
diff --git a/BotSharp.Core/Engines/BotEngineBase.cs b/BotSharp.Core/Engines/BotEngineBase.cs
index 6445072b..c5854450 100644
--- a/BotSharp.Core/Engines/BotEngineBase.cs
+++ b/BotSharp.Core/Engines/BotEngineBase.cs
@@ -4,11 +4,13 @@ using BotSharp.Core.Intents;
using BotSharp.Core.Models;
using EntityFrameworkCore.BootKit;
using Microsoft.EntityFrameworkCore;
+using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
+using System.Threading.Tasks;
namespace BotSharp.Core.Engines
{
@@ -32,6 +34,13 @@ namespace BotSharp.Core.Engines
DbInitializerPath = Path.Join(dataPath, $"DbInitializer");
}
+ public AIResponse TextRequest(AIRequest request)
+ {
+ var preditor = new BotPreditor();
+ var text = preditor.Predict(agent, request);
+ return null;
+ }
+
public Agent LoadAgent(string id)
{
if (agent == null)
@@ -177,9 +186,9 @@ namespace BotSharp.Core.Engines
return corpus;
}
- public virtual void Train()
+ public virtual Task Train()
{
-
+ return Task.CompletedTask;
}
}
diff --git a/BotSharp.Core/Engines/BotPreditor.cs b/BotSharp.Core/Engines/BotPreditor.cs
new file mode 100644
index 00000000..e6522a58
--- /dev/null
+++ b/BotSharp.Core/Engines/BotPreditor.cs
@@ -0,0 +1,53 @@
+using BotSharp.Core.Abstractions;
+using BotSharp.Core.Agents;
+using BotSharp.Core.Models;
+using DotNetToolkit;
+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;
+using System.Threading.Tasks;
+
+namespace BotSharp.Core.Engines
+{
+ public class BotPreditor
+ {
+ public async Task Predict(Agent agent, AIRequest request)
+ {
+ // load model
+ var dir = Path.Join(AppDomain.CurrentDomain.GetData("DataPath").ToString(), "ModelFiles", agent.Id);
+ Console.WriteLine($"Load model from {dir}");
+ var metaJson = File.ReadAllText(Path.Join(dir, "metadata.json"));
+ var meta = JsonConvert.DeserializeObject(metaJson);
+
+ // Get NLP Provider
+ var config = (IConfiguration)AppDomain.CurrentDomain.GetData("Configuration");
+ var assemblies = (string[])AppDomain.CurrentDomain.GetData("Assemblies");
+
+ var providerPipe = meta.Pipeline.First();
+ var provider = TypeHelper.GetInstance(providerPipe.Name, assemblies) as INlpPipeline;
+ provider.Configuration = config.GetSection(meta.Platform);
+
+ var data = JObject.FromObject(new
+ {
+ });
+
+ await provider.Train(agent, data, providerPipe);
+ meta.Pipeline.RemoveAt(0);
+
+ // pipe process
+ meta.Pipeline.ForEach(async pipeMeta =>
+ {
+ var pipe = TypeHelper.GetInstance(pipeMeta.Name, assemblies) as INlpPipeline;
+ pipe.Configuration = provider.Configuration;
+ await pipe.Predict(agent, data, pipeMeta);
+ });
+
+ return "";
+ }
+ }
+}
diff --git a/BotSharp.Core/Engines/BotSharp/BotSharpAi.cs b/BotSharp.Core/Engines/BotSharp/BotSharpAi.cs
index bec9333e..4ea57d6f 100644
--- a/BotSharp.Core/Engines/BotSharp/BotSharpAi.cs
+++ b/BotSharp.Core/Engines/BotSharp/BotSharpAi.cs
@@ -1,21 +1,18 @@
using System;
using System.Collections.Generic;
using System.Text;
+using System.Threading.Tasks;
using BotSharp.Core.Models;
namespace BotSharp.Core.Engines.BotSharp
{
public class BotSharpAi : BotEngineBase, IBotPlatform
{
- public AIResponse TextRequest(AIRequest request)
- {
- throw new NotImplementedException();
- }
- public override void Train()
+ public override async Task Train()
{
agent.Corpus = GetIntentExpressions();
var trainer = new BotTrainer(agent.Id, dc);
- trainer.Train(agent);
+ await trainer.Train(agent);
}
}
}
diff --git a/BotSharp.Core/Engines/BotTrainer.cs b/BotSharp.Core/Engines/BotTrainer.cs
index 9065a3f2..ed56e360 100644
--- a/BotSharp.Core/Engines/BotTrainer.cs
+++ b/BotSharp.Core/Engines/BotTrainer.cs
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Text;
+using System.Threading.Tasks;
using BotSharp.Core.Abstractions;
using BotSharp.Core.Agents;
using BotSharp.Core.Intents;
@@ -9,7 +11,9 @@ using DotNetToolkit;
using EntityFrameworkCore.BootKit;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
+using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
+using Newtonsoft.Json.Serialization;
namespace BotSharp.Core.Engines
{
@@ -25,7 +29,7 @@ namespace BotSharp.Core.Engines
this.agentId = agentId;
}
- public string Train(Agent agent)
+ public async Task Train(Agent agent)
{
agent.Intents = dc.Table()
.Include(x => x.Contexts)
@@ -47,9 +51,37 @@ namespace BotSharp.Core.Engines
string providerName = config.GetSection($"{platform}:Provider").Value;
var provider = TypeHelper.GetInstance(providerName, assemblies) as INlpPipeline;
provider.Configuration = config.GetSection(platform);
- provider.Process(agent, data);
- //var corpus = agent.GrabCorpus(dc);
+ var pipeModel = new PipeModel
+ {
+ Name = providerName,
+ Class = provider.ToString(),
+ Meta = new JObject(),
+ Time = DateTime.UtcNow
+ };
+
+ await provider.Train(agent, data, pipeModel);
+
+ var meta = new ModelMetaData
+ {
+ Platform = platform,
+ Language = agent.Language,
+ TrainingDate = DateTime.UtcNow,
+ Version = config.GetValue($"Version"),
+ Pipeline = new List() { pipeModel }
+ };
+
+ var dirTrain = Path.Join(AppDomain.CurrentDomain.GetData("DataPath").ToString(), "TrainingFiles", agent.Id);
+ if (!Directory.Exists(dirTrain))
+ {
+ Directory.CreateDirectory(dirTrain);
+ }
+
+ var dirModel = Path.Join(AppDomain.CurrentDomain.GetData("DataPath").ToString(), "ModelFiles", agent.Id);
+ if (!Directory.Exists(dirModel))
+ {
+ Directory.CreateDirectory(dirModel);
+ }
// pipe process
var pipelines = provider.Configuration.GetSection($"Pipe").Value
@@ -57,15 +89,34 @@ namespace BotSharp.Core.Engines
.Select(x => x.Trim())
.ToList();
- pipelines.ForEach(pipeName =>
+ pipelines.ForEach(async pipeName =>
{
var pipe = TypeHelper.GetInstance(pipeName, assemblies) as INlpPipeline;
pipe.Configuration = provider.Configuration;
- pipe.Process(agent, data);
+ pipeModel = new PipeModel
+ {
+ Name = pipeName,
+ Class = pipe.ToString(),
+ Time = DateTime.UtcNow
+ };
+ meta.Pipeline.Add(pipeModel);
+
+ await pipe.Train(agent, data, pipeModel);
});
+ // save model meta data
+ var dir = Path.Join(AppDomain.CurrentDomain.GetData("DataPath").ToString(), "ModelFiles", agent.Id);
+ var metaJson = JsonConvert.SerializeObject(meta, new JsonSerializerSettings
+ {
+ Formatting = Formatting.Indented,
+ NullValueHandling = NullValueHandling.Ignore,
+ ContractResolver = new CamelCasePropertyNamesContractResolver()
+ });
+ File.WriteAllText(Path.Join(dir, "metadata.json"), metaJson);
- return "";
+ Console.WriteLine(metaJson);
+
+ return metaJson;
}
}
}
diff --git a/BotSharp.Core/Engines/CRFsuite/CRFsuiteEntityRecognizer.cs b/BotSharp.Core/Engines/CRFsuite/CRFsuiteEntityRecognizer.cs
index 9094bde1..673d27da 100644
--- a/BotSharp.Core/Engines/CRFsuite/CRFsuiteEntityRecognizer.cs
+++ b/BotSharp.Core/Engines/CRFsuite/CRFsuiteEntityRecognizer.cs
@@ -1,17 +1,19 @@
using BotSharp.Core.Abstractions;
using BotSharp.Core.Agents;
using BotSharp.MachineLearning.NLP;
+using DotNetToolkit;
using EntityFrameworkCore.BootKit;
using Microsoft.Extensions.Configuration;
-using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Diagnostics;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
+using System.Threading.Tasks;
namespace BotSharp.Core.Engines.CRFsuite
{
@@ -19,73 +21,70 @@ namespace BotSharp.Core.Engines.CRFsuite
{
public IConfiguration Configuration { get; set; }
- public bool Process(Agent agent, JObject data)
+ public async Task Train(Agent agent, JObject data, PipeModel meta)
{
var dc = new DefaultDataContextLoader().GetDefaultDc();
var corpus = agent.Corpus;
-
- List> tags = data["Tags"].ToObject>>();
+
+ meta.Model = "ner-crf.model";
+
List> tokens = data["Tokens"].ToObject>>();
List> userSays = corpus.UserSays;
List> list = new List>();
- var dir = Path.Join(AppDomain.CurrentDomain.GetData("DataPath").ToString(), "TrainingFiles");
- FileStream fs = new FileStream(Path.Join(dir, "rawTrain.txt"), FileMode.Create);
- StreamWriter sw = new StreamWriter(fs);
+ var dirTrain = Path.Join(AppDomain.CurrentDomain.GetData("DataPath").ToString(), "TrainingFiles", agent.Id);
+ var dirModel = Path.Join(AppDomain.CurrentDomain.GetData("DataPath").ToString(), "ModelFiles", agent.Id);
+ string rawTrainingDataFileName = Path.Join(dirTrain, "ner-crf.corpus.txt");
+ string parsedTrainingDataFileName = Path.Join(dirTrain, "ner-crf.parsed.txt");
+ string modelFileName = Path.Join(dirModel, meta.Model);
- for (int i = 0 ; i < tags.Count; i++)
+ using (FileStream fs = new FileStream(rawTrainingDataFileName, FileMode.Create))
{
- List curLine = Merge(tokens[i], tags[i], userSays[i].Entities);
- list.Add(curLine);
- curLine.ForEach(trainingData =>{
- string[] wordParams = {trainingData.Entity, trainingData.Token, trainingData.Tag, trainingData.Chunk};
- string wordStr = string.Join(" ", wordParams);
- sw.Write(wordStr + "\n");
- });
- sw.Write("\n");
- }
- sw.Flush();
- sw.Close();
- fs.Close();
-
- new MachineLearning.CRFsuite.Ner().NerStart();
-
- Runcmd();
+ using (StreamWriter sw = new StreamWriter(fs))
+ {
+ for (int i = 0; i < tokens.Count; i++)
+ {
+ List curLine = Merge(tokens[i], userSays[i].Entities);
+ curLine.ForEach(trainingData =>
+ {
+ string[] wordParams = { trainingData.Entity, trainingData.Token, trainingData.Pos, trainingData.Chunk };
+ string wordStr = string.Join(" ", wordParams);
+ sw.Write(wordStr + "\n");
+ });
+ list.Add(curLine);
+ sw.Write("\n");
+ }
+ sw.Flush();
+ }
+ }
+
+ var fields = Configuration.GetValue($"CRFsuiteEntityRecognizer:fields");
+ var uniFeatures = Configuration.GetValue($"CRFsuiteEntityRecognizer:uniFeatures");
+ var biFeatures = Configuration.GetValue($"CRFsuiteEntityRecognizer:biFeatures");
+
+ new MachineLearning.CRFsuite.Ner()
+ .NerStart(rawTrainingDataFileName, parsedTrainingDataFileName, fields, uniFeatures.Split(" "), biFeatures.Split(" "));
+
+ var algorithmDir = Path.Join(AppDomain.CurrentDomain.GetData("ContentRootPath").ToString(), "Algorithms");
+
+ CmdHelper.Run(Path.Join(algorithmDir, "crfsuite"), $"learn -m {modelFileName} {parsedTrainingDataFileName}"); // --split=3 -x
+
+ Console.WriteLine($"Saved model to {modelFileName}");
+ meta.Meta = new JObject();
+ meta.Meta["fields"] = fields;
+ meta.Meta["uniFeatures"] = uniFeatures;
+ meta.Meta["biFeatures"] = biFeatures;
return true;
}
- public void Runcmd ()
- {
- var algorithmDir = Path.Join(AppDomain.CurrentDomain.GetData("ContentRootPath").ToString(), "Algorithms");
- var dataDir = Path.Join(AppDomain.CurrentDomain.GetData("DataPath").ToString(), "TrainingFiles");
-
- string cmd = $"{algorithmDir}/crfsuite learn -m {dataDir}/crfsuite/bolo.model {dataDir}/crfsuite/1.txt";
- System.Diagnostics.Process p = new System.Diagnostics.Process();
- p.StartInfo.FileName = "sh";
- p.StartInfo.UseShellExecute = false;
- p.StartInfo.RedirectStandardInput = true;
- p.StartInfo.RedirectStandardOutput = true;
- p.StartInfo.RedirectStandardError = true;
- p.StartInfo.CreateNoWindow = false;
- p.Start();
-
- p.StandardInput.WriteLine(cmd + "&exit");
- p.StandardInput.AutoFlush = false;
-
- string output = p.StandardOutput.ReadToEnd();
-
- p.WaitForExit();//等待程序执行完退出进程
- p.Close();
- Console.WriteLine(output);
- }
- public List Merge(List sentence, List tags, List entities)
+ public List Merge(List tokens, List entities)
{
List trainingTuple = new List();
HashSet entityWordBag = new HashSet();
int wordCandidateCount = 0;
- for (int i = 0; i < sentence.Count; i++)
+ for (int i = 0; i < tokens.Count; i++)
{
TrainingIntentExpressionPart curEntity = null;
if (entities != null)
@@ -97,7 +96,7 @@ namespace BotSharp.Core.Engines.CRFsuite
string[] words = entity.Value.Split(" ");
for (int j = 0; j < words.Length; j++)
{
- if (sentence[i + j].Text == words[j])
+ if (tokens[i + j].Text == words[j])
{
wordCandidateCount++;
if (j == words.Length - 1)
@@ -116,7 +115,7 @@ namespace BotSharp.Core.Engines.CRFsuite
String entityName = curEntity.Entity.Contains(":")? curEntity.Entity.Substring(curEntity.Entity.IndexOf(":") + 1): curEntity.Entity;
foreach(string s in words)
{
- trainingTuple.Add(new TrainingData(entityName, s, tags[i], "I"));
+ trainingTuple.Add(new TrainingData(entityName, s, tokens[i].Pos, "I"));
}
entityFinded = true;
}
@@ -125,50 +124,36 @@ namespace BotSharp.Core.Engines.CRFsuite
}
if (wordCandidateCount == 0)
{
- trainingTuple.Add(new TrainingData("O", sentence[i].Text, tags[i], "O"));
+ trainingTuple.Add(new TrainingData("O", tokens[i].Text, tokens[i].Pos, "O"));
}
else
{
i = i + wordCandidateCount - 1;
}
}
- return trainingTuple;
+ return trainingTuple;
}
+ public async Task Predict(Agent agent, JObject data, PipeModel meta)
+ {
+ return true;
+ }
}
public class TrainingData
{
public String Token { get; set; }
public String Entity { get; set; }
- public String Tag { get; set; }
+ public String Pos { get; set; }
public String Chunk { get; set; }
- public TrainingData(string entity, string token, string tag, string chunk)
+ public TrainingData(string entity, string token, string pos, string chunk)
{
- this.Token = token;
- this.Entity = entity;
- this.Tag = tag;
- this.Chunk = chunk;
+ Token = token;
+ Entity = entity;
+ Pos = pos;
+ Chunk = chunk;
}
}
-
-
- public class Token
- {
- public String Text { get; set; }
- public int Offset { get; set; }
- public int End { get; set; }
- }
-
- public class Entity
- {
- public String EntityName { get; set; }
- public String Value { get; set; }
- public int Start { get; set; }
- public int End { get; set; }
- }
-
-
}
diff --git a/BotSharp.Core/Engines/Classifiers/FasttextClassifier.cs b/BotSharp.Core/Engines/Classifiers/FasttextClassifier.cs
new file mode 100644
index 00000000..3d442da1
--- /dev/null
+++ b/BotSharp.Core/Engines/Classifiers/FasttextClassifier.cs
@@ -0,0 +1,49 @@
+using BotSharp.Core.Abstractions;
+using BotSharp.Core.Agents;
+using DotNetToolkit;
+using Microsoft.Extensions.Configuration;
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace BotSharp.Core.Engines.Classifiers
+{
+ public class FasttextClassifier : INlpPipeline
+ {
+ public IConfiguration Configuration { get; set; }
+
+ public Task Predict(Agent agent, JObject data, PipeModel meta)
+ {
+ throw new NotImplementedException();
+ }
+
+ public async Task Train(Agent agent, JObject data, PipeModel meta)
+ {
+ meta.Model = "classification-fasttext.model";
+ var algorithmDir = Path.Join(AppDomain.CurrentDomain.GetData("ContentRootPath").ToString(), "Algorithms");
+ var dirTrain = Path.Join(AppDomain.CurrentDomain.GetData("DataPath").ToString(), "TrainingFiles", agent.Id);
+ var dirModel = Path.Join(AppDomain.CurrentDomain.GetData("DataPath").ToString(), "ModelFiles", agent.Id);
+
+ string parsedTrainingDataFileName = Path.Join(dirTrain, $"classification-fasttext.parsed.txt");
+ string modelFileName = Path.Join(dirModel, meta.Model);
+
+ // assemble corpus
+ StringBuilder corpus = new StringBuilder();
+ agent.Corpus.UserSays.ForEach(x => corpus.AppendLine($"__label__{x.Intent} {x.Text}"));
+
+ File.WriteAllText(parsedTrainingDataFileName, corpus.ToString());
+
+ var output = CmdHelper.Run(Path.Join(algorithmDir, "fasttext"), $"supervised -input {parsedTrainingDataFileName} -output {modelFileName}");
+
+ Console.WriteLine($"Saved model to {modelFileName}");
+ meta.Meta = new JObject();
+ meta.Meta["compiled at"] = "Aug 3, 2018";
+
+
+ return true;
+ }
+ }
+}
diff --git a/BotSharp.Core/Engines/ModelMetaData.cs b/BotSharp.Core/Engines/ModelMetaData.cs
new file mode 100644
index 00000000..6ea1ea64
--- /dev/null
+++ b/BotSharp.Core/Engines/ModelMetaData.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Core.Engines
+{
+ public class ModelMetaData
+ {
+ public string Platform { get; set; }
+ public string Language { get; set; }
+
+ public string Version { get; set; }
+ public DateTime TrainingDate { get; set; }
+
+ public List Pipeline { get; set; }
+ }
+}
diff --git a/BotSharp.Core/Engines/PipeModel.cs b/BotSharp.Core/Engines/PipeModel.cs
new file mode 100644
index 00000000..d971408a
--- /dev/null
+++ b/BotSharp.Core/Engines/PipeModel.cs
@@ -0,0 +1,29 @@
+using Newtonsoft.Json.Linq;
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace BotSharp.Core.Engines
+{
+ public class PipeModel
+ {
+ ///
+ /// Pipe name
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Pipe type name
+ ///
+ public string Class { get; set; }
+
+ public DateTime Time { get; set; }
+
+ public string Model { get; set; }
+
+ ///
+ /// Extra meta data according to pipe
+ ///
+ public JObject Meta { get; set; }
+ }
+}
diff --git a/BotSharp.Core/Engines/Sebis/AgentImporterInSebis.cs b/BotSharp.Core/Engines/Sebis/AgentImporterInSebis.cs
index 48863211..ba849bfd 100644
--- a/BotSharp.Core/Engines/Sebis/AgentImporterInSebis.cs
+++ b/BotSharp.Core/Engines/Sebis/AgentImporterInSebis.cs
@@ -105,7 +105,7 @@ namespace BotSharp.Core.Engines
Text = say.Text.Substring(entity.Start, entity.Value.Length)
});
- pos = entity.End;
+ pos = entity.End + 1;
if (pos < say.Text.Length && entityIdx == say.Entities.Count - 1)
{
diff --git a/BotSharp.Core/Engines/SpaCy/SpaCyEntitizer.cs b/BotSharp.Core/Engines/SpaCy/SpaCyEntitizer.cs
index 23a596fe..8d32ed33 100644
--- a/BotSharp.Core/Engines/SpaCy/SpaCyEntitizer.cs
+++ b/BotSharp.Core/Engines/SpaCy/SpaCyEntitizer.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
+using System.Threading.Tasks;
using BotSharp.Core.Abstractions;
using BotSharp.Core.Agents;
using BotSharp.Core.Models;
@@ -15,7 +16,12 @@ namespace BotSharp.Core.Engines.SpaCy
{
public IConfiguration Configuration { get; set; }
- public bool Process(Agent agent, JObject data)
+ public async Task Predict(Agent agent, JObject data, PipeModel meta)
+ {
+ return true;
+ }
+
+ public async Task Train(Agent agent, JObject data, PipeModel meta)
{
var client = new RestClient(Configuration.GetSection("SpaCyProvider:Url").Value);
var request = new RestRequest("entitize", Method.GET);
diff --git a/BotSharp.Core/Engines/SpaCy/SpaCyEntityRecognizer.cs b/BotSharp.Core/Engines/SpaCy/SpaCyEntityRecognizer.cs
index d0338412..acfb1f59 100644
--- a/BotSharp.Core/Engines/SpaCy/SpaCyEntityRecognizer.cs
+++ b/BotSharp.Core/Engines/SpaCy/SpaCyEntityRecognizer.cs
@@ -9,6 +9,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using System.Threading.Tasks;
namespace BotSharp.Core.Engines.SpaCy
{
@@ -17,7 +18,7 @@ namespace BotSharp.Core.Engines.SpaCy
List entitiesInTrainingSet = new List();
public IConfiguration Configuration { get; set; }
- public bool Process(Agent agent, JObject data)
+ public async Task Train(Agent agent, JObject data, PipeModel meta)
{
String modelPath = "./entity_rec_output";
String newModelName = "test";
@@ -54,6 +55,11 @@ namespace BotSharp.Core.Engines.SpaCy
return true;
}
+
+ public async Task Predict(Agent agent, JObject data, PipeModel meta)
+ {
+ return true;
+ }
}
public class Result
diff --git a/BotSharp.Core/Engines/SpaCy/SpaCyProvider.cs b/BotSharp.Core/Engines/SpaCy/SpaCyProvider.cs
index 2d214b92..f61d9810 100644
--- a/BotSharp.Core/Engines/SpaCy/SpaCyProvider.cs
+++ b/BotSharp.Core/Engines/SpaCy/SpaCyProvider.cs
@@ -1,26 +1,46 @@
using BotSharp.Core.Abstractions;
using BotSharp.Core.Agents;
using Microsoft.Extensions.Configuration;
+using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
+using System.Threading.Tasks;
namespace BotSharp.Core.Engines.SpaCy
{
public class SpaCyProvider : INlpPipeline
{
public IConfiguration Configuration { get; set; }
-
- public bool Process(Agent agent, JObject data)
+ public async Task Train(Agent agent, JObject data, PipeModel meta)
{
var client = new RestClient(Configuration.GetSection("SpaCyProvider:Url").Value);
var request = new RestRequest("load", Method.GET);
- var response = client.Execute(request);
+ var response = client.Execute(request);
+
+ meta.Meta = JObject.FromObject(response.Data);
+ meta.Meta["models"] = null;
+ meta.Model = response.Data.Models;
return response.IsSuccessful;
}
+
+ public async Task Predict(Agent agent, JObject data, PipeModel meta)
+ {
+ return true;
+ }
+
+ private class Result
+ {
+ [JsonProperty("spaCy ver")]
+ public string Version { get; set; }
+ [JsonProperty("models")]
+ public string Models { get; set; }
+ [JsonProperty("python ver")]
+ public string Python { get; set; }
+ }
}
}
diff --git a/BotSharp.Core/Engines/SpaCy/SpaCyTagger.cs b/BotSharp.Core/Engines/SpaCy/SpaCyTagger.cs
index 46305e6f..15878f76 100644
--- a/BotSharp.Core/Engines/SpaCy/SpaCyTagger.cs
+++ b/BotSharp.Core/Engines/SpaCy/SpaCyTagger.cs
@@ -8,6 +8,7 @@ using System;
using System.Collections.Generic;
using System.Text;
using BotSharp.MachineLearning.NLP;
+using System.Threading.Tasks;
namespace BotSharp.Core.Engines.SpaCy
{
@@ -15,8 +16,7 @@ namespace BotSharp.Core.Engines.SpaCy
{
public IConfiguration Configuration { get; set; }
-
- public bool Process(Agent agent, JObject data)
+ public async Task Train(Agent agent, JObject data, PipeModel meta)
{
var client = new RestClient(Configuration.GetSection("SpaCyProvider:Url").Value);
var request = new RestRequest("tagger", Method.GET);
@@ -36,7 +36,12 @@ namespace BotSharp.Core.Engines.SpaCy
return res;
}
- public class Result
+ public async Task Predict(Agent agent, JObject data, PipeModel meta)
+ {
+ return true;
+ }
+
+ private class Result
{
public List Tags { get; set; }
}
diff --git a/BotSharp.Core/Engines/SpaCy/SpaCyTextCategorizer.cs b/BotSharp.Core/Engines/SpaCy/SpaCyTextCategorizer.cs
index dc95802a..6ae93765 100644
--- a/BotSharp.Core/Engines/SpaCy/SpaCyTextCategorizer.cs
+++ b/BotSharp.Core/Engines/SpaCy/SpaCyTextCategorizer.cs
@@ -9,6 +9,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using System.Threading.Tasks;
namespace BotSharp.Core.Engines.SpaCy
{
@@ -16,7 +17,7 @@ namespace BotSharp.Core.Engines.SpaCy
{
public IConfiguration Configuration { get; set; }
- public bool Process(Agent agent, JObject data)
+ public async Task Train(Agent agent, JObject data, PipeModel meta)
{
//var input = new List>();
@@ -62,6 +63,11 @@ namespace BotSharp.Core.Engines.SpaCy
return true;
}
+ public async Task Predict(Agent agent, JObject data, PipeModel meta)
+ {
+ return true;
+ }
+
public class Result
{
public String ModelName { get; set; }
diff --git a/BotSharp.Core/Engines/SpaCy/SpaCyTokenizer.cs b/BotSharp.Core/Engines/SpaCy/SpaCyTokenizer.cs
index 8835b7f0..f68d5417 100644
--- a/BotSharp.Core/Engines/SpaCy/SpaCyTokenizer.cs
+++ b/BotSharp.Core/Engines/SpaCy/SpaCyTokenizer.cs
@@ -10,6 +10,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
+using System.Threading.Tasks;
namespace BotSharp.Core.Engines.SpaCy
{
@@ -17,32 +18,37 @@ namespace BotSharp.Core.Engines.SpaCy
{
public IConfiguration Configuration { get; set; }
- public bool Process(Agent agent, JObject data)
+ public async Task Train(Agent agent, JObject data, PipeModel meta)
{
var client = new RestClient(Configuration.GetSection("SpaCyProvider:Url").Value);
- var request = new RestRequest("tokenize", Method.GET);
+ var request = new RestRequest("tokenizer", Method.GET);
List> tokens = new List>();
Boolean res = true;
var dc = new DefaultDataContextLoader().GetDefaultDc();
var corpus = agent.Corpus;
corpus.UserSays.ForEach(usersay => {
+ Console.WriteLine(usersay.Text);
request.AddParameter("text", usersay.Text);
var response = client.Execute(request);
+
tokens.Add(response.Data.Tokens);
+
res = res && response.IsSuccessful;
+
});
-
-
-
data.Add("Tokens", JToken.FromObject(tokens));
return res;
}
-
- public class Result
+ public async Task Predict(Agent agent, JObject data, PipeModel meta)
+ {
+ return true;
+ }
+
+ private class Result
{
public List Tokens { get; set; }
}
diff --git a/BotSharp.Core/Engines/SpaCy/SpacyFeaturizer.cs b/BotSharp.Core/Engines/SpaCy/SpacyFeaturizer.cs
index f456b8a2..2293a2fb 100644
--- a/BotSharp.Core/Engines/SpaCy/SpacyFeaturizer.cs
+++ b/BotSharp.Core/Engines/SpaCy/SpacyFeaturizer.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
+using System.Threading.Tasks;
using BotSharp.Core.Abstractions;
using BotSharp.Core.Agents;
using EntityFrameworkCore.BootKit;
@@ -14,7 +15,7 @@ namespace BotSharp.Core.Engines.SpaCy
{
public IConfiguration Configuration { get; set; }
- public bool Process(Agent agent, JObject data)
+ public async Task Train(Agent agent, JObject data, PipeModel meta)
{
var client = new RestClient(Configuration.GetSection("SpaCyProvider:Url").Value);
var request = new RestRequest("featurize", Method.GET);
@@ -35,6 +36,11 @@ namespace BotSharp.Core.Engines.SpaCy
return res;
}
+ public async Task Predict(Agent agent, JObject data, PipeModel meta)
+ {
+ return true;
+ }
+
public class Result
{
public List Vectors { get; set; }
diff --git a/BotSharp.MachineLearning/CRFsuite/Crfutils.cs b/BotSharp.MachineLearning/CRFsuite/Crfutils.cs
index c3db9e11..f725ef1d 100644
--- a/BotSharp.MachineLearning/CRFsuite/Crfutils.cs
+++ b/BotSharp.MachineLearning/CRFsuite/Crfutils.cs
@@ -135,16 +135,19 @@ namespace BotSharp.MachineLearning.CRFsuite
/// an extractor which to do the feature extracting work
/// attributes name seperated by space
/// string whihch seperated by
- public void CRFFileGenerator (System.Action>> FeatureExtractor, string fields, string sep= " ")
+ public void CRFFileGenerator (System.Action>> FeatureExtractor, string fields, string rawFile, string parsedName, string sep= " ")
{
- String fiPath = "/home/bolo/Desktop/BotSharp/TrainingFiles/rawTrain.txt";
- FileStream fs = new FileStream("/home/bolo/Desktop/BotSharp/TrainingFiles/1.txt", FileMode.Create);
+ FileStream fs = new FileStream(parsedName, FileMode.Create);
StreamWriter sw = new StreamWriter(fs);
List F = fields.Split(" ").ToList();
- List>> Xs = Readiter(fiPath, F, " ");
+ List>> Xs = Readiter(rawFile, F, " ");
foreach (List> X in Xs)
{
+ if (X.Any(x => x["w"].ToString() == ""))
+ {
+
+ }
FeatureExtractor(X);
OutputFeatures(sw, X, "y");
}
diff --git a/BotSharp.MachineLearning/CRFsuite/Ner.cs b/BotSharp.MachineLearning/CRFsuite/Ner.cs
index 904fe423..c4b63a90 100644
--- a/BotSharp.MachineLearning/CRFsuite/Ner.cs
+++ b/BotSharp.MachineLearning/CRFsuite/Ner.cs
@@ -11,8 +11,7 @@ namespace BotSharp.MachineLearning.CRFsuite
{
// Separator of field values.
string separator = " ";
- // Field names of the input data.
- string fields = "y w pos chk";
+
Template templates = new Template();
public string GetShape (string token)
@@ -469,15 +468,9 @@ namespace BotSharp.MachineLearning.CRFsuite
}
}
- string[] Uique = new string[]{"w", "wl", "pos", "chk", "shape", "shaped", "type",
- "p1", "p2", "p3", "p4","s1", "s2", "s3", "s4",
- "2d", "4d", "d&a", "d&-", "d&/", "d&,", "d&.", "up",
- "iu", "au", "al", "ad", "ao", "cu", "cl", "ca", "cd", "cs"};
- string[] Bi = new string[]{"w", "pos", "chk", "shaped", "type"};
-
- public void InitialTemplate ()
+ public void InitialTemplate (string[] uniFeatures, string[] biFeatures)
{
- foreach (string name in Uique)
+ foreach (string name in uniFeatures)
{
for (int i = -2 ; i < 3; i++)
{
@@ -487,7 +480,7 @@ namespace BotSharp.MachineLearning.CRFsuite
}
}
- foreach (string name in Bi)
+ foreach (string name in biFeatures)
{
for (int i = -2 ; i < 2; i++)
{
@@ -509,7 +502,6 @@ namespace BotSharp.MachineLearning.CRFsuite
// Apply the feature templates.
new Crfutils().ApplyTemplates(X, templates);
- // Append disjunctive features.
for (int t = 0; t < X.Count ; t++)
{
DisJunctive(X, t, "w", -4, -1);
@@ -522,10 +514,10 @@ namespace BotSharp.MachineLearning.CRFsuite
}
}
- public void NerStart ()
+ public void NerStart(string rawFile, string parsedName, string fields, string[] uniFeatures, string[] biFeatures)
{
- InitialTemplate();
- new Crfutils().CRFFileGenerator(FeatureExtractor, fields, separator);
+ InitialTemplate(uniFeatures, biFeatures);
+ new Crfutils().CRFFileGenerator(FeatureExtractor, fields, rawFile, parsedName, separator);
}
diff --git a/BotSharp.MachineLearning/NLP/NlpToken.cs b/BotSharp.MachineLearning/NLP/NlpToken.cs
index fdd3613f..e6cb34b8 100644
--- a/BotSharp.MachineLearning/NLP/NlpToken.cs
+++ b/BotSharp.MachineLearning/NLP/NlpToken.cs
@@ -8,6 +8,9 @@ namespace BotSharp.MachineLearning.NLP
{
public string Text { get; set; }
public int Offset { get; set; }
+ public string Pos { get; set; }
+ public string Tag { get; set; }
+ public string Lemma { get; set; }
public int End
{
get
diff --git a/BotSharp.MachineLearning/SpaCy/server.py b/BotSharp.MachineLearning/SpaCy/server.py
index 37d47bcf..467b3c0f 100644
--- a/BotSharp.MachineLearning/SpaCy/server.py
+++ b/BotSharp.MachineLearning/SpaCy/server.py
@@ -1,25 +1,35 @@
from bottle import route, run, request
-from spacy.tokenizer import Tokenizer
from spacy.pipeline import EntityRecognizer
from spacy.pipeline import TextCategorizer
+from spacy.gold import GoldParse
+#import plac
+import random
import spacy
nlp = spacy.load('en')
-tokenizer = Tokenizer(nlp.vocab)
ner = EntityRecognizer(nlp.vocab)
@route('/load')
def load():
pass
-@route('/tokenize')
+@route('/tokenizer')
def tokenize():
- tokens = tokenizer(request.query.text)
+ doc = nlp(request.query.text)
+ tokens = []
+ for token in doc:
+ tokens.append({'text': token.text, 'offset': token.idx, 'pos': token.pos_, 'tag': token.tag_, 'lemma': token.lemma_})
+ return {'tokens': tokens}
+
+
+@route('/tagger')
+def tagger():
+ doc = nlp(request.query.text)
list = []
- for token in tokens:
- print(token)
- list.append({'text': token.text, 'offset': token.idx})
- return {'tokens': list}
+ for token in doc:
+ list.append(token.tag_)
+ return {'tags': list}
+
@route('/featurize')
def tokenize():
@@ -46,13 +56,16 @@ def textcategorizer():
texts = request.json["Texts"]
golds = request.json["Golds"]
labels = request.json["Labels"]
- print(labels)
+ i = 1
train_data = []
for index in range(len(texts)):
tuple =(texts[index], golds[index])
train_data.append(tuple)
-
- print("training data body is: {0}".format(train_data))
+ '''
+ for tup in train_data:
+ print("cur tuple {0}, training data body: {1}".format(i, train_data))
+ i = i + 1
+ '''
textcat = nlp.create_pipe('textcat')
nlp.add_pipe(textcat, last=True)
for label in labels:
@@ -68,8 +81,8 @@ def textcategorizer():
return {'ModelName':'textcat_try'}
-@route('/predict')
-def predict():
+@route('/textcategorizerpredict')
+def textcategorizerpredict():
textcat = TextCategorizer(nlp.vocab)
textcat.from_disk('./textcat_try')
@@ -78,15 +91,114 @@ def predict():
doc = nlp(request.query.text)
#scores = textcat.predict([request.query.text])
- #print(scores)
+ print(doc.cats)
list = []
- for label, confidence in doc.cats:
- print(label)
- list.append({'Label': label, 'Confidence': confidence})
+ for key in doc.cats:
+ print(key)
+ list.append({'Label': key, 'Confidence': doc.cats[key]})
print (list)
return {'Labels': list}
-run(host='0.0.0.0', port=5005, debug=True)
+@route('/entityrecognizer', method='POST')
+def entityrecognizer():
+ model = request.json["ModelPath"]
+ new_model_name = request.json["NewModelName"]
+ output_dir = request.json["OutputDir"]
+ n_iter = request.json["IterTimes"]
+ raw_data = request.json["TrainingData"]
+ # generate training_data from raw_data
+ training_data = []
+ for node in raw_data:
+ labels = []
+ for entity in node['Labels']:
+ label = (entity['Start'], entity['End'], entity['Name'])
+ labels.append(label)
+ tup = (node['Text'], labels)
+ training_data.append(tup)
+ print(training_data)
+
+ if model is not None:
+ nlp = spacy.load(model) # load existing spaCy model
+ print("Loaded model '%s'" % model)
+ else:
+ nlp = spacy.blank('en') # create blank Language class
+ print("Created blank 'en' model")
+
+ # Add entity recognizer to model if it's not in the pipeline
+ # nlp.create_pipe works for built-ins that are registered with spaCy
+ if 'ner' not in nlp.pipe_names:
+ ner = nlp.create_pipe('ner')
+ nlp.add_pipe(ner)
+ print("ner created succeed!")
+ # otherwise, get it, so we can add labels to it
+ else:
+ ner = nlp.get_pipe('ner')
+ print("ner loaded succeed!")
+
+ # check whether there are new labels
+ entities_in_training_set = request.json["EntitiesInTrainingSet"]
+ en_labels = ["PERSON","NORP","FAC","ORG","GPE","LOC","PRODUCT",\
+ "EVENT","WORK_OF_ART","LAW","LANGUAGE","DATE","TIME","PERCENT",\
+ "MONEY","QUANTITY","ORDINAL","CARDINAL"]
+
+ extra_labels = nlp.entity.cfg[u'extra_labels'] \
+ if ('extra_labels' in nlp.entity.cfg) else []
+
+ labels = []
+ for entity in entities_in_training_set:
+ if (entity in en_labels or entity in extra_labels):
+ continue
+ labels.append(entity)
+
+ for label in labels:
+ ner.add_label(label) # add new entity label to entity recognizer
+ print("label added succeed!")
+
+ if model is None:
+ optimizer = nlp.begin_training()
+ else:
+ # Note that 'begin_training' initializes the models, so it'll zero out
+ # existing entity types.
+ optimizer = nlp.entity.create_optimizer()
+
+ # get names of other pipes to disable them during training
+ other_pipes = [pipe for pipe in nlp.pipe_names if pipe != 'ner']
+ with nlp.disable_pipes(*other_pipes): # only train NER
+ for itn in range(n_iter):
+ random.shuffle(training_data)
+ losses = {}
+ for text, annotations in training_data:
+ print(text)
+ print(annotations)
+ #
+ doc = nlp.make_doc(text)
+ gold = GoldParse(doc, entities=annotations)
+ #
+ nlp.update([doc], [gold], sgd=optimizer, drop=0.35)#,losses=losses)
+ #print(losses)
+
+ # save model to output directory
+ if output_dir is not None:
+ output_dir = Path(output_dir)
+ if not output_dir.exists():
+ output_dir.mkdir()
+ nlp.meta['name'] = new_model_name # rename model
+ nlp.to_disk(output_dir)
+ print("Saved model to", output_dir)
+ return True
+
+@route('/entityrecognizerpredict')
+def entityrecognizerpredict():
+
+ print("Loading from", './entity_rec_output')
+ nlp2 = spacy.load('./entity_rec_output')
+ doc2 = nlp2(request.query.text)
+ for ent in doc2.ents:
+ print(ent.label_, ent.text)
+
+
+
+run(host='0.0.0.0', port=5005, debug=True)
\ No newline at end of file
diff --git a/BotSharp.RestApi/Dialogs/DialogController.cs b/BotSharp.RestApi/Dialogs/DialogController.cs
index 85487baf..701c541d 100644
--- a/BotSharp.RestApi/Dialogs/DialogController.cs
+++ b/BotSharp.RestApi/Dialogs/DialogController.cs
@@ -13,6 +13,7 @@ namespace BotSharp.RestApi.Dialogs
///
/// Conversation controller
///
+ [Authorize]
[Route("v1/[controller]")]
public class DialogController : ControllerBase
{
@@ -33,16 +34,10 @@ namespace BotSharp.RestApi.Dialogs
///
///
///
- [AllowAnonymous]
[HttpPost("/v1/query")]
public ActionResult Query([FromBody] QueryModel request)
{
- String clientAccessToken = Request.Headers["Authorization"];
- if (String.IsNullOrEmpty(clientAccessToken))
- {
- return Unauthorized();
- }
-
+ String clientAccessToken = Request.Headers["ClientAccessToken"];
var config = new AIConfiguration(clientAccessToken, SupportedLanguage.English);
config.SessionId = request.SessionId;
diff --git a/BotSharp.WebHost/Algorithms/crfsuite.exe b/BotSharp.WebHost/Algorithms/crfsuite.exe
new file mode 100644
index 0000000000000000000000000000000000000000..0a3843bda9ff1d31d9bfa7c39f33063bb1d43bb0
GIT binary patch
literal 215040
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