timheuer / alexa-skills-dotnet

An Amazon Alexa Skills SDK for .NET
MIT License
547 stars 108 forks source link

How do you do multi turn dialogs? #40

Closed nishantpant closed 5 years ago

nishantpant commented 7 years ago

I am unable to figure out how to do multi turn dialogs. If I have 4 slots defined and the user only passes 2 values, I need to delegate slot collection back to Alexa. The documentation says return a Dialog.Delegate but I am not exactly sure what that means.

Here is what I tried but it doesn't work.

        public SkillResponse FunctionHandler(SkillRequest input, ILambdaContext context)
        {

            var requestType = input.GetRequestType();
            if (requestType == typeof(IntentRequest))
            {
                var intentRequest = input.Request as IntentRequest;

                // The intentRequest variable here is the IntentRequest object sent to the skill.
                if (intentRequest.DialogState == DialogState.Started)
                {
                    // Pre-fill slots: update the intent object with slot values for which
                    // you have defaults, then return Dialog.Delegate with this updated intent
                    // in the updatedIntent property.
                    return ResponseBuilder.DialogDelegate(input.Session);
                }
                else if (intentRequest.DialogState != DialogState.Completed)
                {
                    return ResponseBuilder.DialogDelegate(input.Session);
                    // return a Dialog.Delegate directive with no updatedIntent property.
                }
                else
                {
                    // Dialog is now complete and all required slots should be filled,
                    // so call your normal intent handler. 
                    var location = intentRequest.Intent.Slots["Location"].Value;
                        return MakeSkillResponse(
                            $"Do blah blah in {location}",
                            true);
                }

            }
            else
            {
                return MakeSkillResponse(
                        $"I don't know how to handle this intent. Please say something like Alexa, ask {INVOCATION_NAME} blah blah",
                        true);
            }
        }
stoiveyp commented 7 years ago

Hi @nishantpant - happy to help you work through this. The issue I had when I was working with the dialogs was not sending back the updated intent and thinking the state was being kept by Alexa.

Alexa skills don't keep track of the intent for you, the intent request receives the state that the intent is currently in, allows you to manipulate or fill in the blanks if you can, then you have to send it back as part of the dialog delegate - otherwise you end up with a blank intent again and Alexa starts from scratch; The dialog delegate and elicit slot responses just give you a way of asking for the information based on dialog configuration rather than having to build that speech directly into the skill code.

The Dialog.Delegate it's talking about is a directive in the JSON, in this case generated using the DialogDelegateDirective class in the Alexa.NET code, which is what the ResponseBuilder.DialogDelegate method is doing with the information you send it.

That's a general overview to help with how it's supposed to work, but when you say it doesn't work - is it that it's not filling in the slots properly (a sign of needing an updated intent as described) or are you getting a specific error back?

nishantpant commented 7 years ago

First of all. I totally appreciate your reply. Thanks a lot. Also, I am aware that session management needs to be done by your code.

