Build Actions for the Google Assistant

I built some Google Assistant Actions to related to the content of mobile app.

Build Actions for the Google Assistant

After creating my first Alexa Skill, I wanted to have the same functionalities on my Google Assistant (mobile app and Google Home). Let me explain how I did it!

As for Alexa, my goal was to create a vocal version of my Android app, ParisGo, which helps parisiens to get the real time schedules of subways, buses, tramways, trains... in a very simple app.

ParisGo

I know it's possible to port an Alexa Skill to the Google Assistant, but I wanted to start fresh to be able to maintain it myself afterwards.

Note: In this article, I will not share all my source code, for security reasons, as I'm using some personal API/servers

Build the Action

It's similar to Alexa Skills. You have to define and build your Action on the Google Dialogflow platform.

Your Action contains Intents.
Each intent have some utterances.
Each utterance can contains some parameters.
Each parameter will match an Entity, which can be a custom list, or a Google predefined type (date, number, pone, country, movie...).

Example:

dialogflow-types

Utterances for SubwayNextStop Intent (translated in English) :

When is the next metro station Vaugirard
What time is the next metro station Vaugirard
What are the next metro stations Vaugirard

Vaugirard will be my parameter, and is linked to the SubwayStop entity.

dialogflow-utterances

Dialogflow automatically detects parameters based on the already defined entities.

dialogflow-parameters

You can also define if your parameter is required or if it's a list of words.
If it's required, you also need to define the prompts to get the value from the user.

To create your entity, you can import CSV or JSON files.
Sadly, the CSV file is not the same as for Alexa.

The first value will be the entity key, the second one will be the text and the following ones will be the synonyms.
You can also disable synonyms and use only one column as key and value.

dialogflow-entity

After that, you have to choose the fulfillment, which will resolve every request from the user.

You have two choices:

  • Create a custom HTTPS webhook with your own server / resources, which will receive and respond in JSON
  • Use an Google Function, which is a little function hosted by Google in NodeJs. Google provides a library to help you parse the query / respond to the user.

For simplicity, I chose to use the Google Function service, to format a request to one of my API and respond in French.

Build the endpoint

Note: This is not an example of beautiful code, it's just to help you create a simple Google Function

To build the endpoint, you can use the embedded editor in the Fulfillment tab.
You can even add dependencies into the package.json file, and they will be installed before deployment.
Sadly, you cannot add new files. To do so, you will have to download a ZIP file, and upload the code again with the Google Cloud CLI.

dialogflow-fulfillment

Upload with the Firebase CLI

Everything it well explained on the Google Documentation. I will just help you to deploy something quickly.

To install the CLI, with npm:
npm install -g firebase-tools

You have to login once for all:
firebase login

To deploy a 'function' project, you have to create a firebase.json file. You can do it properly with the firebase init command. It will ask you which service you want to activate on your project. Chose Functions, your programming language and so on. It can create you a default index.js file, and a package.json file with all the dependencies.

firebase-init-project

Source code example

To be sure that you source code will be used as a dialogflow fulfillment, you function should be named dialogflowFirebaseFulfillment. I didn't tried to much to change it, as it was working like that.

I find it easier than developing a Skill for Alexa. Sadly, the fulfillments samples are not up to date.

package.json file

{
  "name": "dialogflowFirebaseFulfillment",
  "description": "DialogFlow Fulfillment for ParisGo",
  "version": "1.0.0",
  "private": true,
  "license": "Apache Version 2.0",
  "author": "Jean-Matthieu DECHRISTE",
  "engines": {
    "node": "8"
  },
  "scripts": {
    "start": "firebase serve --only functions:dialogflowFirebaseFulfillment",
    "deploy": "firebase deploy --only functions:dialogflowFirebaseFulfillment"
  },
  "dependencies": {
    "actions-on-google": "2.2.0",
    "firebase-functions": "^2.0.0",
    "firebase-admin": "^5.13.1",
    "request": "^2.87.0",
    "lodash": "^4.17.10"
  }
}

index.js file

'use strict';
 
const {dialogflow} = require('actions-on-google');
const admin = require('firebase-admin');
const functions = require('firebase-functions');
const request = require('request');
const _ = require('lodash');

// TODO: change the base URL
const BASE_URL = 'https://api.domain.com/v1/';

const app = dialogflow({debug: true});

app.intent('WelcomeIntent', (conv) => {
  conv.add(`How can I help you?`);
});

app.intent('FallbackIntent', (conv) => {
  conv.add(`I'm sorry, I didn't understand.`);
  conv.add(`Could you repeat?`);
});

app.intent('SubwayNextStop', nextStopHandler);
app.intent('TramwayNextStop', nextStopHandler);
app.intent('BusNextStop', nextStopHandler);
app.intent('NoctilienNextStop', nextStopHandler);

function nextStopHandler(conv, params) {
  // Get the params value, based on the intent name
  const originStopKey = params[conv.intent.replace('Next', '')];
  
  return new Promise((resolve, reject) => {
      request({
        method: 'GET',
        baseUrl: BASE_URL,
        uri: `${originStopKey}/schedules`,
        json: true
      }, function (err, response, body) {
        if(err) {
          reject(err);
        }
        resolve(body);
      })
    })
    .then(body => {
      // TODO : use the body to generate the 'speechOutput'
      let speechOutput = 'TODO';
      
      conv.close(speechOutput);
    })
    .catch(err => {
      console.error(err);
      conv.close("Sorry, I couldn't get the requested schedules");
    });
}

exports.dialogflowFirebaseFulfillment = functions.https.onRequest(app);

Test you Action

You can now test your action in the Dialogflow simulator, and it will get you the parameter resolution, end even the JSON requests / responses.

dialogflow-test-action

dialogflow-diagnostic

If you want to have a real life test, you can use the Google Assistant simulator

actions-on-google-simulator

Deployment

To deploy your action to THE WORLD, you just have to fill some forms on the Google Action console.

deploy-actions-on-google

You can now deploy in alpha, beta or production. Note that Google will review your action before publishing it.
After few days, you will receive an email explaining why your action has been rejected ;)

After modification and a new review, it will be published!

transports-paris_assistant-google