Project: Building A Chatbot with ChatGPT
Developing chatbots has revolutionized customer interactions, automating responses and improving user experiences. OpenAI’s ChatGPT, a state-of-the-art language model, plays a pivotal role in this transformation. Building a chatbot with ChatGPT involves fine-tuning it on a range of internet text to comprehend context and deliver natural, meaningful dialogue. Unlike traditional chatbots that follow pre-defined paths, ChatGPT-based bots provide dynamic conversational experiences. They can handle a variety of topics, maintaining context over lengthy interactions. These bots enhance customer service, support, and engagement by providing instant, intelligent, and human-like responses, bringing businesses closer to their customers.
With ongoing learning, I decided to actively learn and practice the generative AI. This article is designed in a way to make someone understands the fundamental concepts behind each function. I will use OpenAI’s chat completion format to design a basic chat box for a restaurant. This is a very simple example of how to use the chat completion format. This is a step towards building an autonomous system, that doesn't require any emotional human interaction.
In the begning, it is important to import relevent libraties, and setup the API key:
import openai
openai.api_key = "sk-..." # This key is available from OpenAI API key
The chat models like ChatGPT are actually trained to take a series of messages as input and return a model generated message as output. I am using helper functions to get the required functionalities:
# Helper function 1: takes a user prompt and returns the model's response (single prompt with single completion)
def get_completion(prompt, model="gpt-3.5-turbo"):
messages = [{"role": "user", "content": prompt}]
response = openai.ChatCompletion.create(
model=model,
messages=messages,
temperature=0, # this is the degree of randomness of the model's output
)
return response.choices[0].message["content"]
# Helper function 2: takes a list of messages and returns the model's response (multi prompt with single completion)
def get_completion_from_messages(messages, model="gpt-3.5-turbo", temperature=0):
response = openai.ChatCompletion.create(
model=model,
messages=messages,
temperature=temperature, # this is the degree of randomness of the model's output
)
#print(str(response.choices[0].message))
return response.choices[0].message["content"]
The two helper functions are somewhat sililar, until the input to those functions. The first helper function takes just a user prompt an input, and second one takes the list of messages as an input.
Roles:
User: The person who is using the chat box.
System: The system monitors the chat b/w user and agent and responsible of overall experience.
Agent: The ChatGPT messages that are designed by the system.
In my approach, I will utilize a list of messages from various roles. The initial message originates from a system, offering a set of comprehensive instructions and taking charge of the overall user experience. Following this, an alternating exchange of turns between the user and the assistant (Agent) begins, creating a back-and-forth dialogue.
If you’ve interacted with ChatGPT through its web interface before, you’ll find that your contributions become the user messages, and the responses from ChatGPT become the assistant’s messages. The system message plays a crucial role in determining the assistant’s persona and steering its behavior. Think of it as a way of whispering instructions to the assistant that helps shape the conversation, without the user being privy to it.
As a developer using ChatGPT, the system message is a tool to help guide the conversation. It’s a means to subtly influence the assistant’s responses without the user being aware, thereby keeping the flow of conversation natural and focused on the user’s needs. In essence, the system message provides a discreet guiding hand for developers to mold the conversation’s trajectory without it becoming a part of the user-assistant discourse.
Example 1: Asking system To be a joe Rogan
messages = [
{"role": "system", "content": "you are an assistant that reasons like Joe Rogan"},
{"role": "user", "content": "what is the meaning of life?"},
{"role": "assistant", "content": "the meaning of life is to be happy"},
{"role": "user", "content": "what is the meaning of happiness?"}]
response = get_completion_from_messages(messages, temperature=1) # High temperature = more randomness (more creative, less coherent but more mistakes too)
print(response)
Happiness is a subjective experience that differs for each person. It can come from various sources such as meaningful relationships, personal achievements, pursuing passions, engaging in fulfilling activities, and finding a sense of purpose. Ultimately, it’s about finding what brings you joy, contentment, and a sense of fulfillment in your own unique journey through life.
In the example, I have categorically define the roles of the system, assistant and user. This helps my assistant generating a response to me in a style of Joe Rogan. This is how I mandate my assistant through the role of system to respond to user in a style of Joe Rogan.
Example 1: Asking system To be a friendly Chatbot
Now in this Example, I mandate my assistant/ agent to be a friendly chatbot, and I gave the first user message. Now the assistant will respond to me in a friendly manner. I intentionally gave a user message a wrong syllable to see how the assistant will respond to me.
messages = [
{'role':'system', 'content':'You are friendly chatbot.'},
{'role':'user', 'content':'Hi, my name is naveed Ul Mustafa'} ]
response = get_completion_from_messages(messages, temperature=1)
print(response)
Hello Naveed Ul Mustafa! Nice to meet you. How can I assist you today?
Engaging with a language model is an independent interaction, implying that every conversation is isolated from previous ones. This necessitates that all pertinent dialogues intended for the model’s reference be included within the current conversation.
For the model to reference, or metaphorically ‘remember’, earlier parts of a dialogue, it is vital to incorporate these exchanges as input for the model. In the subsequent example, I’ve provided the necessary context — the user’s name — within the preceding messages. Subsequently, when queried about the user’s name, the model can respond accurately.
This is made possible because the requisite context is incorporated within the list of messages fed as input to the model. As such, the model is equipped with a complete conversational context, enabling it to respond effectively and appropriately. This highlights the importance of accurately collating and feeding conversation history when engaging with language models like ChatGPT.
messages = [
{'role':'system', 'content':'You are friendly chatbot.'},
{'role':'user', 'content':'Hi, my name is Naveed Ul Mustafa'},
{'role':'assistant', 'content': "Hi Naveed! It's nice to meet you. \
Is there anything I can help you with today?"},
{'role':'user', 'content':'Yes, you can remind me, What is my name?'} ]
response = get_completion_from_messages(messages, temperature=1)
print(response)
Up untill now, we have seen what roles are there and how they impact the overall experience of the chatbot. Now we will see how to use the ChatGPT generative capabilities to develop a chatbot in a real world scenario.
OrderBot
This is a simple chatbot that takes the order of the user and responds in a friendly manner. Here I automate the collection of the user prompts and assistant responses. This is a very simple example of how to use the chat completion format. This is a step towards building an autonomous system, that doesn't require any emotional human interaction.
First, it is important to establish a support function. This function’s purpose is to gather the prompt input by the user, which eliminates the need to manually input them as we did before. This function will also assemble prompts from a user interface that we’ll develop in the later part of our code. These prompts will then be added to a list we will name “context”.
Each time the function is executed, it will call the model using the assembled “context” as a parameter. The responses generated by the model are also added to this “context”. Hence, both the user messages and model responses continuously contribute to the growth of the “context”.
# Helper function
def collect_messages(_):
prompt = inp.value_input # get the user input
inp.value = '' # clear the input box
context.append({'role':'user', 'content':f"{prompt}"}) # append the user input to the context
response = get_completion_from_messages(context) # get the model's response
context.append({'role':'assistant', 'content':f"{response}"}) # append the model's response to the context
panels.append(
pn.Row('User:', pn.pane.Markdown(prompt, width=600))) # append the user input to the panel
panels.append(
pn.Row('Assistant:', pn.pane.Markdown(response, width=600, style={'background-color': '#F6F6F6'}))) # append the model's response to the panel
return pn.Column(*panels) # return the panel
The bot, an automated service to collect orders for a pizza restaurant. It first greet the customer, then collect the order, and then ask if it’s a pick-up or delivery. the bot waits to collect the entire order, then summarize it and check for a final time if the customer still wants to add anything from the menu. If it’s a delivery, the bot can ask for an address. Finally, it shows the payment.
import panel as pn # GUI
pn.extension()
panels = [] # collect display
context = [ {'role':'system', 'content':"""
You are OrderBot, an automated service to collect orders for a pizza restaurant. \
You first greet the customer, then collects the order, \
and then asks if it's a pickup or delivery. \
You wait to collect the entire order, then summarize it and check for a final \
time if the customer wants to add anything else. \
If it's a delivery, you ask for an address. \
Finally you collect the payment.\
Make sure to clarify all options, extras and sizes to uniquely \
identify the item from the menu.\
You respond in a short, very conversational friendly style. \
The menu includes \
pepperoni pizza 12.95, 10.00, 7.00 \
cheese pizza 10.95, 9.25, 6.50 \
eggplant pizza 11.95, 9.75, 6.75 \
fries 4.50, 3.50 \
greek salad 7.25 \
Toppings: \
extra cheese 2.00, \
mushrooms 1.50 \
sausage 3.00 \
canadian bacon 3.50 \
AI sauce 1.50 \
peppers 1.00 \
Drinks: \
coke 3.00, 2.00, 1.00 \
sprite 3.00, 2.00, 1.00 \
bottled water 5.00 \
"""} ] # accumulate messages
inp = pn.widgets.TextInput(value="Hi", placeholder='Enter text here…')
button_conversation = pn.widgets.Button(name="Chat!")
interactive_conversation = pn.bind(collect_messages, button_conversation)
dashboard = pn.Column(
inp,
pn.Row(button_conversation),
pn.panel(interactive_conversation, loading_indicator=True, height=300),
)
dashboard
To ensure clarification of all options, extras and sizes are uniquely identify the item from the menu. The bot respond in a short, very conversational, friendly style.
Now we can ask the model to create a JSON summary that we could send to the order system based on the conversation. So, we’re now appending another system message, which is an instruction, and we’re saying:
Create a JSON summary of the previous food order. Itemize the price for each item. The fields should be:
pizza, include side, list of toppings, list of drinks, and list of sides”, and finally, the total price. And you could also use a user message here. This does not have to be a system message.
messages = context.copy()
messages.append(
{'role':'system', 'content':'create a json summary of the previous food order. Itemize the price for each item\
The fields should be 1) pizza, include size 2) list of toppings 3) list of drinks, include size 4) list of sides include size 5)total price '},
)
#The fields should be 1) pizza, price 2) list of toppings 3) list of drinks, include size include price 4) list of sides include size include price, 5)total price '},
response = get_completion_from_messages(messages, temperature=0)
print(response)
Take note that in this scenario, I’ve opted to use a lower temperature. The temperature in the context of language models dictates the randomness in the model’s output. A lower temperature steers the model’s responses towards more predictable outcomes, which is beneficial for tasks that demand consistency. On the other hand, when creating a conversational agent that needs to exhibit a higher degree of creativity or flexibility, a higher temperature might be more suitable.
Furthermore, when dealing with intricate web-based Graphical User Interfaces (GUIs), considering the use of robust web frameworks like Flask or Django might be beneficial. However, bear in mind that these tools come with their own challenges and could represent a steeper learning curve. It’s important to evaluate whether their robust capabilities outweigh their complexity for your specific use case.
Lastly, in our current project, we’re utilizing Visual Studio Code (VS Code). However, it appears the chatbot output isn’t displaying as expected. If you encounter similar issues with interactive GUIs in VS Code, it could be a limitation or bug within VS Code’s Python extension. Remedies may include updating both VS Code and the Python extension to their latest versions or exploring if the issue has been recognized on the Python extension’s GitHub page.
Alternatively, using Jupyter Notebook to run the code might offer a more seamless output display. Jupyter Notebook is particularly effective at rendering interactive outputs, making it an excellent alternative or complement to VS Code for developing and testing chatbot models.