Chatbots are gaining grounds nowadays, more especially intelligent chatbots that can interact effectively with humans.
In this tutorial, I will walk you through building a Chatbot using Flask, Pusher Channels, and Dialogflow. In the process, we’ll build a chatbot that will help us get details of a movie which we’d like to watch during the weekend. To make it more fun, we’ll build the bot in such a way that anybody viewing the bot page will see in realtime conversations going on. This will enable them to see a new and interesting movie to check out.
Here is a preview of what the final app will look like:
The source code for the application is on GitHub.
Before we start, check that you have the necessary tools installed.
This tutorial uses the following:
Make sure you have the right Python version installed by typing the below in your command line:
python --version
Or:
python3 --version
If the command prints something similar to Python 3.6.*
or higher, then we are good to go.
Having a basic knowledge of Python and JavaScript may be helpful to follow along with this tutorial.
Now, we are ready to start writing code, but first, let’s create a simple structure for our project.
Create the below folders and files in any convenient location on your system:
movie_bot
├── .env
├── .flaskenv
├── index.py
├── requirements.txt
├── static
│ ├── custom.js
│ └── style.css
└── templates
└── index.html
If you prefer using the command line to create the folders/files, you can use the below command:
# Create folders
$ mkdir -p movie_bot && cd movie_bot && mkdir templates static
# Create files
$ touch index.py static/{custom.js,style.css} templates/index.html requirements.txt .flaskenv .env
With this, our project root folder is movie_bot
.
The files and folders created:
Now, add the below libraries to requirements.txt
:
Flask==1.0.2
requests==2.18.4
dialogflow==0.4.0
python-dotenv==0.8.2
pusher==2.0.1
virtualenv is a tool to create isolated Python environments. It creates a folder which contains all the necessary executables to use the packages that a Python project would need.
From your command line, cd (change your directory) into the project root folder - movie_bot
- then execute the below command:
$ python3 -m venv env
Or:
$ python -m venv env
The command to use depends on which is associated with your python3 installation.
Finally, activate the virtual environment:
$ source env/bin/activate
If you are using Windows, activate the virtualenv with the below command:
$ env/Scripts/activate
Now, you should see (env)
beside the folder name on your command line.
Next, add the following to the .flaskenv
file:
FLASK_APP=index.py
FLASK_ENV=development
This will instruct Flask to use index.py
as the main entry file and start up the project in development mode.
In the index.py
file, add the following code to it:
# /index.py
from flask import Flask, request, jsonify, render_template
import os
import dialogflow
import requests
import json
import pusher
app = Flask(__name__)
@app.route('/')
def index():
return render_template('index.html')
# run Flask app
if __name__ == "__main__":
app.run()
Here, we created a route - /
-, which will render the index.html
file in the templates
folder.
Now, install the libraries in requirements.txt
:
pip install -r requirements.txt
Once the installation is complete, run the app:
flask run
If it is successful, the output of your command line should look like below:
Now, visit http://127.0.0.1:5000. You'll get a blank page because there is no content in /templates/index.html
yet.
The OMDb API is a RESTful web service to obtain movie information. When a user asks our bot for details about a movie, we’ll make a request to OMDb to get the detail about the movie.
OMDb requires that we have an API key before you can use their service. To get a free API access key, head to OMDb. Sign up for a free account. An email will be sent to you that contains your API key. You also need to activate your account from the email sent to you.
Next, add the API key to the .env
file:
OMDB_API_KEY=API_KEY
Replace API_KEY
with your correct API key. The key looks like this: 6e**bb*f
.
If we want to get the detail of a movie, we’ll make a request to http://www.omdbapi.com/?t=[MOVIE_TITLE]&apikey=[API_KEY]
. The response will be a JSON object.
Dialogflow is a Google-owned developer of human–computer interaction technologies based on natural language conversations. It will make our chatbot intelligent by using machine learning to understand what our users are saying. All we need to do is train it.
Now head to Dialogflow’s website and create a free account if you don’t have one already. You’ll need a google account to be able to create an account. Once you are on the page, click on the sign in with Google button:
If you already have an account, go ahead and click on the button to log in.
When a user submits a message, we'll send the message to Diaglogflow. Then Dialogflow will detect the intent of the message and send back a reply (fulfillment text) to us. This will be the basic flow of our bot.
For example:
User: Hi Bot: hello! User: Who are you? Bot: I am just a messenger User: Please, show me more details about The mummy Bot: ...
Now, for our bot to know the intent of our user, be it greeting, asking about the bot or asking for details of a movie, we need to train our bot to understand that.
The agent is our bot itself.
Movie_Bot
- and then click on the CREATE button to create the bot:For sure, we know, when some users want to interact with our bot. They might want to exchange greetings. By default, Diagflow has this intent set already. All we need to do is enable it. When enabled, The bot will know when the user is greeting and reply with the appropriate response.
On the sidebar of your console dashboard, click on Small Talk:
Now, enable Small Talk using the toggle button as indicated on the image above. Feel free to customize the responses as you like. Once you are done, click on the SAVE button.
When users ask about a movie, at the moment our bot won't know what to respond with because it does not know about movies. So, let's train it to understand when the user is asking about a movie. This will be a movie intent.
Now,
Next, click on ADD TRAINING PHRASES under the Training Phrases section as indicated in the image above.
Now, type in texts that a user is likely going to use to ask about a movie as seen in the image above.
For example:
đź’ˇ The more your input, the more intelligent your bot will be
When you are done typing it in, click on the SAVE button.
The entity is used for extracting parameter values from the user input. Let's train our bot to know which among the texts is the actual movie name. All we need is the name of the movie so we can search for the movie.
Now,
Next, click on Intents on the sidebar. Under the intents lists, click on movie.
Then for each of the intent you have typed earlier, highlight the movie name. You’ll get an entity option, then select @movie.
When you are done, click on the SAVE button.
Finally, on the same page, scroll down to Fulfillment and enable it to use webhook then SAVE.
To make use of Dialogflow Python library, it requires that we have an API key. So we need to get it.
project_id
. Then add the project_id
to the .env
file: DIALOGFLOW_PROJECT_ID=project_id
Replace project_id
with the project ID you just copied.
Once the page loads,
Your API key will be downloaded automatically.
Now, copy the downloaded JSON file ( Movie-Bot-*.json
) to the root folder of the project - movie_bot
.
Note: This is your API key, you should not commit it to a public repository.
Next, add the the path to the file to the .env
file:
GOOGLE_APPLICATION_CREDENTIALS=Movie-Bot-*.json
Make sure to use the correct file name instead of Movie-Bot-*.json
.
Now when a user asks our bot about a movie, it will detect the intent of the user but can't reply with the movie detail. That is where webhooks come in.
When our bot detects the movie keyword, it will make a request to our webhook using the movie keyword. Now using the movie keyword, we'll query OMDb for details about the movie. Then send the result back to Dialogflow.
Lets a create a new route which we'll use as our webhook. Add the following code to index.py
:
# /index.py
...
@app.route('/get_movie_detail', methods=['POST'])
def get_movie_detail():
data = request.get_json(silent=True)
movie = data['queryResult']['parameters']['movie']
api_key = os.getenv('OMDB_API_KEY')
movie_detail = requests.get('http://www.omdbapi.com/?t={0}&apikey={1}'.format(movie, api_key)).content
movie_detail = json.loads(movie_detail)
response = """
Title : {0}
Released: {1}
Actors: {2}
Plot: {3}
""".format(movie_detail['Title'], movie_detail['Released'], movie_detail['Actors'], movie_detail['Plot'])
reply = {
"fulfillmentText": response,
}
return jsonify(reply)
...
In the preceding code:
get_movie_detail
, which is a POST method. request.get_json()
.data['queryResult']['parameters']['movie'][ 'movie']
from the request data.Now that we have created our webhook for getting details about a movie, let's add the webhook to Dialogflow. Oh wait, this is localhost, Dialogflow can’t access it!
Hold on, Ngrok can help us out here.
Open up a new window of your command line, and type the following command:
ngrok http localhost:5000
Now, you should get a screen like below:
Copy any of the forwarding URLs, the URL similar to this https://0a36e90a.ngrok.io
.
Next,
Fulfillment
on the sidebar of your Dialogflow console.https://0a36e90a.ngrok.io/get_movie_detail
(Replace https://0a36e90a.ngrok.io
with yours).Pusher Channels does the job of adding realtime functionality to applications. We’ll use this to add realtime functionality to our bot. But before we can start using the platform, we need an API key.
Head over to Pusher and create a free account here or sign up (if you don’t have an account already).
Once you are logged in, create a new app then note down your Pusher app_id
, key
, secret
and cluster
. We’ll need them later.
Add the following to the .env
file:
PUSHER_APP_ID=app_id
PUSHER_KEY=key
PUSHER_SECRET=secret
PUSHER_CLUSTER=cluster
Replace app_id
, key
, secret
and cluster
with your own Pusher keys which you have noted down earlier.
We'll create a simple interface where users can type a message and send. On the same page, they should also see their message and that of our bot.
We are using Bootstrap to create the page. Now, add the below HTML markup to templates/index.html
:
<!-- /templates/index.html -->
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css">
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<title>Movie Bot</title>
</head>
<body>
<div class="container h-100">
<div class="row align-items-center h-100">
<div class="col-md-8 col-sm-12 mx-auto">
<div class="h-100 justify-content-center">
<div class="chat-container" style="overflow: auto; max-height: 80vh">
<!-- chat messages -->
</div>
<form id="target">
<input class="input" type="text" value="" placeholder="Enter message..." id="input_message"/>
<input type="submit" hidden>
</form>
</div>
</div>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js"></script>
<script src="https://js.pusher.com/4.1/pusher.min.js"></script>
<script src="{{ url_for('static', filename='custom.js')}}"></script>
</body>
</html>
Here we imported bootstrap
, jQuery
, Pusher library
, style.css
and our custom.js
file, which we are going to use later on.
Next, add the below styles to /static/style.css
:
/* /static/style.css */
body,html {
height: 100%;
}
.chat-container {
margin: 0px;
padding: px;
}
.chat-message {
padding: 6px;
border-radius: 3px;
margin-bottom: 3px;
}
.bot-message {
background: green;
color: white;
}
.human-message {
background: dodgerblue;
color: white;
margin: 13px 1px;
}
.input {
width: 100%;
margin: 35px 0px;
height: 60px;
border: 1px solid rosybrown;
border-radius: 3px;
}
When a user submits a message, we’ll send it to Diagflow to detect the intent of the user. Dialogflow will process the text, then send back a fulfillment response.
Add the following code to index.py
:
...
def detect_intent_texts(project_id, session_id, text, language_code):
session_client = dialogflow.SessionsClient()
session = session_client.session_path(project_id, session_id)
if text:
text_input = dialogflow.types.TextInput(
text=text, language_code=language_code)
query_input = dialogflow.types.QueryInput(text=text_input)
response = session_client.detect_intent(
session=session, query_input=query_input)
return response.query_result.fulfillment_text
...
Finally, let’s create a route that this text will be submitted to. Add the following code to index.py
:
# /index.py
@app.route('/send_message', methods=['POST'])
def send_message():
message = request.form['message']
project_id = os.getenv('DIALOGFLOW_PROJECT_ID')
fulfillment_text = detect_intent_texts(project_id, "unique", message, 'en')
response_text = { "message": fulfillment_text }
return jsonify(response_text)
For the sake of simplicity, we used “unique” as the second parameter in the detect_intent_texts
function. This is meant to be a unique id of the user using our bot so that the bot can maintain session among different users.
Now, let’s display all messages and responses our chat bot’s page.
Add the following code to the static/custom.js
file:
// /static/custom.js
function submit_message(message) {
$.post( "/send_message", {message: message}, handle_response);
function handle_response(data) {
// append the bot repsonse to the div
$('.chat-container').append(`
<div class="chat-message col-md-5 offset-md-7 bot-message">
${data.message}
</div>
`)
// remove the loading indicator
$( "#loading" ).remove();
}
}
Here, we created a function - submit_message
- that will be invoked once a user submits a message.
Then, we’ll use jQuery to send the message to our route - /send_message
- from where it will be processed.
Finally, we’ll append the response to the HTML DOM.
Next, add the following code to static/custom.js
:
// /static/custom.js
$('#target').on('submit', function(e){
e.preventDefault();
const input_message = $('#input_message').val()
// return if the user does not enter any text
if (!input_message) {
return
}
$('.chat-container').append(`
<div class="chat-message col-md-5 human-message">
${input_message}
</div>
`)
// loading
$('.chat-container').append(`
<div class="chat-message text-center col-md-2 offset-md-10 bot-message" id="loading">
<b>...</b>
</div>
`)
// clear the text input
$('#input_message').val('')
// send the message
submit_message(input_message)
});
The preceding code will listen for when the user submits the message, then call the submit_message
function to submit the input text from the user.
Good job! Let’s test what we have now.
Restart the server from the terminal:
ctrl + c
.flask run
.Now visit http://localhost:5000 and start chatting with the bot.
Let’s now make our chatbot more interesting. Any user on the chatbot page should see in realtime conversation currently going on.
Initialize the Pusher Python library by adding the below code to index.py
after the imports:
# /index.py
...
# initialize Pusher
pusher_client = pusher.Pusher(
app_id=os.getenv('PUSHER_APP_ID'),
key=os.getenv('PUSHER_KEY'),
secret=os.getenv('PUSHER_SECRET'),
cluster=os.getenv('PUSHER_CLUSTER'),
ssl=True)
...
Next, trigger an event to Pusher when a user sends a message.
Add the following to the send_message
function exactly before *return*
jsonify(response_text)
in index.py
file:
socketId = request.form['socketId']
pusher_client.trigger('movie_bot', 'new_message',
{'human_message': message, 'bot_message': fulfillment_text},
socketId)
In the preceding code:
movie_bot
- with an event named -new_message
message
- and the reply of our bot - fulfillment_text
- along with the connected user socketId
We are passing the socketId
so that the user triggering the event will not get the message back.
Now let’s listen for the new_message
event on the movie_bot
channel. When the event occurs, we’ll attach the message to the HTML DOM.
Add the following code to the topmost part of static/custom.js
:
// /static/custom.js
// Initialize Pusher
const pusher = new Pusher('<PUSHER_KEY>', {
cluster: '<CLUSTER>',
encrypted: true
});
// Subscribe to movie_bot channel
const channel = pusher.subscribe('movie_bot');
// bind new_message event to movie_bot channel
channel.bind('new_message', function(data) {
// Append human message
$('.chat-container').append(`
<div class="chat-message col-md-5 human-message">
${data.human_message}
</div>
`)
// Append bot message
$('.chat-container').append(`
<div class="chat-message col-md-5 offset-md-7 bot-message">
${data.bot_message}
</div>
`)
});
Don’t forget to replace <PUSHER_KEY>
and <CLUSTER>
with your correct Pusher details.
Finally, update the POST request in the submit_message
function in the static/custom.js
file so that it passes along the Pusher socket_id
of the user sending the message:
...
$.post( "/send_message", {
message: message,
socketId: pusher.connection.socket_id
}, handle_response);
...
To test the app, restart the server. Ensure ngrok is running.
Note: if you have restarted ngrok, you will need to add the new address to Dialogflow.
In this tutorial, you have learned how to build a chatbot using Flask and Dialogflow. Also, you learned how to add realtime functionality to the chatbot using Pusher.
Now that you are confident in building chatbots, why not build something! What about building a todo bot for your company? Don’t forget to use Pusher to add realtime to the app so that others will see the todos been added, and manipulated in realtime.
The source code for the application is on GitHub.
© 2019 Pusher Ltd. All rights reserved.
Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 160 Old Street, London, EC1V 9BW.