KB is almost working in V4.

This commit is contained in:
Rodrigo Rodriguez 2018-09-10 16:24:32 -03:00
parent 3fdceda57c
commit ea39f80707
7 changed files with 191 additions and 217 deletions

View file

@ -30,6 +30,7 @@
| | | |
\*****************************************************************************/ \*****************************************************************************/
"use strict"; "use strict";
const logger = require("../../../src/logger"); const logger = require("../../../src/logger");
@ -51,7 +52,7 @@ export class GBConversationalService implements IGBConversationalService {
this.coreService = coreService; this.coreService = coreService;
} }
async sendEvent(dc: any, name: string, value: any) { async sendEvent(dc: any, name: string, value: any): Promise<any> {
const msg = MessageFactory.text(''); const msg = MessageFactory.text('');
msg.value = value; msg.value = value;
msg.type = "event"; msg.type = "event";
@ -71,7 +72,7 @@ export class GBConversationalService implements IGBConversationalService {
serviceEndpoint: min.instance.nlpServerUrl serviceEndpoint: min.instance.nlpServerUrl
}); });
return await model.recognize(dc.context).then(res => { let res = await model.recognize(dc.context);
// Resolve intents returned from LUIS // Resolve intents returned from LUIS
let topIntent = LuisRecognizer.topIntent(res); let topIntent = LuisRecognizer.topIntent(res);
@ -87,24 +88,20 @@ export class GBConversationalService implements IGBConversationalService {
); );
try { try {
dc.replace("/" + intent); await dc.replace("/" + intent);
} catch (error) { } catch (error) {
logger.info("error: intent: [" + intent + "] error: [" + error + "]"); logger.info("error: intent: [" + intent + "] error: [" + error + "]");
await dc.context.sendActivity("Desculpe-me, não encontrei nada a respeito...");
dc.context.sendActivity("Desculpe-me, não encontrei nada a respeito..."); await dc.replace("/ask", { isReturning: true });
dc.replace("/ask", { isReturning: true });
} }
return Promise.resolve({ intent, entities: res.entities }); return Promise.resolve({ intent, entities: res.entities });
} else {
dc.context.sendActivity("Lamento, não achei nada a respeito..."); } else {
dc.replace("/ask", { isReturning: true }); await dc.context.sendActivity("Lamento, não achei nada a respeito...");
resolve(null); await dc.replace("/ask", { isReturning: true });
return Promise.resolve(null);
} }
} }
).catch(err => {
return Promise.reject(new GBError(err, GBERROR_TYPE.nlpGeneralError));
});
}
} }

View file

