AreYouFreeBusy / AlexaSkillsKit.NET

.NET library that simplifies Alexa skills development; same object model as Amazon's AlexaSkillsKit for Java
MIT License
212 stars 99 forks source link

JSON serialization of responses with DialogElicitSlotDirective(s)? #70

Closed BDGLunde closed 6 years ago

BDGLunde commented 6 years ago

Hello, I am playing around with dialog enabled skills, and the response sent by the library is:

{
  "version": "1.0",
  "response": {
    "outputSpeech": {
      "text": "yeeee",
      "type": "PlainText"
    },
    "speechletResponse": {
      "outputSpeech": {
        "text": "yeeee"
      },
      "directives": [
        {
          "slotToElicit": "City",
          "updatedIntent": { ... }
          }
        },
        {
          "template": {
            "title": {
              "richText": "<big>Test0</big>"
            },
            "backButtonBehavior": "VISIBLE"
          }
        }
      ],
      "shouldEndSession": false
    }
  },
  "sessionAttributes": {
    "StreetAddress": "--------------------",
    "intentSequence": "-----------------",
    "ConvoPath": "3",
  }
}

Whereas Amazon's documentation expects something like this:

{
  "version": "1.0",
  "sessionAttributes": {},
  "response": {
    "outputSpeech": {
      "type": "PlainText",
      "text": "From where did you want to start your trip?"
    },
    "shouldEndSession": false,
    "directives": [
      {
        "type": "Dialog.ElicitSlot",
        "slotToElicit": "fromCity",
        "updatedIntent": { ... }
        }
      }
    ]
  }
}

The directives array doesn't seem to be in the "response" object and each directive object doesn't appear to contain the 'Type' parameter which is listed as required.

Am I missing something? What I'm currently doing when the Intent is 'first hit' is to detect if a required slot is missing. If so, then return the following:

var x = new SpeechletResponse(); x.Directives = reprompts; return x;

Where reprompts is a list initialized with the single value: reprompts.Add(new DialogElicitSlotDirective() { SlotToElicit = "City", UpdatedIntent = intent });

ElvenMonky commented 6 years ago

Looking at your response object l can see that you are not telling all the story. First you're also setting outputSpeech. Then you seem to have custom implementation of ISpeechletResponse, that stores SpeechletResponse provided by library. You should not wrap SpeechletResponse into another SpeechletResponse. And you are not using DialogElicitSlotDirective provided by library.

ElvenMonky commented 6 years ago

Actually you're not using library implementations at all. Looks like you are trying to wrap classes provided by another library to use them in this library. Don't do it, unless you are 100% sure you know, what you're doing.

BDGLunde commented 6 years ago

Thanks for your quick response, but I am a bit confused.

Everything I have written so far (with the exception of trying to use the DialogDirectives) has been following the pattern in the sample code for this library.

The 'outputSpeech' json object in my example can be ignored (updated my question to reflect that) - it was there when I was testing whether or not it would affect things.

I'm not using any custom implementations of ISpeechletResponse and I am not using any other .NET Alexa library.

stefann42 commented 6 years ago

@BDGLunde are you saying that this field is missing from the directives serialized by the library? "type": "Dialog.ElicitSlot"

BDGLunde commented 6 years ago

That's what it looks like so far, yes.

I am also a bit curious about the extra "speechletResponse" JSON object that is attached to the response, but it doesn't appear to be hurting the other non-dialog responses I send.

Curious about the directives.template object too.

ElvenMonky commented 6 years ago

Can you provide minimal complete code sample to reproduce the issue?

BDGLunde commented 6 years ago

Sure, let me know if you need even more detail.

Similar to your sample project: Controller -> ISpeechletWithContextAsync.GetResponseAsync() -> Library Stuff -> OnIntentAsync -> Switch logic to determine correct SpeechletResponse helper method to run -> Method that is trying to use the DialogDirective ->

Look to see if required values are available in a slot. If not...

List<Directive> elicitDirectives = new List<Directive>();
elicitDirectives.Add(new DialogElicitSlotDirective() 
{
  SlotToElicit = "string" 
});

return new SpeechletResponse() 
{ 
  Directives = elicitDirectives, 
  shouldEndSession = false 
}