To answer your question. My code which tries to delegate slot collection to Alexa is this :

    public SkillResponse FunctionHandler(SkillRequest input,

ILambdaContext context) {

        var requestType = input.GetRequestType();
        if (requestType == typeof(IntentRequest))
        {
            var intentRequest = input.Request as IntentRequest;

            // The intentRequest variable here is the IntentRequest

object sent to the skill. if (intentRequest.DialogState == DialogState.Started) { // Pre-fill slots: update the intent object with slot values for which // you have defaults, then return Dialog.Delegate with this updated intent // in the updatedIntent property. return ResponseBuilder.DialogDelegate(input.Session); } else if (intentRequest.DialogState != DialogState.Completed) { return ResponseBuilder.DialogDelegate(input.Session); // return a Dialog.Delegate directive with no updatedIntent property. } else { // Dialog is now complete and all required slots should be filled, // so call your normal intent handler. var location = intentRequest.Intent.Slots["Location"].Value; return MakeSkillResponse( $"You would like to find a house in the area {location}", true); }

        }
        else
        {
            return MakeSkillResponse(
                    $"I don't know how to handle this intent. Please

say something like Alexa, ask {INVOCATION_NAME} find a home in Dallas.", true); } }

And the response if I test the Lambda is. It is weird. Because the directives are empty. Plus I don't get where the template is coming from. When I test this using Alexa she just blows up saying I got an unexpected response.

{ "version": "1.0", "response": { "speechletResponse": { "directives": [ {}, { "template": { "backButtonBehavior": "VISIBLE", "title": { "text": "House Hunter" } } } ], "shouldEndSession": false } }, "sessionAttributes": {} }

On Sun, Jul 16, 2017 at 9:02 AM, Steven Pears notifications@github.com wrote:

Hi @nishantpant https://github.com/nishantpant - happy to help you work through this. The issue I had when I was working with the dialogs was not sending back the updated intent and thinking the state was being kept by Alexa.

Alexa skills don't keep track of the intent for you, the intent request receives the state that the intent is currently in, allows you to manipulate or fill in the blanks if you can, then you have to send it back as part of the dialog delegate - otherwise you end up with a blank intent again and Alexa starts from scratch; The dialog delegate and elicit slot responses just give you a way of asking for the information based on dialog configuration rather than having to build that speech directly into the skill code.

The Dialog.Delegate it's talking about is a directive in the JSON, in this case generated using the DialogDelegateDirective class in the Alexa.NET code, which is what the ResponseBuilder.DialogDelegate method is doing with the information you send it.

That's a general overview to help with how it's supposed to work, but when you say it doesn't work - is it that it's not filling in the slots properly (a sign of needing an updated intent as described) or are you getting a specific error back?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/timheuer/alexa-skills-dotnet/issues/40#issuecomment-315611082, or mute the thread https://github.com/notifications/unsubscribe-auth/ABhal2CpMloYTt9VX9tcPS9ne_1aJUW_ks5sOhe7gaJpZM4OZS0X .

stoiveyp commented 7 years ago

@nishantpant - I'm pretty sure that's Java code. We don't currently support the display templates (although there's a PR for it it's not in the NuGet package) and speechletResponse is a class from the Alexa Java library.

Are the config settings definitely pointing to a .NET Lambda?

nishantpant commented 7 years ago

Yes it is set to C#.

[image: Inline image 1]

On Sun, Jul 16, 2017 at 11:30 AM, Steven Pears notifications@github.com wrote:

@nishantpant https://github.com/nishantpant - I'm pretty sure that's Java code. We don't currently support the display templates (although there's a PR for it it's not in the NuGet package) and speechletResponse is a class from the Alexa Java library.

Are the config settings definitely pointing to a .NET Lambda?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/timheuer/alexa-skills-dotnet/issues/40#issuecomment-315620410, or mute the thread https://github.com/notifications/unsubscribe-auth/ABhal4Eo2jtb5UzUXam0DmjHDCi70fywks5sOjqLgaJpZM4OZS0X .

nishantpant commented 7 years ago

I made a tweak ... and I am getting this now if I test lambda. Is the correct response for multi turn dialogs? Still not working through Alexa but I guess that is beyond the scope of the component you have written.

-Thanks Nishant

{ "version" : "1.0", "sessionAttributes" : { }, "response" : { "shouldEndSession" : false, "directives" : [ { "type" : "Dialog.Delegate" } ] } }

On Sun, Jul 16, 2017 at 11:39 AM, Nishant Pant ashu.pant@gmail.com wrote:

Yes it is set to C#.

[image: Inline image 1]

On Sun, Jul 16, 2017 at 11:30 AM, Steven Pears notifications@github.com wrote:

@nishantpant https://github.com/nishantpant - I'm pretty sure that's Java code. We don't currently support the display templates (although there's a PR for it it's not in the NuGet package) and speechletResponse is a class from the Alexa Java library.

Are the config settings definitely pointing to a .NET Lambda?

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/timheuer/alexa-skills-dotnet/issues/40#issuecomment-315620410, or mute the thread https://github.com/notifications/unsubscribe-auth/ABhal4Eo2jtb5UzUXam0DmjHDCi70fywks5sOjqLgaJpZM4OZS0X .

stoiveyp commented 7 years ago

That's much more the response I'd expect from a call to DialogDelegate. Happy to keep helping in case there's an improvement we can make. What's the issue you're getting when you return that response?

acidburn73 commented 7 years ago

Hello @nishantpant !! About that TWEAK you mentioned in your final comment. Could you please elaborate some more on that? I'm facing the same problem as you were, my directives are returning empty in the response, and i'm getting the template thingy!

Would appreciate some help.

ruben-lucrasoft commented 6 years ago

I'm also interested in a solution for this problem!

edit: According to the docs: https://developer.amazon.com/docs/custom-skills/dialog-interface-reference.html#delegate The directive should have the Dialog.Directive type. I am not seeing this in the response.


{
  "version": "1.0",
  "response": {
    "speechletResponse": {
      "directives": [
        {
          MISSING TYPE RIGHT HERE
          "updatedIntent": {
            "name": "ExtendRoomReservationIntent",
            "slots": {
              "ExtensionTime": {
                "name": "ExtensionTime"
              },
              "Room": {
                "name": "Room",
                "value": "ambassador"
              }
            }
          }
        },
        {
          "template": {
            "title": {
              "richText": "<big>Lucrasoft Assistant</big>"
            },
            "backButtonBehavior": "VISIBLE"
          }
        }
      ],
      "shouldEndSession": false
    }
  },
  "sessionAttributes": {}
}
Noctivagan commented 6 years ago

Yes, I am also struggling with creating a dialog (empty directives). Could someone please post a working example?

Thank you.

Noctivagan commented 6 years ago

Ok, I'm answering my own request. The tweak was the updatedIntent in the DialogState.Started. Also, not this works on device but not in the Web interface:

`// The intentRequest variable here is the IntentRequest object sent to the skill. if (intentRequest.DialogState == DialogState.Started) { // Pre-fill slots: update the intent object with slot values for which // you have defaults, then return Dialog.Delegate with this updated intent // in the updatedIntent property. return ResponseBuilder.DialogDelegate(input.Session, intentRequest.Intent);

} else if (intentRequest.DialogState != DialogState.Completed) { return ResponseBuilder.DialogDelegate(input.Session); // return a Dialog.Delegate directive with no updatedIntent property. } else { // Dialog is now complete and all required slots should be filled, // so call your normal intent handler. // check the variables if (intentRequest.Intent.Slots["DAY"].Value == null) { // error }

return MakeSkillResponse(blah blah blah);

} `

RJB888 commented 6 years ago

I am also struggling with this. Does anyone know if the Dialog.Delegate is supported in Python? I can't seem to figure out where to put it. I keep getting a "Dialog" is not defined error.

celoaiza commented 6 years ago

You will need to create a zip file including index.js, node modules with latest alexa sdk. You can get it from here... https://www.npmjs.com/package/alexa-sdk

Then upload the zip to the lambda... and test again, it will work...

ElvenMonky commented 6 years ago

@nishantpant @ruben-lucrasoft It really looks like responses you've posted are produced by another library. "speechletResponse" is not even a valid field inside Response object

sanjeevg77 commented 6 years ago

@nishantpant I am also facing this issue that Dialog.Delegate is not appearing in the response, were you able to solve this issue.

stoiveyp commented 6 years ago

@sanjeevg77 do you have a C# example we can help with?

gregarican commented 6 years ago

I am running into a perhaps similar issue. I have a two-slot intent, where the user is prompted first for a repair job number, then they are prompted for their phone number. The job number is defined as Slot 1 and the phone number is defined as Slot 2 in the Alexa skill. After the user fills the second slot, then a FallbackIntent is returned back. I'll paste the Function.cs source code below. Not sure what's going on, as I had the same problem when using a Python function.

`using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Alexa.NET; using Alexa.NET.Request; using Alexa.NET.Response; using Alexa.NET.Request.Type; using Alexa.NET.Response.Directive; using Newtonsoft.Json; using Amazon.Lambda.Core; using Amazon.Lambda.Serialization;

// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class. [assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]

namespace AWSLambda1 { public class Function { private SkillResponse response = null;

    /// <summary>
    /// The Alexa/Lambda entry point function.
    /// </summary>
    /// <param name="input"></param>
    /// <param name="context"></param>
    /// <returns>An Alexa SkillResponse.</returns>
    public SkillResponse FunctionHandler(SkillRequest input, ILambdaContext context)
    {
        SkillResponse response = new SkillResponse();
        response.Response = new ResponseBody();
        response.Response.ShouldEndSession = false;
        IOutputSpeech innerResponse = null;
        var log = context.Logger;
        log.LogLine($"Skill Request Object:");
        log.LogLine(JsonConvert.SerializeObject(input));

        if (input.GetRequestType() == typeof(LaunchRequest))
        {
            log.LogLine($"Default LaunchRequest made: 'Alexa, diamond cellar repair lookup.");
            innerResponse = new PlainTextOutputSpeech();
            (innerResponse as PlainTextOutputSpeech).Text = "Welcome to Diamond Cellar's repair tracker. Please say Check my repair job for detailed information.";
            response.Response.OutputSpeech = innerResponse;
            response.Version = "1.0";
            log.LogLine($"Skill Response Object...");
            log.LogLine(JsonConvert.SerializeObject(response));
            return response;
        }
        else if (input.GetRequestType() == typeof(IntentRequest))
        {
            var intentRequest = (IntentRequest)input.Request;

            switch (intentRequest.Intent.Name)
            {
                case "AMAZON.CancelIntent":
                    log.LogLine($"AMAZON.CancelIntent: send StopMessage");
                    innerResponse = new PlainTextOutputSpeech();
                    (innerResponse as PlainTextOutputSpeech).Text = "The action has been cancelled.";
                    response.Response.ShouldEndSession = true;
                    break;
                case "AMAZON.StopIntent":
                    log.LogLine($"AMAZON.StopIntent: send StopMessage");
                    innerResponse = new PlainTextOutputSpeech();
                    (innerResponse as PlainTextOutputSpeech).Text = "The action has been stopped.";
                    response.Response.ShouldEndSession = true;
                    break;
                case "AMAZON.HelpIntent":
                    log.LogLine($"AMAZON.HelpIntent: send HelpMessage");
                    innerResponse = new PlainTextOutputSpeech();
                    (innerResponse as PlainTextOutputSpeech).Text = "This skill allows you to check your Diamond Cellar repair job. Please say Check my repair job for detailed informaiton.";
                    break;
                case "Repair":
                    log.LogLine($"Repair: send RepairPrompts");
                    return RepairHandler(input, context);
                    break;
                default:
                    log.LogLine($"Undefined intent: send HelpMessage");
                    innerResponse = new PlainTextOutputSpeech();
                    (innerResponse as PlainTextOutputSpeech).Text = "This skill allows you to check your Diamond Cellar repair job. Please say Check my repair job for detailed information.";
                    break;
            }
        }
        response.Response.OutputSpeech = innerResponse;
        response.Version = "1.0";
        log.LogLine($"Skill Response Object...");
        log.LogLine(JsonConvert.SerializeObject(response));
        return response;
    }

    public SkillResponse RepairHandler(SkillRequest input, ILambdaContext context)
    {
        var intentRequest = (IntentRequest)input.Request;
        // The intentRequest variable here is the IntentRequest object sent to the skill.
        if (intentRequest.DialogState == DialogState.Started)
        {
            //return RepairLookup("5147835", "6147386393");

            // Pre-fill slots: update the intent object with slot values for which
            // you have defaults, then return Dialog.Delegate with this updated intent
            // in the updatedIntent property.
            return ResponseBuilder.DialogDelegate(input.Session, intentRequest.Intent);                
        }
        else if (intentRequest.DialogState != DialogState.Completed)
        {
            return ResponseBuilder.DialogDelegate(input.Session);
            // return a Dialog.Delegate directive with no updatedIntent property.
        }
        else
        {
            // Dialog is now complete and all required slots should be filled,
            // so call your normal intent handler.
            // check the variables
            if (intentRequest.Intent.Slots["phonenumber"].Value == null)
            {
                SkillResponse response = new SkillResponse();
                response.Response = new ResponseBody();
                response.Response.ShouldEndSession = false;
                IOutputSpeech innerResponse = null;
                var log = context.Logger;
                log.LogLine($"Undefined intent: send HelpMessage");
                innerResponse = new PlainTextOutputSpeech();
                (innerResponse as PlainTextOutputSpeech).Text = "This skill allows you to check your Diamond Cellar repair job. Please say Check my repair job for detailed information.";
                response.Response.OutputSpeech = innerResponse;
                response.Version = "1.0";
                log.LogLine($"Skill Response Object...");
                log.LogLine(JsonConvert.SerializeObject(response));
                return response;
            }
            else
            {
                var jobNumber = intentRequest.Intent.Slots["jobnumber"].Value;
                var phoneNumber = intentRequest.Intent.Slots["phonenumber"].Value;
                return RepairLookup(jobNumber, phoneNumber);
            }
        }
    }

    public SkillResponse RepairLookup(string jobNumber, string phoneNumber)
    {
        var armsHelper = new ArmsHelper();
        var jobInfo = armsHelper.CheckRepairStatus(jobNumber, phoneNumber);
        var jobInfoAry = jobInfo.Result.Split('|');
        var salespersonFirstName = jobInfoAry[0].Split(',')[1];
        var salespersonLastName = jobInfoAry[0].Split(',')[0];
        var repairStatus = jobInfoAry[2];
        var promiseDate = jobInfoAry[1];
        var statusMessage = string.Format("Your salesperson was {0} {1}. The job's promise date is {2}, and currently has a status of {3}.", salespersonFirstName, salespersonLastName, promiseDate, repairStatus);
        //var statusMessage = string.Format("Your salesperson was {0} {1}. The job's promise date is {2}, and currently has a status of {3}.", "Ed", "Filbin", "2018-07-05", "overpromised and incomplete");

        SkillResponse response = new SkillResponse();
        response.Response = new ResponseBody();
        response.Response.ShouldEndSession = false;
        IOutputSpeech innerResponse = null;
        innerResponse = new PlainTextOutputSpeech();
        (innerResponse as PlainTextOutputSpeech).Text = statusMessage;
        response.Response.OutputSpeech = innerResponse;
        response.Version = "1.0";
        return response;
    }
}

} `

gregarican commented 6 years ago

Figured out my problem. The issue was the AMAZON.PhoneNumber data type requires manual typing out of the numeric values rather than accepting digit input in the Alexa Simulator. Works fine now!

stoiveyp commented 6 years ago

Thanks for the update @gregarican, glad you got it sorted

UkeHa commented 5 years ago

@stoiveyp - i tried to get intentRequest.DialogState to run through and get values for a slot that get initially returned as '?'. So I've added Slot Fillings that should ask for the value if not understood correctly. Is that how it is supposed?

This is the reply i get after opening the skill and then ask for a function with both variables in it.

{ "body": { "version": "1.0", "response": { "directives": [ { "type": "Dialog.Delegate", "updatedIntent": { "name": "SelectedDiceAndEyes", "confirmationStatus": "NONE", "slots": { "Eyes": { "name": "Eyes", "value": "20", "confirmationStatus": "NONE" }, "Dice": { "name": "Dice", "value": "?", "confirmationStatus": "NONE" } } } } ], "shouldEndSession": false } } }

jaddie commented 5 years ago

@UkeHa Did you get your issue sorted?

timheuer commented 5 years ago

Closing unless updated info noting an issue with the lib.

pratik-kasbe commented 1 year ago

when user say add bill then add_Intent is triggered then alexa will ask to the user tell me your biller name then user will say "greenville" then request goes to the c# backend and at the backend i will check whether the biller name is valid or not and does biller provide supports to alexa skill.if biller supports alexa skill then from backend i will ask question to the user that tell me the bill type then the user will provide the bill type then i will validate the bill type and depending on the bill type i will provide ask question to user that "tell me your account number or map number" after this user will provide account number or map number based on the bill type then i will check the account number if account number is valid then i will add these details in database

can you please help me in this , i don't know how to implement manual delegation

stoiveyp commented 1 year ago

@pratik-kasbe not sure what part of the interaction you require help with at this stage, would you mind creating a separate issue so we can help you and get into some of the detail?