Project: Building a Simple Chatbot with Memory
Project Steps
1. Project Setup
- Create a new directory for your project:
mkdir chatbot_project
cd chatbot_project - Create a virtual environment:
python -m venv venv
- Activate the virtual environment:
- On Windows:
venv\Scripts\activate
- On macOS and Linux:
source venv/bin/activate
- Install the required libraries:
pip install flask openai flask-session python-dotenv streamlit psycopg2
- Create a
.env
file in your project directory and add your OpenAI API key and database url:OPENAI_API_KEY=YOUR_OPENAI_API_KEY
DATABASE_URL=YOUR_DATABASE_URL # e.g., "postgresql://user:password@host:port/database_name" - For the postgres database, make sure you have a postgres database running and the url is correctly configured.
2. Flask Implementation
- Create a file named
app.py
with the following code:from flask import Flask, request, render_template, session, redirect, url_for
import openai
import os
from dotenv import load_dotenv
from flask_session import Session
import psycopg2 #for postgres
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL")
app = Flask(__name__)
app.secret_key = os.urandom(24)
app.config["SESSION_TYPE"] = "filesystem" # Or "postgresql", "redis", etc. For production, use a proper database.
#app.config["SESSION_TYPE"] = "postgresql"
#app.config["SESSION_PERMANENT"] = True # Optional: Make sessions permanent
#app.config["SESSION_SQLALCHEMY"] = {'url': DATABASE_URL} #needs Flask-SQLAlchemy
Session(app)
# --- Database Connection (PostgreSQL) ---
def get_db_connection():
"""Gets a PostgreSQL connection."""
try:
conn = psycopg2.connect(DATABASE_URL)
return conn
except psycopg2.Error as e:
print(f"Database connection error: {e}")
return None
def create_table():
"""Creates the messages table if it doesn't exist."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS messages (
id SERIAL PRIMARY KEY,
session_id TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit()
except psycopg2.Error as e:
print(f"Error creating table: {e}")
finally:
conn.close()
def store_message(session_id: str, role: str, content: str):
"""Stores a message in the database."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute(
"INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
(session_id, role, content),
)
conn.commit()
except psycopg2.Error as e:
print(f"Error storing message: {e}")
finally:
conn.close()
def get_history_from_db(session_id: str) -> List[Dict[str, str]]:
"""Retrieves the conversation history for a given session ID from the database."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute(
"SELECT role, content FROM messages WHERE session_id = %s ORDER BY created_at",
(session_id,),
)
history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
return history
except psycopg2.Error as e:
print(f"Error retrieving history from database: {e}")
return []
finally:
conn.close()
return []
create_table() # Ensure the table exists
# --- Chat route ---
@app.route("/", methods=["GET", "POST"])
def chat():
"""Handles the main chat functionality."""
if "session_id" not in session:
session["session_id"] = os.urandom(16).hex() # Generate unique session ID
session_id = session["session_id"]
history = get_history_from_db(session_id) # Get history from the database
if not history:
history = [{"role": "system", "content": "You are a helpful assistant."}]
session["history"] = history # Initialize session
if request.method == "POST":
user_input = request.form["user_input"]
store_message(session_id, "user", user_input) # Store to database
history.append({"role": "user", "content": user_input})
try:
response = openai.ChatCompletion.create(
model="gpt-4o",
messages=history,
temperature=0.6,
)
assistant_reply = response.choices[0].message.content
store_message(session_id, "assistant", assistant_reply) # Store to database
history.append({"role": "assistant", "content": assistant_reply})
session["history"] = history
session.modified = True # IMPORTANT
return render_template("chat.html", history=history[1:]) # Pass history to template
except openai.error.OpenAIError as e:
error_message = f"OpenAI API Error: {e}"
print(error_message)
store_message(session_id, "assistant", error_message)
history.append({"role": "assistant", "content": error_message})
session["history"] = history
session.modified = True
return render_template("chat.html", history=history[1:])
except Exception as e:
error_message = f"An unexpected error occurred: {e}"
print(error_message)
store_message(session_id, "assistant", error_message)
history.append({"role": "assistant", "content": error_message})
session["history"] = history
session.modified = True
return render_template("chat.html", history=history[1:])
return render_template("chat.html", history=history[1:])
@app.route("/clear", methods=["POST"])
def clear_chat():
session.pop("session_id", None)
session.pop("history", None)
return redirect(url_for("chat"))
if __name__ == "__main__":
app.run(debug=True)Code breakdown:
from flask import ...
: Imports the necessary Flask modules.import openai
: Imports the OpenAI library.import os
: Imports theos
module for interacting with the operating system, such as accessing environment variables.from dotenv import load_dotenv
: Imports theload_dotenv
function to load environment variables from a.env
file.from flask_session import Session
: Imports theSession
class fromflask_session
.import psycopg2
: Imports thepsycopg2
library for interacting with PostgreSQL.load_dotenv()
: Loads the environment variables from a.env
file.openai.api_key = os.getenv("OPENAI_API_KEY")
: Retrieves the OpenAI API key from the environment variable and sets it for the OpenAI library.DATABASE_URL = os.getenv("DATABASE_URL")
: Retrieves the PostgreSQL database URL from the environment variable.app = Flask(__name__)
: Creates a Flask application instance.app.secret_key = os.urandom(24)
: Sets a secret key for the Flask application. This is essential for using Flask sessions.app.config["SESSION_TYPE"] = "filesystem|postgresql|redis|mongodb"
: Configures the session storage type. For this project, we are using a database.Session(app)
: Initializes the Flask-Session extension, binding it to the Flask app.get_db_connection()
:- Establishes a connection to the PostgreSQL database using the URL from the environment variable.
- Returns the connection object.
- Handles potential connection errors with a try...except block.
create_table()
:- Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique
session_id
for each conversation.
- Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique
store_message(session_id, role, content)
:- Stores a message (user or assistant) in the "messages" table, including the
session_id
.
- Stores a message (user or assistant) in the "messages" table, including the
get_history_from_db(session_id)
:- Retrieves the conversation history for a given
session_id
from the database.
- Retrieves the conversation history for a given
@app.route("/", methods=["GET", "POST"])
: Defines the route for the application's main page ("/"). Thechat()
function handles both GET and POST requests to this URL.def chat():
if "session_id" not in session:
: Checks if asession_id
exists in the user's session. If not, it generates a unique ID usingos.urandom(16).hex()
and stores it in the session.session_id = session["session_id"]
: Retrieves thesession_id
from the session.history = get_history_from_db(session_id)
: Retrieves the conversation history from the database using the session ID.if not history
: checks if the history is empty and initializes it.if request.method == "POST":
: Handles POST requests (when the user submits a message).user_input = request.form["user_input"]
: Gets the user's input from the form.store_message(session_id, "user", user_input)
: Stores the user's message in the database, associated with the session ID.- The code then calls the OpenAI API to get a response, stores the response in the database, and updates the session.
- Renders the "chat.html" template, passing the conversation history.
return render_template("chat.html", history=history[1:])
: Handles GET requests (when the user loads the page). Renders the "chat.html" template, passing the conversation history.
@app.route("/clear", methods=["POST"])
: Route for clearing the chat session.def clear_chat():
: clears thesession_id
andhistory
from the session.if __name__ == "__main__":
: Starts the Flask development server if the script is executed directly.
- Create a folder named
templates
in the same directory asapp.py
. - Create a file named
chat.html
inside thetemplates
folder with the following code:<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GPT-4o Chat Assistant</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<style>
/* --- General Styles --- */
body {
font-family: 'Inter', sans-serif;
background-color: #f3f4f6;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
color: #1f2937;
}
.container {
max-width: 800px;
width: 100%;
background-color: #fff;
padding: 30px;
border-radius: 0.75rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
h2 {
font-size: 1.875rem;
font-weight: 600;
margin-bottom: 2rem;
color: #1e293b;
text-align: center;
}
/* --- Message Styles --- */
.message {
margin-bottom: 1.5rem;
display: flex;
flex-direction: column;
}
.message p {
padding: 1rem;
border-radius: 1rem;
max-width: 80%;
}
.user p {
background-color: #e0f2fe;
color: #1d4ed8;
margin-left: auto;
}
.assistant p {
background-color: #f0fdf4;
color: #15803d;
}
.message strong {
font-size: 0.875rem;
font-weight: 600;
margin-bottom: 0.25rem;
}
.user strong {
color: #1e40af;
}
.assistant strong {
color: #16a34a;
}
/* --- Form Styles --- */
form {
margin-top: 2rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
textarea {
width: 100%;
padding: 0.75rem;
border-radius: 0.5rem;
border: 1px solid #d1d5db;
resize: none;
font-size: 1rem;
line-height: 1.5rem;
margin-bottom: 0.25rem;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.06);
}
textarea:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
input[type="submit"] {
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
background-color: #3b82f6;
color: #fff;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.3s ease;
border: none;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
}
input[type="submit"]:hover {
background-color: #2563eb;
}
input[type="submit"]:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
}
/* --- Responsive Adjustments --- */
@media (max-width: 768px) {
.container {
padding: 20px;
}
textarea {
height: 100px;
}
}
.clear-history-button{
margin-top: 2rem;
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
background-color: #3b82f6;
color: #fff;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.3s ease;
border: none;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body>
<div class="container">
<h2>🧠 GPT-4o Chat Assistant</h2>
{% for msg in history %}
<div class="message {{ msg.role }}">
<p><strong>{{ msg.role.capitalize() }}:</strong> {{ msg.content }}</p>
</div>
{% endfor %}
<form method="post">
<textarea name="user_input" placeholder="Type your message..."></textarea><br>
<input type="submit" value="Send Message">
</form>
<form method="post" action="/clear">
<input type="submit" value="Clear Chat" class = "clear-history-button">
</form>
</div>
</body>
</html> - This HTML template provides the structure and styling for the chat interface. It includes a form for user input and displays the conversation history.
3. Streamlit Implementation
Create a file named streamlit_app.py
with the following code:
import streamlit as st
import openai
import os
from dotenv import load_dotenv
import psycopg2 # Import psycopg2
import datetime
from typing import List, Dict
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL")
st.set_page_config(page_title="GPT-4o Chat with Memory", page_icon="🧠")
st.title("🧠 GPT-4o Chatbot with Persistent Memory")
# --- Database Connection (PostgreSQL) ---
def get_db_connection():
"""Gets a PostgreSQL connection."""
try:
conn = psycopg2.connect(DATABASE_URL)
return conn
except psycopg2.Error as e:
st.error(f"Database connection error: {e}")
return None
def create_table():
"""Creates the messages table if it doesn't exist."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS messages (
id SERIAL PRIMARY KEY,
session_id TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit()
except psycopg2.Error as e:
st.error(f"Error creating table: {e}")
finally:
conn.close()
def store_message(session_id: str, role: str, content: str):
"""Stores a message in the database."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute(
"INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
(session_id, role, content),
)
conn.commit()
except psycopg2.Error as e:
st.error(f"Error storing message: {e}")
finally:
conn.close()
def get_history_from_db(session_id: str) -> List[Dict[str, str]]:
"""Retrieves the conversation history for a given session ID from the database."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute(
"SELECT role, content FROM messages WHERE session_id = %s ORDER BY created_at",
(session_id,),
)
history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
return history
except psycopg2.Error as e:
st.error(f"Error retrieving history from database: {e}")
return []
finally:
conn.close()
return []
create_table() # Ensure the table exists
# --- Session Management ---
if "session_id" not in st.session_state:
st.session_state.session_id = os.urandom(16).hex() # Generate unique session ID
session_id = st.session_state.session_id
# --- Initialize chat history ---
if "messages" not in st.session_state:
st.session_state.messages = get_history_from_db(session_id)
if not st.session_state.messages:
st.session_state.messages = [{"role": "system", "content": "You are a helpful assistant."}]
# --- Display chat history ---
for msg in st.session_state.messages[1:]: # Skip system message
speaker = "🧑💻 You" if msg["role"] == "user" else "🤖 Assistant"
with st.chat_message(msg["role"]):
st.markdown(f"**{speaker}:** {msg['content']}")
# --- User input ---
user_input = st.chat_input("Ask something...", key="user_input") # Use key for chat_input
if user_input:
# Store user message
store_message(session_id, "user", user_input)
st.session_state.messages.append({"role": "user", "content": user_input})
# Get assistant reply
with st.chat_message("assistant"):
placeholder = st.empty() # Reserve a placeholder for the response
with st.spinner("Thinking..."):
try:
response = openai.ChatCompletion.create(
model="gpt-4o",
messages=st.session_state.messages,
temperature=0.6,
)
reply = response["choices"][0]["message"]["content"]
store_message(session_id, "assistant", reply) # Store assistant reply
st.session_state.messages.append({"role": "assistant", "content": reply})
placeholder.markdown(f"**🤖 Assistant:** {reply}") # Replace placeholder with full response
except openai.error.OpenAIError as e:
error_message = f"OpenAI API Error: {e}"
store_message(session_id, "assistant", error_message)
st.session_state.messages.append({"role": "assistant", "content": error_message})
st.error(error_message)
placeholder.markdown(f"**🤖 Assistant:** {error_message}")
except Exception as e:
error_message = f"An unexpected error occurred: {e}"
store_message(session_id, "assistant", error_message)
st.session_state.messages.append({"role": "assistant", "content": error_message})
st.error(error_message)
placeholder.markdown(f"**🤖 Assistant:** {error_message}")
# Clear the input field after sending the message
st.session_state.user_input = ""
st.rerun()
Code brakdown:
import streamlit as st
: Imports the Streamlit library.import openai
: Imports the OpenAI library.import os
: Imports theos
module for interacting with the operating system, such as accessing environment variables.from dotenv import load_dotenv
: Imports theload_dotenv
function to load environment variables from a.env
file.import psycopg2
: Imports thepsycopg2
library for interacting with the PostgreSQL database.import datetime
: Imports the datetime module.from typing import List, Dict
: Imports typingload_dotenv()
: Loads the environment variables from a.env
file.openai.api_key = os.getenv("OPENAI_API_KEY")
: Retrieves the OpenAI API key from the environment variable and sets it for the OpenAI library.DATABASE_URL = os.getenv("DATABASE_URL")
: Retrieves the PostgreSQL database URL.st.set_page_config(...)
: Configures the page title and icon.st.title(...)
: Sets the title of the Streamlit application.get_db_connection()
:- Establishes a connection to the PostgreSQL database using the URL from the environment variable.
- Returns the connection object.
- Handles potential connection errors using a
try...except
block, displaying an error message usingst.error()
.
create_table()
:- Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique
session_id
for each conversation. - Handles potential errors during table creation using a
try...except
block.
- Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique
store_message(session_id, role, content)
:- Stores a message (user or assistant) in the "messages" table, along with the
session_id
. - Handles potential errors during message storage.
- Stores a message (user or assistant) in the "messages" table, along with the
- get_history_from_db(session_id):
- Retrieves the conversation history for a specific session_id from the database.
- The main part of the code:
if "session_id" not in st.session_state:
: Checks if a session ID exists in Streamlit's session state. If not, it generates a unique ID usingos.urandom(16).hex()
.session_id = st.session_state.session_id
: Retrieves the session ID.if "messages" not in st.session_state:
:- Retrieves the conversation history from the database.
- If no history exists in the database for the current session, it initializes the chat history with the system message.
- The code then displays the chat history.
user_input = st.chat_input(...)
: Creates a text input field for the user to type their message.- When the user submits a message:
- The message is stored in the database using the
store_message()
function. - The message is added to the chat history in
st.session_state
. - The application calls the OpenAI API to get a response.
- The assistant's response is stored in the database and added to the chat history.
- The assistant's response is displayed in the chat interface.
- The message is stored in the database using the
4. Run the Applications
- Flask:
- Open a terminal, navigate to the project directory, and make sure your virtual environment is activated.
- Run the Flask application:
python app.py
- Open a web browser and go to
http://localhost:5000
to interact with the chatbot.
- Streamlit:
- Open a terminal, navigate to the project directory, and make sure your virtual environment is activated.
- Run the Streamlit application:
streamlit run streamlit_app.py
- A new browser tab will open with the Streamlit chatbot.
5. Enhancements (Optional)
- Implement user authentication:
- Add secure login/registration system using JWT or session tokens
- Create user profiles to store conversation preferences
- Enable private conversations tied to specific user accounts
- Add a "Clear Chat" button feature:
- Implement both complete and selective chat history clearing
- Add confirmation dialogs to prevent accidental deletions
- Include option to export chat history before clearing
- Enhance the front-end interface:
- Create responsive layouts using modern CSS frameworks
- Add real-time message updates using WebSocket
- Implement markdown support and code highlighting
- Deploy to production:
- Set up CI/CD pipelines for automated deployment
- Configure SSL certificates for secure communication
- Implement monitoring and logging systems
- Add vector database capabilities:
- Implement embedding generation using models like BERT or USE
- Set up vector similarity search using Pinecone or Milvus
- Create semantic search functionality for chat history
This project serves as an extensive foundation for developing a chatbot with memory capabilities. Here's what you'll gain from completing it:
First, you'll get practical experience with modern web frameworks - both Flask for traditional web development and Streamlit for rapid prototyping of data applications. You'll learn how these frameworks differ and when to use each one.
Second, you'll master working with the OpenAI API, understanding how to structure prompts, handle responses, and manage API interactions effectively. This knowledge is crucial for building any AI-powered application.
Third, you'll learn database integration principles, including how to design schemas, manage connections, and handle data persistence. This ensures your chatbot can maintain conversations across sessions and scale effectively.
Finally, this project will prepare you for building more sophisticated chatbot applications, such as those incorporating advanced features like sentiment analysis, multiple AI models, or real-time processing. The skills you develop here form the building blocks for more complex AI applications.
Project Steps
1. Project Setup
- Create a new directory for your project:
mkdir chatbot_project
cd chatbot_project - Create a virtual environment:
python -m venv venv
- Activate the virtual environment:
- On Windows:
venv\Scripts\activate
- On macOS and Linux:
source venv/bin/activate
- Install the required libraries:
pip install flask openai flask-session python-dotenv streamlit psycopg2
- Create a
.env
file in your project directory and add your OpenAI API key and database url:OPENAI_API_KEY=YOUR_OPENAI_API_KEY
DATABASE_URL=YOUR_DATABASE_URL # e.g., "postgresql://user:password@host:port/database_name" - For the postgres database, make sure you have a postgres database running and the url is correctly configured.
2. Flask Implementation
- Create a file named
app.py
with the following code:from flask import Flask, request, render_template, session, redirect, url_for
import openai
import os
from dotenv import load_dotenv
from flask_session import Session
import psycopg2 #for postgres
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL")
app = Flask(__name__)
app.secret_key = os.urandom(24)
app.config["SESSION_TYPE"] = "filesystem" # Or "postgresql", "redis", etc. For production, use a proper database.
#app.config["SESSION_TYPE"] = "postgresql"
#app.config["SESSION_PERMANENT"] = True # Optional: Make sessions permanent
#app.config["SESSION_SQLALCHEMY"] = {'url': DATABASE_URL} #needs Flask-SQLAlchemy
Session(app)
# --- Database Connection (PostgreSQL) ---
def get_db_connection():
"""Gets a PostgreSQL connection."""
try:
conn = psycopg2.connect(DATABASE_URL)
return conn
except psycopg2.Error as e:
print(f"Database connection error: {e}")
return None
def create_table():
"""Creates the messages table if it doesn't exist."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS messages (
id SERIAL PRIMARY KEY,
session_id TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit()
except psycopg2.Error as e:
print(f"Error creating table: {e}")
finally:
conn.close()
def store_message(session_id: str, role: str, content: str):
"""Stores a message in the database."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute(
"INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
(session_id, role, content),
)
conn.commit()
except psycopg2.Error as e:
print(f"Error storing message: {e}")
finally:
conn.close()
def get_history_from_db(session_id: str) -> List[Dict[str, str]]:
"""Retrieves the conversation history for a given session ID from the database."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute(
"SELECT role, content FROM messages WHERE session_id = %s ORDER BY created_at",
(session_id,),
)
history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
return history
except psycopg2.Error as e:
print(f"Error retrieving history from database: {e}")
return []
finally:
conn.close()
return []
create_table() # Ensure the table exists
# --- Chat route ---
@app.route("/", methods=["GET", "POST"])
def chat():
"""Handles the main chat functionality."""
if "session_id" not in session:
session["session_id"] = os.urandom(16).hex() # Generate unique session ID
session_id = session["session_id"]
history = get_history_from_db(session_id) # Get history from the database
if not history:
history = [{"role": "system", "content": "You are a helpful assistant."}]
session["history"] = history # Initialize session
if request.method == "POST":
user_input = request.form["user_input"]
store_message(session_id, "user", user_input) # Store to database
history.append({"role": "user", "content": user_input})
try:
response = openai.ChatCompletion.create(
model="gpt-4o",
messages=history,
temperature=0.6,
)
assistant_reply = response.choices[0].message.content
store_message(session_id, "assistant", assistant_reply) # Store to database
history.append({"role": "assistant", "content": assistant_reply})
session["history"] = history
session.modified = True # IMPORTANT
return render_template("chat.html", history=history[1:]) # Pass history to template
except openai.error.OpenAIError as e:
error_message = f"OpenAI API Error: {e}"
print(error_message)
store_message(session_id, "assistant", error_message)
history.append({"role": "assistant", "content": error_message})
session["history"] = history
session.modified = True
return render_template("chat.html", history=history[1:])
except Exception as e:
error_message = f"An unexpected error occurred: {e}"
print(error_message)
store_message(session_id, "assistant", error_message)
history.append({"role": "assistant", "content": error_message})
session["history"] = history
session.modified = True
return render_template("chat.html", history=history[1:])
return render_template("chat.html", history=history[1:])
@app.route("/clear", methods=["POST"])
def clear_chat():
session.pop("session_id", None)
session.pop("history", None)
return redirect(url_for("chat"))
if __name__ == "__main__":
app.run(debug=True)Code breakdown:
from flask import ...
: Imports the necessary Flask modules.import openai
: Imports the OpenAI library.import os
: Imports theos
module for interacting with the operating system, such as accessing environment variables.from dotenv import load_dotenv
: Imports theload_dotenv
function to load environment variables from a.env
file.from flask_session import Session
: Imports theSession
class fromflask_session
.import psycopg2
: Imports thepsycopg2
library for interacting with PostgreSQL.load_dotenv()
: Loads the environment variables from a.env
file.openai.api_key = os.getenv("OPENAI_API_KEY")
: Retrieves the OpenAI API key from the environment variable and sets it for the OpenAI library.DATABASE_URL = os.getenv("DATABASE_URL")
: Retrieves the PostgreSQL database URL from the environment variable.app = Flask(__name__)
: Creates a Flask application instance.app.secret_key = os.urandom(24)
: Sets a secret key for the Flask application. This is essential for using Flask sessions.app.config["SESSION_TYPE"] = "filesystem|postgresql|redis|mongodb"
: Configures the session storage type. For this project, we are using a database.Session(app)
: Initializes the Flask-Session extension, binding it to the Flask app.get_db_connection()
:- Establishes a connection to the PostgreSQL database using the URL from the environment variable.
- Returns the connection object.
- Handles potential connection errors with a try...except block.
create_table()
:- Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique
session_id
for each conversation.
- Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique
store_message(session_id, role, content)
:- Stores a message (user or assistant) in the "messages" table, including the
session_id
.
- Stores a message (user or assistant) in the "messages" table, including the
get_history_from_db(session_id)
:- Retrieves the conversation history for a given
session_id
from the database.
- Retrieves the conversation history for a given
@app.route("/", methods=["GET", "POST"])
: Defines the route for the application's main page ("/"). Thechat()
function handles both GET and POST requests to this URL.def chat():
if "session_id" not in session:
: Checks if asession_id
exists in the user's session. If not, it generates a unique ID usingos.urandom(16).hex()
and stores it in the session.session_id = session["session_id"]
: Retrieves thesession_id
from the session.history = get_history_from_db(session_id)
: Retrieves the conversation history from the database using the session ID.if not history
: checks if the history is empty and initializes it.if request.method == "POST":
: Handles POST requests (when the user submits a message).user_input = request.form["user_input"]
: Gets the user's input from the form.store_message(session_id, "user", user_input)
: Stores the user's message in the database, associated with the session ID.- The code then calls the OpenAI API to get a response, stores the response in the database, and updates the session.
- Renders the "chat.html" template, passing the conversation history.
return render_template("chat.html", history=history[1:])
: Handles GET requests (when the user loads the page). Renders the "chat.html" template, passing the conversation history.
@app.route("/clear", methods=["POST"])
: Route for clearing the chat session.def clear_chat():
: clears thesession_id
andhistory
from the session.if __name__ == "__main__":
: Starts the Flask development server if the script is executed directly.
- Create a folder named
templates
in the same directory asapp.py
. - Create a file named
chat.html
inside thetemplates
folder with the following code:<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GPT-4o Chat Assistant</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<style>
/* --- General Styles --- */
body {
font-family: 'Inter', sans-serif;
background-color: #f3f4f6;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
color: #1f2937;
}
.container {
max-width: 800px;
width: 100%;
background-color: #fff;
padding: 30px;
border-radius: 0.75rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
h2 {
font-size: 1.875rem;
font-weight: 600;
margin-bottom: 2rem;
color: #1e293b;
text-align: center;
}
/* --- Message Styles --- */
.message {
margin-bottom: 1.5rem;
display: flex;
flex-direction: column;
}
.message p {
padding: 1rem;
border-radius: 1rem;
max-width: 80%;
}
.user p {
background-color: #e0f2fe;
color: #1d4ed8;
margin-left: auto;
}
.assistant p {
background-color: #f0fdf4;
color: #15803d;
}
.message strong {
font-size: 0.875rem;
font-weight: 600;
margin-bottom: 0.25rem;
}
.user strong {
color: #1e40af;
}
.assistant strong {
color: #16a34a;
}
/* --- Form Styles --- */
form {
margin-top: 2rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
textarea {
width: 100%;
padding: 0.75rem;
border-radius: 0.5rem;
border: 1px solid #d1d5db;
resize: none;
font-size: 1rem;
line-height: 1.5rem;
margin-bottom: 0.25rem;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.06);
}
textarea:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
input[type="submit"] {
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
background-color: #3b82f6;
color: #fff;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.3s ease;
border: none;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
}
input[type="submit"]:hover {
background-color: #2563eb;
}
input[type="submit"]:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
}
/* --- Responsive Adjustments --- */
@media (max-width: 768px) {
.container {
padding: 20px;
}
textarea {
height: 100px;
}
}
.clear-history-button{
margin-top: 2rem;
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
background-color: #3b82f6;
color: #fff;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.3s ease;
border: none;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body>
<div class="container">
<h2>🧠 GPT-4o Chat Assistant</h2>
{% for msg in history %}
<div class="message {{ msg.role }}">
<p><strong>{{ msg.role.capitalize() }}:</strong> {{ msg.content }}</p>
</div>
{% endfor %}
<form method="post">
<textarea name="user_input" placeholder="Type your message..."></textarea><br>
<input type="submit" value="Send Message">
</form>
<form method="post" action="/clear">
<input type="submit" value="Clear Chat" class = "clear-history-button">
</form>
</div>
</body>
</html> - This HTML template provides the structure and styling for the chat interface. It includes a form for user input and displays the conversation history.
3. Streamlit Implementation
Create a file named streamlit_app.py
with the following code:
import streamlit as st
import openai
import os
from dotenv import load_dotenv
import psycopg2 # Import psycopg2
import datetime
from typing import List, Dict
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL")
st.set_page_config(page_title="GPT-4o Chat with Memory", page_icon="🧠")
st.title("🧠 GPT-4o Chatbot with Persistent Memory")
# --- Database Connection (PostgreSQL) ---
def get_db_connection():
"""Gets a PostgreSQL connection."""
try:
conn = psycopg2.connect(DATABASE_URL)
return conn
except psycopg2.Error as e:
st.error(f"Database connection error: {e}")
return None
def create_table():
"""Creates the messages table if it doesn't exist."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS messages (
id SERIAL PRIMARY KEY,
session_id TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit()
except psycopg2.Error as e:
st.error(f"Error creating table: {e}")
finally:
conn.close()
def store_message(session_id: str, role: str, content: str):
"""Stores a message in the database."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute(
"INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
(session_id, role, content),
)
conn.commit()
except psycopg2.Error as e:
st.error(f"Error storing message: {e}")
finally:
conn.close()
def get_history_from_db(session_id: str) -> List[Dict[str, str]]:
"""Retrieves the conversation history for a given session ID from the database."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute(
"SELECT role, content FROM messages WHERE session_id = %s ORDER BY created_at",
(session_id,),
)
history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
return history
except psycopg2.Error as e:
st.error(f"Error retrieving history from database: {e}")
return []
finally:
conn.close()
return []
create_table() # Ensure the table exists
# --- Session Management ---
if "session_id" not in st.session_state:
st.session_state.session_id = os.urandom(16).hex() # Generate unique session ID
session_id = st.session_state.session_id
# --- Initialize chat history ---
if "messages" not in st.session_state:
st.session_state.messages = get_history_from_db(session_id)
if not st.session_state.messages:
st.session_state.messages = [{"role": "system", "content": "You are a helpful assistant."}]
# --- Display chat history ---
for msg in st.session_state.messages[1:]: # Skip system message
speaker = "🧑💻 You" if msg["role"] == "user" else "🤖 Assistant"
with st.chat_message(msg["role"]):
st.markdown(f"**{speaker}:** {msg['content']}")
# --- User input ---
user_input = st.chat_input("Ask something...", key="user_input") # Use key for chat_input
if user_input:
# Store user message
store_message(session_id, "user", user_input)
st.session_state.messages.append({"role": "user", "content": user_input})
# Get assistant reply
with st.chat_message("assistant"):
placeholder = st.empty() # Reserve a placeholder for the response
with st.spinner("Thinking..."):
try:
response = openai.ChatCompletion.create(
model="gpt-4o",
messages=st.session_state.messages,
temperature=0.6,
)
reply = response["choices"][0]["message"]["content"]
store_message(session_id, "assistant", reply) # Store assistant reply
st.session_state.messages.append({"role": "assistant", "content": reply})
placeholder.markdown(f"**🤖 Assistant:** {reply}") # Replace placeholder with full response
except openai.error.OpenAIError as e:
error_message = f"OpenAI API Error: {e}"
store_message(session_id, "assistant", error_message)
st.session_state.messages.append({"role": "assistant", "content": error_message})
st.error(error_message)
placeholder.markdown(f"**🤖 Assistant:** {error_message}")
except Exception as e:
error_message = f"An unexpected error occurred: {e}"
store_message(session_id, "assistant", error_message)
st.session_state.messages.append({"role": "assistant", "content": error_message})
st.error(error_message)
placeholder.markdown(f"**🤖 Assistant:** {error_message}")
# Clear the input field after sending the message
st.session_state.user_input = ""
st.rerun()
Code brakdown:
import streamlit as st
: Imports the Streamlit library.import openai
: Imports the OpenAI library.import os
: Imports theos
module for interacting with the operating system, such as accessing environment variables.from dotenv import load_dotenv
: Imports theload_dotenv
function to load environment variables from a.env
file.import psycopg2
: Imports thepsycopg2
library for interacting with the PostgreSQL database.import datetime
: Imports the datetime module.from typing import List, Dict
: Imports typingload_dotenv()
: Loads the environment variables from a.env
file.openai.api_key = os.getenv("OPENAI_API_KEY")
: Retrieves the OpenAI API key from the environment variable and sets it for the OpenAI library.DATABASE_URL = os.getenv("DATABASE_URL")
: Retrieves the PostgreSQL database URL.st.set_page_config(...)
: Configures the page title and icon.st.title(...)
: Sets the title of the Streamlit application.get_db_connection()
:- Establishes a connection to the PostgreSQL database using the URL from the environment variable.
- Returns the connection object.
- Handles potential connection errors using a
try...except
block, displaying an error message usingst.error()
.
create_table()
:- Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique
session_id
for each conversation. - Handles potential errors during table creation using a
try...except
block.
- Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique
store_message(session_id, role, content)
:- Stores a message (user or assistant) in the "messages" table, along with the
session_id
. - Handles potential errors during message storage.
- Stores a message (user or assistant) in the "messages" table, along with the
- get_history_from_db(session_id):
- Retrieves the conversation history for a specific session_id from the database.
- The main part of the code:
if "session_id" not in st.session_state:
: Checks if a session ID exists in Streamlit's session state. If not, it generates a unique ID usingos.urandom(16).hex()
.session_id = st.session_state.session_id
: Retrieves the session ID.if "messages" not in st.session_state:
:- Retrieves the conversation history from the database.
- If no history exists in the database for the current session, it initializes the chat history with the system message.
- The code then displays the chat history.
user_input = st.chat_input(...)
: Creates a text input field for the user to type their message.- When the user submits a message:
- The message is stored in the database using the
store_message()
function. - The message is added to the chat history in
st.session_state
. - The application calls the OpenAI API to get a response.
- The assistant's response is stored in the database and added to the chat history.
- The assistant's response is displayed in the chat interface.
- The message is stored in the database using the
4. Run the Applications
- Flask:
- Open a terminal, navigate to the project directory, and make sure your virtual environment is activated.
- Run the Flask application:
python app.py
- Open a web browser and go to
http://localhost:5000
to interact with the chatbot.
- Streamlit:
- Open a terminal, navigate to the project directory, and make sure your virtual environment is activated.
- Run the Streamlit application:
streamlit run streamlit_app.py
- A new browser tab will open with the Streamlit chatbot.
5. Enhancements (Optional)
- Implement user authentication:
- Add secure login/registration system using JWT or session tokens
- Create user profiles to store conversation preferences
- Enable private conversations tied to specific user accounts
- Add a "Clear Chat" button feature:
- Implement both complete and selective chat history clearing
- Add confirmation dialogs to prevent accidental deletions
- Include option to export chat history before clearing
- Enhance the front-end interface:
- Create responsive layouts using modern CSS frameworks
- Add real-time message updates using WebSocket
- Implement markdown support and code highlighting
- Deploy to production:
- Set up CI/CD pipelines for automated deployment
- Configure SSL certificates for secure communication
- Implement monitoring and logging systems
- Add vector database capabilities:
- Implement embedding generation using models like BERT or USE
- Set up vector similarity search using Pinecone or Milvus
- Create semantic search functionality for chat history
This project serves as an extensive foundation for developing a chatbot with memory capabilities. Here's what you'll gain from completing it:
First, you'll get practical experience with modern web frameworks - both Flask for traditional web development and Streamlit for rapid prototyping of data applications. You'll learn how these frameworks differ and when to use each one.
Second, you'll master working with the OpenAI API, understanding how to structure prompts, handle responses, and manage API interactions effectively. This knowledge is crucial for building any AI-powered application.
Third, you'll learn database integration principles, including how to design schemas, manage connections, and handle data persistence. This ensures your chatbot can maintain conversations across sessions and scale effectively.
Finally, this project will prepare you for building more sophisticated chatbot applications, such as those incorporating advanced features like sentiment analysis, multiple AI models, or real-time processing. The skills you develop here form the building blocks for more complex AI applications.
Project Steps
1. Project Setup
- Create a new directory for your project:
mkdir chatbot_project
cd chatbot_project - Create a virtual environment:
python -m venv venv
- Activate the virtual environment:
- On Windows:
venv\Scripts\activate
- On macOS and Linux:
source venv/bin/activate
- Install the required libraries:
pip install flask openai flask-session python-dotenv streamlit psycopg2
- Create a
.env
file in your project directory and add your OpenAI API key and database url:OPENAI_API_KEY=YOUR_OPENAI_API_KEY
DATABASE_URL=YOUR_DATABASE_URL # e.g., "postgresql://user:password@host:port/database_name" - For the postgres database, make sure you have a postgres database running and the url is correctly configured.
2. Flask Implementation
- Create a file named
app.py
with the following code:from flask import Flask, request, render_template, session, redirect, url_for
import openai
import os
from dotenv import load_dotenv
from flask_session import Session
import psycopg2 #for postgres
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL")
app = Flask(__name__)
app.secret_key = os.urandom(24)
app.config["SESSION_TYPE"] = "filesystem" # Or "postgresql", "redis", etc. For production, use a proper database.
#app.config["SESSION_TYPE"] = "postgresql"
#app.config["SESSION_PERMANENT"] = True # Optional: Make sessions permanent
#app.config["SESSION_SQLALCHEMY"] = {'url': DATABASE_URL} #needs Flask-SQLAlchemy
Session(app)
# --- Database Connection (PostgreSQL) ---
def get_db_connection():
"""Gets a PostgreSQL connection."""
try:
conn = psycopg2.connect(DATABASE_URL)
return conn
except psycopg2.Error as e:
print(f"Database connection error: {e}")
return None
def create_table():
"""Creates the messages table if it doesn't exist."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS messages (
id SERIAL PRIMARY KEY,
session_id TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit()
except psycopg2.Error as e:
print(f"Error creating table: {e}")
finally:
conn.close()
def store_message(session_id: str, role: str, content: str):
"""Stores a message in the database."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute(
"INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
(session_id, role, content),
)
conn.commit()
except psycopg2.Error as e:
print(f"Error storing message: {e}")
finally:
conn.close()
def get_history_from_db(session_id: str) -> List[Dict[str, str]]:
"""Retrieves the conversation history for a given session ID from the database."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute(
"SELECT role, content FROM messages WHERE session_id = %s ORDER BY created_at",
(session_id,),
)
history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
return history
except psycopg2.Error as e:
print(f"Error retrieving history from database: {e}")
return []
finally:
conn.close()
return []
create_table() # Ensure the table exists
# --- Chat route ---
@app.route("/", methods=["GET", "POST"])
def chat():
"""Handles the main chat functionality."""
if "session_id" not in session:
session["session_id"] = os.urandom(16).hex() # Generate unique session ID
session_id = session["session_id"]
history = get_history_from_db(session_id) # Get history from the database
if not history:
history = [{"role": "system", "content": "You are a helpful assistant."}]
session["history"] = history # Initialize session
if request.method == "POST":
user_input = request.form["user_input"]
store_message(session_id, "user", user_input) # Store to database
history.append({"role": "user", "content": user_input})
try:
response = openai.ChatCompletion.create(
model="gpt-4o",
messages=history,
temperature=0.6,
)
assistant_reply = response.choices[0].message.content
store_message(session_id, "assistant", assistant_reply) # Store to database
history.append({"role": "assistant", "content": assistant_reply})
session["history"] = history
session.modified = True # IMPORTANT
return render_template("chat.html", history=history[1:]) # Pass history to template
except openai.error.OpenAIError as e:
error_message = f"OpenAI API Error: {e}"
print(error_message)
store_message(session_id, "assistant", error_message)
history.append({"role": "assistant", "content": error_message})
session["history"] = history
session.modified = True
return render_template("chat.html", history=history[1:])
except Exception as e:
error_message = f"An unexpected error occurred: {e}"
print(error_message)
store_message(session_id, "assistant", error_message)
history.append({"role": "assistant", "content": error_message})
session["history"] = history
session.modified = True
return render_template("chat.html", history=history[1:])
return render_template("chat.html", history=history[1:])
@app.route("/clear", methods=["POST"])
def clear_chat():
session.pop("session_id", None)
session.pop("history", None)
return redirect(url_for("chat"))
if __name__ == "__main__":
app.run(debug=True)Code breakdown:
from flask import ...
: Imports the necessary Flask modules.import openai
: Imports the OpenAI library.import os
: Imports theos
module for interacting with the operating system, such as accessing environment variables.from dotenv import load_dotenv
: Imports theload_dotenv
function to load environment variables from a.env
file.from flask_session import Session
: Imports theSession
class fromflask_session
.import psycopg2
: Imports thepsycopg2
library for interacting with PostgreSQL.load_dotenv()
: Loads the environment variables from a.env
file.openai.api_key = os.getenv("OPENAI_API_KEY")
: Retrieves the OpenAI API key from the environment variable and sets it for the OpenAI library.DATABASE_URL = os.getenv("DATABASE_URL")
: Retrieves the PostgreSQL database URL from the environment variable.app = Flask(__name__)
: Creates a Flask application instance.app.secret_key = os.urandom(24)
: Sets a secret key for the Flask application. This is essential for using Flask sessions.app.config["SESSION_TYPE"] = "filesystem|postgresql|redis|mongodb"
: Configures the session storage type. For this project, we are using a database.Session(app)
: Initializes the Flask-Session extension, binding it to the Flask app.get_db_connection()
:- Establishes a connection to the PostgreSQL database using the URL from the environment variable.
- Returns the connection object.
- Handles potential connection errors with a try...except block.
create_table()
:- Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique
session_id
for each conversation.
- Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique
store_message(session_id, role, content)
:- Stores a message (user or assistant) in the "messages" table, including the
session_id
.
- Stores a message (user or assistant) in the "messages" table, including the
get_history_from_db(session_id)
:- Retrieves the conversation history for a given
session_id
from the database.
- Retrieves the conversation history for a given
@app.route("/", methods=["GET", "POST"])
: Defines the route for the application's main page ("/"). Thechat()
function handles both GET and POST requests to this URL.def chat():
if "session_id" not in session:
: Checks if asession_id
exists in the user's session. If not, it generates a unique ID usingos.urandom(16).hex()
and stores it in the session.session_id = session["session_id"]
: Retrieves thesession_id
from the session.history = get_history_from_db(session_id)
: Retrieves the conversation history from the database using the session ID.if not history
: checks if the history is empty and initializes it.if request.method == "POST":
: Handles POST requests (when the user submits a message).user_input = request.form["user_input"]
: Gets the user's input from the form.store_message(session_id, "user", user_input)
: Stores the user's message in the database, associated with the session ID.- The code then calls the OpenAI API to get a response, stores the response in the database, and updates the session.
- Renders the "chat.html" template, passing the conversation history.
return render_template("chat.html", history=history[1:])
: Handles GET requests (when the user loads the page). Renders the "chat.html" template, passing the conversation history.
@app.route("/clear", methods=["POST"])
: Route for clearing the chat session.def clear_chat():
: clears thesession_id
andhistory
from the session.if __name__ == "__main__":
: Starts the Flask development server if the script is executed directly.
- Create a folder named
templates
in the same directory asapp.py
. - Create a file named
chat.html
inside thetemplates
folder with the following code:<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GPT-4o Chat Assistant</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<style>
/* --- General Styles --- */
body {
font-family: 'Inter', sans-serif;
background-color: #f3f4f6;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
color: #1f2937;
}
.container {
max-width: 800px;
width: 100%;
background-color: #fff;
padding: 30px;
border-radius: 0.75rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
h2 {
font-size: 1.875rem;
font-weight: 600;
margin-bottom: 2rem;
color: #1e293b;
text-align: center;
}
/* --- Message Styles --- */
.message {
margin-bottom: 1.5rem;
display: flex;
flex-direction: column;
}
.message p {
padding: 1rem;
border-radius: 1rem;
max-width: 80%;
}
.user p {
background-color: #e0f2fe;
color: #1d4ed8;
margin-left: auto;
}
.assistant p {
background-color: #f0fdf4;
color: #15803d;
}
.message strong {
font-size: 0.875rem;
font-weight: 600;
margin-bottom: 0.25rem;
}
.user strong {
color: #1e40af;
}
.assistant strong {
color: #16a34a;
}
/* --- Form Styles --- */
form {
margin-top: 2rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
textarea {
width: 100%;
padding: 0.75rem;
border-radius: 0.5rem;
border: 1px solid #d1d5db;
resize: none;
font-size: 1rem;
line-height: 1.5rem;
margin-bottom: 0.25rem;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.06);
}
textarea:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
input[type="submit"] {
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
background-color: #3b82f6;
color: #fff;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.3s ease;
border: none;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
}
input[type="submit"]:hover {
background-color: #2563eb;
}
input[type="submit"]:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
}
/* --- Responsive Adjustments --- */
@media (max-width: 768px) {
.container {
padding: 20px;
}
textarea {
height: 100px;
}
}
.clear-history-button{
margin-top: 2rem;
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
background-color: #3b82f6;
color: #fff;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.3s ease;
border: none;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body>
<div class="container">
<h2>🧠 GPT-4o Chat Assistant</h2>
{% for msg in history %}
<div class="message {{ msg.role }}">
<p><strong>{{ msg.role.capitalize() }}:</strong> {{ msg.content }}</p>
</div>
{% endfor %}
<form method="post">
<textarea name="user_input" placeholder="Type your message..."></textarea><br>
<input type="submit" value="Send Message">
</form>
<form method="post" action="/clear">
<input type="submit" value="Clear Chat" class = "clear-history-button">
</form>
</div>
</body>
</html> - This HTML template provides the structure and styling for the chat interface. It includes a form for user input and displays the conversation history.
3. Streamlit Implementation
Create a file named streamlit_app.py
with the following code:
import streamlit as st
import openai
import os
from dotenv import load_dotenv
import psycopg2 # Import psycopg2
import datetime
from typing import List, Dict
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL")
st.set_page_config(page_title="GPT-4o Chat with Memory", page_icon="🧠")
st.title("🧠 GPT-4o Chatbot with Persistent Memory")
# --- Database Connection (PostgreSQL) ---
def get_db_connection():
"""Gets a PostgreSQL connection."""
try:
conn = psycopg2.connect(DATABASE_URL)
return conn
except psycopg2.Error as e:
st.error(f"Database connection error: {e}")
return None
def create_table():
"""Creates the messages table if it doesn't exist."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS messages (
id SERIAL PRIMARY KEY,
session_id TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit()
except psycopg2.Error as e:
st.error(f"Error creating table: {e}")
finally:
conn.close()
def store_message(session_id: str, role: str, content: str):
"""Stores a message in the database."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute(
"INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
(session_id, role, content),
)
conn.commit()
except psycopg2.Error as e:
st.error(f"Error storing message: {e}")
finally:
conn.close()
def get_history_from_db(session_id: str) -> List[Dict[str, str]]:
"""Retrieves the conversation history for a given session ID from the database."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute(
"SELECT role, content FROM messages WHERE session_id = %s ORDER BY created_at",
(session_id,),
)
history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
return history
except psycopg2.Error as e:
st.error(f"Error retrieving history from database: {e}")
return []
finally:
conn.close()
return []
create_table() # Ensure the table exists
# --- Session Management ---
if "session_id" not in st.session_state:
st.session_state.session_id = os.urandom(16).hex() # Generate unique session ID
session_id = st.session_state.session_id
# --- Initialize chat history ---
if "messages" not in st.session_state:
st.session_state.messages = get_history_from_db(session_id)
if not st.session_state.messages:
st.session_state.messages = [{"role": "system", "content": "You are a helpful assistant."}]
# --- Display chat history ---
for msg in st.session_state.messages[1:]: # Skip system message
speaker = "🧑💻 You" if msg["role"] == "user" else "🤖 Assistant"
with st.chat_message(msg["role"]):
st.markdown(f"**{speaker}:** {msg['content']}")
# --- User input ---
user_input = st.chat_input("Ask something...", key="user_input") # Use key for chat_input
if user_input:
# Store user message
store_message(session_id, "user", user_input)
st.session_state.messages.append({"role": "user", "content": user_input})
# Get assistant reply
with st.chat_message("assistant"):
placeholder = st.empty() # Reserve a placeholder for the response
with st.spinner("Thinking..."):
try:
response = openai.ChatCompletion.create(
model="gpt-4o",
messages=st.session_state.messages,
temperature=0.6,
)
reply = response["choices"][0]["message"]["content"]
store_message(session_id, "assistant", reply) # Store assistant reply
st.session_state.messages.append({"role": "assistant", "content": reply})
placeholder.markdown(f"**🤖 Assistant:** {reply}") # Replace placeholder with full response
except openai.error.OpenAIError as e:
error_message = f"OpenAI API Error: {e}"
store_message(session_id, "assistant", error_message)
st.session_state.messages.append({"role": "assistant", "content": error_message})
st.error(error_message)
placeholder.markdown(f"**🤖 Assistant:** {error_message}")
except Exception as e:
error_message = f"An unexpected error occurred: {e}"
store_message(session_id, "assistant", error_message)
st.session_state.messages.append({"role": "assistant", "content": error_message})
st.error(error_message)
placeholder.markdown(f"**🤖 Assistant:** {error_message}")
# Clear the input field after sending the message
st.session_state.user_input = ""
st.rerun()
Code brakdown:
import streamlit as st
: Imports the Streamlit library.import openai
: Imports the OpenAI library.import os
: Imports theos
module for interacting with the operating system, such as accessing environment variables.from dotenv import load_dotenv
: Imports theload_dotenv
function to load environment variables from a.env
file.import psycopg2
: Imports thepsycopg2
library for interacting with the PostgreSQL database.import datetime
: Imports the datetime module.from typing import List, Dict
: Imports typingload_dotenv()
: Loads the environment variables from a.env
file.openai.api_key = os.getenv("OPENAI_API_KEY")
: Retrieves the OpenAI API key from the environment variable and sets it for the OpenAI library.DATABASE_URL = os.getenv("DATABASE_URL")
: Retrieves the PostgreSQL database URL.st.set_page_config(...)
: Configures the page title and icon.st.title(...)
: Sets the title of the Streamlit application.get_db_connection()
:- Establishes a connection to the PostgreSQL database using the URL from the environment variable.
- Returns the connection object.
- Handles potential connection errors using a
try...except
block, displaying an error message usingst.error()
.
create_table()
:- Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique
session_id
for each conversation. - Handles potential errors during table creation using a
try...except
block.
- Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique
store_message(session_id, role, content)
:- Stores a message (user or assistant) in the "messages" table, along with the
session_id
. - Handles potential errors during message storage.
- Stores a message (user or assistant) in the "messages" table, along with the
- get_history_from_db(session_id):
- Retrieves the conversation history for a specific session_id from the database.
- The main part of the code:
if "session_id" not in st.session_state:
: Checks if a session ID exists in Streamlit's session state. If not, it generates a unique ID usingos.urandom(16).hex()
.session_id = st.session_state.session_id
: Retrieves the session ID.if "messages" not in st.session_state:
:- Retrieves the conversation history from the database.
- If no history exists in the database for the current session, it initializes the chat history with the system message.
- The code then displays the chat history.
user_input = st.chat_input(...)
: Creates a text input field for the user to type their message.- When the user submits a message:
- The message is stored in the database using the
store_message()
function. - The message is added to the chat history in
st.session_state
. - The application calls the OpenAI API to get a response.
- The assistant's response is stored in the database and added to the chat history.
- The assistant's response is displayed in the chat interface.
- The message is stored in the database using the
4. Run the Applications
- Flask:
- Open a terminal, navigate to the project directory, and make sure your virtual environment is activated.
- Run the Flask application:
python app.py
- Open a web browser and go to
http://localhost:5000
to interact with the chatbot.
- Streamlit:
- Open a terminal, navigate to the project directory, and make sure your virtual environment is activated.
- Run the Streamlit application:
streamlit run streamlit_app.py
- A new browser tab will open with the Streamlit chatbot.
5. Enhancements (Optional)
- Implement user authentication:
- Add secure login/registration system using JWT or session tokens
- Create user profiles to store conversation preferences
- Enable private conversations tied to specific user accounts
- Add a "Clear Chat" button feature:
- Implement both complete and selective chat history clearing
- Add confirmation dialogs to prevent accidental deletions
- Include option to export chat history before clearing
- Enhance the front-end interface:
- Create responsive layouts using modern CSS frameworks
- Add real-time message updates using WebSocket
- Implement markdown support and code highlighting
- Deploy to production:
- Set up CI/CD pipelines for automated deployment
- Configure SSL certificates for secure communication
- Implement monitoring and logging systems
- Add vector database capabilities:
- Implement embedding generation using models like BERT or USE
- Set up vector similarity search using Pinecone or Milvus
- Create semantic search functionality for chat history
This project serves as an extensive foundation for developing a chatbot with memory capabilities. Here's what you'll gain from completing it:
First, you'll get practical experience with modern web frameworks - both Flask for traditional web development and Streamlit for rapid prototyping of data applications. You'll learn how these frameworks differ and when to use each one.
Second, you'll master working with the OpenAI API, understanding how to structure prompts, handle responses, and manage API interactions effectively. This knowledge is crucial for building any AI-powered application.
Third, you'll learn database integration principles, including how to design schemas, manage connections, and handle data persistence. This ensures your chatbot can maintain conversations across sessions and scale effectively.
Finally, this project will prepare you for building more sophisticated chatbot applications, such as those incorporating advanced features like sentiment analysis, multiple AI models, or real-time processing. The skills you develop here form the building blocks for more complex AI applications.
Project Steps
1. Project Setup
- Create a new directory for your project:
mkdir chatbot_project
cd chatbot_project - Create a virtual environment:
python -m venv venv
- Activate the virtual environment:
- On Windows:
venv\Scripts\activate
- On macOS and Linux:
source venv/bin/activate
- Install the required libraries:
pip install flask openai flask-session python-dotenv streamlit psycopg2
- Create a
.env
file in your project directory and add your OpenAI API key and database url:OPENAI_API_KEY=YOUR_OPENAI_API_KEY
DATABASE_URL=YOUR_DATABASE_URL # e.g., "postgresql://user:password@host:port/database_name" - For the postgres database, make sure you have a postgres database running and the url is correctly configured.
2. Flask Implementation
- Create a file named
app.py
with the following code:from flask import Flask, request, render_template, session, redirect, url_for
import openai
import os
from dotenv import load_dotenv
from flask_session import Session
import psycopg2 #for postgres
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL")
app = Flask(__name__)
app.secret_key = os.urandom(24)
app.config["SESSION_TYPE"] = "filesystem" # Or "postgresql", "redis", etc. For production, use a proper database.
#app.config["SESSION_TYPE"] = "postgresql"
#app.config["SESSION_PERMANENT"] = True # Optional: Make sessions permanent
#app.config["SESSION_SQLALCHEMY"] = {'url': DATABASE_URL} #needs Flask-SQLAlchemy
Session(app)
# --- Database Connection (PostgreSQL) ---
def get_db_connection():
"""Gets a PostgreSQL connection."""
try:
conn = psycopg2.connect(DATABASE_URL)
return conn
except psycopg2.Error as e:
print(f"Database connection error: {e}")
return None
def create_table():
"""Creates the messages table if it doesn't exist."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS messages (
id SERIAL PRIMARY KEY,
session_id TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit()
except psycopg2.Error as e:
print(f"Error creating table: {e}")
finally:
conn.close()
def store_message(session_id: str, role: str, content: str):
"""Stores a message in the database."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute(
"INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
(session_id, role, content),
)
conn.commit()
except psycopg2.Error as e:
print(f"Error storing message: {e}")
finally:
conn.close()
def get_history_from_db(session_id: str) -> List[Dict[str, str]]:
"""Retrieves the conversation history for a given session ID from the database."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute(
"SELECT role, content FROM messages WHERE session_id = %s ORDER BY created_at",
(session_id,),
)
history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
return history
except psycopg2.Error as e:
print(f"Error retrieving history from database: {e}")
return []
finally:
conn.close()
return []
create_table() # Ensure the table exists
# --- Chat route ---
@app.route("/", methods=["GET", "POST"])
def chat():
"""Handles the main chat functionality."""
if "session_id" not in session:
session["session_id"] = os.urandom(16).hex() # Generate unique session ID
session_id = session["session_id"]
history = get_history_from_db(session_id) # Get history from the database
if not history:
history = [{"role": "system", "content": "You are a helpful assistant."}]
session["history"] = history # Initialize session
if request.method == "POST":
user_input = request.form["user_input"]
store_message(session_id, "user", user_input) # Store to database
history.append({"role": "user", "content": user_input})
try:
response = openai.ChatCompletion.create(
model="gpt-4o",
messages=history,
temperature=0.6,
)
assistant_reply = response.choices[0].message.content
store_message(session_id, "assistant", assistant_reply) # Store to database
history.append({"role": "assistant", "content": assistant_reply})
session["history"] = history
session.modified = True # IMPORTANT
return render_template("chat.html", history=history[1:]) # Pass history to template
except openai.error.OpenAIError as e:
error_message = f"OpenAI API Error: {e}"
print(error_message)
store_message(session_id, "assistant", error_message)
history.append({"role": "assistant", "content": error_message})
session["history"] = history
session.modified = True
return render_template("chat.html", history=history[1:])
except Exception as e:
error_message = f"An unexpected error occurred: {e}"
print(error_message)
store_message(session_id, "assistant", error_message)
history.append({"role": "assistant", "content": error_message})
session["history"] = history
session.modified = True
return render_template("chat.html", history=history[1:])
return render_template("chat.html", history=history[1:])
@app.route("/clear", methods=["POST"])
def clear_chat():
session.pop("session_id", None)
session.pop("history", None)
return redirect(url_for("chat"))
if __name__ == "__main__":
app.run(debug=True)Code breakdown:
from flask import ...
: Imports the necessary Flask modules.import openai
: Imports the OpenAI library.import os
: Imports theos
module for interacting with the operating system, such as accessing environment variables.from dotenv import load_dotenv
: Imports theload_dotenv
function to load environment variables from a.env
file.from flask_session import Session
: Imports theSession
class fromflask_session
.import psycopg2
: Imports thepsycopg2
library for interacting with PostgreSQL.load_dotenv()
: Loads the environment variables from a.env
file.openai.api_key = os.getenv("OPENAI_API_KEY")
: Retrieves the OpenAI API key from the environment variable and sets it for the OpenAI library.DATABASE_URL = os.getenv("DATABASE_URL")
: Retrieves the PostgreSQL database URL from the environment variable.app = Flask(__name__)
: Creates a Flask application instance.app.secret_key = os.urandom(24)
: Sets a secret key for the Flask application. This is essential for using Flask sessions.app.config["SESSION_TYPE"] = "filesystem|postgresql|redis|mongodb"
: Configures the session storage type. For this project, we are using a database.Session(app)
: Initializes the Flask-Session extension, binding it to the Flask app.get_db_connection()
:- Establishes a connection to the PostgreSQL database using the URL from the environment variable.
- Returns the connection object.
- Handles potential connection errors with a try...except block.
create_table()
:- Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique
session_id
for each conversation.
- Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique
store_message(session_id, role, content)
:- Stores a message (user or assistant) in the "messages" table, including the
session_id
.
- Stores a message (user or assistant) in the "messages" table, including the
get_history_from_db(session_id)
:- Retrieves the conversation history for a given
session_id
from the database.
- Retrieves the conversation history for a given
@app.route("/", methods=["GET", "POST"])
: Defines the route for the application's main page ("/"). Thechat()
function handles both GET and POST requests to this URL.def chat():
if "session_id" not in session:
: Checks if asession_id
exists in the user's session. If not, it generates a unique ID usingos.urandom(16).hex()
and stores it in the session.session_id = session["session_id"]
: Retrieves thesession_id
from the session.history = get_history_from_db(session_id)
: Retrieves the conversation history from the database using the session ID.if not history
: checks if the history is empty and initializes it.if request.method == "POST":
: Handles POST requests (when the user submits a message).user_input = request.form["user_input"]
: Gets the user's input from the form.store_message(session_id, "user", user_input)
: Stores the user's message in the database, associated with the session ID.- The code then calls the OpenAI API to get a response, stores the response in the database, and updates the session.
- Renders the "chat.html" template, passing the conversation history.
return render_template("chat.html", history=history[1:])
: Handles GET requests (when the user loads the page). Renders the "chat.html" template, passing the conversation history.
@app.route("/clear", methods=["POST"])
: Route for clearing the chat session.def clear_chat():
: clears thesession_id
andhistory
from the session.if __name__ == "__main__":
: Starts the Flask development server if the script is executed directly.
- Create a folder named
templates
in the same directory asapp.py
. - Create a file named
chat.html
inside thetemplates
folder with the following code:<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GPT-4o Chat Assistant</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap" rel="stylesheet">
<style>
/* --- General Styles --- */
body {
font-family: 'Inter', sans-serif;
background-color: #f3f4f6;
padding: 20px;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
margin: 0;
color: #1f2937;
}
.container {
max-width: 800px;
width: 100%;
background-color: #fff;
padding: 30px;
border-radius: 0.75rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
}
h2 {
font-size: 1.875rem;
font-weight: 600;
margin-bottom: 2rem;
color: #1e293b;
text-align: center;
}
/* --- Message Styles --- */
.message {
margin-bottom: 1.5rem;
display: flex;
flex-direction: column;
}
.message p {
padding: 1rem;
border-radius: 1rem;
max-width: 80%;
}
.user p {
background-color: #e0f2fe;
color: #1d4ed8;
margin-left: auto;
}
.assistant p {
background-color: #f0fdf4;
color: #15803d;
}
.message strong {
font-size: 0.875rem;
font-weight: 600;
margin-bottom: 0.25rem;
}
.user strong {
color: #1e40af;
}
.assistant strong {
color: #16a34a;
}
/* --- Form Styles --- */
form {
margin-top: 2rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
textarea {
width: 100%;
padding: 0.75rem;
border-radius: 0.5rem;
border: 1px solid #d1d5db;
resize: none;
font-size: 1rem;
line-height: 1.5rem;
margin-bottom: 0.25rem;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.06);
}
textarea:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.15);
}
input[type="submit"] {
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
background-color: #3b82f6;
color: #fff;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.3s ease;
border: none;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
}
input[type="submit"]:hover {
background-color: #2563eb;
}
input[type="submit"]:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3);
}
/* --- Responsive Adjustments --- */
@media (max-width: 768px) {
.container {
padding: 20px;
}
textarea {
height: 100px;
}
}
.clear-history-button{
margin-top: 2rem;
padding: 0.75rem 1.5rem;
border-radius: 0.5rem;
background-color: #3b82f6;
color: #fff;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: background-color 0.3s ease;
border: none;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
}
</style>
</head>
<body>
<div class="container">
<h2>🧠 GPT-4o Chat Assistant</h2>
{% for msg in history %}
<div class="message {{ msg.role }}">
<p><strong>{{ msg.role.capitalize() }}:</strong> {{ msg.content }}</p>
</div>
{% endfor %}
<form method="post">
<textarea name="user_input" placeholder="Type your message..."></textarea><br>
<input type="submit" value="Send Message">
</form>
<form method="post" action="/clear">
<input type="submit" value="Clear Chat" class = "clear-history-button">
</form>
</div>
</body>
</html> - This HTML template provides the structure and styling for the chat interface. It includes a form for user input and displays the conversation history.
3. Streamlit Implementation
Create a file named streamlit_app.py
with the following code:
import streamlit as st
import openai
import os
from dotenv import load_dotenv
import psycopg2 # Import psycopg2
import datetime
from typing import List, Dict
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
DATABASE_URL = os.getenv("DATABASE_URL")
st.set_page_config(page_title="GPT-4o Chat with Memory", page_icon="🧠")
st.title("🧠 GPT-4o Chatbot with Persistent Memory")
# --- Database Connection (PostgreSQL) ---
def get_db_connection():
"""Gets a PostgreSQL connection."""
try:
conn = psycopg2.connect(DATABASE_URL)
return conn
except psycopg2.Error as e:
st.error(f"Database connection error: {e}")
return None
def create_table():
"""Creates the messages table if it doesn't exist."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS messages (
id SERIAL PRIMARY KEY,
session_id TEXT NOT NULL,
role TEXT NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
)
""")
conn.commit()
except psycopg2.Error as e:
st.error(f"Error creating table: {e}")
finally:
conn.close()
def store_message(session_id: str, role: str, content: str):
"""Stores a message in the database."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute(
"INSERT INTO messages (session_id, role, content) VALUES (%s, %s, %s)",
(session_id, role, content),
)
conn.commit()
except psycopg2.Error as e:
st.error(f"Error storing message: {e}")
finally:
conn.close()
def get_history_from_db(session_id: str) -> List[Dict[str, str]]:
"""Retrieves the conversation history for a given session ID from the database."""
conn = get_db_connection()
if conn is not None:
try:
cursor = conn.cursor()
cursor.execute(
"SELECT role, content FROM messages WHERE session_id = %s ORDER BY created_at",
(session_id,),
)
history = [{"role": row[0], "content": row[1]} for row in cursor.fetchall()]
return history
except psycopg2.Error as e:
st.error(f"Error retrieving history from database: {e}")
return []
finally:
conn.close()
return []
create_table() # Ensure the table exists
# --- Session Management ---
if "session_id" not in st.session_state:
st.session_state.session_id = os.urandom(16).hex() # Generate unique session ID
session_id = st.session_state.session_id
# --- Initialize chat history ---
if "messages" not in st.session_state:
st.session_state.messages = get_history_from_db(session_id)
if not st.session_state.messages:
st.session_state.messages = [{"role": "system", "content": "You are a helpful assistant."}]
# --- Display chat history ---
for msg in st.session_state.messages[1:]: # Skip system message
speaker = "🧑💻 You" if msg["role"] == "user" else "🤖 Assistant"
with st.chat_message(msg["role"]):
st.markdown(f"**{speaker}:** {msg['content']}")
# --- User input ---
user_input = st.chat_input("Ask something...", key="user_input") # Use key for chat_input
if user_input:
# Store user message
store_message(session_id, "user", user_input)
st.session_state.messages.append({"role": "user", "content": user_input})
# Get assistant reply
with st.chat_message("assistant"):
placeholder = st.empty() # Reserve a placeholder for the response
with st.spinner("Thinking..."):
try:
response = openai.ChatCompletion.create(
model="gpt-4o",
messages=st.session_state.messages,
temperature=0.6,
)
reply = response["choices"][0]["message"]["content"]
store_message(session_id, "assistant", reply) # Store assistant reply
st.session_state.messages.append({"role": "assistant", "content": reply})
placeholder.markdown(f"**🤖 Assistant:** {reply}") # Replace placeholder with full response
except openai.error.OpenAIError as e:
error_message = f"OpenAI API Error: {e}"
store_message(session_id, "assistant", error_message)
st.session_state.messages.append({"role": "assistant", "content": error_message})
st.error(error_message)
placeholder.markdown(f"**🤖 Assistant:** {error_message}")
except Exception as e:
error_message = f"An unexpected error occurred: {e}"
store_message(session_id, "assistant", error_message)
st.session_state.messages.append({"role": "assistant", "content": error_message})
st.error(error_message)
placeholder.markdown(f"**🤖 Assistant:** {error_message}")
# Clear the input field after sending the message
st.session_state.user_input = ""
st.rerun()
Code brakdown:
import streamlit as st
: Imports the Streamlit library.import openai
: Imports the OpenAI library.import os
: Imports theos
module for interacting with the operating system, such as accessing environment variables.from dotenv import load_dotenv
: Imports theload_dotenv
function to load environment variables from a.env
file.import psycopg2
: Imports thepsycopg2
library for interacting with the PostgreSQL database.import datetime
: Imports the datetime module.from typing import List, Dict
: Imports typingload_dotenv()
: Loads the environment variables from a.env
file.openai.api_key = os.getenv("OPENAI_API_KEY")
: Retrieves the OpenAI API key from the environment variable and sets it for the OpenAI library.DATABASE_URL = os.getenv("DATABASE_URL")
: Retrieves the PostgreSQL database URL.st.set_page_config(...)
: Configures the page title and icon.st.title(...)
: Sets the title of the Streamlit application.get_db_connection()
:- Establishes a connection to the PostgreSQL database using the URL from the environment variable.
- Returns the connection object.
- Handles potential connection errors using a
try...except
block, displaying an error message usingst.error()
.
create_table()
:- Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique
session_id
for each conversation. - Handles potential errors during table creation using a
try...except
block.
- Creates the "messages" table in the PostgreSQL database if it doesn't exist. The table stores the conversation history, including a unique
store_message(session_id, role, content)
:- Stores a message (user or assistant) in the "messages" table, along with the
session_id
. - Handles potential errors during message storage.
- Stores a message (user or assistant) in the "messages" table, along with the
- get_history_from_db(session_id):
- Retrieves the conversation history for a specific session_id from the database.
- The main part of the code:
if "session_id" not in st.session_state:
: Checks if a session ID exists in Streamlit's session state. If not, it generates a unique ID usingos.urandom(16).hex()
.session_id = st.session_state.session_id
: Retrieves the session ID.if "messages" not in st.session_state:
:- Retrieves the conversation history from the database.
- If no history exists in the database for the current session, it initializes the chat history with the system message.
- The code then displays the chat history.
user_input = st.chat_input(...)
: Creates a text input field for the user to type their message.- When the user submits a message:
- The message is stored in the database using the
store_message()
function. - The message is added to the chat history in
st.session_state
. - The application calls the OpenAI API to get a response.
- The assistant's response is stored in the database and added to the chat history.
- The assistant's response is displayed in the chat interface.
- The message is stored in the database using the
4. Run the Applications
- Flask:
- Open a terminal, navigate to the project directory, and make sure your virtual environment is activated.
- Run the Flask application:
python app.py
- Open a web browser and go to
http://localhost:5000
to interact with the chatbot.
- Streamlit:
- Open a terminal, navigate to the project directory, and make sure your virtual environment is activated.
- Run the Streamlit application:
streamlit run streamlit_app.py
- A new browser tab will open with the Streamlit chatbot.
5. Enhancements (Optional)
- Implement user authentication:
- Add secure login/registration system using JWT or session tokens
- Create user profiles to store conversation preferences
- Enable private conversations tied to specific user accounts
- Add a "Clear Chat" button feature:
- Implement both complete and selective chat history clearing
- Add confirmation dialogs to prevent accidental deletions
- Include option to export chat history before clearing
- Enhance the front-end interface:
- Create responsive layouts using modern CSS frameworks
- Add real-time message updates using WebSocket
- Implement markdown support and code highlighting
- Deploy to production:
- Set up CI/CD pipelines for automated deployment
- Configure SSL certificates for secure communication
- Implement monitoring and logging systems
- Add vector database capabilities:
- Implement embedding generation using models like BERT or USE
- Set up vector similarity search using Pinecone or Milvus
- Create semantic search functionality for chat history
This project serves as an extensive foundation for developing a chatbot with memory capabilities. Here's what you'll gain from completing it:
First, you'll get practical experience with modern web frameworks - both Flask for traditional web development and Streamlit for rapid prototyping of data applications. You'll learn how these frameworks differ and when to use each one.
Second, you'll master working with the OpenAI API, understanding how to structure prompts, handle responses, and manage API interactions effectively. This knowledge is crucial for building any AI-powered application.
Third, you'll learn database integration principles, including how to design schemas, manage connections, and handle data persistence. This ensures your chatbot can maintain conversations across sessions and scale effectively.
Finally, this project will prepare you for building more sophisticated chatbot applications, such as those incorporating advanced features like sentiment analysis, multiple AI models, or real-time processing. The skills you develop here form the building blocks for more complex AI applications.