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)
https://www.comscore.com/Insights/Presentations-and-Whitepapers/2016/The-2016-US-Mobile-App-Report
Mobile app usage in 2016 (2/2)
https://www.comscore.com/Insights/Presentations-and-Whitepapers/2016/The-2016-US-Mobile-App-Report
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