向qnamaker bot添加对话框的最有效方法

时间:2019-07-09 14:59:09

标签: node.js botframework qnamaker

QnA制造商的机器人易于实施,并具有很高的价值。在某些情况下,我需要向QnaMaker机器人添加一个对话框。我正在努力做到这一点的最佳方法。我尝试过的所有示例均始于非QnAmaker主对话框。

我的目标是在QnA服务(#contact)做出一定回答后开始对话(获取联系方式)。一些指导表示赞赏。

我创建了一个对话框组件来检索用户个人资料。我以多提示示例为指导。该对话框确实在QnAMaker查询的特定结果之后开始。

// user requests to be contacted
            case '#Contact': {
                await this.dialog.run(turnContext, this.dialogState);
                break;

对话框集的第一步开始。输入响应后,该过程将失败。答案将再次发送到QnA服务,而不用作对话框组件中下一步的输入(结果)。

我希望原因是所有结果都由onTurn处理程序发送到QnA服务。

我的问题:

  • 甚至可以做到这一点。我能否(无需过多重构)从QnA机器人启动简单对话框。

  • 是否可以检查是否存在活动的对话框。如果是这样,我也许可以通过使用它来解决。

    我正在考虑这样的事情:

 this.onMessage(async (context, next) => {
            console.log('Any active Dialog we need to finish?');
            AciveDialog ? ResumeDialog : const qnaResults = await this.qnaMaker.getAnswers(context);

文档和示例不是很有帮助,因此非常感谢任何帮助。

直到现在我的机器人代码。我没有链接对话框组件,因为我希望这不会成为问题的一部分。

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// Microsoft Bot Framework components
const { AttachmentLayoutTypes, ActivityTypes, ActivityHandler, CardFactory } = require('botbuilder');
const { QnAMaker } = require('botbuilder-ai');

// Making sure the time is mentioned correctly
const moment = require('moment-timezone');
require('moment/locale/nl');

// Helper funtions (forecast, welcome-message, cards, storage)
const helper = require('./helper');

// Introcard for welcome message
const IntroCard = require('./resources/IntroCard.json');

class QnAMakerBot extends ActivityHandler {
    constructor(endpoint, qnaOptions, conversationState, userState, dialog) {
        super();
        this.qnaMaker = new QnAMaker(endpoint, qnaOptions);
        this.conversationState = conversationState;
        this.userState = userState;
        this.dialog = dialog;
        this.dialogState = this.conversationState.createProperty('DialogState');
    }

    async onTurn(turnContext) {
        // First check if a new user joined the webchat, if so, send a greeting message to the user.
        if (turnContext.activity.name === 'webchat/join') {
            await turnContext.sendActivity({ type: 'typing' });
            await turnContext.sendActivity({
                attachments: [CardFactory.adaptiveCard(IntroCard)]
            });
        };
        // if a user sent a message, show some response (1) and construct an answer (2).
        if (turnContext.activity.type === ActivityTypes.Message) {
            // (1)typing indicator with a short delay to improve user experience
            await turnContext.sendActivity({ type: 'typing' });
            // (2) Perform a call to the QnA Maker service to retrieve matching Question and Answer pairs.
            const qnaResults = await this.qnaMaker.getAnswers(turnContext);
            // for learning purposes store all questions with qnaMaker score.
            if (turnContext.activity.name !== 'webchat/join') {
                let score = (qnaResults[0] != null) ? qnaResults[0].score : 'No answer found';
                helper.storeQuestions(turnContext, score);
            };
            // If QnAMaker found an answer that might be correct, first check for responses that need additional work
            // If so, do the additional work, otherwise (default) send the QnA answer to the user
            if (qnaResults[0] && qnaResults[0].score > 0.5) {
                switch (qnaResults[0].answer) {
                // user requests a weatherforecast
                case '#Weather': {
                    var weatherForecast = await helper.getWeatherForecast(turnContext);
                    await turnContext.sendActivity({
                        attachments: [CardFactory.adaptiveCard(weatherForecast)]
                    });
                    break;
                }
                // user requests current date and/or time
                case '#DateTime': {
                    await turnContext.sendActivity(moment().tz('Europe/Amsterdam').format('[Today is ]LL[ and the time is  ] LT'));
                    break;
                }
                // user requests help or a startmenu
                case '#Help': {
                    await turnContext.sendActivity({
                        attachments: [CardFactory.adaptiveCard(IntroCard)]
                    });
                    break;
                }
                // user requests an overview of current bots
                case '#Bots': {
                    await turnContext.sendActivity({
                        attachments: helper.createBotsGallery(turnContext),
                        attachmentLayout: AttachmentLayoutTypes.Carousel
                    });
                    break;
                }
                // user requests to be contacted. This is were the magic should happen ;-)
                case '#Contact': {
                    await this.dialog.run(turnContext, this.dialogState);
                    break;
                }
                // if no 'special' requests, send the answer found in QnaMaker
                default: {
                    await turnContext.sendActivity(qnaResults[0].answer);
                    break;
                }
                }
            // QnAmaker did not find an answer with a high probability
            } else {
                await turnContext.sendActivity('Some response');
            }
        }
    }

    async onMessage(turnContext, next) {
        // Run the Dialog with the new message Activity.
        await this.dialog.run(turnContext, this.dialogState);

        await next();
    };

    async onDialog(turnContext, next) {
        // Save any state changes. The load happened during the execution of the Dialog.
        await this.conversationState.saveChanges(turnContext, false);
        await this.userState.saveChanges(turnContext, false);
        await next();
    };
}

module.exports.QnAMakerBot = QnAMakerBot;

2 个答案:

答案 0 :(得分:1)

最简单的方法是使用botbuilder-dialogs库https://github.com/microsoft/botbuilder-js/tree/master/libraries/botbuilder-dialogs

使用botbuilder预先打包的库/对话框类会更容易,然后尝试从头开始做。简单提示之类的东西很容易获得。

Botbuilder-Samples存储库具有特定于功能的示例,因此您不会因浏览大型bot代码或阅读Microsoft令人困惑的文档来查找所需内容而感到不知所措。

您似乎只是想提示输入内容,因此可以满足您的需求 https://github.com/microsoft/BotBuilder-Samples/tree/master/samples/javascript_nodejs/44.prompt-for-user-input

答案 1 :(得分:1)

您可以通过使用component dialogs来实现这一点。

在下面的示例中,我有一个组件对话框,可以“监听”用户输入。在这种情况下,让用户输入与获取用户名有关的内容。如果存在匹配项,它将进行QnA调用以检索答案/响应。检索并显示答案后,机器人将开始一个中间(子级)对话框,然后返回主对话框。

首先,在成功的QnA响应之后,创建要路由到的组件对话框。我已将此文件命名为“ getUserNameDialog.js”。

const {
  TextPrompt,
  ComponentDialog,
  WaterfallDialog
} = require('botbuilder-dialogs');

const GET_USER_NAME_DIALOG = 'GET_USER_NAME_DIALOG';
const TEXT_PROMPT = 'TEXT_PROMPT';
const WATERFALL_DIALOG = 'WATERFALL_DIALOG';

class GetUserNameDialog extends ComponentDialog {
  constructor() {
    super(GET_USER_NAME_DIALOG);

    this.addDialog(new TextPrompt(TEXT_PROMPT));
    this.addDialog(new WaterfallDialog(WATERFALL_DIALOG, [
      this.getNameStep.bind(this),
      this.displayNameStep.bind(this)
    ]));
    this.initialDialogId = WATERFALL_DIALOG;
  }

  async getNameStep(stepContext) {
    return await stepContext.prompt(TEXT_PROMPT, "Let's makeup a user name for fun. Enter something.");

    // return stepContext.next();
  }
  async displayNameStep(stepContext) {
    const stepResults = stepContext.result;
    await stepContext.context.sendActivity(`${ stepResults } is a fine name!`);

    return stepContext.endDialog();
  }
}
module.exports.GetUserNameDialog = GetUserNameDialog;
module.exports.GET_USER_NAME_DIALOG = GET_USER_NAME_DIALOG;

接下来,创建QnA对话框(我将其命名为qnaResponseDialog.js)。我的QnA凭据存储在.env文件中,可从中获取它们。请注意,我需要上面创建的“ getUserNameDialog”文件。

当QnA出现匹配/响应时(我正在寻找对“用户名”的引用),然后调用beginDialog()来启动子对话框。我通过映射QnA响应内返回的问题并在用户输入上进行匹配来做到这一点。如果任何一个问题中都包含“用户”和/或“名称”,那么我将返回true。如果为true,则返回QnA响应并开始子对话框。

此匹配过程非常简单,并且更多用于演示,但是如果对您有用,那就太好了。但是,我建议您考虑使用LUIS来匹配用户意图。它将使此过程更加清洁和易于维护。

const { ComponentDialog } = require('botbuilder-dialogs');
const { QnAMaker } = require('botbuilder-ai');
const { GetUserNameDialog, GET_USER_NAME_DIALOG } = require('./getUserNameDialog');

class QnAResponseDialog extends ComponentDialog {
  constructor() {
    super(GET_USER_NAME_DIALOG);
    this.addDialog(new GetUserNameDialog());

    try {
      this.qnaMaker = new QnAMaker({
        knowledgeBaseId: process.env.QnAKnowledgebaseId,
        endpointKey: process.env.QnAAuthKey,
        host: process.env.QnAEndpointHostName
      });
    } catch (err) {
      console.warn(`QnAMaker Exception: ${ err } Check your QnAMaker configuration in .env`);
    }
  }

  async onBeginDialog(innerDc, options) {
    const result = await this.interrupt(innerDc);
    if (result) {
      return result;
    }
    return await super.onBeginDialog(innerDc, options);
  }

  async onContinueDialog(innerDc) {
    const result = await this.interrupt(innerDc);
    if (result) {
      return result;
    }
    return await super.onContinueDialog(innerDc);
  }

  async interrupt(innerDc) {
    if (innerDc.context.activity.type === 'message') {
      const text = innerDc.context.activity.text.toLowerCase();

      const stepResults = innerDc.context;

      let qnaResults = await this.qnaMaker.getAnswers(stepResults);
      console.log(qnaResults[0]);
      stepResults.qna = qnaResults[0];

      if (qnaResults[0]) {
        let mappedResult = null;
        const includesText = qnaResults[0].questions.map((question) => {
          if (text.includes('user') || text.includes('name')) {
            mappedResult = true;
          } else {
            mappedResult = false;
          }
          console.log('RESULTS: ', mappedResult);
        });

        console.log('MAPPED: ', mappedResult);

        switch (mappedResult) {
        case true:
          let answer = stepResults.qna.answer;
          await innerDc.context.sendActivity(answer);
          return await innerDc.beginDialog(GET_USER_NAME_DIALOG);
        }
      }
    }
  }
}

module.exports.QnAResponseDialog = QnAResponseDialog;

最后,在您的主对话框或顶级对话框中,包括以下内容:

const { QnAResponseDialog } = require('./qnaResponseDialog');

class MainDialg extends QnAResponseDialog {
  [...]
}

在这一点上,如果所有配置正确,则当用户键入一个短语时,QnA会识别并接受该短语,这将中断当前对话框,显示QnA响应,开始子组件对话框,并在完成后返回到父级对话框。

enter image description here

enter image description here