@ -269,15 +269,19 @@ export class GBMinService {
} }
} }
else if (context.activity.type === 'message') { else if (context.activity.type === 'message') {
// Check to see if anyone replied. If not then start echo dialog // Check to see if anyone replied. If not then start echo dialog
if (context.activity.text === "admin") { if (context.activity.text === "admin") {
await dc.begin("/admin"); await dc.begin("/admin");
} }
else { else {
await dc.continue(); await dc.continue();
} }
} }
else if (context.activity.type === 'event') { else if (context.activity.type === 'event') {
if (context.activity.name === "whoAmI") { if (context.activity.name === "whoAmI") {
await dc.begin("/whoAmI"); await dc.begin("/whoAmI");
} }
@ -293,8 +297,8 @@ export class GBMinService {
await dc.begin("/faq"); await dc.begin("/faq");
} }
else if (context.activity.name === "ask") { else if (context.activity.name === "ask") {
dc.begin("/answer", { await dc.begin("/answer", {
// TODO: query: context.activity.data, query: (context.activity as any).data,
fromFaq: true fromFaq: true
}); });
} }

View file

@ -52,8 +52,7 @@ export class FeedbackDialog extends IGBDialog {
const service = new CSService(); const service = new CSService();
min.dialogs.add("/feedbackNumber", [ min.dialogs.add("/feedbackNumber", [
async (dc, args) => { async (dc) => {
let messages = [ let messages = [
"O que achou do meu atendimento, de 1 a 5?", "O que achou do meu atendimento, de 1 a 5?",
"Qual a nota do meu atendimento?", "Qual a nota do meu atendimento?",
@ -88,11 +87,8 @@ export class FeedbackDialog extends IGBDialog {
await dc.prompt('textPrompt', messages[0]); await dc.prompt('textPrompt', messages[0]);
}, },
async (dc, value) => { async (dc, value) => {
AzureText.getSentiment( let rate = await AzureText.getSentiment(min.instance.textAnalyticsKey, "pt-br", value);
min.instance.textAnalyticsKey, if (rate > 0) {
value,
(err, rate) => {
if (!err && rate > 0) {
dc.context.sendActivity("Bom saber que você gostou. Conte comigo."); dc.context.sendActivity("Bom saber que você gostou. Conte comigo.");
} else { } else {
dc.context.sendActivity( dc.context.sendActivity(
@ -100,9 +96,6 @@ export class FeedbackDialog extends IGBDialog {
); );
} }
dc.replace('/ask', { isReturning: true }); dc.replace('/ask', { isReturning: true });
} }]);
);
}
]);
} }
} }

View file

@ -50,7 +50,7 @@ export class AskDialog extends IGBDialog {
*/ */
static setup(bot: BotAdapter, min: GBMinInstance) { static setup(bot: BotAdapter, min: GBMinInstance) {
const service = new KBService(); const service = new KBService(min.core.sequelize);
const model = new LuisRecognizer({ const model = new LuisRecognizer({
appId: min.instance.nlpAppId, appId: min.instance.nlpAppId,
@ -65,7 +65,14 @@ export class AskDialog extends IGBDialog {
// Initialize values. // Initialize values.
const user = min.userState.get(dc.context); const user = min.userState.get(dc.context);
let text = ""; let text = args.query;
if (!text) {
throw new Error(`/answer being called with no args.query text.`)
}
// Stops any content on projector.
await min.conversationalService.sendEvent(dc, "stop", null);
// Handle extra text from FAQ. // Handle extra text from FAQ.
@ -78,35 +85,31 @@ export class AskDialog extends IGBDialog {
`Aguarde, por favor, enquanto acho sua resposta...` `Aguarde, por favor, enquanto acho sua resposta...`
]; ];
dc.context.sendActivity(messages[0]); // TODO: Handle rnd. await dc.context.sendActivity(messages[0]); // TODO: Handle rnd.
} }
// Spells check the input text before sending Search or NLP. // Spells check the input text before sending Search or NLP.
// DISABLED: if (min.instance.spellcheckerKey) {
// AzureText.getSpelledText( let data = await AzureText.getSpelledText(
// min.instance.spellcheckerKey, min.instance.spellcheckerKey,
// text, text);
// async (data, err) => {
// var data = res.text;
// if (data != text) {
// logger.info("Spelled Text: " + data);
// text = data;
// }
user.lastQuestion = text; if (data != text) {
logger.info(`Spelling corrected: ${data}`);
text = data;
}
}
// Searches KB for the first time. // Searches KB for the first time.
user.lastQuestion = text;
let resultsA = await service.ask( let resultsA = await service.ask(
min.instance, min.instance,
text, text,
min.instance.searchScore, min.instance.searchScore,
user.subjects); user.subjects);
// Stops any content on projector.
min.conversationalService.sendEvent(dc, "stop", null);
// If there is some result, answer immediately. // If there is some result, answer immediately.
@ -119,10 +122,7 @@ export class AskDialog extends IGBDialog {
// Sends the answer to all outputs, including projector. // Sends the answer to all outputs, including projector.
service.sendAnswer(min.conversationalService, await service.sendAnswer(min.conversationalService, dc, resultsA.answer);
dc,
resultsA.answer
);
// Goes to ask loop, again. // Goes to ask loop, again.
@ -132,11 +132,8 @@ export class AskDialog extends IGBDialog {
// Second time running Search, now with no filter. // Second time running Search, now with no filter.
let resultsB = await service.ask( let resultsB = await service.ask(min.instance, text,
min.instance, min.instance.searchScore, null);
text,
min.instance.searchScore,
null);
// If there is some result, answer immediately. // If there is some result, answer immediately.
@ -148,7 +145,7 @@ export class AskDialog extends IGBDialog {
user.isAsking = false; user.isAsking = false;
user.lastQuestionId = resultsB.questionId; user.lastQuestionId = resultsB.questionId;
// Inform user that a broader search will be used. // Informs user that a broader search will be used.
if (user.subjects.length > 0) { if (user.subjects.length > 0) {
let subjectText = let subjectText =
@ -161,25 +158,17 @@ export class AskDialog extends IGBDialog {
`Vou te responder de modo mais abrangente... `Vou te responder de modo mais abrangente...
Não apenas sobre ${subjectText}` Não apenas sobre ${subjectText}`
]; ];
dc.context.sendActivity(messages[0]); // TODO: Handle rnd. await dc.context.sendActivity(messages[0]); // TODO: Handle rnd.
} }
// Sends the answer to all outputs, including projector. // Sends the answer to all outputs, including projector.
service.sendAnswer(min.conversationalService, await service.sendAnswer(min.conversationalService, dc, resultsB.answer);
dc, await dc.replace("/ask", { isReturning: true });
resultsB.answer
);
dc.replace("/ask", { isReturning: true });
} else { } else {
await min.conversationalService.runNLP(
dc,
min,
text,
(data, error) => {
let data = await min.conversationalService.runNLP(dc, min, text);
if (!data) { if (!data) {
let messages = [ let messages = [
"Desculpe-me, não encontrei nada a respeito.", "Desculpe-me, não encontrei nada a respeito.",
@ -190,15 +179,11 @@ export class AskDialog extends IGBDialog {
dc.context.sendActivity(messages[0]); // TODO: Handle rnd. dc.context.sendActivity(messages[0]); // TODO: Handle rnd.
dc.replace("/ask", { isReturning: true }); dc.replace("/ask", { isReturning: true });
} }
}).catch(err => {
console.log(err);
});
} }
} }
} }
]); ]);
min.dialogs.add("/ask", [ min.dialogs.add("/ask", [
async (dc, args) => { async (dc, args) => {
const user = min.userState.get(dc.context); const user = min.userState.get(dc.context);

View file

@ -46,7 +46,7 @@ export class FaqDialog extends IGBDialog {
*/ */
static setup(bot: BotAdapter, min: GBMinInstance) { static setup(bot: BotAdapter, min: GBMinInstance) {
const service = new KBService(); const service = new KBService(min.core.sequelize);
min.dialogs.add("/faq", [ min.dialogs.add("/faq", [
async (dc, args) => { async (dc, args) => {

View file

@ -60,7 +60,7 @@ export class MenuDialog extends IGBDialog {
*/ */
static setup(bot: BotAdapter, min: GBMinInstance) { static setup(bot: BotAdapter, min: GBMinInstance) {
var service = new KBService(); var service = new KBService(min.core.sequelize);
bot bot
min.dialogs.add("/menu", [ min.dialogs.add("/menu", [

View file

@ -41,6 +41,7 @@ const path = require("path")
const asyncPromise = require('async-promises') const asyncPromise = require('async-promises')
const walkPromise = require('walk-promise') const walkPromise = require('walk-promise')
import { Sequelize } from 'sequelize-typescript'
import { GBConfigService } from './../../core.gbapp/services/GBConfigService' import { GBConfigService } from './../../core.gbapp/services/GBConfigService'
import { GuaribasQuestion, GuaribasAnswer, GuaribasSubject } from "../models" import { GuaribasQuestion, GuaribasAnswer, GuaribasSubject } from "../models"
import { IGBCoreService, IGBConversationalService, IGBInstance } from "botlib" import { IGBCoreService, IGBConversationalService, IGBInstance } from "botlib"
@ -55,6 +56,12 @@ export class KBServiceSearchResults {
export class KBService { export class KBService {
sequelize: Sequelize
constructor(sequelize: Sequelize) {
this.sequelize = sequelize
}
async getAnswerById( async getAnswerById(
instanceId: number, instanceId: number,
answerId: number answerId: number
@ -78,32 +85,26 @@ export class KBService {
instanceId: number, instanceId: number,
text: string text: string
): Promise<any> { ): Promise<any> {
return new Promise(
(resolve, reject) => {
GuaribasQuestion.findOne({ const Op = Sequelize.Op
let question = await GuaribasQuestion.findOne({
where: { where: {
instanceId: instanceId, instanceId: instanceId,
content: `%${text.trim()}%` content: { [Op.like]: `%${text.trim()}%` }
} }
}).then((question: GuaribasQuestion) => { })
if (question) { if (question) {
GuaribasAnswer.findAll({ let answer = await GuaribasAnswer.findOne({
where: { where: {
instanceId: instanceId, instanceId: instanceId,
answerId: question.answerId answerId: question.answerId
} }
}).then((answer: GuaribasAnswer[]) => {
resolve({ question: question, answer: answer[0] })
}) })
return Promise.resolve({ question: question, answer: answer })
} }
else { return Promise.resolve(null)
resolve(null)
}
}).error((reason) => {
reject(reason)
})
})
} }
async addAnswer(obj: GuaribasAnswer): Promise<GuaribasAnswer> { async addAnswer(obj: GuaribasAnswer): Promise<GuaribasAnswer> {
@ -119,26 +120,24 @@ export class KBService {
async ask( async ask(
instance: IGBInstance, instance: IGBInstance,
what: string, query: string,
searchScore: number, searchScore: number,
subjects: GuaribasSubject[] subjects: GuaribasSubject[]
): Promise<KBServiceSearchResults> { ): Promise<KBServiceSearchResults> {
// Builds search query. // Builds search query.
what = what.toLowerCase() query = query.toLowerCase()
what = what.replace("?", " ") query = query.replace("?", " ")
what = what.replace("!", " ") query = query.replace("!", " ")
what = what.replace(".", " ") query = query.replace(".", " ")
what = what.replace("/", " ") query = query.replace("/", " ")
what = what.replace("\\", " ") query = query.replace("\\", " ")
if (subjects) { if (subjects) {
let text = KBService.getSubjectItemsSeparatedBySpaces( let text = KBService.getSubjectItemsSeparatedBySpaces(subjects)
subjects
)
if (text) { if (text) {
what = `${what} ${text}` query = `${query} ${text}`
} }
} }
// TODO: Filter by instance. what = `${what}&$filter=instanceId eq ${instanceId}` // TODO: Filter by instance. what = `${what}&$filter=instanceId eq ${instanceId}`
@ -150,7 +149,7 @@ export class KBService {
instance.searchIndex, instance.searchIndex,
instance.searchIndexer instance.searchIndexer
) )
let results = await service.search(what) let results = await service.search(query)
if (results && results.length > 0 && if (results && results.length > 0 &&
results[0]["@search.score"] >= searchScore) { results[0]["@search.score"] >= searchScore) {
let value = await this.getAnswerById( let value = await this.getAnswerById(
@ -159,7 +158,7 @@ export class KBService {
return Promise.resolve({ answer: value, questionId: results[0].questionId }) return Promise.resolve({ answer: value, questionId: results[0].questionId })
} }
} else { } else {
let data = await this.getAnswerByText(instance.instanceId, what) let data = await this.getAnswerByText(instance.instanceId, query)
return Promise.resolve( return Promise.resolve(
{ answer: data.answer, questionId: data.question.questionId } { answer: data.answer, questionId: data.question.questionId }
) )
@ -365,7 +364,7 @@ export class KBService {
delimiter: "\t" delimiter: "\t"
} }
let data = await parse(file, opts); let data = await parse(file, opts)
return asyncPromise.eachSeries(data, async line => { return asyncPromise.eachSeries(data, async line => {
// Extracts values from columns in the current line. // Extracts values from columns in the current line.
@ -422,7 +421,7 @@ export class KBService {
content: answer, content: answer,
format: format, format: format,
packageId: packageId packageId: packageId
}); })
await GuaribasQuestion.create({ await GuaribasQuestion.create({
from: from, from: from,
to: to, to: to,
@ -434,7 +433,7 @@ export class KBService {
instanceId: instanceId, instanceId: instanceId,
answerId: answer1.answerId, answerId: answer1.answerId,
packageId: packageId packageId: packageId
}); })
return Promise.resolve(question) return Promise.resolve(question)
@ -448,23 +447,25 @@ export class KBService {
}) })
} }
sendAnswer(conversationalService: IGBConversationalService, async sendAnswer(conversationalService: IGBConversationalService,
dc: any, answer: GuaribasAnswer) { dc: any, answer: GuaribasAnswer): Promise<any> {
if (answer.content.endsWith('.mp4')) { if (answer.content.endsWith('.mp4')) {
conversationalService.sendEvent(dc, "play", { await conversationalService.sendEvent(dc, "play", {
playerType: "video", playerType: "video",
data: answer.content data: answer.content
}) })
} else if (answer.content.length > 140 && } else if (answer.content.length > 140 &&
dc.message.source != "directline") { dc.context._activity.channelId === "webchat") {
let messages = [ let messages = [
"Vou te responder na tela para melhor visualização...", "Vou te responder na tela para melhor visualização...",
"A resposta está na tela...", "A resposta está na tela...",
"Veja a resposta na tela..." "Veja a resposta na tela..."
] ]
dc.context.sendActivity(messages[0]) // TODO: Handle rnd.
await dc.context.sendActivity(messages[0]) // TODO: Handle rnd.
var html = answer.content var html = answer.content
if (answer.format === ".md") { if (answer.format === ".md") {
marked.setOptions({ marked.setOptions({
renderer: new marked.Renderer(), renderer: new marked.Renderer(),
@ -479,10 +480,10 @@ export class KBService {
}) })
html = marked(answer.content) html = marked(answer.content)
} }
conversationalService.sendEvent(dc, "play", { playerType: "markdown", data: html }) await conversationalService.sendEvent(dc, "play", { playerType: "markdown", data: html })
} else { } else {
dc.context.sendActivity(answer.content) await dc.context.sendActivity(answer.content)
conversationalService.sendEvent(dc, "stop", null) await conversationalService.sendEvent(dc, "stop", null)
} }
} }
@ -490,15 +491,14 @@ export class KBService {
localPath: string, localPath: string,
packageStorage: GuaribasPackage, packageStorage: GuaribasPackage,
instance: IGBInstance instance: IGBInstance
): Promise<GuaribasQuestion[]> { ): Promise<any> {
// Imports subjects tree into database and return it. // Imports subjects tree into database and return it.
return this.importSubjectFile( await this.importSubjectFile(
packageStorage.packageId, packageStorage.packageId,
UrlJoin(localPath, "subjects.json"), UrlJoin(localPath, "subjects.json"),
instance instance);
).then((value: GuaribasQuestion[]) => {
// Import all .tsv files in the tabular directory. // Import all .tsv files in the tabular directory.
@ -507,9 +507,9 @@ export class KBService {
instance, instance,
packageStorage.packageId packageStorage.packageId
) )
})
} }
async importKbTabularDirectory( async importKbTabularDirectory(
localPath: string, localPath: string,
instance: IGBInstance, instance: IGBInstance,
@ -523,9 +523,9 @@ export class KBService {
return this.importKbTabularFile( return this.importKbTabularFile(
UrlJoin(file.root, file.name), UrlJoin(file.root, file.name),
instance.instanceId, instance.instanceId,
packageId); packageId)
} }
})); }))
} }
@ -549,13 +549,13 @@ export class KBService {
title: item.title, title: item.title,
description: item.description, description: item.description,
packageId: packageId packageId: packageId
}); })
if (item.children) { if (item.children) {
return Promise.resolve(doIt(item.children, value.subjectId)) return Promise.resolve(doIt(item.children, value.subjectId))
} }
else { else {
return Promise.resolve(item); return Promise.resolve(item)
} }
}) })
@ -564,31 +564,26 @@ export class KBService {
return doIt(subjects.children, null) return doIt(subjects.children, null)
} }
undeployKbFromStorage( async undeployKbFromStorage(
instance: IGBInstance, instance: IGBInstance,
packageId: number packageId: number
) { ) {
// TODO: call reject.
return new Promise( await GuaribasQuestion.destroy({
(resolve, reject) => {
GuaribasQuestion.destroy({
where: { instanceId: instance.instanceId, packageId: packageId } where: { instanceId: instance.instanceId, packageId: packageId }
}).then(value => { })
GuaribasAnswer.destroy({ await GuaribasAnswer.destroy({
where: { instanceId: instance.instanceId, packageId: packageId } where: { instanceId: instance.instanceId, packageId: packageId }
}).then(value => { })
GuaribasSubject.destroy({ await GuaribasSubject.destroy({
where: { instanceId: instance.instanceId, packageId: packageId } where: { instanceId: instance.instanceId, packageId: packageId }
}).then(value => { })
GuaribasPackage.destroy({ await GuaribasPackage.destroy({
where: { instanceId: instance.instanceId, packageId: packageId } where: { instanceId: instance.instanceId, packageId: packageId }
}).then(value => {
resolve(null)
})
})
})
})
}) })
return Promise.resolve()
} }
/** /**