Types of Chatbots

Learn about the different types of chatbots and get introduced to Gradio.

Chatbots come in all shapes and forms. We might have even used a few without even noticing them. Let’s review them in order of increasing complexity. Reading about them would not be fun, so we’ll interact with them throughout this course. Before we begin, let’s take a brief look at Gradio, the framework we will use to interact with chatbots.

Hello Gradio

Gradio is an open-source Python framework for creating interactive demos for Python programs. It provides easy-to-use methods for creating a wide range of visualizations. Fortunately, it provides a quick and simple way to create a chatbot.

import gradio as gr

with gr.Blocks() as demo:
    chatbot = gr.Chatbot(type="messages", height=300)
    msg = gr.Textbox()

    def respond(message, chat_history):
        bot_message = "You said:" + message
        chat_history.append({"role": "user", "content": message})
        chat_history.append({"role": "assistant", "content": bot_message})
        return "", chat_history

    msg.submit(respond, [msg, chatbot], [msg, chatbot])

demo.launch(server_name="0.0.0.0")
A simple echo bot that replies with the chat input

Here’s what is happening in the code:

  • Line 1: We import Gradio as gr.

  • Line 3: We use gr.Blocks() to create the interface for our code. Using demo is a common convention with Gradio, but we can choose any variable of our liking.

  • Line 4: We create the chatbot using the gr.Chatbot() method, with the type set to messages and the height set to 300. Setting the type to messages will store the chat messages in a list of dictionaries. Each dictionary will have the role and content keys. This is the most common format that is used by most LLM APIs.

  • Line 5: We create an input text box with the gr.Textbox() method.

  • Lines 7–11: We create a function named respond that will take in the user input as message and the chat_history which is a reference to our chatbot object. We prepend the phrase “You said: ” to the user’s message. The users’ message and the response are then appended to the chat_history as two dictionaries. For the message sent by the user, the dictionary keys role and content are set to user and the user’s message, respectively. For the chatbot’s response, the key role is set to assistant instead, and the content is set as the chatbot’s response. Finally, we return an empty string and the chat_history object.

  • Line 13: The textbox object has a submit method that can be used to call a function each time the user submits their input. We pass three things to this method.

    • The first is the function name, which is the respond function in our case. This can also be explicitly defined by using the fn parameter name.

    • Next, we pass the list of inputs to the function, in this case, it is the textbox object and the chatbot object. This can also be explicitly defined by using the inputs parameter name. This must always be a list, even if it is empty.

    • Finally, we pass the list of outputs from the function, which, in this case, is the textbox object and the chatbot object again. Returning the empty string from the function and choosing the textbox as the output ensures that we clear the textbox upon submission. The outputs can be explicitly defined by using the outputs parameter name. This must also always be a list, even if it is empty.

  • Line 15: Finally, we use the launch() method of the Blocks() object to run our server. We have also passed an optional server_name parameter to ensure that the demo is accessible.

Now that we know how to create the frontend, let’s look at some chatbots.

When re-running the applications, you might encounter an “Error reloading the application” error. You can safely ignore this. This occurs because of how the Gradio watcher interacts with the code files on the Educative platform.

We encourage you the modify the chatbots and re-run them to view the changes!

Press + to interact
Gradio error that can be safely ignored on the Educative platform
Gradio error that can be safely ignored on the Educative platform

Rule-based chatbots

Rule-based chatbots are the simplest type of chatbots. They normally operate on a defined set of rules and use “if-then” logic to determine responses. They are straightforward to build and maintain for specific, limited use cases.

import gradio as gr

with gr.Blocks() as demo:
    chatbot = gr.Chatbot(type="messages", height=300)
    msg = gr.Textbox()

    def respond(message, chat_history):
        lower_case_message = message.lower()
        if "hello" in lower_case_message:
            bot_message = "Hello there!"
        elif "how are you" in lower_case_message:
            bot_message = "I'm doing well, thanks for asking!"
        elif "bye" in lower_case_message:
            bot_message = "Goodbye!"
        else:
            bot_message = "I didn't understand that."
        
        chat_history.append({"role": "user", "content": message})
        chat_history.append({"role": "assistant", "content": bot_message})
        return "", chat_history

    msg.submit(respond, [msg, chatbot], [msg, chatbot])

demo.launch(server_name="0.0.0.0")
A simple rule-based chatbot

We modified our echo bot code to have a few if conditions. Based on the user’s input, an appropraite response is sent back. The if-else conditions can be made much more complex to increase the bots’ interactivity. While these chatbots have no contextual understanding and are not able to handle variations in language or unexpected input, they can still be used as simple FAQ bots or menu bots.

Let’s take it a step further and introduce pattern-matching.

Pattern-matching chatbots

Pattern-matching chatbots use pattern recognition techniques, often using regular expressions (regex)Regex (Regular Expression) is a powerful tool used for searching, matching, and manipulating text based on specific patterns. It's commonly used in programming and text processing tasks to identify strings that match a particular pattern. to identify patterns in user input. These patterns are matched to pre-defined responses. To make the responses feel natural and non-repetitive, we can use random.choice() to randomly sample a response from the list of responses.

import gradio as gr
import re
import random

