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")
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. Usingdemo
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 thetype
set tomessages
and theheight
set to300
. Setting thetype
tomessages
will store the chat messages in a list of dictionaries. Each dictionary will have therole
andcontent
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 asmessage
and thechat_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 thechat_history
as two dictionaries. For the message sent by the user, the dictionary keysrole
andcontent
are set touser
and the user’s message, respectively. For the chatbot’s response, the keyrole
is set toassistant
instead, and thecontent
is set as the chatbot’s response. Finally, we return an empty string and thechat_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 thefn
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 theBlocks()
object to run our server. We have also passed an optionalserver_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!
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")
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 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")
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.
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.
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")
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 thechat_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 therespond
function after theadd_message
function.