File size: 9,065 Bytes
04ab479 3619850 05db081 04ab479 852a59a 3619850 734bdd7 b032864 86a6fb4 3619850 70ee470 86a6fb4 70ee470 4b36a0a 3619850 734bdd7 b032864 734bdd7 b032864 3619850 d5f21dc 3619850 d5f21dc 3619850 04ab479 3619850 04ab479 3619850 734bdd7 3619850 b032864 734bdd7 b032864 734bdd7 b032864 734bdd7 b032864 3619850 734bdd7 3619850 b032864 734bdd7 b032864 734bdd7 b032864 3619850 b032864 3619850 04ab479 3619850 04ab479 3619850 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 | import streamlit as st
import os
import re, ast
import sqlite3
import streamlit as st
#from langchain.chat_models import ChatOpenAI
from langchain_openai import ChatOpenAI
from langchain_community.utilities import SQLDatabase
from langchain_core.messages import HumanMessage
# ---- Session State Initialization ----
if "conversation_stage" not in st.session_state:
st.session_state.conversation_stage = "IDLE"
if "pending_order_id" not in st.session_state:
st.session_state.pending_order_id = None
if "pending_action" not in st.session_state:
st.session_state.pending_action = None
DB_PATH = "customer_orders.db"
def initialize_database():
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS orders (
order_id INTEGER PRIMARY KEY,
cust_id TEXT,
order_time TEXT,
order_status TEXT,
payment_status TEXT,
item_in_order TEXT,
preparing_eta TEXT,
prepared_time TEXT,
delivery_eta TEXT,
delivery_time TEXT
)
""")
cursor.execute("SELECT COUNT(*) FROM orders")
count = cursor.fetchone()[0]
# Populate only once
if count == 0:
cursor.executemany("""
INSERT INTO orders VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
""", [
(12486, "C1011", "12:00", "preparing food", "COD", "Burger, Fries", "12:15", None, None, None),
(12487, "C1012", "12:05", "cancelled", "cancelled", "Pizza", None, None, None, None),
(12488, "C1013", "12:10", "delivered", "completed", "Sandwich, Soda", "12:25", "12:25", "12:55", "13:00"),
(12489, "C1014", "12:15", "picked up", "COD", "Salad", "12:30", "12:30", "12:45", None),
(12490, "C1015", "12:20", "delivered", "completed", "Pasta", "12:35", "12:35", "13:05", "13:10"),
(12491,"C1016","12:25","preparing food","COD","Burger","12:40",None,None,None),
(12492,"C1017","12:30","delivered","completed","Sushi, Salad","12:45","12:45","13:15","13:15"),
(12493,"C1018","12:35","picked up","COD","Steak","12:50","12:50","01:10",None),
(12494,"C1019","12:40","canceled","canceled","Pizza, Garlic Bread",None,None,None,None),
(12495,"C1020","12:45","preparing food","COD","Wrap, Juice","13:00",None,None,None),
(12496,"C1021","12:50","preparing food","COD","Taco, Nachos","13:05",None,None,None),
(12497,"C1022","12:55","delivered","completed","Burrito","13:10","13:10","13:15","13:15"),
(12498,"C1023","12:56","picked up","COD","Pancakes, Coffee","13:11","13:11","13:40",None),
(12499,"C1024","12:57","canceled","canceled","Waffle",None,None,None,None),
(12500,"C1025","12:58","delivered","completed","Omelette, Toast","13:13","13:13","13:15","13:15"),
(12501,"C1026","12:59","preparing food","COD","Burger, Fries, Soda","13:14",None,None,None),
(12502,"C1027","12:00","picked up","COD","Salad, Soup","13:15","13:15","13:45",None),
(12503,"C1028","12:40","delivered","completed","Steak, Pizza","13:15","13:15","13:15","13:15"),
(12504,"C1029","12:35","canceled","canceled","Hotdog",None,None,None,None),
(12505,"C1030","12:50","delivered","completed","Pasta, Garlic Bread","13:10","13:10","13:15","13:15")
])
conn.commit()
conn.close()
initialize_database()
# ---------------- PAGE CONFIG ----------------
st.set_page_config(
page_title="FoodHub AI Customer Support",
page_icon="π",
layout="centered"
)
# ---------------- API KEY ----------------
#os.environ["OPENAI_API_KEY"] = "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"
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
# ---------------- LOAD DB & LLM ----------------
#db = SQLDatabase.from_uri("sqlite:///./customer_orders.db")
DB_PATH = "customer_orders.db"
db = SQLDatabase.from_uri(f"sqlite:///{DB_PATH}")
llm = ChatOpenAI(
model="gpt-4o-mini",
temperature=0.2
)
# ---------------- handel Cancel ----------------
def detect_intent(text: str):
text = text.lower()
if "cancel" in text:
return "CANCEL"
if "add" in text or "update" in text or "extra" in text:
return "ADD_ITEM"
return "STATUS"
# ---------------- GUARDRAILS ----------------
def is_malicious_or_invalid(query: str) -> bool:
blocked_keywords = [
"hacker", "hack", "all orders",
"database", "dump", "steal"
]
return any(word in query.lower() for word in blocked_keywords)
import re
def extract_order_id(text: str):
"""
Extract order ID from flexible user phrases.
Supports:
- order_id 1023
- order id 1023
- order 1023
- my order is 1023
"""
text = text.lower()
patterns = [
r"order[_\s]?id\s*(\d+)",
r"order\s*(\d+)",
r"order\s*is\s*(\d+)"
]
for pattern in patterns:
match = re.search(pattern, text)
if match:
return match.group(1)
return None
# ---------------- TOOLS ----------------
def order_query_tool(order_id: str):
query = f"SELECT * FROM orders WHERE order_id = {order_id}"
result = db.run(query)
if not result:
return None
if isinstance(result, str):
result = ast.literal_eval(result)
return result[0]
def answer_tool(order_data):
prompt = f"""
You are a FoodHub customer support assistant.
Generate a polite and professional response.
Order Data: {order_data}
"""
return llm.invoke(prompt).content
# ---------------- CHAT AGENT ----------------
def chatagent(user_query: str):
# π« Guardrail
if is_malicious_or_invalid(user_query):
return "π« Unauthorized request. I can only help with your own order."
# ---------------------------------
# Handle follow-up confirmation
# ---------------------------------
if st.session_state.conversation_stage == "WAITING_FOR_CONFIRMATION":
order_id = st.session_state.pending_order_id
action = st.session_state.pending_action
st.session_state.conversation_stage = "IDLE"
st.session_state.pending_order_id = None
st.session_state.pending_action = None
return (
f"β
Thank you for confirming.\n\n"
f"Your request regarding **Order #{order_id}** has been "
f"forwarded to our **human support team**.\n\n"
f"π A support executive will contact you shortly to assist "
f"with item updates and payment."
)
# ---------------------------------
# Normal flow
# ---------------------------------
order_id = extract_order_id(user_query)
if not order_id:
return "βΉοΈ Please provide your order ID to proceed."
intent = detect_intent(user_query)
order_data = order_query_tool(order_id)
if not order_data:
return f"β No order found with ID {order_id}."
order_status = order_data[3].lower() # order_status column
# ---------------------------------
# ADD ITEM FLOW
# ---------------------------------
if intent == "ADD_ITEM":
if "preparing" in order_status:
st.session_state.conversation_stage = "WAITING_FOR_CONFIRMATION"
st.session_state.pending_order_id = order_id
st.session_state.pending_action = "ADD_ITEM"
return (
f"π Your order **#{order_id}** is currently being prepared.\n\n"
f"β Adding items at this stage may require **additional payment**.\n\n"
f"β Please confirm if you want to proceed. "
f"Reply with **Yes** to connect with a human support agent."
)
else:
return (
f"β οΈ Item modification is not possible because "
f"your order **#{order_id}** is already **{order_status}**."
)
# ---------------------------------
# CANCEL FLOW (already implemented)
# ---------------------------------
if intent == "CANCEL":
st.session_state.conversation_stage = "WAITING_FOR_REASON"
st.session_state.pending_order_id = order_id
return (
f"β οΈ You have requested to cancel **Order #{order_id}**.\n\n"
f"β Please tell us the reason for cancellation."
)
# ---------------------------------
# STATUS FLOW
# ---------------------------------
return answer_tool(order_data)
# ---------------- UI ----------------
st.title("π FoodHub AI Customer Support Chatbot")
st.markdown(
"""
Ask questions about your **order status, delivery ETA, or cancellation**.
**Example queries:**
- Where is my order with order_id 1023?
- I want to cancel my order with order_id 1023
"""
)
user_query = st.text_input("π¬ Enter your question here")
if st.button("Ask"):
if user_query.strip():
with st.spinner("Fetching your order details..."):
response = chatagent(user_query)
st.success(response)
else:
st.warning("Please enter a question.") |