Search code examples
node.jsdialogflow-esactions-on-googleapi-ai

API.ai Actions on Google - Failed to parse JSON response string with 'INVALID_ARGUMENT' error: ": Cannot find field."


This error is similar to what I asked here, but this time it's with NodeJs client.

I am trying to find directions to a location. As soon as the intent is triggered on my webhook, I am calculating the directions using GoogleMapAPI. But before it can finish and send a response, I receive the error on my Actions Console.

I checked total response time and it is less than 2 seconds which is less than 5 seconds timeout by Google.

Where I am wrong???

My API.ai Intent
enter image description here enter image description here

Using express.js with Action-on-Google Node Client

'use strict';

const express = require('express');
const bodyParser = require('body-parser');
const intentHandler = require('./intent_handler')

const app = express();
app.use(bodyParser.json());

const ApiAiAssistant = require('actions-on-google').ApiAiAssistant;

// Create functions to handle requests here
....
....
const DIRECTION_INTENT = 'action_direction';

function MyAssistant(req, res) {
    const assistant = new ApiAiAssistant({request: req, response: res});
    assistant.handleRequest(responseHandler(assistant));
}


function responseHandler (assistant) {
    // intent contains the name of the intent you defined in the Actions area of API.AI
    let intent = assistant.getIntent();
    switch (intent) {
        case WELCOME_INTENT:
            ...
            break;
        case WELCOME_FALLBACK_PERMISSION_INTENT:
            ...
            break;
        case DIRECTION_INTENT:
            console.log(">>>>>>>DIRECTION_INTENT<<<<<<<");
            intentHandler.directionIntent(assistant);
            break;
    }
}
app.post('/', function (req, res) {
   MyAssistant(req, res);
});
app.listen(8080, function () {
    console.log('app listening on port 8080!')
});


Handler Code

'use strict';
const speech = require("./speech_template");

const direction = require("./directionModule");

const intent_handler = {

    'welcomeIntent': function (assistant) {
        .....
    },

    'welcomeFallbackPermissionIntent': function (assistant) {
        .....

    },

    'directionIntent':function (assistant) {
        console.log('direction intent');
        direction.getDirectionWithSavedAddress(function (response) {
            assistant.ask(response);
        });
    }
};

module.exports = intent_handler;


Direction Extraction --- ERROR comes on Action Console before this get finished

'use strict';

const striptags = require('striptags');
const speech = require("./speech_template");

let googleMapsClient = require('@google/maps').createClient({
    key: global.GOOGLE_DIRECTION_KEY
});

const directionModule = {
    'getDirectionWithSavedAddress': function (eventCallback) {
        let myAdd = <From Saved Data>;
        if (myAdd === undefined) {
            console.log("error......");
        }
        let destination = <From Saved Data>;
        this.getDirectionWithAddress(myAdd, destination, function (dir) {
            ....
            if(SUCCESS){
                eventCallback(`<speak> ${steps} </speak>`);
            }else{
                eventCallback(`<speak> ${speech.ERROR_DIRECTIONS} </speak>`);
            }
        });
    },
    'getDirectionWithAddress': function (add1, add2, eventCallback) {
        let dir = {};
        googleMapsClient.directions({
            origin: add1,
            destination: add2,
            mode: "driving",
            departure_time: "now"
        }, function (err, response) {
            if (!err) {
                console.log(response.json.routes[0]);
                ....
                ....
                ....
            } else {
                console.log(`Error --> ${err.toString()}`);
                ....
            }
            eventCallback(dir);
        });
    }
};

module.exports = directionModule;

UPDATE I am running the code locally via WebStorm and exposing webhook via port forwarding using ngrok.

Update2
BAD REQUEST 400

enter image description here

{
    "originalRequest": {
        "source": "google",
        "version": "2",
        "data": {
            "isInSandbox": true,
            "surface": {
                "capabilities": [
                    {
                        "name": "actions.capability.AUDIO_OUTPUT"
                    }
                ]
            },
            "inputs": [
                {
                    "rawInputs": [
                        {
                            "query": "get me there",
                            "inputType": "VOICE"
                        }
                    ],
                    "arguments": [
                        {
                            "rawText": "get me there",
                            "textValue": "get me there",
                            "name": "text"
                        }
                    ],
                    "intent": "actions.intent.TEXT"
                }
            ],
            "user": {
                "locale": "en-US",
                "userId": "<uID>"
            },
            "device": {},
            "conversation": {
                "conversationId": "<cID>",
                "type": "ACTIVE",
                "conversationToken": "[\"_actions_on_google_\",\"defaultwelcomeintent-followup\"]"
            }
        }
    },
    "id": "<ID>",
    "timestamp": "2017-09-12T17:08:10.321Z",
    "lang": "en",
    "result": {
        "source": "agent",
        "resolvedQuery": "get me there",
        "speech": "",
        "action": "action_direction",
        "actionIncomplete": false,
        "parameters": {},
        "contexts": [
            {
                "name": "_actions_on_google_",
                "parameters": {},
                "lifespan": 99
            },
            {
                "name": "google_assistant_input_type_voice",
                "parameters": {},
                "lifespan": 0
            },
            {
                "name": "actions_capability_audio_output",
                "parameters": {},
                "lifespan": 0
            },
            {
                "name": "defaultwelcomeintent-followup",
                "parameters": {},
                "lifespan": 4
            }
        ],
        "metadata": {
            "intentId": "<iID>",
            "webhookUsed": "true",
            "webhookForSlotFillingUsed": "false",
            "nluResponseTime": 15,
            "intentName": "DirectionIntent"
        },
        "fulfillment": {
            "speech": "",
            "messages": [
                {
                    "type": 0,
                    "speech": ""
                }
            ]
        },
        "score": 1
    },
    "status": {
        "code": 200,
        "errorType": "success"
    },
    "sessionId": "<sID>"
}

This looks like before my callback is finished, my webhook is sending empty response to Google Actions.
Why is this happening and How to resolve it?????


Solution

  • The problem lies in how your directionIntent() function calls, and handles the result of, your getDirectionWithSavedAddress() function. It expects getDirectionWithSavedAddress() returns a function, when it does not. Instead, getDirectionWithSavedAddress() expects to send its results to a callback.

    So after it makes its call to getDirectionWithAddress(), the function ends, returning nothing. This "nothing" is sent to assistant.ask(), which returns that to Google's server. This is an invalid response, so you're getting the error.

    Fixing this should be straightforward. You need to call getDirectionWithSavedAddress() with a callback function. Inside this function you should call assistant.ask() with the value sent to the callback.

    So directionIntent() might look something like

        'directionIntent':function (assistant) {
          console.log('direction intent');
          direction.getDirectionWithSavedAddress( function( msg ){
            assistant.ask( msg );
          } );
        }
    

    Updated

    This line makes no sense:

    assistant.handleRequest(responseHandler(assistant));
    

    The assistant.handleRequest() function is supposed to be passed a Map of Intent names to functions to call to handle the event. You're doing this manually in the responseHandler() function and you're not returning a Map. Since you're not returning a Map, it fails when trying to do the handleRequest() and generates the error "Action Error: Request handler can NOT be empty".

    You can fix this by just calling responseHandler(assistant) and not dealing with handleRequest() at all. Or you can create the map that handleRequest() is expecting and get rid of responseHandler() completely.