Chatbots 101
Brighton Web Dev Meetup - Alex Nicol @nicol_alexandre - #RiseOfTheBots
bot2018.webnicol.fr
Me
Alex Nicol - @nicol_alexandre - Github
Developer @ Digital Innovation team // EDF Energy R&D UK Centre
Web / Mobile / AR / Chatbots / Blockchain
What is a chatbot?
Chatbot: An automated computer program that mimics human behaviour in a conversation (text or voice), often by using natural language processing.
Why now?
NLP (AI in general) has never been so cheap and accessible
More than 50% of website traffic is on mobile
People install less and less mobile applications
But there is more and more messaging applications
Virtual assistant everywhere!
https://www.statista.com/statistics/277125/share-of-website-traffic-coming-from-mobile-devices/https://www.comscore.com/Insights/Presentations-and-Whitepapers/2017/The-Global-Mobile-Report
The internet is always evolving
Browser + Website
Smartphone OS + Mobile app
Messaging platform + Chatbots
Advantages
Asynchronous conversation
Multidevice
Excellent to do simple repetitive task quickly
Minimalist UI
24/7 Availability
No queues
Challenges
Platform specific design (voice != text, slack != facebook)
Users expectations are high
Failing gracefully
Discovery
Conversational UX is hard new
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/
The are not new
The first one: Eliza
HANDS ON!
Let's create a Cinema Bot!
Toolbox:
JavaScript / Node.js basic knowledge
AWS Lambda + Serverless (or a server if you prefer)
Dialogflow
Facebook Messenger
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
Create a page
Create an messenger app
https://www.facebook.com/pages/create/https://developers.facebook.com
0. 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'); | |
| } | |
| } |
1. Hello World
1. Hello World
| const data = request.body; | |
| 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) { | |
| Facebook.sendTypingOn(event.sender.id); | |
| //Sending the request to Dialogflow | |
| ApiaiPromised.run(event.message.text, event.sender.id) | |
| .then(apiaiData => { | |
| switch (apiaiData.result.action) { | |
| case 'helloworld': | |
| //sending the message to Facebook | |
| Facebook.sendTextMessage(event.sender.id, 'Bonjour!'); | |
| break; |
2. Small Talk
2. Small Talk
| ApiaiPromised.run(event.message.text, event.sender.id) | |
| .then(apiaiData => { | |
| if (apiaiData.result.source === "domains") { | |
| // if the result comes from a domain, then we just pass the fulfillment | |
| Facebook.sendTextMessage(event.sender.id, apiaiData.result.fulfillment.speech); | |
| } else { | |
| // otherwise we process the action | |
| switch (apiaiData.result.action) { | |
| case 'helloworld': | |
| Facebook.sendTextMessage(event.sender.id, 'Bonjour!'); | |
| break; |
3. Get a list of movies (and use entities)
3. Get a list of movies (and use entities)
| const sendMoviesListResponse = (senderId, movies) => { | |
| const moviesTrailerUrl = movies.map((movie) => movie.url); | |
| Facebook.sendWhiteListedDomains(moviesTrailerUrl); | |
| const response = 'Currently, these are the movies you can see at the cinema:'; | |
| Facebook.sendTextMessage(senderId, response); | |
| Facebook.sendGenericTemplates(senderId, movies); | |
| } | |
| /* | |
| [...] | |
| */ | |
| switch (apiaiData.result.action) { | |
| case 'helloworld': | |
| Facebook.sendTextMessage(event.sender.id, 'Bonjour!'); | |
| break; | |
| case 'listMovies': | |
| let movies = undefined; | |
| if (apiaiData.result.parameters.date !== '' && apiaiData.result.parameters.Cinemas !== '') { | |
| movies = Data.getMoviesAtDateAndCinema(apiaiData.result.parameters.Cinemas, apiaiData.result.parameters.date); | |
| } else if (apiaiData.result.parameters.date !== '') { | |
| movies = Data.getMoviesAtDate(apiaiData.result.parameters.date); | |
| } else if (apiaiData.result.parameters.Cinemas !== '') { | |
| movies = Data.getMoviesAtCinema(apiaiData.result.parameters.Cinemas); | |
| } else { | |
| movies = Data.getMovies(); | |
| } | |
| if(movies) sendMoviesListResponse(event.sender.id, movies); | |
| break; |
| class Facebook { | |
| static sendGenericTemplates(recipientId, templates) { | |
| const messageData = { | |
| recipient: { | |
| id: recipientId | |
| }, | |
| message: { | |
| attachment: { | |
| type: "template", | |
| payload: { | |
| template_type:"generic", | |
| elements:[ | |
| ] | |
| } | |
| } | |
| } | |
| }; | |
| for (let template of templates){ | |
| messageData.message.attachment.payload.elements.push({ | |
| title: template.title, | |
| image_url: template.image, | |
| default_action: { | |
| type: "web_url", | |
| url: template.url, | |
| webview_height_ratio: "tall" | |
| }, | |
| buttons:[ | |
| { | |
| type:"web_url", | |
| url:template.url, | |
| title:"Trailer", | |
| webview_height_ratio: "tall" | |
| },{ | |
| type: "postback", | |
| title: "Screenings", | |
| payload: JSON.stringify({ | |
| type: "WHERE_AND_WHEN", | |
| data: { | |
| movie: template.title | |
| } | |
| }) | |
| } | |
| ] | |
| }) | |
| } | |
| Facebook.sendTypingOff(recipientId); | |
| Facebook.callSendAPI(messageData); | |
| } |
4. Use Contexts and Postback
4. Use Contexts and Postback
| entry.messaging.forEach(function(event) { | |
| if (event.message) { | |
| /* [...] */ | |
| } else if (event.postback) { | |
| let payload = JSON.parse(event.postback.payload); | |
| console.log(payload); | |
| if (payload.type === "WHERE_AND_WHEN"){ | |
| let movie = payload.data.movie; | |
| Facebook.sendTypingOn(event.sender.id); | |
| ApiaiPromised.run(`Where can I see ${movie}`, event.sender.id) | |
| .then(apiaiData => { | |
| const contextInfo = _.find(apiaiData.result.contexts, {"name": "info"}) | |
| let contextDate = undefined; | |
| let contextCinemas = undefined; | |
| if(contextInfo){ | |
| if(contextInfo.parameters.date !== "") { | |
| contextDate = contextInfo.parameters.date; | |
| } | |
| if(contextInfo.parameters.Cinemas !== "") { | |
| contextCinemas = contextInfo.parameters.Cinemas; | |
| } | |
| } | |
| console.log('info', contextDate, contextCinemas, movie); | |
| // do something |
5. Prompts
5. Prompts
| ApiaiPromised.run(event.message.text, event.sender.id) | |
| .then(apiaiData => { | |
| if (apiaiData.result.source === "domains" || apiaiData.result.actionIncomplete) { | |
| // if the result comes from a domain, or is a prompt, then we just pass the fulfillment | |
| Facebook.sendTextMessage(event.sender.id, apiaiData.result.fulfillment.speech); | |
| } else { | |
| // otherwise we process the action | |
| switch (apiaiData.result.action) { | |
| case 'helloworld': | |
| Facebook.sendTextMessage(event.sender.id, 'Bonjour!'); | |
| break; |
6. Bonus: Event, follow ups and quick reply
6. Bonus: Event, follow ups and quick reply
| case 'triggerevent': | |
| ApiaiPromised.event('FEEDBACK', event.sender.id) | |
| .then(apiaiData => { | |
| Facebook.sendQuickReply(recipientId, 'Do you like me?', ['💔','❤️']); | |
| }); | |
| break; | |
| case 'feedback.positive': | |
| Facebook.sendTextMessage(recipientId, '❤️'); | |
| break; | |
| case 'feedback.negative': | |
| Facebook.sendQuickReply(recipientId,'💔'); | |
| break; |
It's yours!
https://github.com/alexandrenicol/cinemabot-bwdmReminders
• Choose your platform
• 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
• Not everything canshould be bot-ed