asksuite-core
Version:
217 lines (194 loc) • 6.89 kB
JavaScript
/* eslint-disable eqeqeq */
/* eslint-disable no-throw-literal */
const request = require('request');
const StringUtils = require('../util/StringUtils');
const AsksuiteProcessingNaturalUtils = require('../AsksuiteProcessingNaturalUtils');
const { ErrorHandlerService } = require('./ErrorHandlerService');
class RasaAccessor {
constructor(RASA_URL, RASA_CONFIDENCE_THRESHOLD) {
this.RASA_URL = RASA_URL;
this.RASA_CONFIDENCE_THRESHOLD = RASA_CONFIDENCE_THRESHOLD;
this.utils = new AsksuiteProcessingNaturalUtils();
}
/**
* Parses the text in a default format so understanding functions better.
* MUST BE EXACTLY THE SAME PARSING AS asksuite-control's
* @param text
*/
normalizeText(text) {
let result = StringUtils.unaccent(text).toLowerCase();
result = result.replace(/-/g, ' ');
result = result.replace(/[^A-Za-z\d\s]/g, '');
return result.trim();
}
resolveText(data, needsEntityConfident = true) {
return new Promise((resolve) => {
const executor = async () => {
const { text, language: requestLanguage, languages, defaultLanguage } = data;
const language = this.utils.resolveLanguage(requestLanguage, languages);
let { companyId } = data;
const cleanText = this.normalizeText(text);
companyId = this.parseCompanyId(companyId);
const url = this.RASA_URL + '/parse';
let processingStep;
try {
if (language.substr(0, 2) !== defaultLanguage.substr(0, 2)) {
const result = await this.resolveText(
{
...data,
defaultLanguage: language,
companyId: `${companyId}_${language.substr(0, 2)}`,
},
needsEntityConfident,
);
if (result.intent && result.intent !== 'naoentendi') {
resolve(result);
}
}
const result = await this.requestData(url, {
q: cleanText,
project: companyId,
});
console.timeEnd('RasaAccessor.requestData');
const response = JSON.parse(result.body);
if (!response.intent) {
throw { message: result.statusMessage, code: result.statusCode };
}
const passConfidence = needsEntityConfident
? response.intent.confidence >= this.RASA_CONFIDENCE_THRESHOLD &&
this.hasEntityConfident(response.entities, this.RASA_CONFIDENCE_THRESHOLD)
: response.intent.confidence >= this.RASA_CONFIDENCE_THRESHOLD ||
this.hasEntityConfident(response.entities, this.RASA_CONFIDENCE_THRESHOLD);
if (passConfidence) {
const intentBot = data.intents.find((item) => {
return item.intent == response.intent.name;
});
if (intentBot) {
intentBot.processingStep = {
fallbackReason: intentBot.dialog ? null : 'dialog_empty',
confidence: response.intent.confidence,
intentFound: response.intent.name,
text: cleanText,
};
resolve(intentBot);
} else if (!needsEntityConfident) {
const entity = this.getEntityConfident(
response.entities,
this.RASA_CONFIDENCE_THRESHOLD,
);
const resolvedEntityText = await this.resolveText(
Object.assign(data, { text: entity.value }),
true,
);
resolve(resolvedEntityText);
} else {
processingStep = {
fallbackReason: 'intent_not_mapped',
confidence: response.intent.confidence,
intentFound: response.intent.name,
text: cleanText,
};
}
} else {
processingStep = {
fallbackReason: 'intent_not_found',
confidence: response.intent.confidence,
intentFound: response.intent.name,
text: cleanText,
};
}
} catch (e) {
const errorData = ErrorHandlerService.accessorErrorHandler(e);
processingStep = {
fallbackReason: 'intent_not_found',
intentFound: null,
text,
language,
resolver: 'RASA',
errorData,
};
}
// this.removeLoadedModels(companyId).catch(()=>{});
resolve({
...data.findIntent,
processingStep,
});
};
executor();
});
}
parseCompanyId(name) {
let companyId = name;
if (companyId.indexOf('.') > -1) {
const splited = companyId.split('.');
companyId = splited[splited.length - 1];
}
return companyId;
}
requestData(url, qs) {
return new Promise((resolve, reject) => {
request.get(url, { qs, timeout: 6000 }, (err, res) => {
if (err) {
if (err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT') {
console.log('RasaAccessor.requestData timeout error!');
}
reject(err);
}
resolve(res);
});
});
}
hasEntityConfident(entities, threshold) {
for (const entity of entities) {
if (entity.value === entity.entity || (entity.confidence && entity.confidence >= threshold)) {
// entry is entity or synonym
return true;
}
}
return false;
}
getEntityConfident(entities, threshold) {
for (const entity of entities) {
if (entity.value === entity.entity || (entity.confidence && entity.confidence >= threshold)) {
// entry is entity or synonym
return entity;
}
}
}
removeLoadedModels(companyId) {
const statusUrl = `${this.RASA_URL}/status`;
return new Promise((resolve, reject) => {
request.get(statusUrl, (err, res) => {
if (!err) {
try {
const status = JSON.parse(res.body);
const availableProjects = status.available_projects;
const project = availableProjects[companyId];
const loadedModels = project.loaded_models;
if (loadedModels.length) {
for (const model of loadedModels) {
this.deleteModel(companyId, model).catch(() => {}); // Waiting is not necessary
}
}
resolve();
return;
} catch (e) {}
}
reject(err);
});
});
}
deleteModel(companyId, model) {
const deleteUrl = `${this.RASA_URL}/models`;
return new Promise((resolve, reject) => {
request.delete(deleteUrl, { qs: { project: companyId, model } }, (err, res) => {
if (!err) {
resolve(res);
return;
}
reject(err);
});
});
}
}
module.exports = RasaAccessor;