The Rise Of The Bots
@asyncjs - Alex Nicol @nicol_alexandre - #RiseOfTheBots
riseofthebots.webnicol.fr
Table of Contents
-
What is a bot?
-
Chatbots vs Mobile app
-
Hands on
Me
Alex Nicol - webnicol.fr - @nicol_alexandre
Developer @ EDF Energy R&D UK Centre
Web / Mobile / AR / Bots
What is a bot?

Bots are seen by some as "a sophisticated, artificial-intelligence-infused creation, capable of human interaction"
Wired UK.
Chatbot: Automated agent/program that mimic human conversation, potentially using AI.


A bit of history
The first one: Eliza

Virtual assistants
Google Now / Google assistant
Siri
Alexa
Cortana
Natural Language Processing
"NLP is a way for computers to analyze, understand, and derive meaning from human language in a smart and useful way."
http://blog.algorithmia.com/introduction-natural-language-processing-nlp/
NLP/NLU Services
Rasa NLU
wit.ai
api.ai
Amazon Lex
...
Advantages
24/7 Availability
No queues
Asynchronous conversation
Excellent to do simple repetitive task
Minimalist UI
New communication channel
Chatbots vs Mobile app
Mobile app usage in 2016 (1/2)

Mobile app usage in 2016 (2/2)

And, loads of messaging apps
Kik
Facebook Messenger
Google Allo
Telegram
Skype
Slack
(SMS)

The internet is always evolving
Browser + Website
Smartphone OS + Mobile app
Messaging platform + Chatbots
Conversational UI/UX

