alexa / alexa-skills-kit-sdk-for-nodejs

The Alexa Skills Kit SDK for Node.js helps you get a skill up and running quickly, letting you focus on skill logic instead of boilerplate code.
Apache License 2.0
3.12k stars 737 forks source link

`addDelegateDirective` always trigger auto delegation logic. #629

Closed max-grigoriev closed 4 years ago

max-grigoriev commented 4 years ago

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[X] Bug report  
[ ] Performance issue
[ ] Feature request
[ ] Documentation issue or request
[ ] Other... Please describe:

I want to trigger dialog behavior from one intent to another one and fill default values for known slots.

Expected Behavior

I have first intent where user can say his name and action type. And then I want to to go to another intent:

        if (action == "listen") {
            console.warn("Listening...")
            responseBuilder.addDelegateDirective({
                name: "GetTasksForPerson",
                confirmationStatus: 'NONE',
                slots: {}
            });
        } else {
            console.warn("Not the right action: ", action);
        }
        return responseBuilder.speak("Welcome back " + currentFamilyMember.name)
            .getResponse();

I disabled auto delegation for GetTasksForPerson intent and later for the whole of my skill.

handle(handlerInput: HandlerInput) {
        const request = getRequest<IntentRequest>(handlerInput.requestEnvelope);
        const sessionService = new SessionService(handlerInput);
        const slots = request.intent.slots;

        console.info(IntentTypes.GetTasksForPerson, "# ", " me dialog: ", request.dialogState);
        console.info(IntentTypes.GetTasksForPerson, "#", "Slots: ", request.intent?.slots);

        if (slots != null && slots.personName.value == null && request.dialogState != "COMPLETED") {
            sessionService.CurrentFamilyMember.map(m => slots.personName.value = m.name);
            return handlerInput.responseBuilder
                .addDelegateDirective(request.intent)
                .speak("In Process")
                .getResponse();
        }

        if (request.dialogState == "COMPLETED") {
            const personName = slots?.personName.value;
            console.log("Person: ", personName)
            const speechText =  "Tasks for " + personName;

            return handlerInput.responseBuilder
                .speak(speechText)
                .getResponse();
        }

        return handlerInput.responseBuilder.addDelegateDirective(request.intent).getResponse();
    }

I expect that GetTasksForPerson intent receive first request without slots and Alexa will not asked for personName slot automatically and I can try to set default values.

Current Behavior

But each time I go GetTasksForPerson from the first intent I'm asked for personName and only after saying it request comes to my GetTasksForPerson intent. I see in logs that there isn't START dialog state and intent receives a request with IN_PROCESS state only after Alexa by itself asks for name and fills the slot.

Possible Solution

Steps to Reproduce (for bugs)

Context

Your Environment

Node.js and NPM Info

ShenChen93 commented 4 years ago

Hi @max-grigoriev

Thanks for using our SDK ! For your issue, I am wondering if you marked the personName as required in dialog model ? If so Alexa will ask users to fill the slot first. Would be great if you could provide your InteractionModel for the GetTasksForPerson for further investigation

Thanks, Shen

max-grigoriev commented 4 years ago

hi @ShenChen-Amazon Yes, this field is required:

