Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Microsoft Bot Framework with luis result directing to QNA Maker and graph api

I made a Bot using Microsoft bot framework and made use of Luis for matching intents. Some of the intents directs it to QNA and and some other intents directs it to graph api.

My Question is what is the best approach for identifying whether it should go to qna for searching the related intents in qna or whether it should go to graph api for fetching results.

As of now i did it using multiple Luis Intents for matching the correct intent and then redirect it according to the intent functionality needed(whether to direct it to qna dialog or graph api dialog).

` [LuisModel("model id", "key")]

[Serializable]
public class RootDialog : DispatchDialog
{

    //this intent directs to graph api dialog
    [LuisIntent(DialogMatches.GraphApiIntent)]
    public async Task RunGraphapiIntent(IDialogContext context, IActivity activity)
    {
            UserMessage = (activity as IMessageActivity).Text;
            await context.Forward(new GraphApiDailog(), EndDialog, context.Activity, CancellationToken.None);
    }

      //This intent directs to qna dialog
      [LuisIntent(DialogMatches.BlogMatch)]
      public async Task RunBlogDialogQna(IDialogContext context, LuisResult result)
      {
        var userQuestion = (context.Activity as Activity).Text;
        (context.Activity as Activity).Text = DialogMatches.BlogMatch;
        await context.Forward(new BasicQnAMakerDialog(), this.EndDialog, context.Activity, CancellationToken.None);
      }
      `

But this approach requires me to match every intents using [LuisIntent("intentstring")].. Since i can have 50 or 100's of intent, this is not practical to write 50 functions for 50 intents.

I found out a way to call an api for fetching intent from utterances in Quickstart: Analyze text using C#

it makes use of "https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/df67dcdb-c37d-46af-88e1-8b97951ca1c2?subscription-key=&q=turn on the bedroom light" api for fetching intent.

Again My Question is how will i know whether i should redirect it to QnaDialog or Graph Api Dialog for fetching data using the intent result that i got?

Thanks in advance

like image 531
Akshay Antony Avatar asked Sep 14 '18 18:09

Akshay Antony


1 Answers

If you want things to scale you will be better off by writing your own Nlp service that calls the Luis API to detect the intent. I think the best way to handle dialog redirection by intent is to make something like an IntentDetectorDialog whose only job is to analyze the user utterance and forwarding to the dialog that corresponds with the detected intent.

Here's a neat approach I've been using for a while:

public abstract class BaseDialog : IDialog<BaseResult>
{
    public bool DialogForwarded { get; protected set; }

    public async Task StartAsync(IDialogContext context)
    {
        context.Wait(OnMessageReceivedAsync);
    }

    public async Task OnMessageReceivedAsync(
        IDialogContext context,
        IAwaitable<IMessageActivity> result)
    {
        var message = await result;

        var dialogFinished = await HandleMessageAsync(context, message);

        if (DialogForwarded) return;

        if (!dialogFinished)
        {
            context.Wait(OnMessageReceivedAsync);
        }
        else
        {
            context.Done(new DefaultDialogResult());
        }
    }

    protected abstract Task<bool> HandleMessageAsync(IDialogContext context, IMessageActivity message);

    protected async Task ForwardToDialog(IDialogContext context, 
        IMessageActivity message, BaseDialog dialog)
    {
        DialogForwarded = true;
        await context.Forward(dialog, (dialogContext, result) =>
        {
            // Dialog resume callback
            // this method gets called when the child dialog calls context.Done()
            DialogForwarded = false;
            return Task.CompletedTask;
        }, message);
    }
}

The base dialog, parent of all other dialogs, will handle the general flow of the dialog. If the dialog has not yet finished, it will notify the bot framework by calling context.Wait otherwise it will end the dialog with context.Done. It will also force all the child dialogs to implement the method HandleMessageAsync which returns a bool indicating whether the dialog has finished or not. And also exposes a reusable method ForwardToDialog that our IntentDetectorDialog will use to handle intent redirection.

public class IntentDetectorDialog : BaseDialog
{
    private readonly INlpService _nlpService;

    public IntentDetectorDialog(INlpService nlpService)
    {
        _nlpService = nlpService;
    }

    protected override async Task<bool> HandleMessageAsync(IDialogContext context, IMessageActivity message)
    {
        var intentName = await _nlpService.AnalyzeAsync(message.Text);

        switch (intentName)
        {
            case "GoToQnaDialog":
                await ForwardToDialog(context, message, new QnaDialog());
                break;
            case "GoToGraphDialog":
                await ForwardToDialog(context, message, new GraphDialog());
                break;
        }

        return false;
    }
}

That is the IntentRedetectorDialog: son of BaseDialog whose only job is to detect the intent and forward to the corresponding dialog. To make things more scalable you could implement a IntentDialogFactory which can build dialogs based on the detected intent.

public class QnaDialog : BaseDialog
{
    protected override async Task<bool> HandleMessageAsync(IDialogContext context, IMessageActivity message)
    {
        if (message.Text == "My name is Javier")
        {
            await context.PostAsync("What a cool name!");

            // question was answered -> end the dialog
            return true;
        }
        else
        {
            await context.PostAsync("What is your name?");

            // wait for the user response
            return false;
        }
    }
}

And finally we have our QnaDialog: also son of BaseDialog whose only job is to ask for the user's name and wait for the response.

Edit

Based on your comments, in your NlpService you can have:

public class NlpServiceDispatcher : INlpService
{
    public async Task<NlpResult> AnalyzeAsync(string utterance)
    {
        var qnaResult = await _qnaMakerService.AnalyzeAsync(utterance);

        var luisResult = await _luisService.AnalyzeAsync(utterance);

        if (qnaResult.ConfidenceThreshold > luisResult.ConfidenceThreshold)
        {
            return qnaResult;
        }
        else
        {
            return luisResult;
        }
    }
}

Then change the IntentDetectorDialog to:

public class UtteranceAnalyzerDialog : BaseDialog
{
    private readonly INlpService _nlpService;

    public UtteranceAnalyzerDialog(INlpService nlpService)
    {
        _nlpService = nlpService;
    }

    protected override async Task<bool> HandleMessageAsync(IDialogContext context, IMessageActivity message)
    {
        var nlpResult = await _nlpService.AnalyzeAsync(message.Text);

        switch (nlpResult)
        {
            case QnaMakerResult qnaResult:
                await context.PostAsync(qnaResult.Answer);
                return true;

            case LuisResult luisResult:
                var dialog = _dialogFactory.BuildDialogByIntentName(luisResult.IntentName);
                await ForwardToDialog(context, message, dialog);
                break;
        }

        return false;
    }
}

And there you have it! You don't need to repeat utterances in Luis and QnaMaker, you can just use both and set your strategy based on the more confident result!

like image 181
Javier Capello Avatar answered Sep 24 '22 12:09

Javier Capello