Back to search

Build a chatbot with Flask and Dialogflow

  • Gideon Onwuka
June 20th, 2018
You will need Python 3.6+ and ngrok installed on your machine.

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:

flask-movie-chatbot-demo

The source code for the application is on GitHub.

Prerequisite

Before we start, check that you have the necessary tools installed.

This tutorial uses the following:

  • Python 3.6 (If you are a Windows user, you can follow this guide to install it. For Mac/Linux users, you can install it by following the guide here or here respectively if you don’t have it installed already)
  • Pusher Channels
  • JavaScript (jQuery)
  • Dialogflow
  • Ngrok (Download and install it here if you don’t have it already)

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.

Creating our project

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:

  • index.py: the main entry point of our project.
  • requirements.txt: all libraries our project will be using.
  • static: contains Static files like css and js.
  • templates: our HTML files will live here.
  • .flaskenv: this is where we’ll store Flask environment variables.
  • .env: we’ll use this to store our private keys.

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
  • requests: we’ll use this library to make a request to external URLs.
  • dialogflow: to interact with Dialogflow’s API.
  • python-dotenv: will be used by Flask to load environment configurations files.
  • pusher: to add realtime to our chatbot.

Creating a virtual environment

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.

Adding Flask environment configuration

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.

Starting up Flask

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:

flask-movie-chatbot-flask-run

Now, visit http://127.0.0.1:5000. You'll get a blank page because there is no content in /templates/index.html yet.

Getting our movie API key

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.

Setting up your Dialogflow account

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:

flask-movie-chatbot-login-dialogflow

If you already have an account, go ahead and click on the button to log in.

Dialogflow setup

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.

Creating our agent

The agent is our bot itself.

flask-movie-chatbot-create-agent-1

  • Then, click on Create Agent to create a new agent.
  • Next, type in the bot name - Movie_Bot - and then click on the CREATE button to create the bot:

flask-movie-chatbot-create-agent-2

Small talk

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:

flask-movie-chatbot-enable-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.

Movie intent

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,

  • Click on Intents on the sidebar of your dashboard
  • Then click on the CREATE INTENT button
  • Next, type in movie as the intent name
  • Finally, click on the SAVE button

flask-movie-chatbot-create-intent

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:

  • Show details about The Mummy
  • More about Black Panther
  • Give me synopsis about The Godfather
  • I want to know more about Upgrade

💡 The more your input, the more intelligent your bot will be

When you are done typing it in, click on the SAVE button.

Creating our entity

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,

  • Click on Entities on the sidebar.
  • Next, click on the CREATE ENTITY button.
  • Type in movie as the ENTITY NAME as seen in the image below.
  • Then add one or more movie name of your choice as seen in the image below.
  • Finally, click on the SAVE button.

flask-movie-chatbot-create-entity

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.

flask-movie-chatbot-populate-entity

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.

flask-movie-chatbot-enable-webhooks-dialogflow

Getting our API keys

To make use of Dialogflow Python library, it requires that we have an API key. So we need to get it.

  • Select our bot project name - Movie-Bot (This is the same as the name we gave our Agent)

flask-movie-chatbot-project-id

  • Copy out the 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.

  • Next, go to APIs & Services then Credentials

flask-movie-chatbot-credentials-1

  • Under Create credentials, click on Service account key

flask-movie-chatbot-credentials-2

Once the page loads,

  • Select Dialogflow integrations under Service account.
  • Then select JSON under key type.

flask-movie-chatbot-credentials-3

  • Finally, click on the Create button to download your API key

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.

Setting up a webhook

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:

  • We created a route named get_movie_detail, which is a POST method.
  • Then, we got the json request that will be sent by Diagflow to the route using request.get_json().
  • Next, we extracted the movie keyword using data['queryResult']['parameters']['movie'][ 'movie'] from the request data.
  • Finally, we returned a JSON response of the movie detail.

Exposing our localhost to the world

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:

flask-movie-chatbot-ngrok

Copy any of the forwarding URLs, the URL similar to this https://0a36e90a.ngrok.io.

Next,

  • Click on Fulfillment on the sidebar of your Dialogflow console.
  • Next, enable the webhook using the toggle button as indicated on the image below.
  • Then, add the URL you just copied from Ngrok - https://0a36e90a.ngrok.io/get_movie_detail (Replace https://0a36e90a.ngrok.io with yours).
  • Finally, click the SAVE button to save.

flask-movie-chatbot-add-ngrok-to-dialogflow

Getting our Pusher API key

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.

Adding our Pusher keys

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.

Creating our the chat bot user interface

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;
    }

Sending messages to Dialogflow and displaying responses

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.

Displaying messages

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.

Testing it out

Good job! Let’s test what we have now.

Restart the server from the terminal:

  • Terminate the server by typing ctrl + c.
  • Restart the server using flask run.

Now visit http://localhost:5000 and start chatting with the bot.

Adding realtime functionality

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)

    ...

Triggering an event

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:

  • We’ll trigger an event to Pusher on a channel - movie_bot - with an event named -new_message
  • Then, we’ll also pass along the message the user typed - 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.

Listening for an event

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.

Conclusion

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.

  • Channels

© 2018 Pusher Ltd. All rights reserved.

Pusher Limited is a company registered in England and Wales (No. 07489873) whose registered office is at 28 Scrutton Street, London EC2A 4RP.