botbuilder-js icon indicating copy to clipboard operation
botbuilder-js copied to clipboard

Adding activeLearning capabilities to qnaMakerRecognizer

Open akgargo opened this issue 3 years ago • 1 comments

Use this query to search for the most popular feature requests.

Is your feature request related to a problem? Please describe. I am migrating a bot using botframework 4.3 to use AdaptiveDialogs, the issue comes when I try to maintain the same functionality in QnA dialogs, I reviewed the source and I see that you are not checking if the activeLearning feature is enabled so it can't be used with qnaMakerRecognizer

Describe the solution you'd like Please add the corresponding logic for checking if activeLearning is enabled using getAnswersRaw instead of getAnswers in the recognize method, as well as the callTrainAsync method for sending the suggestions to QnAMaker

Describe alternatives you've considered Keep everything using the old code, or copy the files from your package into a local folder, so I can modify them and use that instead

Additional context `/** * Queries the knowledgebase and either passes result to the next step or constructs and displays an active learning card * if active learning is enabled and multiple score close answers are returned. **/ async callGenerateAnswer(step) { const dialogOptions = step.activeDialog.state[this.options]; dialogOptions.qnaMakerOptions.qnaId = 0; dialogOptions.qnaMakerOptions.context = { previousQnAId: 0, previousUserQuery: '' };

    step.values[this.currentQuery] = step.context.activity.text;
    const previousContextData = step.activeDialog.state[this.qnAContextData] || {};
    var previousQnAId = step.activeDialog.state[this.previousQnAId] || 0;

    if (previousQnAId > 0) {
        dialogOptions.qnaMakerOptions.context = { previousQnAId: previousQnAId, previousUserQuery: '' };
        if (previousContextData[step.context.activity.text]) {
            dialogOptions.qnaMakerOptions.qnaId = previousContextData[step.context.activity.text];
        }
    }

    const qna = this.getQnAClient();

    const response = await qna.getAnswersRaw(step.context, dialogOptions.qnaMakerOptions);

    const qnaResponse = {
        activeLearningEnabled: response.activeLearningEnabled,
        answers: response.answers
    };

    previousQnAId = -1;
    step.activeDialog.state[this.previousQnAId] = previousQnAId;
    const isActiveLearningEnabled = qnaResponse.activeLearningEnabled;

    step.values[this.qnAData] = response.answers;

    if (isActiveLearningEnabled && qnaResponse.answers.length > 0 && qnaResponse.answers[0].score <= this.maximumScoreForLowScoreVariation) {
        qnaResponse.answers = qna.getLowScoreVariation(qnaResponse.answers);

        if (qnaResponse.answers && qnaResponse.answers.length > 1) {
            var suggestedQuestions = [];

            qnaResponse.answers.forEach(answer => {
                suggestedQuestions.push(answer.questions[0]);
            });

            var message = QnACardBuilder.getSuggestionsCard(suggestedQuestions, dialogOptions.qnaDialogResponseOptions.activeLearningCardTitle, dialogOptions.qnaDialogResponseOptions.cardNoMatchText);
            await step.context.sendActivity(message);

            step.activeDialog.state[this.options] = dialogOptions;

            return Dialog.EndOfTurn;
        }
    }
    const result = [];

    if (response.answers && response.answers.length > 0) {
        result.push(response.answers[0]);
    }

    step.values[this.qnAData] = result;
    step.activeDialog.state[this.options] = dialogOptions;
    return await step.next(result);
}
/**
 * If active learning options were displayed in the previous step and the user has selected an option other
 * than 'no match' then the training API is called, passing the user's chosen question back to the knowledgebase.
 * If no active learning options were displayed in the previous step, the incoming result is immediately passed to the next step.
**/
async callTrain(step) {
    const dialogOptions = step.activeDialog.state[this.options];
    const trainResponses = step.values[this.qnAData];
    const currentQuery = step.values[this.currentQuery];

    const reply = step.context.activity.text;

    if (trainResponses && trainResponses.length > 1) {
        const qnaResult = trainResponses.filter(r => r.questions[0] == reply);

        if (qnaResult && qnaResult.length > 0) {
            var results = [];
            results.push(qnaResult[0]);
            step.values[this.qnAData] = results;

            var records = [];
            records.push({
                userId: step.context.activity.id,
                userQuestion: currentQuery,
                qnaId: qnaResult[0].id.toString()
            });

            var feedbackRecords = { feedbackRecords: records };

            await this.getQnAClient().callTrainAsync(feedbackRecords);

            return await step.next(qnaResult);
        } else if (reply == dialogOptions.qnaDialogResponseOptions.cardNoMatchText) {
            const activity = dialogOptions.qnaDialogResponseOptions.cardNoMatchResponse;
            await step.context.sendActivity(activity || this.defaultCardNoMatchResponse);
            return step.endDialog();
        }
        else {
            return await super.runStep.call(this, step, 0, DialogReason.beginCalled);
        }
    }
    return await step.next(step.result);
}`

akgargo avatar Jan 13 '22 18:01 akgargo

@sahithimurty, please review this issue and let us know if changes are needed.

mrivera-ms avatar Mar 03 '22 22:03 mrivera-ms