For the complete "conversation" to really happen, I know more needs to be done (specifically to handle the subsequent response from the ElicitDirective) but I haven't even been able to get this first part to work yet.

Update: Just tried changing the List to be explicitly of type DialogElicitSlotDirective to see if that may coax the serializer differently, but no luck.

BDGLunde commented 6 years ago

Potentially interesting... here is the JSON I get back after calling ToJson manually on

                        var x = new SpeechletResponse();
                        x.Card = new SimpleCard() { Content = "ee", Title = "ee" };
                        x.OutputSpeech = new PlainTextOutputSpeech() { Text = "yeeee" };
                        x.Directives = reprompts;
                        string thingy = new SpeechletResponseEnvelope()
                        {
                            Response = x
                        }.ToJson();
"{
    "response":{
        "card":{
            "type":"Simple",
            "title":"ee",
            "content":"ee"
        },
        "directives":[
            {
                "slotToElicit":"City",
                "type":"Dialog.ElicitSlot"
            },
            {
                "slotToElicit": "State",
                "type":"Dialog.ElicitSlot"
            }
        ],
        "outputSpeech":{
            "type":"PlainText",
            "text":"yeeee"
        },
        "shouldEndSession":false
    }
}"
stefann42 commented 6 years ago

This JSON serialization is according to spec so if anything it proves the library is working correctly. There are too many missing pieces in your description of the issue for us to be able to reliably reproduce. Without a repro we can't move forward with this investigation.

ElvenMonky commented 6 years ago

What is the exact name of NuGet package you are using? There are still no any helper methods for SpeechletResponse in this library. Can you share the code of the method, that produce unexpected output? Have you tried to debug your skill in IDE? You can write unit tests for your helper methods?

BDGLunde commented 6 years ago

I'm using AlexaSkillsKit.NET version 1.6.0. All dependencies are at the minimum version listed for your package, except for NewtonSoft which is at 10.0.3 (though I've tried with the minimum listed - 7.0.1 and still have the same problem).

Sorry, my usage of the phrase "helper method" may have been ambiguous.

My speechlet (following the pattern in your sample project) called from the main Controller class simply calls other methods (defined elsewhere) that return a SpeechletResponse - similar to the SetNameInSessionAndSayHello and GetNameFromSessionAndSayHello "helper" methods you have in the sample project.

Basically, this is what is inside my OnIntentAsync method:

                if (intentName.Equals("HelloWorldIntent"))
                {
                    return await AlexaHandlers.SayHello(intent, session, context);
                }
                else if (intentName.Equals("DeviceFlavorIntent"))
                {
                    return await AlexaHandlers.DeviceFlavorHandler(intent, session, context);
                }
                else if (intentName.Equals("FlavorDayIntent"))
                {
                    return await AlexaHandlers.FlavorDayHandler(intent, session, context);
                }
                else
                {
                    throw new SpeechletException("Invalid Intent");
                }

AlexaHandlers is just a static class that contains these "helpers/handlers" I've written that return a SpeechletResponse.

AlexaHandlers.FlavorDayHandler is the one I'm specifically having issues with since I'm trying to use the DialogElicitSlotDirective. Everything else is working great so far. The FlavorDayHandler's intent has two slots that are required - if either of them are missing I'm simply returning:

List<Directive> elicitDirectives = new List<Directive>();
elicitDirectives.Add(new DialogElicitSlotDirective() 
{
  SlotToElicit = "string" 
});

return new SpeechletResponse() 
{ 
  Directives = elicitDirectives, 
  shouldEndSession = false 
}

From your sample project, the BuildSpeechletResponse method returns also returns Directives (not a DialogDirective, however) but they don't seem to have issues.

Do you have a working sample where a DialogDirective is used and working? That would be super helpful/appreciated.

ElvenMonky commented 6 years ago

Probably, I will prepare sample project for dialog interface usage, but it will take some time. Can you prepare sample project reproducing the issue for us? Currently I don't understand where ISpeechletResponse implementation with SpeechletResponse field comes from.

ElvenMonky commented 6 years ago

I've prepared new sample solution, that shows Dialog Interface usage. Sample also goes with some functional tests.