Chatbot workshop

with Dialogflow and Cloud Functions

Discovering the Cloud Console and creating our first Cloud Function

Go to console.cloud.google.com.

Create a new project:

Once the project is created, navigate to the project :

Enable the Cloud Functions API:

Note: If you don’t see the “Enable API” on the Cloud Functions page, please click on the hamburger menu, then API Manager, click on “Enable API”, search for the Cloud Functions API, click on it, and you should see another button “Enable API”.

Create a function:

Let’s call the function “agent”.

Select the “HTTP” trigger option.

Select “inline” editor.

Update the function definition line to read:

exports.agent = function agent(req, res) {

And specify the function to execute to “agent” too:

Note: Be sure to double check that the exported function (ie. exports.agent = …) and the one in the field “Function to execute”, do match, and are called alike.

You must create a “stage bucket”. Click on “browse”, you should see the browser, but no buckets exist for now:

Click on the little + button to create the bucket:

Specify a unique name for the bucket (you can use your project name, for instance), and you can select “regional” storage, and keep the default region (us-central1).

Then you can click back on the “select” button in the previous window.

Then on the “create” button to actually create the function.

The function will be created and deployed:

Note: the first deployment of the function might take up to a minute or two.

Click on the “agent” function line:

In the “general” tab you can see some statistics (initially empty, as our function hasn’t been called yet).

In the “trigger” tab you can see the HTTPS URL created for your function:

In the “source” tab, you can see the sources, we’ll update that in a moment.

In the “testing” tab, you can invoke your function.

Let’s try invoking our function from there, with the payload

{"message": "Hello!"}

And if all goes fine, in the lower pane, you should see the result:

Success: Hello!

One last test, using cURL on the command-line (replace the URL):

curl -X POST -H "Content-type: application/json" -d '{"message": "Guillaume"}' https://us-central1-starwars-pedia-sin.cloudfunctions.net/agent

And you should see a success message like before within the Google Cloud Console.

Discovering the Dialogflow interface

Go to Dialogflow in your browser.

Create an account, or login if you have one already.

Play with the welcome intent

Let’s create our Dialogflow project, select the Google Cloud Project we used before, and English for the language:

Click on the “intents” menu item, and select the “default welcome intent”.

Add a “hi” message in the “user says” section, that’ll be the message people can say to trigger this default welcome intent:

Remove the welcome messages at the bottom, and all “Welcome, young padawan” as “default message”.

Save your changes by clicking the “save” button.

Then in the testing area, in the top right hand corner, you can type “hi”, and check that it replies with “Welcome, young padawan!”:

You can have a look at what the JSON payload exchanged looks like, by clicking the “show JSON” button at the bottom.

Code for the “what time is it” intent:

'use strict';

const functions = require('firebase-functions'); // Cloud Functions for Firebase library

const DialogflowApp = require('actions-on-google').DialogflowApp; // Google Assistant helper library

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {

    const app = new DialogflowApp({request, response});

    const actionMap = new Map();

    actionMap.set('input.time', (app) => {

        app.tell(`It's ${new Date().toTimeString().substring(0,5)}`);

    });

    app.handleRequest(actionMap);

});

A new intent to add numbers

Let’s create a new intent, by clicking on the + sign beside the “intents” left menu item.

Call the intent “plus”.

In the “User says” box, add a sentence “one plus two”, and hit enter.

Notice how it figures out you have two numbers in this phrase, and that it created them as parameters of this intent.

Double click on the parameter names to change their names and call them “left” and “right”.

In the Action field, put “input.plus”.

Note: be sure to put a name in the action field, as we’ll need this action ID in our cloud function later on.

In the “Response” section, add a static text response for now: “The result of $left plus $right is always 42!”

Notice that we can reuse the parameters with the dollar notation:

Note: Don’t forget to click the “save” button to save your new intent!

Now in the test area on the right, try entering “one plus two” or any other addition and notice the result:

It returned our static response, but inserted the values of the input parameters.

You can also try to click on the microphone icon to input your query using your own voice.

But we’d like to have the real result of the operation!

That’s where we’ll be plugging in Cloud Functions to do the calculation, and be our business logic.

Putting it all together!

Implementing and deploying the business logic

In the Cloud Console, let’s create a new Cloud Function called “adder”, as an HTTP trigger, with inline code (“inline editor”) option, with the default bucket we had created before, and the name of the function to invoke as “adder”:

Now, time to code our function!

We’re going to need two files: the “index.js” file will contain the JavaScript / Node.JS logic, and the “package.json” file will the Node package definition, including the dependencies we’ll need in our function.

Here’s our package.json file, with the dependency on the actions-on-google NPM module to ease the integration with Dialogflow and the Actions on Google platform that allows you to extend the Google Assistant with your own extensions (usable from Google Home):