with gr.Blocks() as demo:
    chatbot = gr.Chatbot(type="messages", height=300)
    msg = gr.Textbox()

    # Define patterns and responses
    patterns = {
        r"hello|hi": ["Hello there!", "Hi!", "Greetings!"],
        r"how are you": ["I'm doing well, thanks for asking!", "I'm okay."],
        r"what is your name": ["You can call me Chatty!", "I'm Chatty."],
        r"bye|goodbye": ["Goodbye!", "See you later!"],
        r"(.*)": ["I didn't quite understand that. Can you rephrase?"]
    }

    def respond(message, chat_history):
        for pattern, response in patterns.items():
            match = re.search(pattern, message.lower())
            if match:
                bot_message = random.choice(response)
                chat_history.append({"role": "user", "content": message})
                chat_history.append({"role": "assistant", "content": bot_message})
                return "", chat_history

    msg.submit(respond, [msg, chatbot], [msg, chatbot])

demo.launch(server_name="0.0.0.0")
A simple pattern-matching chatbot

Our simple implementation uses a patterns dictionary of inputs and their corresponding responses. It uses regex to search for the inputs in the user’s message and returns a random output. Chatbots that use pattern-matching are more flexible than purely rule-based bots and can handle phrasing and sentence structure variations. They do not require exact keyword matches either. However, they still lack the contextual awareness and learning capabilities of more advanced chatbots.

Speaking of advanced chatbots, machine learning-based chatbots were the norm a few years ago before they were outclassed by generative AI-based chatbots. We will create a machine learning-based chatbot in an upcoming lesson. For now, let’s jump ahead and discuss the current state-of-the-art offerings.

State-of-the-art chatbots

The leading chatbots of today are powered by large language models (LLMs). These models represent a significant leap in AI, pushing the boundaries of what chatbots can achieve. These models are trained on huge amounts of data and use neural networks with billions or perhaps even trillions of parameters.

This has allowed these new models to:

  • Generate new and contextually relevant (retrieval-based) text on the fly.

  • Adapt to new topics and tasks with minimal or even no specific training data.

  • Bridge text with other modalities like images, audio, and video.

This industry is moving rapidly, with new models or variants releasing every month. Here are a few popular chatbots at the time of writing this course.

Press + to interact

Bonus: Adding streaming to the chatbot

Streaming allows responses to be displayed as the chatbot generates them. This provides a more interactive and engaging user experience. Users can see the response being formed gradually rather than waiting for the entire response to be generated before it appears. Streaming can also make the chatbot feel more responsive and natural, as if it is thinking and responding in real-time. While the chatbots we created in this lesson do not generate the responses step-by-step, we can still create the illusion of streaming.

You can interact with the widget below to visualize this concept.

Please login to launch live app!

Let’s understand the workflow. Currently, when the user submits their query, our respond function is called that matches the query to a response and returns two things, an empty message that will clear the input field and the chatbot object with the updated query and response.

First, we will create an add_message function that will take the user’s message and the chatbot history as input. We’ll return an empty message to clear the input and add the user’s message to the history. Here’s the code:

def add_message(user_message, chat_history):
return "", chat_history + [{"role": "user", "content": user_message}]

Next, we will modify the respond function. Our function now only needs to take the chatbot history as input. We can retrieve the users’s most recent message by accessing the value of the content key of the last dictionary in the chat history as such chat_history[-1]["content"]. Now, instead of returning the empty input and the chat_history, we will write a generator that will iterate over the response, character by character. Let’s examine the complete code to understand how everything will piece together.

import gradio as gr
import random
import time
import re

with gr.Blocks() as demo:
    chatbot = gr.Chatbot(type="messages", height=300)
    msg = gr.Textbox()

    patterns = {
        r"hello|hi": ["Hello there!", "Hi!", "Greetings!"],
        r"how are you": ["I'm doing well, thanks for asking!", "I'm okay."],
        r"what is your name": ["You can call me Chatty!", "I'm Chatty."],
        r"bye|goodbye": ["Goodbye!", "See you later!"],
        r"(.*)": ["I didn't quite understand that. Can you rephrase?"]
    }

    def add_message(user_message, chat_history):
        return "", chat_history + [{"role": "user", "content": user_message}]

    def respond(chat_history):
        message = chat_history[-1]["content"]
        bot_message = ''
        for pattern, response in patterns.items():
            match = re.search(pattern, message.lower())
            if match:
                bot_message = random.choice(response)
                break
        chat_history.append({"role": "assistant", "content": ""})
        for character in bot_message:
            chat_history[-1]["content"] += character
            time.sleep(0.05)
            yield chat_history

    msg.submit(add_message, [msg, chatbot], [msg, chatbot]).then(
        respond, chatbot, chatbot
    )

demo.launch(server_name="0.0.0.0")
Adding streaming functionality to the chatbot

Here are the changes we made:

  • Lines 18–19: We added the add_message function to handle the input and update the chat history.

  • Lines 22–23: Instead of the message being passed as input, we retrieve it from the chat history. We also initialized a bot_message variable to store the response.

  • Line 28: We break out of the loop once a response is found.

  • Line 29: We set the chatbot’s response to an empty string. This will allow us to perform string concatenation.

  • Lines 30–33: We iterate over the response character by character, append each character to the chatbot’s response in the chat history, pause for 0.05 seconds, and finally yield the chat_history. Feel free to modify the pause duration to see how that affects the output.

  • Lines 35–36: Since we now want to call two functions when the user submits the query, we use the .then method to call the respond function after the add_message function.