Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DialogFlow V2 Webhook - Expects Speech responses Immediately and not after async requests

I have a DialogFlow V2 node.js webhook.

I have an intent that is called with a webhook action:

const { WebhookClient } = require('dialogflow-fulfillment');
const app = new WebhookClient({request: req, response: res});

function exampleIntent(app) {

   app.add("Speak This Out on Google Home!");   // this speaks out fine. no error. 
}

Now, if I have an async request which finishes successfully, and I do app.add in the success block like this:

 function exampleIntent(app) {  
      myClient.someAsyncCall(function(result, err) {
          app.add("This will not be spoken out");  // no dice  :(
      }
      // app.add("but here it works... so it expects it immediately");
  }

... then Dialog Flow does not wait for the speech to be returned. I get the error in the Response object:

  "message": "Failed to parse Dialogflow response into AppResponse, exception thrown with message: Empty speech response",

How can I make DialogFlow V2 wait for the Webhook's Async operations to complete instead expecting a speech response immediately?

NOTE: This problem only started happening in V2. In V1, app.ask worked fine at the tail-end of async calls.

exampleIntent is being called by the main mapper of the application like this:

let actionMap = new Map();
actionMap.set("my V2 intent name", exampleIntent);
app.handleRequest(actionMap);

And my async request inside myClient.someAsyncCall is using Promises:

exports.someAsyncCall = function someAsyncCall(callback) {
    var apigClient = getAWSClient(); // uses aws-api-gateway-client
    apigClient.invokeApi(params, pathTemplate, method, additionalParams, body)
    .then(function(result){
        var result = result.data;
        var message = result['message'];
        console.log('SUCCESS: ' + message);
        callback(message, null);  // this succeeds and calls back fine. 
    }).catch( function(error){
        console.log('ERROR: ' + error);
        callback(error, null);
    });
};
like image 891
FranticRock Avatar asked Mar 19 '18 14:03

FranticRock


1 Answers

The reason it worked in V1 is that ask() would actually send the request.

With V2, you can call add() multiple times to send everything to the user in the same reply. So it needs to know when it should send the message. It does this as part of dealing with the response from your handler.

If your handler is synchronous, it sends the reply immediately.

If your handler is asynchronous, however, it assumes that you are returning a Promise and waits till that Promise resolves before sending the reply. So to deal with your async call, you need to return a Promise.

Since your call is using Promises already, then you're in very good shape! The important part is that you also return a Promise and work with it. So something like this might be your async call (which returns a Promise):

exports.someAsyncCall = function someAsyncCall() {
    var apigClient = getAWSClient(); // uses aws-api-gateway-client
    return apigClient.invokeApi(params, pathTemplate, method, additionalParams, body)
    .then(function(result){
        var result = result.data;
        var message = result['message'];
        console.log('SUCCESS: ' + message);
        return Promise.resolve( message );
    }).catch( function(error){
        console.log('ERROR: ' + error);
        return Promise.reject( error );
    });
};

and then your Intent handler would be something like

function exampleIntent(app) {  
  return myClient.someAsyncCall()
    .then( function( message ){
      app.add("You should hear this message": message);
      return Promise.resolve();
    })
    .catch( function( err ){
      app.add("Uh oh, something happened.");
      return Promise.resolve();  // Don't reject again, or it might not send the reply
    })
}
like image 166
Prisoner Avatar answered Nov 13 '22 22:11

Prisoner