Call LUIS from FormFlow in C#

2019-03-22 06:02发布

问题:

I am using the Microsoft bot framework to create a bot for questioning the user and then understanding the answer. The user is questioned using the FormFlow API in bot framework and answers are retrieved. Here is the code for the formflow:

public enum Genders { none, Male, Female, Other};

[Serializable]
public class RegisterPatientForm
{

    [Prompt("What is the patient`s name?")]
    public string person_name;

    [Prompt("What is the patients gender? {||}")]
    public Genders gender;

    [Prompt("What is the patients phone number?")]
    [Pattern(@"(<Undefined control sequence>\d)?\s*\d{3}(-|\s*)\d{4}")]
    public string phone_number;

    [Prompt("What is the patients Date of birth?")]
    public DateTime DOB;

    [Prompt("What is the patients CNIC number?")]
    public string cnic;


    public static IForm<RegisterPatientForm> BuildForm()
    {
        OnCompletionAsyncDelegate<RegisterPatientForm> processHotelsSearch = async (context, state) =>
        {
            await context.PostAsync($"Patient {state.person_name} registered");
        };

        return new FormBuilder<RegisterPatientForm>()
            .Field(nameof(person_name),
            validate: async (state, value) =>
            {
                //code here for calling luis
            })
            .Field(nameof(gender))
            .Field(nameof(phone_number))
            .Field(nameof(DOB))
            .Field(nameof(cnic))
            .OnCompletion(processHotelsSearch)
            .Build();
    }

}

The user may enter when asked for name :

my name is James Bond

Also the name could be of variable length. I would be better to call luis from here and get the entity(name) for the intent. I am currently not aware on how could i call a luis dialog from the formflow.

回答1:

You can use the API method of LUIS, instead of the dialog method.

Your code would be - RegisterPatientForm Class :

public enum Genders { none, Male, Female, Other };

[Serializable]
public class RegisterPatientForm
{

    [Prompt("What is the patient`s name?")]
    public string person_name;

    [Prompt("What is the patients gender? {||}")]
    public Genders gender;

    [Prompt("What is the patients phone number?")]
    [Pattern(@"(<Undefined control sequence>\d)?\s*\d{3}(-|\s*)\d{4}")]
    public string phone_number;

    [Prompt("What is the patients Date of birth?")]
    public DateTime DOB;

    [Prompt("What is the patients CNIC number?")]
    public string cnic;


    public static IForm<RegisterPatientForm> BuildForm()
    {
        OnCompletionAsyncDelegate<RegisterPatientForm> processHotelsSearch = async (context, state) =>
        {
            await context.PostAsync($"Patient {state.person_name} registered");
        };

        return new FormBuilder<RegisterPatientForm>()
            .Field(nameof(person_name),
            validate: async (state, response) =>
            {
                var result = new ValidateResult { IsValid = true, Value = response };

                //Query LUIS and get the response
                LUISOutput LuisOutput = await GetIntentAndEntitiesFromLUIS((string)response);

                //Now you have the intents and entities in LuisOutput object
                //See if your entity is present in the intent and then retrieve the value
                if (Array.Find(LuisOutput.intents, intent => intent.Intent == "GetName") != null)
                {
                    LUISEntity LuisEntity = Array.Find(LuisOutput.entities, element => element.Type == "name");

                    if (LuisEntity != null)
                    {
                        //Store the found response in resut
                        result.Value = LuisEntity.Entity;
                    }
                    else
                    {
                        //Name not found in the response
                        result.IsValid = false;
                    }
                }
                else
                {
                    //Intent not found
                    result.IsValid = false;
                }
                return result;
            })
            .Field(nameof(gender))
            .Field(nameof(phone_number))
            .Field(nameof(DOB))
            .Field(nameof(cnic))
            .OnCompletion(processHotelsSearch)
            .Build();
    }

    public static async Task<LUISOutput> GetIntentAndEntitiesFromLUIS(string Query)
    {
        Query = Uri.EscapeDataString(Query);
        LUISOutput luisData = new LUISOutput();
        try
        {
            using (HttpClient client = new HttpClient())
            {
                string RequestURI = WebConfigurationManager.AppSettings["LuisModelEndpoint"] + Query;
                HttpResponseMessage msg = await client.GetAsync(RequestURI);
                if (msg.IsSuccessStatusCode)
                {
                    var JsonDataResponse = await msg.Content.ReadAsStringAsync();
                    luisData = JsonConvert.DeserializeObject<LUISOutput>(JsonDataResponse);
                }
            }
        }
        catch (Exception ex)
        {

        }
        return luisData;
    }
}

Here GetIntentAndEntitiesFromLUIS method does the querying to LUIS using the endpoint exposed by your Luis app. Add the endpoint to your Web.config with key LuisModelEndpoint

Find your luis endpoint by going to Publish tab in your luis app

Your web.config would look like this

<appSettings>
  <!-- update these with your BotId, Microsoft App Id and your Microsoft App Password-->
  <add key="BotId" value="YourBotId" />
  <add key="MicrosoftAppId" value="" />
  <add key="MicrosoftAppPassword" value="" />
  <add key="LuisModelEndpoint" value="https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/YOUR_MODEL_ID?subscription-key=YOUR_SUBSCRIPTION_KEY&amp;verbose=true&amp;timezoneOffset=0&amp;q="/>
</appSettings>

I have created a LUISOutput class to deserialize the response:

public class LUISOutput
{
    public string query { get; set; }
    public LUISIntent[] intents { get; set; }
    public LUISEntity[] entities { get; set; }
}
public class LUISEntity
{
    public string Entity { get; set; }
    public string Type { get; set; }
    public string StartIndex { get; set; }
    public string EndIndex { get; set; }
    public float Score { get; set; }
}
public class LUISIntent
{
    public string Intent { get; set; }
    public float Score { get; set; }
}

Emulator Response



回答2:

In this situation, it may be best to call a luis intent outside of formflow. The functionality you are looking for does not exactly exist. You could call a "Name" intent then call you form like this:

[LuisIntent("Name")]
public async Task Name(IDialogContext context, LuisResult result)
{
    //save result in variable
    var name = result.query
    //call your form
    var pizzaForm = new FormDialog<PizzaOrder>(new PizzaOrder(), this.MakePizzaForm, FormOptions.PromptInStart, entities);
    context.Call<PizzaOrder>(pizzaForm, PizzaFormComplete);
    context.Wait(this.MessageReceived);
}