refactor IBotPlatform structure
This commit is contained in:
parent
9e12cdd06a
commit
1c5b714603
|
|
@ -1,4 +1,5 @@
|
|||
using System;
|
||||
using BotSharp.Core.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
|
|
@ -6,5 +7,7 @@ namespace BotSharp.Core.Engines
|
|||
{
|
||||
public interface IBotPlatform
|
||||
{
|
||||
AIResponse TextRequest(AIRequest request);
|
||||
void Train();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,5 +67,10 @@ namespace BotSharp.Core.Agents
|
|||
|
||||
[Required]
|
||||
public DateTime CreatedDate { get; set; }
|
||||
|
||||
public Boolean IsSkillSet { get; set; }
|
||||
|
||||
[ForeignKey("AgentId")]
|
||||
public AgentMlConfig MlConfig { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ namespace BotSharp.Core.Agents
|
|||
return dc.Table<Agent>()
|
||||
.Include(x => x.Intents).ThenInclude(x => x.Contexts)
|
||||
.Include(x => x.Entities).ThenInclude(x => x.Entries).ThenInclude(x => x.Synonyms)
|
||||
.Include(x => x.MlConfig)
|
||||
.FirstOrDefault(x => x.ClientAccessToken == aiConfig.ClientAccessToken || x.DeveloperAccessToken == aiConfig.ClientAccessToken);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,15 +7,21 @@ using System.Text;
|
|||
|
||||
namespace BotSharp.Core.Agents
|
||||
{
|
||||
[Table("Bot_AgentTrainConfig")]
|
||||
public class AgentTrainConfig : DbRecord, IDbRecord
|
||||
[Table("Bot_AgentMlConfig")]
|
||||
public class AgentMlConfig : DbRecord, IDbRecord
|
||||
{
|
||||
[Required]
|
||||
[StringLength(36)]
|
||||
public String AgentId { get; set; }
|
||||
|
||||
public decimal ClassificationThreshould { get; set; }
|
||||
|
||||
[Required]
|
||||
public decimal MinConfidence { get; set; }
|
||||
|
||||
[Required]
|
||||
[MaxLength(64)]
|
||||
public string CustomClassifierMode { get; set; }
|
||||
|
||||
[MaxLength(64)]
|
||||
public String Pipeline { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -32,11 +32,15 @@
|
|||
<DefineConstants>TRACE;MODEL_PER_CONTEXTS</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="Engines\Dialogflow\ApiAi.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DotNetToolkit" Version="1.4.0" />
|
||||
<PackageReference Include="EntityFrameworkCore.BootKit" Version="1.6.0" />
|
||||
<PackageReference Include="EntityFrameworkCore.BootKit" Version="1.6.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
|
||||
<PackageReference Include="RestSharp" Version="106.3.0" />
|
||||
<PackageReference Include="RestSharp" Version="106.3.1" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ namespace BotSharp.Core.Conversations
|
|||
[StringLength(36)]
|
||||
public String AgentId { get; set; }
|
||||
|
||||
[Required]
|
||||
[StringLength(36)]
|
||||
public String UserId { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ namespace BotSharp.Core.Models
|
|||
{
|
||||
public class AIConfiguration
|
||||
{
|
||||
private const string SERVICE_PROD_URL = "";
|
||||
private const string SERVICE_DEV_URL = "";
|
||||
private const string SERVICE_PROD_URL = "https://api.api.ai/v1/";
|
||||
private const string SERVICE_DEV_URL = "https://dev.api.ai/api/";
|
||||
|
||||
private const string CURRENT_PROTOCOL_VERSION = "20190322";
|
||||
private const string CURRENT_PROTOCOL_VERSION = "20150910";
|
||||
|
||||
public string ClientAccessToken { get; private set; }
|
||||
|
||||
|
|
|
|||
179
BotSharp.Core/Engines/Dialogflow/AIDataService.cs
Normal file
179
BotSharp.Core/Engines/Dialogflow/AIDataService.cs
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
using BotSharp.Core.Engines.Dialogflow.Http;
|
||||
using BotSharp.Core.Models;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace BotSharp.Core.Engines.Dialogflow
|
||||
{
|
||||
public class AIDataService
|
||||
{
|
||||
private readonly AIConfiguration config;
|
||||
|
||||
public string SessionId { get; }
|
||||
|
||||
public AIDataService(AIConfiguration config)
|
||||
{
|
||||
this.config = config;
|
||||
|
||||
if (string.IsNullOrEmpty(config.SessionId))
|
||||
{
|
||||
SessionId = Guid.NewGuid().ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
SessionId = config.SessionId;
|
||||
}
|
||||
}
|
||||
|
||||
public AIResponse Request(AIRequest request)
|
||||
{
|
||||
request.Language = config.Language.code;
|
||||
request.Timezone = TimeZone.CurrentTimeZone.StandardName;
|
||||
request.SessionId = SessionId;
|
||||
|
||||
try
|
||||
{
|
||||
var httpRequest = (HttpWebRequest)WebRequest.Create(config.RequestUrl);
|
||||
httpRequest.Method = "POST";
|
||||
httpRequest.ContentType = "application/json; charset=utf-8";
|
||||
httpRequest.Accept = "application/json";
|
||||
|
||||
httpRequest.Headers.Add("Authorization", "Bearer " + config.ClientAccessToken);
|
||||
|
||||
var jsonSettings = new JsonSerializerSettings
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
};
|
||||
|
||||
var jsonRequest = JsonConvert.SerializeObject(request, Formatting.None, jsonSettings);
|
||||
|
||||
if (config.DebugLog)
|
||||
{
|
||||
Debug.WriteLine("Request: " + jsonRequest);
|
||||
}
|
||||
|
||||
using (var streamWriter = new StreamWriter(httpRequest.GetRequestStream()))
|
||||
{
|
||||
streamWriter.Write(jsonRequest);
|
||||
streamWriter.Close();
|
||||
}
|
||||
|
||||
var httpResponse = httpRequest.GetResponse() as HttpWebResponse;
|
||||
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
|
||||
{
|
||||
var result = streamReader.ReadToEnd();
|
||||
|
||||
if (config.DebugLog)
|
||||
{
|
||||
Debug.WriteLine("Response: " + result);
|
||||
}
|
||||
|
||||
var aiResponse = JsonConvert.DeserializeObject<AIResponse>(result);
|
||||
|
||||
CheckForErrors(aiResponse);
|
||||
|
||||
return aiResponse;
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new AIServiceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public AIResponse VoiceRequest(Stream voiceStream, RequestExtras requestExtras = null)
|
||||
{
|
||||
var request = new AIRequest();
|
||||
request.Language = config.Language.code;
|
||||
request.Timezone = TimeZone.CurrentTimeZone.StandardName;
|
||||
request.SessionId = SessionId;
|
||||
|
||||
if (requestExtras != null)
|
||||
{
|
||||
requestExtras.CopyTo(request);
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var httpRequest = (HttpWebRequest)WebRequest.Create(config.RequestUrl);
|
||||
httpRequest.Method = "POST";
|
||||
httpRequest.Accept = "application/json";
|
||||
|
||||
httpRequest.Headers.Add("Authorization", "Bearer " + config.ClientAccessToken);
|
||||
|
||||
var jsonSettings = new JsonSerializerSettings
|
||||
{
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
};
|
||||
|
||||
var jsonRequest = JsonConvert.SerializeObject(request, Formatting.None, jsonSettings);
|
||||
|
||||
if (config.DebugLog)
|
||||
{
|
||||
Debug.WriteLine("Request: " + jsonRequest);
|
||||
}
|
||||
|
||||
var multipartClient = new MultipartHttpClient(httpRequest);
|
||||
multipartClient.connect();
|
||||
|
||||
multipartClient.addStringPart("request", jsonRequest);
|
||||
multipartClient.addFilePart("voiceData", "voice.wav", voiceStream);
|
||||
|
||||
multipartClient.finish();
|
||||
|
||||
var responseJsonString = multipartClient.getResponse();
|
||||
|
||||
if (config.DebugLog)
|
||||
{
|
||||
Debug.WriteLine("Response: " + responseJsonString);
|
||||
}
|
||||
|
||||
var aiResponse = JsonConvert.DeserializeObject<AIResponse>(responseJsonString);
|
||||
|
||||
CheckForErrors(aiResponse);
|
||||
|
||||
return aiResponse;
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new AIServiceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public bool ResetContexts()
|
||||
{
|
||||
var cleanRequest = new AIRequest("empty_query_for_resetting_contexts");
|
||||
cleanRequest.ResetContexts = true;
|
||||
try
|
||||
{
|
||||
var response = Request(cleanRequest);
|
||||
return !response.IsError;
|
||||
}
|
||||
catch (AIServiceException e)
|
||||
{
|
||||
Debug.WriteLine("Exception while contexts clean." + e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void CheckForErrors(AIResponse aiResponse)
|
||||
{
|
||||
if (aiResponse == null)
|
||||
{
|
||||
throw new AIServiceException("API.AI response parsed as null. Check debug log for details.");
|
||||
}
|
||||
|
||||
if (aiResponse.IsError)
|
||||
{
|
||||
throw new AIServiceException(aiResponse);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ namespace BotSharp.Core.Models
|
|||
public enum AIResponseMessageType
|
||||
{
|
||||
Text = 0,
|
||||
Card = 1,
|
||||
Custom = 4
|
||||
}
|
||||
}
|
||||
|
|
|
|||
49
BotSharp.Core/Engines/Dialogflow/AIServiceException.cs
Normal file
49
BotSharp.Core/Engines/Dialogflow/AIServiceException.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
using BotSharp.Core.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BotSharp.Core.Engines.Dialogflow
|
||||
{
|
||||
public class AIServiceException : Exception
|
||||
{
|
||||
public AIResponse Response { get; set; }
|
||||
|
||||
public AIServiceException()
|
||||
{
|
||||
}
|
||||
|
||||
public AIServiceException(string message) : base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public AIServiceException(string message, Exception innerException) : base(message, innerException)
|
||||
{
|
||||
}
|
||||
|
||||
public AIServiceException(Exception e) : base(e.Message, e)
|
||||
{
|
||||
}
|
||||
|
||||
public AIServiceException(AIResponse response)
|
||||
{
|
||||
Response = response;
|
||||
}
|
||||
|
||||
public override string Message
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Response != null && Response.IsError)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(Response.Status.ErrorDetails))
|
||||
{
|
||||
return Response.Status.ErrorDetails;
|
||||
}
|
||||
}
|
||||
|
||||
return base.Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,12 @@ namespace BotSharp.Core.Engines
|
|||
agent.Id = Guid.NewGuid().ToString();
|
||||
agent.Name = agentId;
|
||||
|
||||
return agent.ToObject<Agent>();
|
||||
var result = agent.ToObject<Agent>();
|
||||
result.MlConfig = agent.ToObject<AgentMlConfig>();
|
||||
result.MlConfig.MinConfidence = agent.MlMinConfidence;
|
||||
result.MlConfig.AgentId = agent.Id;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void LoadCustomEntities(Agent agent, string agentDir)
|
||||
|
|
@ -161,6 +166,8 @@ namespace BotSharp.Core.Engines
|
|||
Lifespan = x.Lifespan
|
||||
}).ToList();
|
||||
|
||||
int millSeconds = 0;
|
||||
|
||||
newResponse.Messages = res.MessageList.Where(x => x.Speech != null || x.Payload != null)
|
||||
.Select(x =>
|
||||
{
|
||||
|
|
@ -170,7 +177,8 @@ namespace BotSharp.Core.Engines
|
|||
{
|
||||
Payload = JObject.FromObject(x.Payload),
|
||||
PayloadJson = JsonConvert.SerializeObject(x.Payload),
|
||||
Type = x.Type
|
||||
Type = x.Type,
|
||||
UpdatedTime = DateTime.UtcNow.AddMilliseconds(millSeconds++)
|
||||
};
|
||||
}
|
||||
else
|
||||
|
|
@ -182,7 +190,8 @@ namespace BotSharp.Core.Engines
|
|||
return new IntentResponseMessage
|
||||
{
|
||||
Speech = speech,
|
||||
Type = x.Type
|
||||
Type = x.Type,
|
||||
UpdatedTime = DateTime.UtcNow.AddMilliseconds(millSeconds++)
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
61
BotSharp.Core/Engines/Dialogflow/ApiAi.cs
Normal file
61
BotSharp.Core/Engines/Dialogflow/ApiAi.cs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
using BotSharp.Core.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
namespace BotSharp.Core.Engines.Dialogflow
|
||||
{
|
||||
public class ApiAi : ApiAiBase, IBotPlatform
|
||||
{
|
||||
private readonly AIConfiguration config;
|
||||
private readonly AIDataService dataService;
|
||||
|
||||
public ApiAi(AIConfiguration config)
|
||||
{
|
||||
this.config = config;
|
||||
|
||||
dataService = new AIDataService(this.config);
|
||||
}
|
||||
|
||||
public AIResponse TextRequest(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
throw new ArgumentNullException("text");
|
||||
}
|
||||
|
||||
return TextRequest(new AIRequest(text));
|
||||
}
|
||||
|
||||
public AIResponse TextRequest(AIRequest request)
|
||||
{
|
||||
if (request == null)
|
||||
{
|
||||
throw new ArgumentNullException("request");
|
||||
}
|
||||
|
||||
return dataService.Request(request);
|
||||
}
|
||||
|
||||
public AIResponse TextRequest(string text, RequestExtras requestExtras)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
throw new ArgumentNullException("text");
|
||||
}
|
||||
|
||||
return TextRequest(new AIRequest(text, requestExtras));
|
||||
}
|
||||
|
||||
public AIResponse VoiceRequest(Stream voiceStream, RequestExtras requestExtras = null)
|
||||
{
|
||||
if (config.Language == SupportedLanguage.Italian)
|
||||
{
|
||||
throw new AIServiceException("Sorry, but Italian language now is not supported in Speaktoit recognition. Please use some another speech recognition engine.");
|
||||
}
|
||||
|
||||
return dataService.VoiceRequest(voiceStream, requestExtras);
|
||||
}
|
||||
}
|
||||
}
|
||||
75
BotSharp.Core/Engines/Dialogflow/ApiAiBase.cs
Normal file
75
BotSharp.Core/Engines/Dialogflow/ApiAiBase.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BotSharp.Core.Engines.Dialogflow
|
||||
{
|
||||
public class ApiAiBase
|
||||
{
|
||||
protected float[] TrimSilence(float[] samples)
|
||||
{
|
||||
if (samples == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
const float min = 0.000001f;
|
||||
|
||||
var startIndex = 0;
|
||||
var endIndex = samples.Length;
|
||||
|
||||
for (var i = 0; i < samples.Length; i++)
|
||||
{
|
||||
|
||||
if (Math.Abs(samples[i]) > min)
|
||||
{
|
||||
startIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = samples.Length - 1; i > 0; i--)
|
||||
{
|
||||
if (Math.Abs(samples[i]) > min)
|
||||
{
|
||||
endIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (endIndex <= startIndex)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var result = new float[endIndex - startIndex];
|
||||
Array.Copy(samples, startIndex, result, 0, endIndex - startIndex);
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
protected static byte[] ConvertArrayShortToBytes(short[] array)
|
||||
{
|
||||
var numArray = new byte[array.Length * 2];
|
||||
Buffer.BlockCopy(array, 0, numArray, 0, numArray.Length);
|
||||
return numArray;
|
||||
}
|
||||
|
||||
protected static short[] ConvertIeeeToPcm16(float[] source)
|
||||
{
|
||||
var resultBuffer = new short[source.Length];
|
||||
for (var i = 0; i < source.Length; i++)
|
||||
{
|
||||
var f = source[i] * 32768f;
|
||||
|
||||
if (f > (double)short.MaxValue)
|
||||
f = short.MaxValue;
|
||||
else if (f < (double)short.MinValue)
|
||||
f = short.MinValue;
|
||||
resultBuffer[i] = Convert.ToInt16(f);
|
||||
}
|
||||
|
||||
return resultBuffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,10 @@ namespace BotSharp.Core.Adapters.Dialogflow
|
|||
public String Description { get; set; }
|
||||
public Boolean Published { get; set; }
|
||||
|
||||
public String DefaultTimezone { get; set; }
|
||||
public String Language { get; set; }
|
||||
|
||||
public decimal MlMinConfidence { get; set; }
|
||||
public string CustomClassifierMode { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
95
BotSharp.Core/Engines/Dialogflow/Http/MultipartHttpClient.cs
Normal file
95
BotSharp.Core/Engines/Dialogflow/Http/MultipartHttpClient.cs
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
|
||||
namespace BotSharp.Core.Engines.Dialogflow.Http
|
||||
{
|
||||
public class MultipartHttpClient
|
||||
{
|
||||
private const string delimiter = "--";
|
||||
private string boundary = "SwA" + DateTime.UtcNow.Ticks.ToString("x") + "SwA";
|
||||
private HttpWebRequest request;
|
||||
private BinaryWriter os;
|
||||
|
||||
public MultipartHttpClient(HttpWebRequest request)
|
||||
{
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
public void connect()
|
||||
{
|
||||
request.ContentType = "multipart/form-data; boundary=" + boundary;
|
||||
request.SendChunked = true;
|
||||
request.KeepAlive = true;
|
||||
|
||||
os = new BinaryWriter(request.GetRequestStream(), Encoding.UTF8);
|
||||
}
|
||||
|
||||
public void addStringPart(string paramName, string data)
|
||||
{
|
||||
WriteString(delimiter + boundary + "\r\n");
|
||||
WriteString("Content-Type: application/json\r\n");
|
||||
WriteString("Content-Disposition: form-data; name=\"" + paramName + "\"\r\n");
|
||||
WriteString("\r\n" + data + "\r\n");
|
||||
}
|
||||
|
||||
public void addFilePart(string paramName, string fileName, Stream data)
|
||||
{
|
||||
WriteString(delimiter + boundary + "\r\n");
|
||||
WriteString("Content-Disposition: form-data; name=\"" + paramName + "\"; filename=\"" + fileName + "\"\r\n");
|
||||
WriteString("Content-Type: audio/wav\r\n");
|
||||
|
||||
WriteString("\r\n");
|
||||
|
||||
int bufferSize = 4096;
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
|
||||
int bytesActuallyRead;
|
||||
|
||||
bytesActuallyRead = data.Read(buffer, 0, bufferSize);
|
||||
while (bytesActuallyRead > 0)
|
||||
{
|
||||
os.Write(buffer, 0, bytesActuallyRead);
|
||||
bytesActuallyRead = data.Read(buffer, 0, bufferSize);
|
||||
}
|
||||
|
||||
WriteString("\r\n");
|
||||
}
|
||||
|
||||
public void finish()
|
||||
{
|
||||
WriteString(delimiter + boundary + delimiter + "\r\n");
|
||||
os.Close();
|
||||
}
|
||||
|
||||
private void WriteString(string str)
|
||||
{
|
||||
os.Write(Encoding.UTF8.GetBytes(str));
|
||||
}
|
||||
|
||||
public string getResponse()
|
||||
{
|
||||
try
|
||||
{
|
||||
var httpResponse = request.GetResponse() as HttpWebResponse;
|
||||
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
|
||||
{
|
||||
var result = streamReader.ReadToEnd();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
catch (WebException we)
|
||||
{
|
||||
using (var stream = we.Response.GetResponseStream())
|
||||
{
|
||||
using (var reader = new StreamReader(stream))
|
||||
{
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -42,47 +42,82 @@ namespace BotSharp.Core.Engines
|
|||
aiConfig.DevMode = agent.DeveloperAccessToken == aiConfig.ClientAccessToken;
|
||||
}
|
||||
|
||||
public string Train()
|
||||
public AIResponse TextRequest(AIRequest request)
|
||||
{
|
||||
var client = new RestClient($"{Database.Configuration.GetSection("Rasa:Nlu").Value}");
|
||||
var rest = new RestRequest("train", Method.POST);
|
||||
rest.AddQueryParameter("project", agent.Id);
|
||||
AIResponse aiResponse = new AIResponse();
|
||||
|
||||
var corpus = agent.GrabCorpus(dc);
|
||||
#if MODEL_PER_CONTEXTS
|
||||
string model = RasaRequestExtension.GetModelPerContexts(agent, AiConfig, request, dc);
|
||||
var result = CallRasa(agent.Id, request.Query.First(), model);
|
||||
#else
|
||||
var result = CallRasa(rasa.agent.Id, request.Query.First(), rasa.agent.Id);
|
||||
#endif
|
||||
result.Content.Log();
|
||||
|
||||
string json = JsonConvert.SerializeObject(new { rasa_nlu_data = corpus },
|
||||
new JsonSerializerSettings
|
||||
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 => x.Value),
|
||||
Score = response.Intent.Confidence,
|
||||
Metadata = new AIResponseMetadata { IntentId = intentResponse?.IntentId, IntentName = intentResponse?.IntentName },
|
||||
Fulfillment = new AIResponseFulfillment
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
||||
string trainingConfig = agent.Language == "zh" ? "config_jieba_mitie_sklearn.yml" : "config_spacy.yml";
|
||||
string body = File.ReadAllText($"{Database.ContentRootPath}{Path.DirectorySeparatorChar}Settings{Path.DirectorySeparatorChar}{trainingConfig}");
|
||||
body = $"{body}\r\ndata: {json}";
|
||||
rest.AddParameter("application/x-yml", body, ParameterType.RequestBody);
|
||||
}).ToList()
|
||||
}
|
||||
};
|
||||
|
||||
var response = client.Execute(rest);
|
||||
RasaRequestExtension.HandleContext(dc, AiConfig, intentResponse, aiResponse);
|
||||
|
||||
if (response.IsSuccessful)
|
||||
{
|
||||
var result = JObject.Parse(response.Content);
|
||||
Console.WriteLine(JsonConvert.SerializeObject(aiResponse.Result));
|
||||
|
||||
string modelName = result["info"].Value<String>().Split(": ")[1];
|
||||
|
||||
return modelName;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = JObject.Parse(response.Content);
|
||||
|
||||
Console.WriteLine(result["error"]);
|
||||
|
||||
return String.Empty;
|
||||
}
|
||||
return aiResponse;
|
||||
}
|
||||
|
||||
public void TrainWithContexts()
|
||||
private IRestResponse<RasaResponse> CallRasa(string projectId, string text, string model)
|
||||
{
|
||||
var client = new RestClient($"{Database.Configuration.GetSection("Rasa:Nlu").Value}");
|
||||
|
||||
var rest = new RestRequest("parse", Method.POST);
|
||||
string json = JsonConvert.SerializeObject(new { Project = projectId, Q = text, Model = model },
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
});
|
||||
rest.AddParameter("application/json", json, ParameterType.RequestBody);
|
||||
|
||||
return client.Execute<RasaResponse>(rest);
|
||||
}
|
||||
|
||||
public void Train()
|
||||
{
|
||||
var corpus = agent.GrabCorpus(dc);
|
||||
var client = new RestClient($"{Database.Configuration.GetSection("Rasa:Nlu").Value}");
|
||||
|
|
@ -174,7 +209,7 @@ namespace BotSharp.Core.Engines
|
|||
var rest = new RestRequest("train", Method.POST);
|
||||
rest.AddQueryParameter("project", agent.Id);
|
||||
rest.AddQueryParameter("model", ctx);
|
||||
string trainingConfig = agent.Language == "zh" ? "config_jieba_mitie_sklearn.yml" : "config_spacy.yml";
|
||||
string trainingConfig = agent.Language == "zh" ? "config_jieba_mitie_sklearn.yml" : "config_mitie_sklearn.yml";
|
||||
string body = File.ReadAllText($"{Database.ContentRootPath}{Path.DirectorySeparatorChar}Settings{Path.DirectorySeparatorChar}{trainingConfig}");
|
||||
body = $"{body}\r\ndata: {json}";
|
||||
rest.AddParameter("application/x-yml", body, ParameterType.RequestBody);
|
||||
|
|
@ -196,5 +231,47 @@ namespace BotSharp.Core.Engines
|
|||
});
|
||||
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public string TrainWithoutContext()
|
||||
{
|
||||
var client = new RestClient($"{Database.Configuration.GetSection("Rasa:Nlu").Value}");
|
||||
var rest = new RestRequest("train", Method.POST);
|
||||
rest.AddQueryParameter("project", agent.Id);
|
||||
|
||||
var corpus = agent.GrabCorpus(dc);
|
||||
|
||||
string json = JsonConvert.SerializeObject(new { rasa_nlu_data = corpus },
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver(),
|
||||
NullValueHandling = NullValueHandling.Ignore
|
||||
});
|
||||
|
||||
string trainingConfig = agent.Language == "zh" ? "config_jieba_mitie_sklearn.yml" : "config_spacy.yml";
|
||||
string body = File.ReadAllText($"{Database.ContentRootPath}{Path.DirectorySeparatorChar}Settings{Path.DirectorySeparatorChar}{trainingConfig}");
|
||||
body = $"{body}\r\ndata: {json}";
|
||||
rest.AddParameter("application/x-yml", body, ParameterType.RequestBody);
|
||||
|
||||
var response = client.Execute(rest);
|
||||
|
||||
if (response.IsSuccessful)
|
||||
{
|
||||
var result = JObject.Parse(response.Content);
|
||||
|
||||
string modelName = result["info"].Value<String>().Split(": ")[1];
|
||||
|
||||
return modelName;
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = JObject.Parse(response.Content);
|
||||
|
||||
Console.WriteLine(result["error"]);
|
||||
|
||||
return String.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,80 +18,18 @@ using System.Text.RegularExpressions;
|
|||
|
||||
namespace BotSharp.Core.Engines
|
||||
{
|
||||
public static class RequestExtension
|
||||
public static class RasaRequestExtension
|
||||
{
|
||||
public static AIResponse TextRequest(this RasaAi rasa, string text, RequestExtras requestExtras)
|
||||
{
|
||||
return rasa.TextRequest(new AIRequest(text, requestExtras));
|
||||
}
|
||||
|
||||
public static AIResponse TextRequest(this RasaAi rasa, AIRequest request)
|
||||
public static IntentResponse HandleIntentPerContextIn(Agent agent, AIConfiguration aiConfig, AIRequest request, RasaResponse response, Database dc)
|
||||
{
|
||||
AIResponse aiResponse = new AIResponse();
|
||||
Database dc = rasa.dc;
|
||||
|
||||
#if MODEL_PER_CONTEXTS
|
||||
string model = GetModelPerContexts(rasa, request);
|
||||
var result = CallRasa(rasa.agent.Id, request.Query.First(), model);
|
||||
#else
|
||||
var result = CallRasa(rasa.agent.Id, request.Query.First(), rasa.agent.Id);
|
||||
#endif
|
||||
result.Content.Log();
|
||||
|
||||
RasaResponse response = result.Data;
|
||||
aiResponse.Id = Guid.NewGuid().ToString();
|
||||
aiResponse.Lang = rasa.agent.Language;
|
||||
aiResponse.Status = new AIResponseStatus { };
|
||||
aiResponse.SessionId = rasa.AiConfig.SessionId;
|
||||
aiResponse.Timestamp = DateTime.UtcNow;
|
||||
|
||||
var intentResponse = HandleIntentPerContextIn(rasa, request, result.Data);
|
||||
HandleParameter(rasa.agent, intentResponse, response, request);
|
||||
|
||||
HandleMessage(intentResponse);
|
||||
|
||||
aiResponse.Result = new AIResponseResult
|
||||
{
|
||||
Source = "agent",
|
||||
ResolvedQuery = request.Query.First(),
|
||||
Action = intentResponse?.Action,
|
||||
Parameters = intentResponse?.Parameters?.ToDictionary(x => x.Name, x=> 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()
|
||||
}
|
||||
};
|
||||
|
||||
HandleContext(dc, rasa, intentResponse, aiResponse);
|
||||
|
||||
Console.WriteLine(JsonConvert.SerializeObject(aiResponse.Result));
|
||||
|
||||
return aiResponse;
|
||||
}
|
||||
|
||||
private static IntentResponse HandleIntentPerContextIn(RasaAi rasa, AIRequest request, RasaResponse response)
|
||||
{
|
||||
Database dc = rasa.dc;
|
||||
|
||||
// Merge input contexts
|
||||
var contexts = dc.Table<ConversationContext>()
|
||||
.Where(x => x.ConversationId == rasa.AiConfig.SessionId && x.Lifespan > 0)
|
||||
.Where(x => x.ConversationId == aiConfig.SessionId && x.Lifespan > 0)
|
||||
.ToList()
|
||||
.Select(x => new AIContext { Name = x.Context.ToLower(), Lifespan = x.Lifespan })
|
||||
.ToList();
|
||||
|
|
@ -100,7 +38,7 @@ namespace BotSharp.Core.Engines
|
|||
contexts = contexts.OrderBy(x => x.Name).ToList();
|
||||
|
||||
// search all potential intents which input context included in contexts
|
||||
var intents = rasa.agent.Intents.Where(it =>
|
||||
var intents = agent.Intents.Where(it =>
|
||||
{
|
||||
if (contexts.Count == 0)
|
||||
{
|
||||
|
|
@ -121,13 +59,13 @@ namespace BotSharp.Core.Engines
|
|||
};
|
||||
}
|
||||
|
||||
response.IntentRanking = response.IntentRanking.Where(x => x.Confidence > decimal.Parse("0.3")).ToList();
|
||||
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 = rasa.agent.Intents.FirstOrDefault(x => x.Name == "Default Fallback Intent");
|
||||
var defaultFallbackIntent = agent.Intents.FirstOrDefault(x => x.Name == "Default Fallback Intent");
|
||||
response.IntentRanking.Add(new RasaResponseIntent
|
||||
{
|
||||
Name = defaultFallbackIntent.Name,
|
||||
|
|
@ -137,7 +75,7 @@ namespace BotSharp.Core.Engines
|
|||
|
||||
response.Intent = response.IntentRanking.First();
|
||||
|
||||
var intent = (dc.Table<Intent>().Where(x => x.AgentId == rasa.agent.Id && x.Name == response.Intent.Name)
|
||||
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();
|
||||
|
|
@ -157,7 +95,7 @@ namespace BotSharp.Core.Engines
|
|||
/// <param name="response"></param>
|
||||
/// <param name="request"></param>
|
||||
/// <returns>Required field is missed</returns>
|
||||
private static void HandleParameter(Agent agent, IntentResponse intentResponse, RasaResponse response, AIRequest request)
|
||||
public static void HandleParameter(Agent agent, IntentResponse intentResponse, RasaResponse response, AIRequest request)
|
||||
{
|
||||
if (intentResponse == null) return;
|
||||
|
||||
|
|
@ -197,7 +135,7 @@ namespace BotSharp.Core.Engines
|
|||
});
|
||||
}
|
||||
|
||||
private static void HandleMessage(IntentResponse intentResponse)
|
||||
public static void HandleMessage(IntentResponse intentResponse)
|
||||
{
|
||||
if (intentResponse == null) return;
|
||||
|
||||
|
|
@ -254,7 +192,7 @@ namespace BotSharp.Core.Engines
|
|||
return text;
|
||||
}
|
||||
|
||||
private static void HandleContext(Database dc, RasaAi rasa, IntentResponse intentResponse, AIResponse aiResponse)
|
||||
public static void HandleContext(Database dc, AIConfiguration AiConfig, IntentResponse intentResponse, AIResponse aiResponse)
|
||||
{
|
||||
if (intentResponse == null) return;
|
||||
|
||||
|
|
@ -262,7 +200,7 @@ namespace BotSharp.Core.Engines
|
|||
// override if exists, otherwise add, delete if lifespan is zero
|
||||
dc.DbTran(() =>
|
||||
{
|
||||
var sessionContexts = dc.Table<ConversationContext>().Where(x => x.ConversationId == rasa.AiConfig.SessionId).ToList();
|
||||
var sessionContexts = dc.Table<ConversationContext>().Where(x => x.ConversationId == AiConfig.SessionId).ToList();
|
||||
|
||||
// minus 1 round
|
||||
sessionContexts.Where(x => !intentResponse.Contexts.Select(ctx => ctx.Name).Contains(x.Context))
|
||||
|
|
@ -288,7 +226,7 @@ namespace BotSharp.Core.Engines
|
|||
{
|
||||
dc.Table<ConversationContext>().Add(new ConversationContext
|
||||
{
|
||||
ConversationId = rasa.AiConfig.SessionId,
|
||||
ConversationId = AiConfig.SessionId,
|
||||
Context = ctx.Name,
|
||||
Lifespan = ctx.Lifespan
|
||||
});
|
||||
|
|
@ -297,33 +235,16 @@ namespace BotSharp.Core.Engines
|
|||
});
|
||||
|
||||
aiResponse.Result.Contexts = dc.Table<ConversationContext>()
|
||||
.Where(x => x.Lifespan > 0 && x.ConversationId == rasa.AiConfig.SessionId)
|
||||
.Where(x => x.Lifespan > 0 && x.ConversationId == AiConfig.SessionId)
|
||||
.Select(x => new AIContext { Name = x.Context.ToLower(), Lifespan = x.Lifespan })
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
private static IRestResponse<RasaResponse> CallRasa(string projectId, string text, string model)
|
||||
public static string GetModelPerContexts(Agent agent, AIConfiguration aiConfig, AIRequest request, Database dc)
|
||||
{
|
||||
var client = new RestClient($"{Database.Configuration.GetSection("Rasa:Nlu").Value}");
|
||||
|
||||
var rest = new RestRequest("parse", Method.POST);
|
||||
string json = JsonConvert.SerializeObject(new { Project = projectId, Q = text, Model = model },
|
||||
new JsonSerializerSettings
|
||||
{
|
||||
ContractResolver = new CamelCasePropertyNamesContractResolver()
|
||||
});
|
||||
rest.AddParameter("application/json", json, ParameterType.RequestBody);
|
||||
|
||||
return client.Execute<RasaResponse>(rest);
|
||||
}
|
||||
|
||||
private static string GetModelPerContexts(RasaAi rasa, AIRequest request)
|
||||
{
|
||||
Database dc = rasa.dc;
|
||||
|
||||
// Merge input contexts
|
||||
var contexts = dc.Table<ConversationContext>()
|
||||
.Where(x => x.ConversationId == rasa.AiConfig.SessionId && x.Lifespan > 0)
|
||||
.Where(x => x.ConversationId == aiConfig.SessionId && x.Lifespan > 0)
|
||||
.ToList()
|
||||
.Select(x => new AIContext { Name = x.Context.ToLower(), Lifespan = x.Lifespan })
|
||||
.ToList();
|
||||
|
|
@ -332,7 +253,7 @@ namespace BotSharp.Core.Engines
|
|||
contexts = contexts.OrderBy(x => x.Name).ToList();
|
||||
|
||||
// search all potential intents which input context included in contexts
|
||||
var intents = rasa.agent.Intents.Where(it =>
|
||||
var intents = agent.Intents.Where(it =>
|
||||
{
|
||||
if (contexts.Count == 0)
|
||||
{
|
||||
|
|
@ -28,6 +28,9 @@ namespace BotSharp.Core.Intents
|
|||
[ForeignKey("IntentId")]
|
||||
public List<IntentInputContext> Contexts { get; set; }
|
||||
|
||||
[ForeignKey("IntentId")]
|
||||
public List<IntentEvent> Events { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Get input contexts hash
|
||||
/// </summary>
|
||||
|
|
|
|||
20
BotSharp.Core/Intents/IntentEvent.cs
Normal file
20
BotSharp.Core/Intents/IntentEvent.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using EntityFrameworkCore.BootKit;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using System.Text;
|
||||
|
||||
namespace BotSharp.Core.Intents
|
||||
{
|
||||
[Table("Bot_IntentEvent")]
|
||||
public class IntentEvent : DbRecord, IDbRecord
|
||||
{
|
||||
[Required]
|
||||
[StringLength(36)]
|
||||
public String IntentId { get; set; }
|
||||
|
||||
[MaxLength(64)]
|
||||
public String Name { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,11 @@ namespace BotSharp.Core.Intents
|
|||
|
||||
public AIResponseMessageType Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Platform like: facebook, slack
|
||||
/// </summary>
|
||||
public String Platform { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// json list data
|
||||
/// </summary>
|
||||
|
|
@ -31,6 +36,8 @@ namespace BotSharp.Core.Intents
|
|||
[MaxLength(1024)]
|
||||
public String PayloadJson { get; set; }
|
||||
|
||||
public String CardJson { get; set; }
|
||||
|
||||
[NotMapped]
|
||||
public JObject Payload { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,20 +70,7 @@ namespace BotSharp.UnitTest
|
|||
|
||||
var rasa = new RasaAi(dc, config);
|
||||
|
||||
string msg = rasa.Train();
|
||||
|
||||
Assert.IsTrue(!String.IsNullOrEmpty(msg));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public void TrainAgentPerContextTest()
|
||||
{
|
||||
var config = new AIConfiguration(BOT_CLIENT_TOKEN, SupportedLanguage.English);
|
||||
config.SessionId = Guid.NewGuid().ToString();
|
||||
|
||||
var rasa = new RasaAi(dc, config);
|
||||
|
||||
rasa.TrainWithContexts();
|
||||
rasa.Train();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="2.1.1" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.7.2" />
|
||||
<PackageReference Include="MSTest.TestAdapter" Version="1.3.2" />
|
||||
<PackageReference Include="MSTest.TestFramework" Version="1.3.2" />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"Rasa": {
|
||||
"Nlu": "http://gtx.local:5000"
|
||||
"Nlu": "http://localhost:5000"
|
||||
},
|
||||
|
||||
"BotSharpAi": {
|
||||
|
|
|
|||
Loading…
Reference in a new issue