From 5ecf922999bb932d6ec601ab867fe096e7bfdb0f Mon Sep 17 00:00:00 2001 From: "Rodrigo Rodriguez (pragmatismo.io)" Date: Sun, 4 Nov 2018 09:19:03 -0200 Subject: [PATCH] Removing bugs after updating to BOT Framework latest dialog pattern. --- .../services/AzureDeployerService.ts | 13 +- .../services/GBConversationalService.ts | 8 +- packages/core.gbapp/services/GBDeployer.ts | 23 +- packages/core.gbapp/services/GBMinService.ts | 4 +- packages/kb.gbapp/dialogs/AskDialog.ts | 276 +++++++++--------- src/app.ts | 17 +- 6 files changed, 185 insertions(+), 156 deletions(-) diff --git a/packages/azuredeployer.gbapp/services/AzureDeployerService.ts b/packages/azuredeployer.gbapp/services/AzureDeployerService.ts index f61a5feb..4e52297c 100644 --- a/packages/azuredeployer.gbapp/services/AzureDeployerService.ts +++ b/packages/azuredeployer.gbapp/services/AzureDeployerService.ts @@ -47,7 +47,7 @@ import * as simplegit from "simple-git/promise"; import { AppServicePlan } from "azure-arm-website/lib/models"; import { GBConfigService } from "../../../packages/core.gbapp/services/GBConfigService"; import { GBAdminService } from "../../../packages/admin.gbapp/services/GBAdminService"; -import { GBCorePackage } from "packages/core.gbapp"; +import { GBCorePackage } from "../../../packages/core.gbapp"; const Spinner = require("cli-spinner").Spinner; const scanf = require("scanf"); @@ -148,7 +148,7 @@ export class AzureDeployerService extends GBService { logger.info(`Deploying Search...`); let searchName = `${name}-search`; await this.createSearch(name, searchName, instance.cloudLocation); - let searchKeys = await this.searchClient.queryKeys.listBySearchService( + let searchKeys = await this.searchClient.adminKeys.get( name, searchName ); @@ -502,11 +502,11 @@ export class AzureDeployerService extends GBService { displayName: name, endpoint: endpoint, iconUrl: iconUrl, - //luisAppIds: [nlpAppId], - //luisKey: nlpKey, + luisAppIds: [nlpAppId], + luisKey: nlpKey, msaAppId: appId, msaAppPassword: appPassword, - //enabledChannels: ["webchat"], // , "skype", "facebook"], + enabledChannels: ["webchat"], // , "skype", "facebook"], configuredChannels: ["webchat"] // , "skype", "facebook"] } }; @@ -528,9 +528,6 @@ export class AzureDeployerService extends GBService { return; } - logger.info(`Bot creation request done waiting for key generation...`); - resolve(instance); - setTimeout(async () => { try { query = `subscriptions/${subscriptionId}/resourceGroups/${group}/providers/Microsoft.BotService/botServices/${botId}/channels/WebChatChannel/listChannelWithKeys?api-version=${ diff --git a/packages/core.gbapp/services/GBConversationalService.ts b/packages/core.gbapp/services/GBConversationalService.ts index a23e931b..30ba6e92 100644 --- a/packages/core.gbapp/services/GBConversationalService.ts +++ b/packages/core.gbapp/services/GBConversationalService.ts @@ -108,10 +108,16 @@ export class GBConversationalService implements IGBConversationalService { try { nlp = await model.recognize(step.context); } catch (error) { + if (error.statusCode == 404){ + logger.warn ('NLP application still not publish and there are no other options for answering.') + return Promise.resolve(false); + } + else{ let msg = `Error calling NLP server, check if you have a published model and assigned keys on the service. Error: ${ error.statusCode ? error.statusCode : "" } ${error.message}`; - return Promise.reject(new Error(msg)); + return Promise.reject(new Error(msg));} + } // Resolves intents returned from LUIS. diff --git a/packages/core.gbapp/services/GBDeployer.ts b/packages/core.gbapp/services/GBDeployer.ts index ecb7983a..d76b6c28 100644 --- a/packages/core.gbapp/services/GBDeployer.ts +++ b/packages/core.gbapp/services/GBDeployer.ts @@ -177,7 +177,6 @@ export class GBDeployer { Path.extname(filename) === ".gbapp" || Path.extname(filename) === ".gblib" ) { - /** Themes for bots. */ } else if (Path.extname(filename) === ".gbtheme") { server.use("/themes/" + filenameOnly, express.static(filename)); @@ -341,15 +340,31 @@ export class GBDeployer { let connectionString = GBDeployer.getConnectionStringFromInstance(instance); const dsName = "gb"; - await search.deleteDatasource(dsName); - await search.createDatasource( + try { + await search.deleteDataSource(dsName); + } catch (err) { + if (err.code != 404) { + // First time, nothing to delete. + throw err; + } + } + + await search.createDataSource( dsName, dsName, "GuaribasQuestion", "azuresql", connectionString ); - await search.deleteIndex(); + + try { + await search.deleteIndex(); + } catch (err) { + if (err.code != 404) { + // First time, nothing to delete. + throw err; + } + } await search.createIndex( AzureDeployerService.getKBSearchSchema(instance.searchIndex), dsName diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index 0acbe7a2..44fddb98 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -429,7 +429,7 @@ export class GBMinService { // Otherwise, continue to the active dialog in the stack. } else { if (step.activeDialog) { - await step.continue(); + await step.continueDialog(); } else { await step.beginDialog("/answer", { query: context.activity.text @@ -466,7 +466,7 @@ export class GBMinService { let token = (context.activity as any).data; await step.beginDialog("/adminUpdateToken", { token: token }); } else { - await step.continue(); + await step.continueDialog(); } } } catch (error) { diff --git a/packages/kb.gbapp/dialogs/AskDialog.ts b/packages/kb.gbapp/dialogs/AskDialog.ts index cf55db18..5272d2c9 100644 --- a/packages/kb.gbapp/dialogs/AskDialog.ts +++ b/packages/kb.gbapp/dialogs/AskDialog.ts @@ -52,175 +52,181 @@ export class AskDialog extends IGBDialog { static setup(bot: BotAdapter, min: GBMinInstance) { const service = new KBService(min.core.sequelize); - min.dialogs.add(new WaterfallDialog("/answerEvent", [ - async step => { - if (step.options && step.options["questionId"]) { - let question = await service.getQuestionById( - min.instance.instanceId, - step.options["questionId"] - ); - let answer = await service.getAnswerById( - min.instance.instanceId, - question.answerId - ); + min.dialogs.add( + new WaterfallDialog("/answerEvent", [ + async step => { + if (step.options && step.options["questionId"]) { + let question = await service.getQuestionById( + min.instance.instanceId, + step.options["questionId"] + ); + let answer = await service.getAnswerById( + min.instance.instanceId, + question.answerId + ); - // Sends the answer to all outputs, including projector. + // Sends the answer to all outputs, including projector. - await service.sendAnswer(min.conversationalService, step, answer); + await service.sendAnswer(min.conversationalService, step, answer); - await step.replaceDialog("/ask", { isReturning: true }); - } - return await step.next(); - } - ])); - - min.dialogs.add(new WaterfallDialog("/answer", [ - async step => { - const user = await min.userProfile.get(step.context, {}); - let text = step.options["query"]; - if (!text) { - throw new Error(`/answer being called with no args.query text.`); - } - - let locale = step.context.activity.locale; - - // Stops any content on projector. - - await min.conversationalService.sendEvent(step, "stop", null); - - // Handle extra text from FAQ. - - if (step.options && step.options["query"]) { - text = step.options["query"]; - } else if (step.options && step.options["fromFaq"]) { - await step.context.sendActivity(Messages[locale].going_answer); - } - - // Spells check the input text before sending Search or NLP. - - if (min.instance.spellcheckerKey) { - let data = await AzureText.getSpelledText( - min.instance.spellcheckerKey, - text - ); - - if (data != text) { - logger.info(`Spelling corrected: ${data}`); - text = data; + await step.replaceDialog("/ask", { isReturning: true }); } + return await step.next(); } + ]) + ); - // Searches KB for the first time. + min.dialogs.add( + new WaterfallDialog("/answer", [ + async step => { + const user = await min.userProfile.get(step.context, {}); + let text = step.options["query"]; + if (!text) { + throw new Error(`/answer being called with no args.query text.`); + } - user.lastQuestion = text; - await min.userProfile.set(step.context, user); - let resultsA = await service.ask( - min.instance, - text, - min.instance.searchScore, - user.subjects - ); + let locale = step.context.activity.locale; - // If there is some result, answer immediately. + // Stops any content on projector. - if (resultsA && resultsA.answer) { - // Saves some context info. + await min.conversationalService.sendEvent(step, "stop", null); - user.isAsking = false; - user.lastQuestionId = resultsA.questionId; + // Handle extra text from FAQ. + + if (step.options && step.options["query"]) { + text = step.options["query"]; + } else if (step.options && step.options["fromFaq"]) { + await step.context.sendActivity(Messages[locale].going_answer); + } + + // Spells check the input text before sending Search or NLP. + + if (min.instance.spellcheckerKey) { + let data = await AzureText.getSpelledText( + min.instance.spellcheckerKey, + text + ); + + if (data != text) { + logger.info(`Spelling corrected: ${data}`); + text = data; + } + } + + // Searches KB for the first time. + + user.lastQuestion = text; await min.userProfile.set(step.context, user); - - // Sends the answer to all outputs, including projector. - - await service.sendAnswer( - min.conversationalService, - step, - resultsA.answer - ); - - // Goes to ask loop, again. - - await step.replaceDialog("/ask", { isReturning: true }); - } else { - // Second time running Search, now with no filter. - - let resultsB = await service.ask( + let resultsA = await service.ask( min.instance, text, min.instance.searchScore, - null + user.subjects ); // If there is some result, answer immediately. - if (resultsB && resultsB.answer) { + if (resultsA && resultsA.answer) { // Saves some context info. - const user = await min.userProfile.get(step.context, {}); - user.isAsking = false; - user.lastQuestionId = resultsB.questionId; + user.lastQuestionId = resultsA.questionId; await min.userProfile.set(step.context, user); - // Informs user that a broader search will be used. - - if (user.subjects.length > 0) { - let subjectText = `${KBService.getSubjectItemsSeparatedBySpaces( - user.subjects - )}`; - await step.context.sendActivity(Messages[locale].wider_answer); - } - // Sends the answer to all outputs, including projector. await service.sendAnswer( min.conversationalService, step, - resultsB.answer + resultsA.answer ); - await step.replaceDialog("/ask", { isReturning: true }); + + // Goes to ask loop, again. + + return await step.replaceDialog("/ask", { isReturning: true }); } else { - if (!(await min.conversationalService.routeNLP(step, min, text))) { - await step.context.sendActivity(Messages[locale].did_not_find); - await step.replaceDialog("/ask", { isReturning: true }); + // Second time running Search, now with no filter. + + let resultsB = await service.ask( + min.instance, + text, + min.instance.searchScore, + null + ); + + // If there is some result, answer immediately. + + if (resultsB && resultsB.answer) { + // Saves some context info. + + const user = await min.userProfile.get(step.context, {}); + + user.isAsking = false; + user.lastQuestionId = resultsB.questionId; + await min.userProfile.set(step.context, user); + + // Informs user that a broader search will be used. + + if (user.subjects.length > 0) { + let subjectText = `${KBService.getSubjectItemsSeparatedBySpaces( + user.subjects + )}`; + await step.context.sendActivity(Messages[locale].wider_answer); + } + + // Sends the answer to all outputs, including projector. + + await service.sendAnswer( + min.conversationalService, + step, + resultsB.answer + ); + return await step.replaceDialog("/ask", { isReturning: true }); + } else { + if ( + !(await min.conversationalService.routeNLP(step, min, text)) + ) { + await step.context.sendActivity(Messages[locale].did_not_find); + return await step.replaceDialog("/ask", { isReturning: true }); + } } } } - return await step.next(); - } - ])); + ]) + ); - min.dialogs.add(new WaterfallDialog("/ask", [ - async step => { - const locale = step.context.activity.locale; - const user = await min.userProfile.get(step.context, {}); - user.isAsking = true; - if (!user.subjects) { - user.subjects = []; + min.dialogs.add( + new WaterfallDialog("/ask", [ + async step => { + const locale = step.context.activity.locale; + const user = await min.userProfile.get(step.context, {}); + user.isAsking = true; + if (!user.subjects) { + user.subjects = []; + } + let text; + + // Three forms of asking. + + if (step.options && step.options["firstTime"]) { + text = Messages[locale].ask_first_time; + } else if (step.options && step.options["isReturning"]) { + text = Messages[locale].anything_else; + } else if (user.subjects.length > 0) { + text = Messages[locale].which_question; + } else { + throw new Error("Invalid use of /ask"); + } + + if (text.length > 0) { + return await step.prompt("textPrompt", text); + } + return await step.next(); + }, + async step => { + return await step.replaceDialog("/answer", { query: step.result }); } - let text; - - // Three forms of asking. - - if (step.options && step.options["firstTime"]) { - text = Messages[locale].ask_first_time; - } else if (step.options && step.options["isReturning"]) { - text = Messages[locale].anything_else; - } else if (user.subjects.length > 0) { - text = Messages[locale].which_question; - } else { - throw new Error("Invalid use of /ask"); - } - - if (text.length > 0) { - await step.prompt("textPrompt", text); - } - return await step.next(); - }, - async step => { - await step.replaceDialog("/answer", { query: step.result }); - return await step.next(); - } - ])); + ]) + ); } } diff --git a/src/app.ts b/src/app.ts index e0b4479b..13091bf8 100644 --- a/src/app.ts +++ b/src/app.ts @@ -61,17 +61,20 @@ let appPackages = new Array(); * General Bots open-core entry point. */ export class GBServer { + /** * Program entry-point. */ static run() { + + logger.info(`The Bot Server is in STARTING mode...`); + // Creates a basic HTTP server that will serve several URL, one for each // bot instance. This allows the same server to attend multiple Bot on // the Marketplace until GB get serverless. let port = process.env.port || process.env.PORT || 4242; - logger.info(`The Bot Server is in STARTING mode...`); let server = express(); server.use(bodyParser.json()); // to support JSON-encoded bodies @@ -86,7 +89,7 @@ export class GBServer { server.listen(port, () => { (async () => { try { - logger.info(`Accepting connections on ${port}...`); + logger.info(`Now accepting connections on ${port}...`); // Reads basic configuration, initialize minimal services. @@ -95,7 +98,7 @@ export class GBServer { // Ensures cloud / on-premises infrastructure is setup. - logger.info(`Establishing a development local proxy...`); + logger.info(`Establishing a development local proxy (ngrok)...`); let proxyAddress = await core.ensureProxy(port); let azureDeployer = new AzureDeployerService(); @@ -103,17 +106,17 @@ export class GBServer { try { await core.initDatabase(); } catch (error) { - logger.info(`Deploying cognitive infrastructure...`); + logger.info(`Deploying cognitive infrastructure (on the cloud / on premises)...`); try { bootInstance = await azureDeployer.deployFarm(proxyAddress); } catch (error) { logger.warn( - "In case of error, please cleanup any infrastructure (cloud or on-premises) objects created before running again." + "In case of error, please cleanup any infrastructure objects created during this procedure before running again." ); throw error; } core.writeEnv(bootInstance); - logger.info(`File .env written, starting...`); + logger.info(`File .env written, starting General Bots...`); GBConfigService.init(); await core.initDatabase(); @@ -157,6 +160,7 @@ export class GBServer { try { instances = await core.loadInstances(); let instance = instances[0]; + if (process.env.NODE_ENV === "development") { logger.info(`Updating bot endpoint to local reverse proxy (ngrok)...`); @@ -199,6 +203,7 @@ export class GBServer { logger.info(`Deploying packages...`); let deployer = new GBDeployer(core, new GBImporter(core)); + await deployer.rebuildIndex(instances[0]); await deployer.deployPackages(core, server, appPackages); // If instances is undefined here it's because storage has been formatted.