Search code examples
javascriptnode.jsazurebotframeworkazure-qna-maker

Botmaker resolve Qnamaker Followup Questions


for my current project i try to use context-only follow-up prompts.

My problem is now, that some of the questions from the follow-up prompts are the same. qna example

In the qna ui the separations of the context works fine, but when the user answers the question from the chat i implementen in my application, qna returns the wrong answer.

Code: Botbuilder version: 4.5.3 NodeJS: 12.10.0

onMessage(){.....

let results = qna.getAnswer(context);
if(results[0].context.prompts.length){ //  Answer with Follow-up
      return context.sendActivity(CardHelper.GetHeroCard(results[0].answer, results[0].context.prompts))
   }else{ // normal answer
      return context.sendActivity(results[0].answer)
   }
}

Sample Questions:

Expected Answer:
I want to learn programming => java => here is our guide
Real Answer:
I want to learn programming => java => that is the java test

Expected Answer:
I want to do a test => java => that is the java test
Real Answer:
I want to do a test => java => that is the java test

How is it possible to implement these follow-up prompts in the code and don't lose the follow-up context?


Solution

  • The first thing to understand is that multi-turn conversations and follow-up prompts are in preview. This generally means one should expect bugs and missing functionality. In this case it means the feature is not only missing from the SDK, it's even missing from the API reference. You can see in the Generate Answer reference that a call to the generateAnswer endpoint includes a context property in its body, but the type of that object is undocumented. It links to the Context object type that gets returned in the response rather than what you're supposed to put in the request.

    Since your question mentions CardHelper.GetHeroCard, I presume you're already familiar with the QnA Maker prompting sample. If you are by some chance not familiar with that sample, it is the ultimate source of truth when it comes to multi-turn conversations in QnA Maker. That sample contains the entire answer to your question so I'm unsure why you're not using it. However, you should have also seen what you need to do in the documentation you should be following:

    A JSON request to return a non-initial answer and follow-up prompts

    Fill the context object to include the previous context.

    In the following JSON request, the current question is Use Windows Hello to sign in and the previous question was accounts and signing in.

    {
      "question": "Use Windows Hello to sign in",
      "top": 10,
      "userId": "Default",
      "isTest": false,
      "qnaId": 17,
      "context": {
        "previousQnAId": 15,
        "previousUserQuery": "accounts and signing in"
      }
    }
    

    QnA Maker doesn't save any state on its own, so it depends on your bot to give it the context from the previous turn. Your bot isn't doing that, and that's why it's not working. Here's a simplified version of the code from the sample to help you understand what you need to do:

    async testQnAMaker(turnContext) {
        var qna = new QnAMaker({
            knowledgeBaseId: '<GUID>',
            endpointKey: '<GUID>',
            host: 'https://<APPNAME>.azurewebsites.net/qnamaker'
        });
    
        var context = await this.qnaState.get(turnContext) || {
            PreviousQnaId: 0,
            PreviousUserQuery: null
        };
    
        // We're passing a context property into the QnAMakerOptions
        // even though it's not part of the interface yet
        var results = await qna.getAnswers(turnContext, { context });
        var firstResult = results[0];
    
        if (firstResult) {
            var answer = firstResult.answer;
            var resultContext = firstResult.context;
            var prompts = resultContext && resultContext.prompts;
    
            if (prompts && prompts.length) {
                await this.qnaState.set(turnContext, {
                    PreviousQnaId: firstResult.id,
                    PreviousUserQuery: turnContext.activity.text
                });
    
                answer = ChoiceFactory.forChannel(
                    turnContext,
                    prompts.map(prompt => prompt.displayText),
                    answer);
            }
    
            await turnContext.sendActivity(answer);
        } else {
            await turnContext.sendActivity("I can't answer that");
        }
    }
    

    Because you're using a feature that's currently in preview, you will need to use your own ingenuity when it comes to figuring out how to use it. I'm just using a state property accessor to save the context of the previous question on each turn, but it's likely that you'll want to build this into a dialog and save each user query in the dialog state. The point is that you'll have to save the QnA Maker state if you want the follow-up prompts to work.

    EDIT: It turns out there is a way to use follow-up prompts without bot state if you put the context in the actions themselves, but that will only work if the user clicks the buttons instead of typing something: Display Text for QnAMaker follow-on prompts