{

 "name": "dialogflowFirebaseFulfillment",

 "version": "0.0.1",

 "main": "index.js",

 "dependencies": {

   "actions-on-google": "^1.8.2",

   "firebase-functions": "^0.8.1",

   "firebase-admin": "^5.8.2"

 }

}

In the index.js file, here’s our code:

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

function add(assistant) {

 var left = parseInt(assistant.getArgument("left"));

 var right = parseInt(assistant.getArgument("right"));

  assistant.ask(`The result of ${left} plus ${right} is ${left + right}`);

}

exports.adder = function(request, response) {

   var assistant = new ApiAiAssistant({request: request, response: response});

   var actionMap = new Map();

   actionMap.set("input.plus", add);

   assistant.handleRequest(actionMap);

};

const functions = require('firebase-functions');

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

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {

    console.log("Start");

    const app = new DialogflowApp({request: request, response: response});

    const actionMap = new Map();

    actionMap.set("input.plus", (theApp) => {

        console.log("plus action called");

        const left = parseInt(theApp.getArgument("left"));

        const right = parseInt(theApp.getArgument("right"));

        theApp.ask(`The result of ${left} plus ${right} is ${left + right}.`)

    });

    app.handleRequest(actionMap);

});

We require the actions-on-google NPM module.

We define an add function that will retrieve the left and right parameters.

We use the ask() method to let the assistant send a result back to the user with the result of our calculation.

We export a function, where we’re using the actions-on-google module’s ApiAiAssistant class to handle the incoming request.

We create a map that maps “intents” from Dialogflow to a JavaScript function.

Then we call the handleRequest() to handle the request.

Note: double check that the exported “adder” function in your code has indeed the same name as the one in the field “function to execute”.

Once done, don’t forget to click the “create” function button!

It will deploy the function in the cloud.

Configuring a webhook in Dialogflow

Now we need to instruct Dialogflow to point at our function, which implements the business logic (ie. the computation of the addition of our two numbers)

In the Google Cloud Console, you’ll need to note down the URL of your function:

Now in your Dialogflow project, you will enable “fulfillment”, by clicking on the “fulfillment” menu item from the left column navigation:

Check the “enabled” flip, and enter the URL of your Cloud Function.

Then hit “save” to save this configuration.

Now go back to your “plus” intent, by clicking the “intents” menu item, and then the “plus” intent from the list of intents.

Scroll down to the bottom of the page, and you will see a “fulfillment” section. Click on this label to show the fulfillment configuration:

Note: Don’t forget to save your changes by clicking the “save” button in the top of the page.

Now, when this intent is invoked, instead of serving the default response of 42, it’ll call our function to do the calculation. Let’s test that.

In the testing area, try a new operation, by typing “3 + 4”:

Yay, victory, the reply is now “The result of 3 plus 4 is 7”, as returned by our Cloud Function!

Bonus points: the Star Wars chatbot!

We are going to use the Star Wars API: http://swapi.co/

This REST API offers information about the characters of the movies, about planets, species, spaceships, and more.

The goal of this new exercise will be to find Star Wars character’s eye color!

You will need to follow similar steps as in the first part of the workshop, for function creation, or within Dialogflow.

However, instead of writing code inline in the Cloud Console, and deploying each time you make some changes, we’ll introduce you to a local development workflow using the Cloud Functions Emulator to run Cloud Functions locally on your development machine, and the ngrok tool and service, to expose your emulated function via a web-accessible UR, by creating a tunnel to your machine.

CloudShell: the Cloud Console shell

If you’re programming locally on your machine, no problem. But if you’re using one of our provided accounts, you can opt for programming directly from a shell, on a VM running in the context of your Google Cloud project.

To launch the cloud shell, you’ll see a like icon “>_” in the top right hand menu bar, close to your avatar:

Click it, and it will launch a shell, in the lower part of your window, within which you’ll be able to run commands. Tools like npm, and many others are already pre-installed.

Note: You can run several shell instances at once, by clicking the little + sign near the header tab of the first running shell. This will be useful later on, to have one place to edit your code, and another shell for running the ngrok tunnel command.

Cloud Functions emulator

The Cloud Functions emulator, as its name implies, emulates the real Cloud Functions cloud service, but running locally on your machine. It supports live reloading of your changes to your local Node code, as well as Chrome debugging.

We will install the Cloud Functions emulator with:

$ npm install @google-cloud/functions-emulator

You’ll be able to start the emulator with:

$ ./node_modules/.bin/functions start

Note: If you’re not running this in cloud shell, you can install with the -g flag with npm, to install the “functions” command-line tool globally, so that it’s available directly on your PATH. Then, to run it, just run: $ functions start.

To deploy a function locally, you can use the deploy sub-command:

