Самый эффективный способ добавить диалог в бот qnamaker - PullRequest
1 голос
/ 09 июля 2019

Боты QnA Maker просты в реализации и предлагают отличную стоимость.В некоторых случаях мне нужно добавить диалог к ​​боту QnaMaker.Я борюсь с лучшим подходом для этого.Все примеры, которые я попробовал, начинаются с основного диалогового окна, отличного от QnAmaker.

Моей целью является начало диалога (для получения контактных данных) после определенного ответа от службы QnA (#contact).Приветствуются некоторые рекомендации.

Я создал компонент диалога для получения профиля пользователя.Я использовал пример с несколькими подсказками в качестве руководства.Диалог действительно запускается после определенного результата запроса QnAMaker.

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

Начинается первый шаг набора диалогов.И после ввода ответа процесс завершается неудачно.Ответ снова отправляется в службу QnA и не используется в качестве ввода (результата) для следующего шага в компоненте диалога.

Я предполагаю, что причина в том, что все результаты отправляются в службу QnA с помощью onTurnОбработчик.

Мои вопросы:

  • Может ли это быть даже сделано.Смогу ли я (без особого рефакторинга) запустить простой диалог с ботом 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 ]

1 голос
/ 10 июля 2019

Этого можно добиться, используя диалоговые окна компонентов .

В следующем примере у меня есть одно диалоговое окно компонентов, которое «слушает» пользовательский ввод.В этом случае пользователю нужно ввести что-то, связанное с получением имени пользователя.Если есть совпадение, он делает вызов 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, и сопоставляя их с пользовательским вводом.Если в любом из вопросов есть «пользователь» и / или «имя», я возвращаю «истина».Если это правда, тогда я возвращаю ответ 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

1 голос
/ 09 июля 2019

Самым простым способом будет использование библиотеки botbuilder-dialogs https://github.com/microsoft/botbuilder-js/tree/master/libraries/botbuilder-dialogs

Использование предварительно упакованных библиотек / классов диалогов Botbuilder будет проще, чем попытка сделать это с нуля.Такие вещи, как простые подсказки, легко доступны.

В репозитории Botbuilder-Samples есть примеры конкретных функций, так что вы не будете перегружены, просматривая крупномасштабный код бота или читая запутанную документацию Microsoft, пытаясь найти то, что вам нужно.

Похоже, вы просто ищете запрос ввода, так что это будет идеально для того, что вам нужно https://github.com/microsoft/BotBuilder-Samples/tree/master/samples/javascript_nodejs/44.prompt-for-user-input

...