Facebook wants to be the new "OS"
In-app browsing
Instant article
Games
Texts and Calls in Messenger
Messenger Bots
Facebook Messenger Bot API
-
• Text
• Audio
• Image
• Video
• File
-
Templates:
• List
• Receipt
• Button
• Airline
-
Buttons:
• Url
• Call
• Buy
• Share
-
• Webview
• Instant article
• Log In/Out (OAuth2)
• Quick replies
• Menu
As a developer
No responsive design that need to match 947163 size of Android Screens
No Apple certificates/provisioning profile/ @£$*^%$£
Brand design guidelines, what?
NLP/NLU is really satisfying
UI != UX
HANDS ON!
Let's create a Weather Station Bot!
Requirements:
JavaScript / Node.js basic knowledge
A node.js server (or lambda)
A api.ai account
A facebook account
create a page
create an app
https://www.facebook.com/pages/create/https://developers.facebook.com
Link your facebook app to your server
// ----- Express/Node.js server ----- | |
/* some code */ | |
app.get('/webhook', function (req, res) { | |
if (req.query['hub.verify_token'] === 'YOUR_VERIFY_TOKEN') { | |
res.send(req.query['hub.challenge']); | |
} else { | |
res.send('Error, wrong validation token'); | |
} | |
}); | |
/* some code */ | |
// ----- AWS Lambda/Javascript ----- | |
exports.handler = (event, context, callback) => { | |
const done = (err, res) => callback(null, { | |
statusCode: err ? '500' : '200', | |
body: err ? err.message : res, | |
headers: { | |
'Content-Type': 'application/json' | |
} | |
}); | |
if (event.queryStringParameters['hub.verify_token'] === 'YOUR_VERIFY_TOKEN') { | |
done(null, event.queryStringParameters['hub.challenge']); | |
} else { | |
done(null, 'Error, wrong validation token'); | |
} | |
} |
Repeat bot
const Facebook = require('Facebook'); | |
exports.handler = (event, context, callback) => { | |
/* ... | |
done function for AWS Lambda | |
...*/ | |
var data = JSON.parse(event.body); | |
// Make sure this is a page subscription | |
if (data.object === 'page') { | |
// Iterate over each entry - there may be multiple if batched | |
data.entry.forEach(function(entry) { | |
// Iterate over each messaging event | |
entry.messaging.forEach(function(event) { | |
if (event.message) { | |
const message = event.message.text; | |
const senderID = event.sender.id; | |
Facebook.sendTextMessage(senderID, `you said: ${message}`); | |
} else { | |
console.log("Webhook received unknown event: ", event); | |
} | |
}); | |
}); | |
done(null, true); | |
} | |
} |
const request = require('request'); | |
class Facebook { | |
static sendTextMessage(recipientId, message) { | |
const messageData = { | |
recipient: { | |
id: recipientId | |
}, | |
message: { | |
text: message | |
} | |
}; | |
const options = { | |
uri: 'https://graph.facebook.com/v2.6/me/messages', | |
qs: { access_token: '*****************' }, | |
method: 'POST', | |
json: messageData | |
}; | |
// send the message | |
request(options, function(err, status, body){ | |
if (!error && response.statusCode == 200) { | |
console.log('Message sent'); | |
} else { | |
console.log(JSON.stringify(err)); | |
} | |
}); | |
} | |
} | |
module.exports = Facebook; |
Link facebook and api.ai
const request = require('request'); | |
class Apiai { | |
static nlu(query, sessionId) { | |
return new Promise(function(resolve, reject){ | |
const options = { | |
uri: 'https://api.api.ai/v1/query?v=20150910', | |
body: { | |
query: query, | |
lang: 'en', | |
sessionId: sessionId //Facebook user Id | |
}, | |
headers: { | |
'content-type': 'application/json', | |
authorization: 'Bearer ****************' // apiai server key | |
}, | |
json: true, | |
method: 'POST' | |
} | |
request(options, function (error, response, body) { | |
if (!error && response.statusCode == 200) { | |
resolve(body); | |
} else { | |
reject({error: error, response: response}); | |
} | |
}); | |
}); | |
} | |
} | |
module.exports(Apiai); |
const Facebook = require('Facebook'); | |
const Apiai = require('Apiai'); | |
exports.handler = (event, context, callback) => { | |
/* ... | |
done function for AWS Lambda | |
...*/ | |
var data = JSON.parse(event.body); | |
// Make sure this is a page subscription | |
if (data.object === 'page') { | |
// Iterate over each entry - there may be multiple if batched | |
data.entry.forEach(function(entry) { | |
// Iterate over each messaging event | |
entry.messaging.forEach(function(event) { | |
if (event.message) { | |
const message = event.message.text; | |
const senderID = event.sender.id; | |
Apiai.nlu(message, senderID) | |
.then(function(apiaiData){ | |
if (apiaiData.result.source === 'domains') { | |
Facebook.sendTextMessage(senderID, apiaiData.result.fulfillment.speech); | |
} else if (apiaiData.result.source === 'agent') { | |
// TODO Get weather station data | |
} else { | |
Facebook.sendTextMessage(senderID, `Oops, something happened...`); | |
} | |
}) | |
.catch(function(err){ | |
Facebook.sendTextMessage(senderID, `Oops, something happened...`); | |
}); | |
} else { | |
console.log("Webhook received unknown event: ", event); | |
} | |
}); | |
}); | |
done(null, true); | |
} | |
} |
Get data from the weather stations
const Facebook = require('Facebook'); | |
const Apiai = require('Apiai'); | |
const WeatherStation = require('WeatherStation'); | |
exports.handler = (event, context, callback) => { | |
/*... | |
get info from messenger https://gist.github.com/alexandrenicol/f01f867cb175cd89e3e100c508cc6f4a#file-index-js | |
const message, senderID | |
...*/ | |
Apiai.nlu(message, senderID) | |
.then(function(apiaiData){ | |
if (apiaiData.result.source === 'domains') { | |
Facebook.sendTextMessage(senderID, apiaiData.result.fulfillment.speech); | |
} else if (apiaiData.result.source === 'agent') { | |
const action = apiaiData.result.action; | |
const result = apiaiData.result; | |
switch(action){ | |
case 'needwhere': | |
Facebook.sendQuickReply(senderID, result.fulfillment.speech, result.fulfillment.messages[1].replies); | |
break; | |
case 'humidity': | |
WeatherStation.getData() | |
.then(function(weatherStationData){ | |
let value = null; | |
if (WeatherStation.getLocation(result) === 'indoor') { | |
value = weatherStationData.body.devices[0].dashboard_data.Humidity; | |
} else { | |
value = weatherStationData.body.devices[0].modules[0].dashboard_data.Humidity; | |
}; | |
Facebook.sendTextMessage(senderID, `The Humidity is ${value}% `); | |
}).catch(function(err){ | |
Facebook.sendTextMessage(senderID, `Hum, something is wrong with Netatmo :/ `); | |
}); | |
break; | |
case 'temperature': | |
WeatherStation.getData() | |
.then(function(weatherStationData){ | |
let value = null; | |
if (WeatherStation.getLocation(result) === 'indoor') { | |
value = weatherStationData.body.devices[0].dashboard_data.Temperature; | |
} else { | |
value = weatherStationData.body.devices[0].modules[0].dashboard_data.Temperature; | |
}; | |
Facebook.sendTextMessage(senderID, `The temperature is ${value}ºC `); | |
}).catch(function(err){ | |
Facebook.sendTextMessage(senderID, `Hum, something is wrong with Netatmo :/ `); | |
}); | |
break; | |
default: | |
Facebook.sendTextMessage(senderID, `Oops, something happened...`); | |
break; | |
} | |
} else { | |
Facebook.sendTextMessage(senderID, `Oops, something happened...`); | |
} | |
}) | |
.catch(function(err){ | |
Facebook.sendTextMessage(senderID, `Oops, something happened...`); | |
}); | |
/* ... | |
some code https://gist.github.com/alexandrenicol/f01f867cb175cd89e3e100c508cc6f4a#file-index-js | |
... */ | |
} |
Quick reply
class Facebook { | |
/*... | |
static sendTextMessage() {} | |
...*/ | |
static sendQuickReplies(recipientId, text, replies) { | |
// replies --> ['indoor', 'outdoor']; | |
const messageData = { | |
recipient: { | |
id: recipientId | |
}, | |
message: { | |
text: text, | |
metadata: 'needwhere', | |
quick_replies: [] | |
} | |
}; | |
for (let reply of replies){ | |
messageData.message.quick_replies.push({ | |
content_type: 'text', | |
title: reply.capitalize(), | |
payload: reply | |
}) | |
} | |
/*... | |
send the request to Facebook - see https://gist.github.com/alexandrenicol/06b34605925a5c27688032e2a3046b55 | |
...*/ | |
} | |
} |
Landing page
POST /v2.6/me/thread_settings?access_token=************** HTTP/1.1 | |
Host: graph.facebook.com | |
Content-Type: application/json | |
{ | |
"setting_type":"greeting", | |
"greeting":{ | |
"text":"Weather station bot :-)" | |
} | |
} |
Get started
POST /v2.6/me/thread_settings?access_token=************** HTTP/1.1 | |
Host: graph.facebook.com | |
Content-Type: application/json | |
{ | |
"setting_type":"call_to_actions", | |
"thread_state":"new_thread", | |
"call_to_actions":[ | |
{ "payload":"get_started" } | |
] | |
} |
const Facebook = require('Facebook'); | |
exports.handler = (event, context, callback) => { | |
/* ... | |
Get messaging event // see https://gist.github.com/alexandrenicol/f01f867cb175cd89e3e100c508cc6f4a#file-index-js | |
... */ | |
// Iterate over each messaging event | |
entry.messaging.forEach(function(event) { | |
if (event.message) { | |
const message = event.message.text; | |
const senderID = event.sender.id; | |
Facebook.sendTextMessage(senderID, `you said: ${message}`); | |
} else if (event.postback) { | |
if (event.postback.payload === 'get_started'){ | |
Facebook.sendTextMessage(event.sender.id, `Welcome to the Weather station Bot`); | |
} else if (event.postback.payload === 'help'){ | |
Facebook.sendTextMessage(event.sender.id, `You can ask for the current temperature or humidity, either for your indoor or outdoor module`); | |
} else { | |
console.log("Webhook received unknown event: ", event); | |
} | |
} else { | |
console.log("Webhook received unknown event: ", event); | |
} | |
}); | |
}); | |
/*... | |
some code // see https://gist.github.com/alexandrenicol/f01f867cb175cd89e3e100c508cc6f4a#file-index-js | |
... */ | |
} |
Menu
POST /v2.6/me/thread_settings?access_token=************* HTTP/1.1 | |
Host: graph.facebook.com | |
Content-Type: application/json | |
{ | |
"setting_type" : "call_to_actions", | |
"thread_state" : "existing_thread", | |
"call_to_actions":[ | |
{ | |
"type":"postback", | |
"title":"Help", | |
"payload":"help" | |
}, | |
{ | |
"type":"web_url", | |
"title":"Webnicol.fr", | |
"url":"http://webnicol.fr/" | |
} | |
] | |
} |
const Facebook = require('Facebook'); | |
exports.handler = (event, context, callback) => { | |
/* ... | |
Get messaging event // see https://gist.github.com/alexandrenicol/f01f867cb175cd89e3e100c508cc6f4a#file-index-js | |
... */ | |
// Iterate over each messaging event | |
entry.messaging.forEach(function(event) { | |
if (event.message) { | |
const message = event.message.text; | |
const senderID = event.sender.id; | |
Facebook.sendTextMessage(senderID, `you said: ${message}`); | |
} else if (event.postback) { | |
if (event.postback.payload === 'get_started'){ | |
Facebook.sendTextMessage(event.sender.id, `Welcome to the Weather station Bot`); | |
} else if (event.postback.payload === 'help'){ | |
Facebook.sendTextMessage(event.sender.id, `You can ask for the current temperature or humidity, either for your indoor or outdoor module`); | |
} else { | |
console.log("Webhook received unknown event: ", event); | |
} | |
} else { | |
console.log("Webhook received unknown event: ", event); | |
} | |
}); | |
}); | |
/*... | |
some code // see https://gist.github.com/alexandrenicol/f01f867cb175cd89e3e100c508cc6f4a#file-index-js | |
... */ | |
} |
What about an Alexa skill?
https://developer.amazon.com/alexa-skills-kit
https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs
Reminders
• Don't lie, let your users know they're talking to a bot
• Handle errors, let your users know if it fails
• If needed, handover the query
• Create a character for your bot, but don't give them multiple personalities
• Keep it simple
• Listen to your users, and improve their experience
• Not everything can be bot-ed
• We're not good at doing repetitive tasks, we're good at experimenting, trying, failing, and that's how we learn