Removing bugs after updating to BOT Framework latest dialog pattern.

This commit is contained in:
Rodrigo Rodriguez (pragmatismo.io) 2018-11-04 09:19:03 -02:00
parent fbdae843cf
commit 5ecf922999
6 changed files with 185 additions and 156 deletions

View file

@ -47,7 +47,7 @@ import * as simplegit from "simple-git/promise";
import { AppServicePlan } from "azure-arm-website/lib/models"; import { AppServicePlan } from "azure-arm-website/lib/models";
import { GBConfigService } from "../../../packages/core.gbapp/services/GBConfigService"; import { GBConfigService } from "../../../packages/core.gbapp/services/GBConfigService";
import { GBAdminService } from "../../../packages/admin.gbapp/services/GBAdminService"; 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 Spinner = require("cli-spinner").Spinner;
const scanf = require("scanf"); const scanf = require("scanf");
@ -148,7 +148,7 @@ export class AzureDeployerService extends GBService {
logger.info(`Deploying Search...`); logger.info(`Deploying Search...`);
let searchName = `${name}-search`; let searchName = `${name}-search`;
await this.createSearch(name, searchName, instance.cloudLocation); await this.createSearch(name, searchName, instance.cloudLocation);
let searchKeys = await this.searchClient.queryKeys.listBySearchService( let searchKeys = await this.searchClient.adminKeys.get(
name, name,
searchName searchName
); );
@ -502,11 +502,11 @@ export class AzureDeployerService extends GBService {
displayName: name, displayName: name,
endpoint: endpoint, endpoint: endpoint,
iconUrl: iconUrl, iconUrl: iconUrl,
//luisAppIds: [nlpAppId], luisAppIds: [nlpAppId],
//luisKey: nlpKey, luisKey: nlpKey,
msaAppId: appId, msaAppId: appId,
msaAppPassword: appPassword, msaAppPassword: appPassword,
//enabledChannels: ["webchat"], // , "skype", "facebook"], enabledChannels: ["webchat"], // , "skype", "facebook"],
configuredChannels: ["webchat"] // , "skype", "facebook"] configuredChannels: ["webchat"] // , "skype", "facebook"]
} }
}; };
@ -528,9 +528,6 @@ export class AzureDeployerService extends GBService {
return; return;
} }
logger.info(`Bot creation request done waiting for key generation...`);
resolve(instance);
setTimeout(async () => { setTimeout(async () => {
try { try {
query = `subscriptions/${subscriptionId}/resourceGroups/${group}/providers/Microsoft.BotService/botServices/${botId}/channels/WebChatChannel/listChannelWithKeys?api-version=${ query = `subscriptions/${subscriptionId}/resourceGroups/${group}/providers/Microsoft.BotService/botServices/${botId}/channels/WebChatChannel/listChannelWithKeys?api-version=${

View file

@ -108,10 +108,16 @@ export class GBConversationalService implements IGBConversationalService {
try { try {
nlp = await model.recognize(step.context); nlp = await model.recognize(step.context);
} catch (error) { } 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: ${ 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.statusCode ? error.statusCode : ""
} ${error.message}`; } ${error.message}`;
return Promise.reject(new Error(msg)); return Promise.reject(new Error(msg));}
} }
// Resolves intents returned from LUIS. // Resolves intents returned from LUIS.

View file

@ -177,7 +177,6 @@ export class GBDeployer {
Path.extname(filename) === ".gbapp" || Path.extname(filename) === ".gbapp" ||
Path.extname(filename) === ".gblib" Path.extname(filename) === ".gblib"
) { ) {
/** Themes for bots. */ /** Themes for bots. */
} else if (Path.extname(filename) === ".gbtheme") { } else if (Path.extname(filename) === ".gbtheme") {
server.use("/themes/" + filenameOnly, express.static(filename)); server.use("/themes/" + filenameOnly, express.static(filename));
@ -341,15 +340,31 @@ export class GBDeployer {
let connectionString = GBDeployer.getConnectionStringFromInstance(instance); let connectionString = GBDeployer.getConnectionStringFromInstance(instance);
const dsName = "gb"; const dsName = "gb";
await search.deleteDatasource(dsName); try {
await search.createDatasource( await search.deleteDataSource(dsName);
} catch (err) {
if (err.code != 404) {
// First time, nothing to delete.
throw err;
}
}
await search.createDataSource(
dsName, dsName,
dsName, dsName,
"GuaribasQuestion", "GuaribasQuestion",
"azuresql", "azuresql",
connectionString connectionString
); );
await search.deleteIndex();
try {
await search.deleteIndex();
} catch (err) {
if (err.code != 404) {
// First time, nothing to delete.
throw err;
}
}
await search.createIndex( await search.createIndex(
AzureDeployerService.getKBSearchSchema(instance.searchIndex), AzureDeployerService.getKBSearchSchema(instance.searchIndex),
dsName dsName

View file

@ -429,7 +429,7 @@ export class GBMinService {
// Otherwise, continue to the active dialog in the stack. // Otherwise, continue to the active dialog in the stack.
} else { } else {
if (step.activeDialog) { if (step.activeDialog) {
await step.continue(); await step.continueDialog();
} else { } else {
await step.beginDialog("/answer", { await step.beginDialog("/answer", {
query: context.activity.text query: context.activity.text
@ -466,7 +466,7 @@ export class GBMinService {
let token = (context.activity as any).data; let token = (context.activity as any).data;
await step.beginDialog("/adminUpdateToken", { token: token }); await step.beginDialog("/adminUpdateToken", { token: token });
} else { } else {
await step.continue(); await step.continueDialog();
} }
} }
} catch (error) { } catch (error) {

View file

@ -52,175 +52,181 @@ export class AskDialog extends IGBDialog {
static setup(bot: BotAdapter, min: GBMinInstance) { static setup(bot: BotAdapter, min: GBMinInstance) {
const service = new KBService(min.core.sequelize); const service = new KBService(min.core.sequelize);
min.dialogs.add(new WaterfallDialog("/answerEvent", [ min.dialogs.add(
async step => { new WaterfallDialog("/answerEvent", [
if (step.options && step.options["questionId"]) { async step => {
let question = await service.getQuestionById( if (step.options && step.options["questionId"]) {
min.instance.instanceId, let question = await service.getQuestionById(
step.options["questionId"] min.instance.instanceId,
); step.options["questionId"]
let answer = await service.getAnswerById( );
min.instance.instanceId, let answer = await service.getAnswerById(
question.answerId 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 }); 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;
} }
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; let locale = step.context.activity.locale;
await min.userProfile.set(step.context, user);
let resultsA = await service.ask(
min.instance,
text,
min.instance.searchScore,
user.subjects
);
// If there is some result, answer immediately. // Stops any content on projector.
if (resultsA && resultsA.answer) { await min.conversationalService.sendEvent(step, "stop", null);
// Saves some context info.
user.isAsking = false; // Handle extra text from FAQ.
user.lastQuestionId = resultsA.questionId;
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); await min.userProfile.set(step.context, user);
let resultsA = await service.ask(
// 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(
min.instance, min.instance,
text, text,
min.instance.searchScore, min.instance.searchScore,
null user.subjects
); );
// If there is some result, answer immediately. // If there is some result, answer immediately.
if (resultsB && resultsB.answer) { if (resultsA && resultsA.answer) {
// Saves some context info. // Saves some context info.
const user = await min.userProfile.get(step.context, {});
user.isAsking = false; user.isAsking = false;
user.lastQuestionId = resultsB.questionId; user.lastQuestionId = resultsA.questionId;
await min.userProfile.set(step.context, user); 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. // Sends the answer to all outputs, including projector.
await service.sendAnswer( await service.sendAnswer(
min.conversationalService, min.conversationalService,
step, step,
resultsB.answer resultsA.answer
); );
await step.replaceDialog("/ask", { isReturning: true });
// Goes to ask loop, again.
return await step.replaceDialog("/ask", { isReturning: true });
} else { } else {
if (!(await min.conversationalService.routeNLP(step, min, text))) { // Second time running Search, now with no filter.
await step.context.sendActivity(Messages[locale].did_not_find);
await step.replaceDialog("/ask", { isReturning: true }); 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", [ min.dialogs.add(
async step => { new WaterfallDialog("/ask", [
const locale = step.context.activity.locale; async step => {
const user = await min.userProfile.get(step.context, {}); const locale = step.context.activity.locale;
user.isAsking = true; const user = await min.userProfile.get(step.context, {});
if (!user.subjects) { user.isAsking = true;
user.subjects = []; 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();
}
]));
} }
} }

View file

@ -61,17 +61,20 @@ let appPackages = new Array<IGBPackage>();
* General Bots open-core entry point. * General Bots open-core entry point.
*/ */
export class GBServer { export class GBServer {
/** /**
* Program entry-point. * Program entry-point.
*/ */
static run() { static run() {
logger.info(`The Bot Server is in STARTING mode...`);
// Creates a basic HTTP server that will serve several URL, one for each // 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 // bot instance. This allows the same server to attend multiple Bot on
// the Marketplace until GB get serverless. // the Marketplace until GB get serverless.
let port = process.env.port || process.env.PORT || 4242; let port = process.env.port || process.env.PORT || 4242;
logger.info(`The Bot Server is in STARTING mode...`);
let server = express(); let server = express();
server.use(bodyParser.json()); // to support JSON-encoded bodies server.use(bodyParser.json()); // to support JSON-encoded bodies
@ -86,7 +89,7 @@ export class GBServer {
server.listen(port, () => { server.listen(port, () => {
(async () => { (async () => {
try { try {
logger.info(`Accepting connections on ${port}...`); logger.info(`Now accepting connections on ${port}...`);
// Reads basic configuration, initialize minimal services. // Reads basic configuration, initialize minimal services.
@ -95,7 +98,7 @@ export class GBServer {
// Ensures cloud / on-premises infrastructure is setup. // 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 proxyAddress = await core.ensureProxy(port);
let azureDeployer = new AzureDeployerService(); let azureDeployer = new AzureDeployerService();
@ -103,17 +106,17 @@ export class GBServer {
try { try {
await core.initDatabase(); await core.initDatabase();
} catch (error) { } catch (error) {
logger.info(`Deploying cognitive infrastructure...`); logger.info(`Deploying cognitive infrastructure (on the cloud / on premises)...`);
try { try {
bootInstance = await azureDeployer.deployFarm(proxyAddress); bootInstance = await azureDeployer.deployFarm(proxyAddress);
} catch (error) { } catch (error) {
logger.warn( 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; throw error;
} }
core.writeEnv(bootInstance); core.writeEnv(bootInstance);
logger.info(`File .env written, starting...`); logger.info(`File .env written, starting General Bots...`);
GBConfigService.init(); GBConfigService.init();
await core.initDatabase(); await core.initDatabase();
@ -157,6 +160,7 @@ export class GBServer {
try { try {
instances = await core.loadInstances(); instances = await core.loadInstances();
let instance = instances[0]; let instance = instances[0];
if (process.env.NODE_ENV === "development") { if (process.env.NODE_ENV === "development") {
logger.info(`Updating bot endpoint to local reverse proxy (ngrok)...`); logger.info(`Updating bot endpoint to local reverse proxy (ngrok)...`);
@ -199,6 +203,7 @@ export class GBServer {
logger.info(`Deploying packages...`); logger.info(`Deploying packages...`);
let deployer = new GBDeployer(core, new GBImporter(core)); let deployer = new GBDeployer(core, new GBImporter(core));
await deployer.rebuildIndex(instances[0]);
await deployer.deployPackages(core, server, appPackages); await deployer.deployPackages(core, server, appPackages);
// If instances is undefined here it's because storage has been formatted. // If instances is undefined here it's because storage has been formatted.