$ ./node_modules/.bin/functions deploy <function-name> --trigger-http

Note: replace <function-name> with the exported name of your cloud function. In the example above, our function is called adder. So the command would be functions deploy adder --trigger-http.

Ngrok to make your localhost available on the web

For a faster development loop, we’ll use ngrok to create a bridge between the web and your functions running locally via localhost.

To install ngrok, use:

$ npm install ngrok

Note: On your local machine, you can use the -g flag again, to make ngrok available via the PATH.

To run ngrok locally, to create the tunnel, type:

$ ./node_modules/.bin/ngrok http 8010

Note: We use port 8010 as this is the port used by the Cloud Functions emulator by default.

You should see it running:

Notice the https URL provided, which is a URL accessible from the Web, and thus from Dialogflow’s webhook URL configuration.

Note: Dialogflow expects you to provide only an HTTPS URL, not an HTTP one without SSL/TLS.

Ngrok offers a nice built-in web interface to look inside requests and responses, however, it’s running by default on port 4040, which is not exposed by cloud shell, which only exposes 8080 through 8084. So we’ll configure ngrok to allow a custom port of 8084 with:

$ mkdir $HOME/.ngrok2

$ echo "web_addr: 8084" > $HOME/.ngrok2/ngrok.yml

Then use the web preview account to select port 8084 and click to open automatically your browser to the ngrok built-in web interface:

It will open a browser on the web interface, which will allow you to inspect the incoming and outgoing traffic:

Editing files with Vim or the built-in file editor

Depending on how brave you are (and if you know how to exit Vim or not), you might decide to use the vim editor to edit your JavaScript code, or you might prefer using the built-in file editor, by clicking the file icon:

It will open a file editor in a new browser tab.

Note: It might take a couple minutes to load the first time.

Sample code

Here’s some sample code to get you started: a package.json file to define your dependencies, and an index.js file to add your business logic.

In addition to the actions-on-google package, we will also use the node-fetch NPM module which simplifies calling REST web APIs.

package.json

{

 "name": "star-wars",

 "version": "0.0.1",

 "main": "index.js",

 "dependencies": {

   "actions-on-google": "^1.8.2",

   "node-fetch": "^2.0.0"

 }

}

index.js

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

const fetch = require('node-fetch');

function eyesColor(theApp) {

   let name = theApp.getArgument('name');

   let searchUrl = `https://swapi.co/api/people/?search=${encodeURI(name)}`;

   fetch(searchUrl)

       .then(response => response.json())

       .then(data => {

           if (data.count === 0) {

               theApp.ask(`No one called ${name} on this side of the galaxy.`);

           } else {

               theApp.ask(`${name} has ${data.results[0].eye_color} eyes.`);

           }

       }).catch(err => {

           console.log(err);

           theApp.ask('Too strong, the dark side of the force was.');

       });

}

exports.eyesColor = function(request, response) {

   var app = new DialogflowApp({request: request, response: response});

   var actionMap = new Map();

   actionMap.set("input.sw-eye-color", eyesColor);

   app.handleRequest(actionMap);

};

Running your cloud function

Before we can run our eyesColor function, we need first to install the new dependencies that we added in our package.json:

$ npm install

Now that your cloud function is ready, use the cloud function emulator to deploy your function locally:

$ ./node_modules/.bin/functions deploy eyesColor --trigger-http

This command will deploy your eyesColor function locally and give you the following result:

Make also sure your ngrok is still running in the background i.e. in another shell:

With both the cloud function you just deployed and the ngrok tunnel open, you can access this function thought your browser:

https://837821e7.ngrok.io/swagent-56ac2/us-central1/eyesColor 

https://837821e7.ngrok.io

The tunnel URL created by ngrok

swagent-56ac2/us-central1/eyesColor 

The path to your function on GCP

Accessing this URL via a GET request will give a you the following error message (which is fine):

This function uses the ApiAiAssistant SDK which only listens for POST request (those coming from Dialogflow for instance).

Configuring Dialogflow

Now back in to your Dialogflow console, add the URL of the cloud function you deployed locally in your GCP shell instance, to the fulfillment webhook section:

Create a new intent, and call it “eyes.color” and make sure it has the following configuration:

Now, you can test your agent:

Congratulations!

Deploying your function to production

Once you finished implementing and tweaking your function, you are now to deploy it to production. In your shell instance, run the following command:

$ gcloud beta functions deploy eyesColor \

                                           --project swagent-56ac2 \

                                           --trigger-http \

                                           --stage-bucket gs://swagent-56ac2.appspot.com/

This command will give you the following result:

Giving you for instance the HTTPS URL of your function in production:

https://us-central1-swagent-56ac2.cloudfunctions.net/eyesColor

You can now use this production URL in your Dialogflow project.