0

The [LuisIntent("")] attribute uses the ILuisService to query the LUIS based on LUISModel attributes' value, then finds out the top scoring intent and matches the IntentHandler Delegate in the overridden MessageRecievedAsync method and loads the LUISResult dynamically in the following method while debugging a class which inherits from LuisDialog.

public async Task None(IDialogContext context, LuisResult result)
{
  //some stuff
}

My question is how to make a custom attribute which maps it to the correct intent handler.

I am trying to use RASA NLU Service as my NLP engine along with Microsoft Bot Framework in C#.

I am trying to map LUISEmulatedResult which I get from HttpClient.GetAsync() method which queries RASA NLU to get LUISResult type JSON to a method which follows this delegate.

Progress I have done:

The [RASAIntentAttribute("IntentName")]

using Microsoft.Bot.Builder.Dialogs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;

namespace DemoFlightRASA.Models
{
    public class RASAIntentAttribute : Attribute
    {
        private readonly string _intentName;

        public RASAIntentAttribute(string IntentName)
        {
            this._intentName = IntentName;
        }

        //The intent handler delegate
        //Now I want to map LUISEmulatedResult which I get from HttpClient.GetAsync() method to a method which follows this delegate
        public delegate Task RASAIntentHandler(IDialogContext context, LUISEmulatedResult result);


    }
}

The LUISEmulatedResult model class:

namespace DemoFlightRASA.Models
{
    public class LUISEmulatedResult
    {
        public string query { get; set; }
        public Topscoringintent topScoringIntent { get; set; }
        public Intent[] intents { get; set; }
        public Entity[] entities { get; set; }
    }

    public class Topscoringintent
    {
        public string intent { get; set; }
        public float score { get; set; }
    }

    public class Intent
    {
        public string intent { get; set; }
        public float score { get; set; }
    }

    public class Entity
    {
        public string entity { get; set; }
        public string type { get; set; }
        public int startIndex { get; set; }
        public int endIndex { get; set; }
        public Resolution resolution { get; set; }
        public float score { get; set; }
    }

    public class Resolution
    {
        public string[] values { get; set; }
    }

}

Also I tried this one, but this doesn't seem to work

I want to create a end-to-end flow which uses RASA NLU opposed to LUIS in bot framework. I have the RASA NLU endpoint ready, just not able to make the RASAIntentAttribute.

Any pointers, tips, tutorials, code snippets as to how to map the delegate to the method will be greatly appreciated. Thank you.

Kunal Mukherjee
  • 5,775
  • 3
  • 25
  • 53
  • 1
    Have you checked the code for the LuisIntent and how it works? – Ezequiel Jadib Nov 09 '17 at 20:16
  • 1
    The code for the LuisDialog can be found here: https://github.com/Microsoft/BotBuilder/blob/b6cd3ff85e8dc57ac586a11db489a0c75c635ae2/CSharp/Library/Microsoft.Bot.Builder/Dialogs/LuisDialog.cs#L141 – Eric Dahlvang Nov 09 '17 at 23:59
  • Yes, I saw how it works, but I am stuck at some point. Like how to map the delegate RASAIntentHandler to the LUISResult. I am confused about how it is getting mapped. Any pointers would be helpful – Kunal Mukherjee Nov 10 '17 at 05:30

1 Answers1

1

Note: I don't think you can have a delegate as an attribute parameter: Is it possible to have a delegate as attribute parameter? How are you planning to use the RASAIntentHandler delegate?

The .net Bot Framework sdk uses reflection to determine which methods in the LuisDialog are intent handlers: https://github.com/Microsoft/BotBuilder/blob/b6cd3ff85e8dc57ac586a11db489a0c75c635ae2/CSharp/Library/Microsoft.Bot.Builder/Dialogs/LuisDialog.cs#L362

public static IEnumerable<KeyValuePair<string, IntentActivityHandler>> EnumerateHandlers(object dialog)
        {
            var type = dialog.GetType();
            var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
            foreach (var method in methods)
            {
                var intents = method.GetCustomAttributes<LuisIntentAttribute>(inherit: true).ToArray();
                IntentActivityHandler intentHandler = null;

                try
                {
                    intentHandler = (IntentActivityHandler)Delegate.CreateDelegate(typeof(IntentActivityHandler), dialog, method, throwOnBindFailure: false);
                }
                catch (ArgumentException)
                {
                    // "Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type."
                    // https://github.com/Microsoft/BotBuilder/issues/634
                    // https://github.com/Microsoft/BotBuilder/issues/435
                }

                // fall back for compatibility
                if (intentHandler == null)
                {
                    try
                    {
                        var handler = (IntentHandler)Delegate.CreateDelegate(typeof(IntentHandler), dialog, method, throwOnBindFailure: false);

                        if (handler != null)
                        {
                            // thunk from new to old delegate type
                            intentHandler = (context, message, result) => handler(context, result);
                        }
                    }
                    catch (ArgumentException)
                    {
                        // "Cannot bind to the target method because its signature or security transparency is not compatible with that of the delegate type."
                        // https://github.com/Microsoft/BotBuilder/issues/634
                        // https://github.com/Microsoft/BotBuilder/issues/435
                    }
                }

                if (intentHandler != null)
                {
                    var intentNames = intents.Select(i => i.IntentName).DefaultIfEmpty(method.Name);

                    foreach (var intentName in intentNames)
                    {
                        var key = string.IsNullOrWhiteSpace(intentName) ? string.Empty : intentName;
                        yield return new KeyValuePair<string, IntentActivityHandler>(intentName, intentHandler);
                    }
                }
                else
                {
                    if (intents.Length > 0)
                    {
                        throw new InvalidIntentHandlerException(string.Join(";", intents.Select(i => i.IntentName)), method);
                    }
                }
            }
        }

Here is the code in LuisDialog that calls the handler that corresponds to the best intent in the IntentRecommendation returned from LUISs: https://github.com/Microsoft/BotBuilder/blob/b6cd3ff85e8dc57ac586a11db489a0c75c635ae2/CSharp/Library/Microsoft.Bot.Builder/Dialogs/LuisDialog.cs#L252

protected virtual async Task DispatchToIntentHandler(IDialogContext context,
                                                            IAwaitable<IMessageActivity> item,
                                                            IntentRecommendation bestIntent,
                                                            LuisResult result)
        {
            if (this.handlerByIntent == null)
            {
                this.handlerByIntent = new Dictionary<string, IntentActivityHandler>(GetHandlersByIntent());
            }

            IntentActivityHandler handler = null;
            if (result == null || !this.handlerByIntent.TryGetValue(bestIntent.Intent, out handler))
            {
                handler = this.handlerByIntent[string.Empty];
            }

            if (handler != null)
            {
                await handler(context, item, result);
            }
            else
            {
                var text = $"No default intent handler found.";
                throw new Exception(text);
            }
        }

This is how LUIS results are mapped to methods marked with the LUISIntentAttribute on LUISDialogs. This functionality, or something similar, will need to be in the RASA implementation.

Eric Dahlvang
  • 8,252
  • 4
  • 29
  • 50