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 { 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=${

View file

@ -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.

View file

@ -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

View file

@ -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) {

View file

@ -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();
}
]));
])
);
}
}

View file

@ -61,17 +61,20 @@ let appPackages = new Array<IGBPackage>();
* 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.