"dialog": {
            "intents": [
...
{
                    "name": "GetTasksForPerson",
                    "confirmationRequired": false,
                    "prompts": {},
                    "slots": [
                        {
                            "name": "personName",
                            "type": "AMAZON.FirstName",
                            "confirmationRequired": false,
                            "elicitationRequired": true,
                            "prompts": {
                                "elicitation": "Elicit.Slot.567068266854.260703350568"
                            }
                        }
                    ]
                }
...
"delegationStrategy": "SKILL_RESPONSE"
}

but I thought the dialog isn't enabled without required field. So I can't make addDelegateDirective from one intent to another one.

mikelid-zz commented 4 years ago

Dialog.delegate is specifically passing control the DM service to do something. In this case the updated intent doesn't yet have the personName slot so it asks the user for it, as per the dialog definition. The setting for auto-delegation doesn't apply to this scenario as you are explicitly delegating.

max-grigoriev commented 4 years ago

@mikelid what is the difference between enabled auto delegation and disabled? I thought when I have auto delegation then Alexa asks the user to fill empty slots. And when it's disabled then it's the intent task to fill the slot.

mikelid-zz commented 4 years ago

For the purposes of this discussion, delegation means "control passed to the dialog-management service". And note that the intent doesn't "do anything".. it is merely data. The only active things are your skill code and the DM service.

explicit delegation means "the custom skill code was running and it chose to pass control to the dialog-management service to finish the turn, by means of the Dialog.Delegate directive".

auto delegation means "control is passed to the dialog-management service at the start of any turn involving that intent".

So...

If auto-delegation is turned on the dialog management service gets first option and it asks a question if it wants to (for required slots or to confirm a slot or the intent). The intent is only passed to your skill code once DM has no more questions to ask.

If auto-delegation is turned off the intent will always be based to your skill code. It is up to you to decide if you want to delegate to the DM service to ask a question.

max-grigoriev commented 4 years ago

@mikelid thanks for the clarifications.

if I have and intent with a couple of slots and I can go to this intent from different other intents. And some of them can have some values for some slots. What are the best practices to fill intent's slots and then ask a user to fill remaining empty slots?

ShenChen93 commented 4 years ago

@max-grigoriev ,

I think the best way is to provide slot value while sending the directive. If your Intent B has slot C and D need to be filled, and you send directive to B from Intent A, you could do the following in intentA:

.addDelegateDirective({
    name: 'IntentB',
    confirmationStatus: 'NONE',
    slots: {
          C: 'someValue'
    }
 })

Thus only the slot D is not filled, and alexa will ask user for it if slotD is also marked as required.

max-grigoriev commented 4 years ago

@ShenChen-Amazon that's my initial step. I did

responseBuilder.addDelegateDirective({
                name: 'GetTasksForPerson',
                confirmationStatus: 'NONE',
                slots: {
                    personName: {
                        name: 'personName',
                        confirmationStatus: "NONE",
                        value: 'Maksym',
                    }
                }
            });

but in this case, Alexa doesn't ask for a name but it's just waiting for this name to be said. And then Intent code is invoked with a new name. I see in logs IN_PROGRESS only when user says his name but nothing from the previous Intent:

INFO GetTasksForPerson # me dialog: IN_PROGRESS
INFO GetTasksForPerson # Slots: { personName: { name: 'personName', value: 'newname', confirmationStatus: 'NONE', source: 'USER' } }

I found that if I use delegation from Launch intent to InitMe (my intent)

.addDelegateDirective({
                name: 'InitMe',
                confirmationStatus: 'NONE',
                slots: {
                    personName: {
                        name: 'personName',
                        confirmationStatus: "NONE",
                        value: "Maksym",
                    }
                }
            })

And everything is OK. But then I try to delegate from InitMe to GetTasksForPerson and nothing works.

max-grigoriev commented 4 years ago

I tried to go from Launch to GetTasksForPerson and it doesn't work. When I change the intent name in Launch delegation then everything works. I found that the difference is only that one intent has a single slop but another has two slots. So I removed the second slot and after that this intent stopped working too. And when I add a new slot to GetTasksForPerson then personName begins working.

ShenChen93 commented 4 years ago

@max-grigoriev ,

I created a simple skill and could reproduce your issue. Thus, the issue is: Intent B has only one slot need to be filled, and Intent A send delegate directive to intent B with required slot provided. However, Intent B requestHandler is not invoked at all as Dialog Management service is not asking for required slot(already be filled).

I am not sure if it's a valid use case or it's an expected behavior, thus I will need to reach out to DM team for help. At the same time, could you please log this issue to Alexa forum ? Where you can get help from people who has better context on this issue.

Thanks, Shen

max-grigoriev commented 4 years ago

Done - https://forums.developer.amazon.com/questions/227380/adddelegatedirective-always-trigger-delegation-log.html