...
/Types of Chatbots: Rule-Based, Pattern-Matching, and GenAI
Types of Chatbots: Rule-Based, Pattern-Matching, and GenAI
Learn about the different types of chatbots and get introduced to Gradio.
We'll cover the following...
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. Usingdemois 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 thetypeset tomessagesand theheightset to300. Setting thetypetomessageswill store the chat messages in a list of dictionaries. Each dictionary will have theroleandcontentkeys. 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
respondthat will take in the user input asmessageand thechat_historywhich 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_historyas two dictionaries. For the message sent by the user, the dictionary keysroleandcontentare set touserand the user’s message, respectively. For the chatbot’s response, the keyroleis set toassistantinstead, and thecontentis set as the chatbot’s response. Finally, we return an empty string and thechat_historyobject.Line 13: The textbox object has a
submitmethod 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
respondfunction in our case. This can also be explicitly defined by using thefnparameter 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
inputsparameter 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
outputsparameter 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_nameparameter 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_messagefunction 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_messagevariable 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.05seconds, 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
.thenmethod to call therespondfunction after theadd_messagefunction.