2
votes

I am writing an Alexa dialog with intent confirmation. When confirmation is denied I want to restart the same dialog again by delegating to this very dialog. I am proceeding like described in this stack overflow question. As described in the solution to this question I do the delegation when the dialogState is still IN_PROGRESS. In my case Alexa always responds with the not very meaningful message There was a problem with the requested skill's response. No error message in the application log.

My skill model and the lambda code are as follows:

{
  "interactionModel": {
    "languageModel": {
      "invocationName": "hello",
      "intents": [
        {
          "name": "UserIntent",
          "slots": [
            {
              "name": "UserName",
              "type": "AMAZON.FirstName",
              "samples": [
                "My name is {UserName}",
                "I am {UserName}",
                "{UserName}"
              ]
            }
          ],
          "samples": [
            "My name is {UserName}",
            "I am {UserName}"
          ]
        }
      ],
      "types": []
    },
    "dialog": {
      "delegationStrategy": "SKILL_RESPONSE",      
      "intents": [
        {
          "name": "UserIntent",
          "confirmationRequired": true,
          "prompts": {
            "confirmation": "Confirm.Intent.UserName"
          },
          "slots": [
            {
              "name": "UserName",
              "type": "AMAZON.FirstName",
              "confirmationRequired": false,
              "elicitationRequired": true,
              "prompts": {
                "elicitation": "Elicit.Slot.UserName"
              }
            }
          ]
        }
      ]
    },
    "prompts": [
      {
        "id": "Elicit.Slot.UserName",
        "variations": [
          {
            "type": "PlainText",
            "value": "What is your name?"
          }
        ]
      },
      {
        "id": "Confirm.Intent.UserName",
        "variations": [
          {
            "type": "PlainText",
            "value": "You are {UserName}. Is this right?"
          }
        ]
      }
    ]
  }
}
const DeniedUserIntentHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest' &&
      request.intent.name === 'UserIntent' &&
      request.dialogState === 'IN_PROGRESS' &&
      request.intent.confirmationStatus === 'DENIED';
  },

  async handle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    const currentIntent = request.intent;
    const userName = Alexa.getSlotValue(handlerInput.requestEnvelope, 'UserName');

    console.log(`DeniedUserIntentHandler: 
    request.dialogState=${request.dialogState}, request.intent.confirmationStatus=${request.intent.confirmationStatus}, userName=${userName}`);

    return handlerInput.responseBuilder
      .speak('Username was not confirmed. Please try again.')
      .addDelegateDirective({
        name: 'UserIntent',
        confirmationStatus: 'NONE',
        slots: {}
      })
      .getResponse();
  }
};

What I am missing?

3

3 Answers

1
votes

You did not specified whether you DeniedUserIntentHandler get triggered or not. if the error is generated inside DeniedUserIntentHandler then it is due to the wrong format of Delegate Directive.

your return response should be like :

return handlerInput.responseBuilder
  .speak('Username was not confirmed. Please try again.')
  .addDelegateDirective({
                         "type": "Dialog.Delegate",
                         "updatedIntent": {
                               name:"UserIntent",
                               confirmationStatus:"NONE",
                               slots: {
                                 UserName: {
                                 name:"UserName",
                                 confirmationStatus:"NONE",
                                 source:"USER"
                                 }
                               }
                            }
                         })
                         .getResponse();

The reason you are deleting the intent's previous state because you want your intent action to start from beginning.

You can also use your code like this: reference https://forums.developer.amazon.com/questions/92334/answering-no-to-intent-confirmation.html?childToView=206243#comment-206243

currentIntent.confirmationStatus = "NONE";
Object.keys(currentIntent.slots).forEach(
    (slotName) => {
      var slot = intent.slots[slotName];
      delete slot.value;
      slot.confirmationStatus = "NONE";
    }
);

var delegatedirective = {"type": "Dialog.Delegate",
                         "updatedIntent": currentIntent};
1
votes

The problem you are facing now is real one. This issue is also mentioned in amazon forum. However you can achieve similar behavior with slight modification. Activate slot value confirmation for UserName and remove confirmation for UserIntent. Your interaction model would be similar like below:

{
"interactionModel": {
    "languageModel": {
        "invocationName": "demo app",
        "intents": [
            {
                "name": "UserIntent",
                "slots": [
                    {
                        "name": "UserName",
                        "type": "AMAZON.FirstName",
                        "samples": [
                            "My name is {UserName}",
                            "I am {UserName}",
                            "{UserName}"
                        ]
                    }
                ],
                "samples": [
                    "My name is {UserName}",
                    "I am {UserName}"
                ]
            },
            {
                "name": "AMAZON.NavigateHomeIntent",
                "samples": []
            }
        ],
        "types": []
    },
    "dialog": {
        "intents": [
            {
                "name": "UserIntent",
                "delegationStrategy": "SKILL_RESPONSE",
                "confirmationRequired": false,
                "prompts": {},
                "slots": [
                    {
                        "name": "UserName",
                        "type": "AMAZON.FirstName",
                        "confirmationRequired": true,
                        "elicitationRequired": true,
                        "prompts": {
                            "confirmation": "Confirm.Slot.247378890994.1277345498514",
                            "elicitation": "Elicit.Slot.UserName"
                        }
                    }
                ]
            }
        ],
        "delegationStrategy": "ALWAYS"
    },
    "prompts": [
        {
            "id": "Elicit.Slot.UserName",
            "variations": [
                {
                    "type": "PlainText",
                    "value": "What is your name?"
                }
            ]
        },
        {
            "id": "Confirm.Slot.247378890994.1277345498514",
            "variations": [
                {
                    "type": "PlainText",
                    "value": "your name is {UserName} , right ?"
                }
            ]
        }
    ]
  }
}

you can add this single code handler:

const UserIntenStartedHandler = {
  canHandle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    return request.type === 'IntentRequest' &&
    request.intent.name === 'UserIntent';
  },
  async handle(handlerInput) {
    const request = handlerInput.requestEnvelope.request;
    const currentIntent = Alexa.getIntentName(handlerInput.requestEnvelope);
    const slot = Alexa.getSlot(handlerInput.requestEnvelope, 'UserName');

    if (slot.confirmationStatus !== 'CONFIRMED') {
      return handlerInput.responseBuilder
     .addDelegateDirective(request.intent) 
     .getResponse();
    } else {
      return handlerInput.responseBuilder
     .speak('your Final name is ' + slot.value + '. cheers')
     .withShouldEndSession(true)
     .getResponse();
    }
  }  
};
0
votes

Thanks to the reply of @tahiat I was able to figure out my original problem. In the updated intent the slots object must contain the intent's slots (without a value). But his first code snippet contains an error. Ether use

.addDirective({
  "type": "Dialog.Delegate", 
  "updatedIntent": { 
     name:"UserIntent", 
     ...
  }
})

or use

.addDelegateDirective({
  name:"UserIntent", 
  ...
})

since addDelegateDirective expects an intent as a parameter.

But now I am facing another problem. I use confirmation in my dialog. When I go back to the initial state of UserIntent after confirmation was denied I never get prompted with the confirmation message. This is because request.intent.confirmationStatus keeps its value, which is 'DENIED', although I have reset it in updateIntent to 'NONE'.