UNPKG

asksuite-core

Version:
217 lines (194 loc) 6.89 kB
/* 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;