bluebeandatachatbot / src /streamlit_app.py
krinya's picture
Enhance chatbot response message to provide clearer user feedback and assurance
0a63321
Raw
History Blame Contribute Delete
13.3 kB
from dotenv import load_dotenv
import streamlit as st
from openai import OpenAI
import json
import os
import csv
from datetime import datetime
from pypdf import PdfReader
from chatbot_data.system_promt import system_prompt
from css_input import STREAMLIT_CSS
from email_utils import send_user_welcome_email, send_unknown_question_email
# Load environment variables
load_dotenv()
# App version for deployment tracking
__version__ = "1.0.5"
# Page configuration with light theme enforcement
st.set_page_config(
page_title="Blue Bean Data - AI Assistant",
page_icon="☕",
layout="centered",
initial_sidebar_state="collapsed"
)
st.markdown(STREAMLIT_CSS, unsafe_allow_html=True)
def load_knowledge_base():
"""Load and combine all knowledge base files"""
knowledge_base = ""
# Load FAQ data
try:
with open('src/chatbot_data/faq_blue_bean_data.md', 'r', encoding='utf-8') as file:
faq_content = file.read()
knowledge_base += faq_content + "\n\n"
except FileNotFoundError:
st.error("FAQ file not found.")
knowledge_base += "FAQ data not available.\n\n"
# Load PDF CV
try:
reader = PdfReader('src/chatbot_data/kristof_linkedin_cv.pdf')
kristof_cv = ""
for page in reader.pages:
kristof_cv += page.extract_text()
knowledge_base += f"# Kristof's Professional Background\n{kristof_cv}\n\n"
except FileNotFoundError:
st.error("Kristof's CV file not found.")
except Exception as e:
st.error(f"Error reading PDF: {str(e)}")
return knowledge_base
def generate_conversation_summary(messages):
"""Generate an AI summary of the conversation for context"""
try:
# Filter out system messages and get only user/assistant exchanges
conversation_messages = [msg for msg in messages if msg["role"] in ["user", "assistant"]]
if len(conversation_messages) <= 2: # Just welcome message and one exchange
return "Brief initial inquiry about Blue Bean Data services."
# Create a prompt for summarization
conversation_text = ""
for msg in conversation_messages:
role = "Customer" if msg["role"] == "user" else "Assistant"
conversation_text += f"{role}: {msg['content']}\n\n"
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
summary_response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{
"role": "system",
"content": (
"You are an assistant tasked with summarizing a conversation between a customer and Blue Bean Data's AI assistant. "
"Write a clear, professional summary (3-4 sentences) that covers: "
"1) What the customer is interested in, "
"2) Their main questions or challenges, "
"3) Any relevant business context they mentioned. "
"The summary should be actionable for a sales or support follow-up. "
"Only include information explicitly stated in the conversation, and avoid assumptions or internal notes. "
"Do not include anything that a user cannot see or that was not visible to the user in the conversation."
)
},
{
"role": "user",
"content": f"Summarize this conversation:\n\n{conversation_text}"
}
],
max_tokens=150
)
return summary_response.choices[0].message.content.strip()
except Exception as e:
print(f"Error generating conversation summary: {str(e)}")
# Fallback: create a simple summary from user messages
user_messages = [msg["content"] for msg in messages if msg["role"] == "user"]
if user_messages:
return f"Customer inquired about: {'; '.join(user_messages[-2:])}" # Last 2 user messages
return "Customer expressed interest in Blue Bean Data services."
def record_user_details(email, name="Name not provided", notes="not provided"):
"""Send welcome email to user and notification to company"""
try:
# Send emails using SMTP
email_sent = send_user_welcome_email(email, name, notes)
if email_sent:
# Show success message to user - handle name display properly
if name and name.strip() and name.lower() not in ['name not provided', 'not provided', '']:
st.success(f"✅ Thank you {name}! I've sent a welcome email to {email}. Someone from our team will reach out to you soon.")
else:
st.success(f"✅ Thank you! I've sent a welcome email to {email}. Someone from our team will reach out to you soon.")
return {"recorded": "ok"}
else:
st.error("There was an issue sending the email. Please try again or contact us directly at info@bluebeandata.com")
return {"recorded": "error", "message": "Email sending failed"}
except Exception as e:
st.error(f"Error processing your request: {str(e)}")
return {"recorded": "error", "message": str(e)}
def record_unknown_question(question):
"""Send unknown question to company email"""
try:
# Send email to company
email_sent = send_unknown_question_email(question)
if email_sent:
return {"recorded": "ok"}
else:
return {"recorded": "error", "message": "Email sending failed"}
except Exception as e:
st.error(f"Error recording unknown question: {str(e)}")
return {"recorded": "error", "message": str(e)}
# Tool definitions for OpenAI function calling
record_user_details_json = {
"name": "record_user_details",
"description": "Use this tool to record that a user is interested in being in touch and provided an email address. Always provide a meaningful summary of the conversation context.",
"parameters": {
"type": "object",
"properties": {
"email": {
"type": "string",
"description": "The email address of this user"
},
"name": {
"type": "string",
"description": "The user's name, if they provided it"
},
"notes": {
"type": "string",
"description": "A brief summary of what the user is interested in, their main questions, and business context from the conversation"
}
},
"required": ["email"]
}
}
record_unknown_question_json = {
"name": "record_unknown_question",
"description": "Use this to record questions that cannot be answered based on the knowledge base",
"parameters": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The question that couldn't be answered"
}
},
"required": ["question"]
}
}
def get_chatbot_response(messages, knowledge_base):
"""Get response from OpenAI with function calling capabilities"""
try:
# Initialize OpenAI client
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# Create session ID for tracking (could be user's session)
session_id = st.session_state.get('session_id', f"session_{datetime.now().strftime('%Y%m%d_%H%M%S')}")
if 'session_id' not in st.session_state:
st.session_state.session_id = session_id
# Add system message with knowledge base
system_message = {
"role": "system",
"content": f"{system_prompt}\n\nKNOWLEDGE BASE:\n{knowledge_base}"
}
full_messages = [system_message] + messages
# Call OpenAI API with function calling
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=full_messages,
tools=[
{"type": "function", "function": record_user_details_json},
{"type": "function", "function": record_unknown_question_json}
],
tool_choice="auto",
user=session_id # Track sessions for OpenAI usage analytics
)
# Handle function calls and prepare response
assistant_message = response.choices[0].message
response_content = assistant_message.content
if assistant_message.tool_calls:
for tool_call in assistant_message.tool_calls:
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
if function_name == "record_user_details":
# Generate conversation summary and enhance notes
conversation_summary = generate_conversation_summary(messages)
# If notes from AI are minimal, use the generated summary
original_notes = function_args.get("notes", "")
if not original_notes or original_notes.lower() in ["not provided", "none", ""]:
function_args["notes"] = conversation_summary
else:
# Combine AI notes with conversation summary
function_args["notes"] = f"{original_notes}\n\nConversation Summary: {conversation_summary}"
record_user_details(**function_args)
elif function_name == "record_unknown_question":
record_unknown_question(**function_args)
# If there's no content but there were function calls, provide a default response
if not response_content:
response_content = "Thank you! I've recorded your information and notified our team. We'll follow up soon and update our knowledge base as needed. Is there anything else I can assist you with?"
# Ensure we always return a string, never None/null
return response_content or "I'm here to help! What would you like to know about Blue Bean Data?"
except Exception as e:
st.error(f"Error getting response: {str(e)}")
return "I'm sorry, I'm having trouble connecting right now. Please try again later."
def main():
"""Main Streamlit application"""
# Header
st.markdown('<div class="main-header">', unsafe_allow_html=True)
st.title("Blue Bean Data AI Assistant ☕")
st.markdown("*We help you grow.*")
st.markdown('</div>', unsafe_allow_html=True)
# Initialize session state
if "messages" not in st.session_state:
st.session_state.messages = []
if "knowledge_base" not in st.session_state:
with st.spinner("Loading knowledge base..."):
st.session_state.knowledge_base = load_knowledge_base()
# Display welcome message
if not st.session_state.messages:
welcome_msg = (
"👋 **Welcome to Blue Bean Data!**\n\n"
"I'm your AI assistant. Ask me about:\n"
"- Our data consulting services & expertise \n"
"- The Blue Bean Data team \n"
"- Or leave your contact details for a follow-up\n\n"
"How can I assist you today?"
)
st.session_state.messages.append({"role": "assistant", "content": welcome_msg})
# Display chat messages
for message in st.session_state.messages:
with st.chat_message(message["role"]):
st.markdown(message["content"])
# Chat input
if prompt := st.chat_input("Type your question here..."):
# Add user message to chat history
st.session_state.messages.append({"role": "user", "content": prompt})
# Display user message
with st.chat_message("user"):
st.markdown(prompt)
# Get and display assistant response
with st.chat_message("assistant"):
with st.spinner("💡 Thinking... (aka. AI at work)"):
response = get_chatbot_response(
st.session_state.messages.copy(),
st.session_state.knowledge_base
)
st.markdown(response)
# Add assistant response to chat history
st.session_state.messages.append({"role": "assistant", "content": response})
# Sidebar with information
with st.sidebar:
st.markdown("### About Blue Bean Data")
st.markdown("""
We're a data solutions company based in the Netherlands, founded by brothers Kristof and Marton.
**Our Services:**
- Data Strategy & Consulting
- BI & Dashboards
- Data Pipeline Development
- AI & Automation
- Predictive Modeling & Analytics
- Database Design & Optimization
**Get Started:**
Contact us for a free consultation!
""")
if st.button("Clear Chat History"):
st.session_state.messages = []
st.rerun()
if __name__ == "__main__":
main()