node.js

# Writing a dialogflow bot

The idea was to try to build a chat bot and see how far we can get in a week and a half.
Let's see what we did, how, where we got blocked and what are the lessons we learned along the way.
Spoiler alert: we went pretty far.

Some people at Novoda already experimented a bit and created some chatbots on their own, slack bots, Alexa skills and other Google actions. All of them were pretty simple and only a handful were actually dynamic (i.e they didn't require any backend work). For our little experiment we decided to build a small bot that is able to give us information about a tv show and when it airs on tv. After asking around it seemed that the perfect choice was to use api.ai (now dialogflow) to create this bot as we could just start to play with the interface to create a static bot and later move to a proper backend to have something a bit more dynamic. The real plus is also to be able to export this bot to multiple platforms (here we exported to google assistant and slack).

First step is to create your project, by following this that should be pretty straightforward. You can then play a bit with the static responses and see how the basics work. When you are tired to have the same static response, we can start to dig deeper. I highly recommend to read all the documentation concerning the key concepts to understand better what is possible and what's not, especially the bits regarding the context.

An easy way to start hacking around is to expose your localhost to the internet using ngrok. Here is a simple piece of code written using Node.js, it starts a server on the port 5000 and return a well formatted response according to the action, parameters and contexts thanks to our magical resolve method.

import express from 'express'
import bodyParser from 'body-parser'

const app = express()
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))

const server = app.listen(5000, () => {
console.log(Server listening on port ${server.address().port}) }) app.post('/webhook', async (request, response) => { const requestBody = request.body.result console.log(requestBody) const result = await resolve(requestBody) return response.json(result) })  We can then expose our localhost using ngrok: $ ngrok http 5000

Under fulfillment we can then use the generated URL that points to our localhost and start playing!
This is how our intent looks like (don't forget to check Use Webhook under fulfillment in your intent as well)

Don't worry about the context for now and @tv-show is just a simple entity containing a few tv shows we want to be able to pick up.

After you retrieve your dynamic information, the response need to be as simple as:

return {
speech: 'spoken response (google home for instance)',
displayText: 'written response (google assistant or slack for instance)'
}


Testing our new intent on the console this is what we get

This is already good but we can do better! Depending on the platform, when sending our response, we can include an extra data object that contains specifics for each of these platforms. In our case, we want to support better slack and google so we include them both:

data: {
slack: {},
}


For slack it's pretty easy thanks to the extensive documentation

slack: {
{
attachments: [
{
title: show.brand.title,
text: show.brand.summary,
image_url: show.brand.image.href
}
]
}
}


For google it's a bit harder as the documentation is pretty sparse, mixing both the actions sdk and the api.ai one, some dead links here and there and some not really clear errors, this one has to be my favorite:

{
"response": "Televison guide isn't responding right now. Try again soon.\n",
"audioResponse": "//NExAASW...",
"debugInfo": {
"sharedDebugInfo": [
{
"name": "ExecutionResponse",
"debugInfo": "Failed to..."
}
]
},
"visualResponse": {}
}


Anyway, after some trial and errors, here the structure for the google card:

{
expectUserResponse: true,
richResponse: {
items: [
{
simpleResponse: {
textToSpeech: textToSpeech
}
},
{
basicCard: {
title: show.brand.title,
formattedText: show.brand.summary,
image: {
url: show.brand.image.href,
accessibilityText: show.title + ' poster'
}
}
}
]
}
}


How about we now want to know when this show is airing? We could just ask When is The Big Bang Theory starting? but wouldn't that be even better to just have to ask When is it starting?. In other terms, that would be pretty awesome if our bot could keep some context.
When we are returning a response that is linked to a question containing a tv show, such as What is SHOW about? we add a contextOut array with a context object which is going to be send back to us in the next requests:

return {
speech: message,
displayText: message,
contextOut: [this.contextForShow(show)],
data
}

contextForShow(show) {
return {
name: 'show',
lifespan: 5,
parameters: {
name: show.brand.title
}
}
}


You'll need to create two intents: one that take a context and one that doesn't but then expect to contain a tv show in the question:

On the code side, you can then have the same function handling the creation of the response, trying to fetch the tv show name either from the context or from the parameters:

const show = parameters['tv-show'] || this.extractParametersFromContext()


This is where we stop.
I hope this short glimpse into what's possible gave you a good starting point on your chatbot adventure!