Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -18,6 +18,8 @@ st.set_page_config(page_title="RAG Customer Support Chatbot", layout="wide")
|
|
18 |
DEFAULT_TWILIO_ACCOUNT_SID_FALLBACK = ""
|
19 |
DEFAULT_TWILIO_AUTH_TOKEN_FALLBACK = ""
|
20 |
DEFAULT_GROQ_API_KEY_FALLBACK = ""
|
|
|
|
|
21 |
DEFAULT_TWILIO_BOT_WHATSAPP_IDENTITY = st.secrets.get("TWILIO_PHONE_NUMBER", "whatsapp:+14155238886")
|
22 |
DEFAULT_EMBEDDING_MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
|
23 |
DEFAULT_POLLING_INTERVAL_S = 30
|
@@ -31,8 +33,11 @@ FAQ_PDF_FILE = os.path.join(DOCS_FOLDER, "FAQ.pdf")
|
|
31 |
APP_TWILIO_ACCOUNT_SID = st.secrets.get("TWILIO_ACCOUNT_SID")
|
32 |
APP_TWILIO_AUTH_TOKEN = st.secrets.get("TWILIO_AUTH_TOKEN")
|
33 |
APP_GROQ_API_KEY = st.secrets.get("GROQ_API_KEY")
|
|
|
|
|
34 |
APP_TWILIO_BOT_WHATSAPP_IDENTITY_SECRET = st.secrets.get("TWILIO_BOT_WHATSAPP_IDENTITY")
|
35 |
|
|
|
36 |
# --- RAG Processing Utilities ---
|
37 |
def load_json_data(file_path):
|
38 |
try:
|
@@ -197,6 +202,7 @@ def generate_response_groq(_groq_client, query, context, model="llama3-8b-8192",
|
|
197 |
|
198 |
item_description = item_name if item_name else "the ordered item(s)"
|
199 |
|
|
|
200 |
core_info_parts = [
|
201 |
f"your order {order_id}",
|
202 |
f"for {item_description}",
|
@@ -214,9 +220,10 @@ def generate_response_groq(_groq_client, query, context, model="llama3-8b-8192",
|
|
214 |
core_info_parts.append("(delivery date not specified)")
|
215 |
|
216 |
core_information_to_include = ", ".join(core_info_parts[:-1]) + (f" {core_info_parts[-1]}" if len(core_info_parts) > 1 else "")
|
217 |
-
if not order_status.lower() == "delivered" and len(core_info_parts) > 1:
|
218 |
core_information_to_include = f"your order {order_id} for {item_description} has a status of '{order_status}'"
|
219 |
|
|
|
220 |
user_prompt = (
|
221 |
f"Customer: {customer_name}\n"
|
222 |
f"Order ID: {order_id}\n"
|
@@ -237,8 +244,10 @@ def generate_response_groq(_groq_client, query, context, model="llama3-8b-8192",
|
|
237 |
f"Ensure the sentence flows naturally and uses the details you've been given.\n"
|
238 |
f"Respond now with ONLY that single sentence."
|
239 |
)
|
|
|
|
|
240 |
|
241 |
-
else:
|
242 |
system_message = "You are a helpful customer support assistant."
|
243 |
user_prompt = f"""Use the following context to answer the user's question.
|
244 |
If the context doesn't contain the answer, state that you don't have enough information or ask clarifying questions.
|
@@ -258,16 +267,17 @@ Assistant Answer:
|
|
258 |
{"role": "user", "content": user_prompt}
|
259 |
],
|
260 |
model=model,
|
261 |
-
temperature=0.5,
|
262 |
max_tokens=1024,
|
263 |
top_p=1
|
264 |
)
|
265 |
-
response = chat_completion.choices[0].message.content.strip()
|
266 |
return response
|
267 |
except Exception as e:
|
268 |
st.error(f"Error calling GROQ API: {e}")
|
269 |
return "Sorry, I encountered an error while trying to generate a response."
|
270 |
|
|
|
271 |
def initialize_groq_client(api_key_val):
|
272 |
if not api_key_val:
|
273 |
st.warning("GROQ API Key is missing.")
|
@@ -291,53 +301,62 @@ def initialize_twilio_client(acc_sid, auth_tkn):
|
|
291 |
st.error(f"Failed to initialize Twilio client: {e}")
|
292 |
return None
|
293 |
|
294 |
-
def get_new_whatsapp_messages(twilio_client,
|
|
|
295 |
if not twilio_client:
|
296 |
st.warning("Twilio client not initialized.")
|
297 |
return []
|
|
|
|
|
|
|
298 |
if not bot_whatsapp_identity_val:
|
299 |
st.warning("Twilio Bot WhatsApp Identity not provided.")
|
300 |
return []
|
301 |
|
302 |
new_messages_to_process = []
|
303 |
try:
|
304 |
-
|
305 |
-
|
|
|
|
|
306 |
|
307 |
for conv in conversations:
|
308 |
if conv.date_updated and conv.date_updated > bot_start_time_utc:
|
309 |
messages = twilio_client.conversations.v1 \
|
|
|
310 |
.conversations(conv.sid) \
|
311 |
.messages \
|
312 |
-
.list(order='desc', limit=10)
|
313 |
|
314 |
for msg in messages:
|
315 |
if msg.sid in processed_message_sids:
|
316 |
-
continue
|
317 |
|
318 |
-
# Check if message is from WhatsApp and not from the bot
|
319 |
if msg.author and msg.author.lower() != bot_whatsapp_identity_val.lower() and \
|
320 |
-
msg.date_created and msg.date_created > bot_start_time_utc
|
321 |
-
msg.author.startswith('whatsapp:'):
|
322 |
new_messages_to_process.append({
|
323 |
"conversation_sid": conv.sid, "message_sid": msg.sid,
|
324 |
"author_identity": msg.author, "message_body": msg.body,
|
325 |
"timestamp_utc": msg.date_created
|
326 |
})
|
327 |
-
break
|
328 |
except Exception as e:
|
329 |
st.error(f"Error fetching Twilio messages: {e}")
|
330 |
-
return sorted(new_messages_to_process, key=lambda m: m['timestamp_utc'])
|
331 |
|
332 |
-
def send_whatsapp_message(twilio_client, conversation_sid, message_body, bot_identity_val):
|
333 |
if not twilio_client:
|
334 |
st.error("Twilio client not initialized for sending message.")
|
335 |
return False
|
|
|
|
|
|
|
336 |
if not bot_identity_val:
|
337 |
st.error("Bot identity not provided for sending message.")
|
338 |
return False
|
339 |
try:
|
340 |
twilio_client.conversations.v1 \
|
|
|
341 |
.conversations(conversation_sid) \
|
342 |
.messages \
|
343 |
.create(author=bot_identity_val, body=message_body)
|
@@ -347,27 +366,6 @@ def send_whatsapp_message(twilio_client, conversation_sid, message_body, bot_ide
|
|
347 |
st.error(f"Error sending Twilio message to {conversation_sid}: {e}")
|
348 |
return False
|
349 |
|
350 |
-
# --- Simple Intent Classifier ---
|
351 |
-
def simple_intent_classifier(query):
|
352 |
-
query_lower = query.lower()
|
353 |
-
order_keywords = ["order", "status", "track", "delivery"]
|
354 |
-
order_id_match = re.search(r'\b(ord\d{3,})\b', query_lower, re.IGNORECASE)
|
355 |
-
|
356 |
-
if any(k in query_lower for k in order_keywords):
|
357 |
-
if order_id_match:
|
358 |
-
return "ORDER_STATUS", order_id_match.group(1).upper()
|
359 |
-
return "ORDER_STATUS", None
|
360 |
-
|
361 |
-
product_keywords = ["product", "item", "buy", "price", "feature", "stock"]
|
362 |
-
product_id_match = re.search(r'\b(prd\d{3,})\b', query_lower, re.IGNORECASE)
|
363 |
-
if any(k in query_lower for k in product_keywords) or product_id_match:
|
364 |
-
return "PRODUCT_INFO", None
|
365 |
-
|
366 |
-
if any(k in query_lower for k in ["return", "policy", "refund", "exchange", "faq", "question", "how to", "support"]):
|
367 |
-
return "GENERAL_POLICY_FAQ", None
|
368 |
-
|
369 |
-
return "UNKNOWN", None
|
370 |
-
|
371 |
# --- Main Application Logic & UI ---
|
372 |
st.title("🤖 RAG-Based Customer Support Chatbot")
|
373 |
st.markdown("Powered by Streamlit, Twilio, GROQ LLaMA3, and FAISS.")
|
@@ -396,10 +394,16 @@ else:
|
|
396 |
st.sidebar.warning("Secret 'GROQ_API_KEY' not found.")
|
397 |
groq_api_key_to_use = st.sidebar.text_input("GROQ API Key (Enter Manually)", value=DEFAULT_GROQ_API_KEY_FALLBACK, type="password")
|
398 |
|
|
|
|
|
|
|
|
|
|
|
|
|
399 |
twilio_bot_whatsapp_identity_to_use = st.sidebar.text_input(
|
400 |
"Twilio Bot WhatsApp Identity",
|
401 |
value=APP_TWILIO_BOT_WHATSAPP_IDENTITY_SECRET or DEFAULT_TWILIO_BOT_WHATSAPP_IDENTITY,
|
402 |
-
help="e.g., 'whatsapp:+1234567890'"
|
403 |
)
|
404 |
embedding_model_name_to_use = st.sidebar.text_input(
|
405 |
"Embedding Model Name",
|
@@ -421,6 +425,27 @@ if "bot_start_time_utc" not in st.session_state: st.session_state.bot_start_time
|
|
421 |
if "processed_message_sids" not in st.session_state: st.session_state.processed_message_sids = set()
|
422 |
if "manual_chat_history" not in st.session_state: st.session_state.manual_chat_history = []
|
423 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
424 |
# --- Main Application Controls ---
|
425 |
col1, col2, col3, col4 = st.columns(4)
|
426 |
with col1:
|
@@ -481,8 +506,8 @@ with col2:
|
|
481 |
st.rerun()
|
482 |
with col3:
|
483 |
if st.button("💬 Start WhatsApp Bot", disabled=not st.session_state.app_started or st.session_state.bot_started, use_container_width=True):
|
484 |
-
if not all([twilio_account_sid_to_use, twilio_auth_token_to_use, twilio_bot_whatsapp_identity_to_use]):
|
485 |
-
st.error("Twilio Account SID, Auth Token, and Bot WhatsApp Identity are all required.")
|
486 |
else:
|
487 |
st.session_state.twilio_client = initialize_twilio_client(twilio_account_sid_to_use, twilio_auth_token_to_use)
|
488 |
if st.session_state.twilio_client:
|
@@ -535,6 +560,7 @@ if st.session_state.get("app_started") and st.session_state.get("rag_pipeline_re
|
|
535 |
extracted_customer_name, extracted_item_name, extracted_shipping_address, \
|
536 |
extracted_delivery_date, extracted_order_id, extracted_order_status = [None] * 6
|
537 |
|
|
|
538 |
if intent == "ORDER_STATUS":
|
539 |
order_id_to_check = None
|
540 |
if potential_oid_from_intent:
|
@@ -546,6 +572,9 @@ if st.session_state.get("app_started") and st.session_state.get("rag_pipeline_re
|
|
546 |
|
547 |
if order_id_to_check:
|
548 |
raw_context_data = get_order_details(order_id_to_check, st.session_state.customer_orders_data)
|
|
|
|
|
|
|
549 |
context_for_llm = raw_context_data
|
550 |
|
551 |
if isinstance(raw_context_data, str) and not raw_context_data.startswith("No order found") and not raw_context_data.startswith("Customer order data is not loaded"):
|
@@ -556,23 +585,23 @@ if st.session_state.get("app_started") and st.session_state.get("rag_pipeline_re
|
|
556 |
if items and len(items) > 0 and isinstance(items[0], dict):
|
557 |
extracted_item_name = items[0].get("name", "your item(s)")
|
558 |
else:
|
559 |
-
extracted_item_name = "your item(s)"
|
560 |
extracted_shipping_address = order_data_dict.get("shipping_address")
|
561 |
extracted_delivery_date = order_data_dict.get("delivered_on")
|
562 |
extracted_order_status = order_data_dict.get("status")
|
563 |
-
extracted_order_id = order_data_dict.get("order_id")
|
564 |
except json.JSONDecodeError:
|
565 |
st.warning(f"Could not parse order details JSON for {order_id_to_check} for personalization.")
|
566 |
context_for_llm = f"Error parsing order details for {order_id_to_check}. Raw data: {raw_context_data}"
|
567 |
-
elif isinstance(raw_context_data, str):
|
568 |
-
context_for_llm = raw_context_data
|
569 |
else:
|
570 |
context_for_llm = "To check an order status, please provide a valid Order ID (e.g., ORD123)."
|
571 |
raw_context_data = {"message": "Order ID needed or not found in query."}
|
572 |
|
573 |
elif intent == "PRODUCT_INFO":
|
574 |
raw_context_data = get_product_info(user_query_manual, st.session_state.products_data)
|
575 |
-
context_for_llm = raw_context_data
|
576 |
|
577 |
elif intent == "GENERAL_POLICY_FAQ" or intent == "UNKNOWN":
|
578 |
if st.session_state.faiss_index_pdfs and st.session_state.embedding_model and st.session_state.indexed_pdf_chunks:
|
@@ -598,7 +627,7 @@ if st.session_state.get("app_started") and st.session_state.get("rag_pipeline_re
|
|
598 |
item_name=extracted_item_name,
|
599 |
shipping_address=extracted_shipping_address,
|
600 |
delivery_date=extracted_delivery_date,
|
601 |
-
order_id=extracted_order_id,
|
602 |
order_status=extracted_order_status
|
603 |
)
|
604 |
|
@@ -617,7 +646,7 @@ if st.session_state.get("app_started") and st.session_state.get("rag_pipeline_re
|
|
617 |
except (json.JSONDecodeError, TypeError):
|
618 |
st.text(str(raw_context_data))
|
619 |
st.session_state.manual_chat_history.append({"role": "assistant", "content": llm_response, "context": raw_context_data})
|
620 |
-
st.rerun()
|
621 |
|
622 |
# --- Twilio Bot Polling Logic ---
|
623 |
if st.session_state.get("bot_started") and st.session_state.get("rag_pipeline_ready"):
|
@@ -628,17 +657,18 @@ if st.session_state.get("bot_started") and st.session_state.get("rag_pipeline_re
|
|
628 |
if (current_time - st.session_state.last_twilio_poll_time) > polling_interval_to_use:
|
629 |
st.session_state.last_twilio_poll_time = current_time
|
630 |
|
631 |
-
if not st.session_state.get("twilio_client") or
|
632 |
-
|
|
|
|
|
|
|
633 |
else:
|
634 |
with st.spinner(f"Checking WhatsApp messages (last poll: {datetime.fromtimestamp(st.session_state.last_twilio_poll_time).strftime('%H:%M:%S')})..."):
|
635 |
-
new_messages = get_new_whatsapp_messages(
|
636 |
-
|
637 |
-
|
638 |
-
|
639 |
-
|
640 |
-
)
|
641 |
-
|
642 |
if new_messages:
|
643 |
st.info(f"Found {len(new_messages)} new WhatsApp message(s) to process.")
|
644 |
for msg_data in new_messages:
|
@@ -658,6 +688,7 @@ if st.session_state.get("bot_started") and st.session_state.get("rag_pipeline_re
|
|
658 |
wa_customer_name, wa_item_name, wa_shipping_address, \
|
659 |
wa_delivery_date, wa_order_id, wa_order_status = [None] * 6
|
660 |
|
|
|
661 |
if intent_whatsapp == "ORDER_STATUS":
|
662 |
order_id_to_check_whatsapp = None
|
663 |
if potential_oid_whatsapp:
|
@@ -669,7 +700,7 @@ if st.session_state.get("bot_started") and st.session_state.get("rag_pipeline_re
|
|
669 |
|
670 |
if order_id_to_check_whatsapp:
|
671 |
raw_context_data_whatsapp = get_order_details(order_id_to_check_whatsapp, st.session_state.customer_orders_data)
|
672 |
-
context_for_llm_whatsapp = raw_context_data_whatsapp
|
673 |
|
674 |
if isinstance(raw_context_data_whatsapp, str) and not raw_context_data_whatsapp.startswith("No order found") and not raw_context_data_whatsapp.startswith("Customer order data is not loaded"):
|
675 |
try:
|
@@ -687,4 +718,63 @@ if st.session_state.get("bot_started") and st.session_state.get("rag_pipeline_re
|
|
687 |
except json.JSONDecodeError:
|
688 |
st.warning(f"Could not parse order details JSON for {order_id_to_check_whatsapp} (WhatsApp) for personalization.")
|
689 |
context_for_llm_whatsapp = f"Error parsing order details for {order_id_to_check_whatsapp}. Raw data: {raw_context_data_whatsapp}"
|
690 |
-
elif isinstance(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
18 |
DEFAULT_TWILIO_ACCOUNT_SID_FALLBACK = ""
|
19 |
DEFAULT_TWILIO_AUTH_TOKEN_FALLBACK = ""
|
20 |
DEFAULT_GROQ_API_KEY_FALLBACK = ""
|
21 |
+
|
22 |
+
#DEFAULT_TWILIO_CONVERSATION_SERVICE_SID = ""
|
23 |
DEFAULT_TWILIO_BOT_WHATSAPP_IDENTITY = st.secrets.get("TWILIO_PHONE_NUMBER", "whatsapp:+14155238886")
|
24 |
DEFAULT_EMBEDDING_MODEL_NAME = "sentence-transformers/all-MiniLM-L6-v2"
|
25 |
DEFAULT_POLLING_INTERVAL_S = 30
|
|
|
33 |
APP_TWILIO_ACCOUNT_SID = st.secrets.get("TWILIO_ACCOUNT_SID")
|
34 |
APP_TWILIO_AUTH_TOKEN = st.secrets.get("TWILIO_AUTH_TOKEN")
|
35 |
APP_GROQ_API_KEY = st.secrets.get("GROQ_API_KEY")
|
36 |
+
|
37 |
+
#APP_TWILIO_CONVERSATION_SERVICE_SID_SECRET = st.secrets.get("TWILIO_CONVERSATION_SERVICE_SID")
|
38 |
APP_TWILIO_BOT_WHATSAPP_IDENTITY_SECRET = st.secrets.get("TWILIO_BOT_WHATSAPP_IDENTITY")
|
39 |
|
40 |
+
|
41 |
# --- RAG Processing Utilities ---
|
42 |
def load_json_data(file_path):
|
43 |
try:
|
|
|
202 |
|
203 |
item_description = item_name if item_name else "the ordered item(s)"
|
204 |
|
205 |
+
# Construct the core information string that the LLM needs to build upon
|
206 |
core_info_parts = [
|
207 |
f"your order {order_id}",
|
208 |
f"for {item_description}",
|
|
|
220 |
core_info_parts.append("(delivery date not specified)")
|
221 |
|
222 |
core_information_to_include = ", ".join(core_info_parts[:-1]) + (f" {core_info_parts[-1]}" if len(core_info_parts) > 1 else "")
|
223 |
+
if not order_status.lower() == "delivered" and len(core_info_parts) > 1 : # for non-delivered, avoid 'and' before status
|
224 |
core_information_to_include = f"your order {order_id} for {item_description} has a status of '{order_status}'"
|
225 |
|
226 |
+
|
227 |
user_prompt = (
|
228 |
f"Customer: {customer_name}\n"
|
229 |
f"Order ID: {order_id}\n"
|
|
|
244 |
f"Ensure the sentence flows naturally and uses the details you've been given.\n"
|
245 |
f"Respond now with ONLY that single sentence."
|
246 |
)
|
247 |
+
# For LLM's deeper reference, though the primary instruction is above:
|
248 |
+
# user_prompt += f"\n\nFull database context for your reference if needed: {context}"
|
249 |
|
250 |
+
else: # Default prompt structure for other intents or if details are missing
|
251 |
system_message = "You are a helpful customer support assistant."
|
252 |
user_prompt = f"""Use the following context to answer the user's question.
|
253 |
If the context doesn't contain the answer, state that you don't have enough information or ask clarifying questions.
|
|
|
267 |
{"role": "user", "content": user_prompt}
|
268 |
],
|
269 |
model=model,
|
270 |
+
temperature=0.5, # Slightly lower temperature might help with stricter adherence
|
271 |
max_tokens=1024,
|
272 |
top_p=1
|
273 |
)
|
274 |
+
response = chat_completion.choices[0].message.content.strip() # Added strip()
|
275 |
return response
|
276 |
except Exception as e:
|
277 |
st.error(f"Error calling GROQ API: {e}")
|
278 |
return "Sorry, I encountered an error while trying to generate a response."
|
279 |
|
280 |
+
|
281 |
def initialize_groq_client(api_key_val):
|
282 |
if not api_key_val:
|
283 |
st.warning("GROQ API Key is missing.")
|
|
|
301 |
st.error(f"Failed to initialize Twilio client: {e}")
|
302 |
return None
|
303 |
|
304 |
+
def get_new_whatsapp_messages(twilio_client, conversation_service_sid_val, bot_start_time_utc,
|
305 |
+
processed_message_sids, bot_whatsapp_identity_val):
|
306 |
if not twilio_client:
|
307 |
st.warning("Twilio client not initialized.")
|
308 |
return []
|
309 |
+
if not conversation_service_sid_val:
|
310 |
+
st.warning("Twilio Conversation Service SID not provided.")
|
311 |
+
return []
|
312 |
if not bot_whatsapp_identity_val:
|
313 |
st.warning("Twilio Bot WhatsApp Identity not provided.")
|
314 |
return []
|
315 |
|
316 |
new_messages_to_process = []
|
317 |
try:
|
318 |
+
conversations = twilio_client.conversations.v1 \
|
319 |
+
.services(conversation_service_sid_val) \
|
320 |
+
.conversations \
|
321 |
+
.list(limit=50)
|
322 |
|
323 |
for conv in conversations:
|
324 |
if conv.date_updated and conv.date_updated > bot_start_time_utc:
|
325 |
messages = twilio_client.conversations.v1 \
|
326 |
+
.services(conversation_service_sid_val) \
|
327 |
.conversations(conv.sid) \
|
328 |
.messages \
|
329 |
+
.list(order='desc', limit=10)
|
330 |
|
331 |
for msg in messages:
|
332 |
if msg.sid in processed_message_sids:
|
333 |
+
continue
|
334 |
|
|
|
335 |
if msg.author and msg.author.lower() != bot_whatsapp_identity_val.lower() and \
|
336 |
+
msg.date_created and msg.date_created > bot_start_time_utc:
|
|
|
337 |
new_messages_to_process.append({
|
338 |
"conversation_sid": conv.sid, "message_sid": msg.sid,
|
339 |
"author_identity": msg.author, "message_body": msg.body,
|
340 |
"timestamp_utc": msg.date_created
|
341 |
})
|
342 |
+
break
|
343 |
except Exception as e:
|
344 |
st.error(f"Error fetching Twilio messages: {e}")
|
345 |
+
return sorted(new_messages_to_process, key=lambda m: m['timestamp_utc'])
|
346 |
|
347 |
+
def send_whatsapp_message(twilio_client, conversation_service_sid_val, conversation_sid, message_body, bot_identity_val):
|
348 |
if not twilio_client:
|
349 |
st.error("Twilio client not initialized for sending message.")
|
350 |
return False
|
351 |
+
if not conversation_service_sid_val:
|
352 |
+
st.error("Twilio Conversation Service SID not provided for sending message.")
|
353 |
+
return False
|
354 |
if not bot_identity_val:
|
355 |
st.error("Bot identity not provided for sending message.")
|
356 |
return False
|
357 |
try:
|
358 |
twilio_client.conversations.v1 \
|
359 |
+
.services(conversation_service_sid_val) \
|
360 |
.conversations(conversation_sid) \
|
361 |
.messages \
|
362 |
.create(author=bot_identity_val, body=message_body)
|
|
|
366 |
st.error(f"Error sending Twilio message to {conversation_sid}: {e}")
|
367 |
return False
|
368 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
369 |
# --- Main Application Logic & UI ---
|
370 |
st.title("🤖 RAG-Based Customer Support Chatbot")
|
371 |
st.markdown("Powered by Streamlit, Twilio, GROQ LLaMA3, and FAISS.")
|
|
|
394 |
st.sidebar.warning("Secret 'GROQ_API_KEY' not found.")
|
395 |
groq_api_key_to_use = st.sidebar.text_input("GROQ API Key (Enter Manually)", value=DEFAULT_GROQ_API_KEY_FALLBACK, type="password")
|
396 |
|
397 |
+
# twilio_conversation_service_sid_to_use = st.sidebar.text_input(
|
398 |
+
# "Twilio Conversation Service SID (IS...)",
|
399 |
+
# value=APP_TWILIO_CONVERSATION_SERVICE_SID_SECRET or DEFAULT_TWILIO_CONVERSATION_SERVICE_SID,
|
400 |
+
# type="password",
|
401 |
+
# help="The SID of your Twilio Conversations Service. Can be set by 'TWILIO_CONVERSATION_SERVICE_SID' secret."
|
402 |
+
# )
|
403 |
twilio_bot_whatsapp_identity_to_use = st.sidebar.text_input(
|
404 |
"Twilio Bot WhatsApp Identity",
|
405 |
value=APP_TWILIO_BOT_WHATSAPP_IDENTITY_SECRET or DEFAULT_TWILIO_BOT_WHATSAPP_IDENTITY,
|
406 |
+
help="e.g., 'whatsapp:+1234567890'. Can be set by 'TWILIO_BOT_WHATSAPP_IDENTITY' secret."
|
407 |
)
|
408 |
embedding_model_name_to_use = st.sidebar.text_input(
|
409 |
"Embedding Model Name",
|
|
|
425 |
if "processed_message_sids" not in st.session_state: st.session_state.processed_message_sids = set()
|
426 |
if "manual_chat_history" not in st.session_state: st.session_state.manual_chat_history = []
|
427 |
|
428 |
+
# --- Helper: Simple Intent Classifier ---
|
429 |
+
def simple_intent_classifier(query):
|
430 |
+
query_lower = query.lower()
|
431 |
+
order_keywords = ["order", "status", "track", "delivery"]
|
432 |
+
order_id_match = re.search(r'\b(ord\d{3,})\b', query_lower, re.IGNORECASE)
|
433 |
+
|
434 |
+
if any(k in query_lower for k in order_keywords):
|
435 |
+
if order_id_match:
|
436 |
+
return "ORDER_STATUS", order_id_match.group(1).upper()
|
437 |
+
return "ORDER_STATUS", None
|
438 |
+
|
439 |
+
product_keywords = ["product", "item", "buy", "price", "feature", "stock"]
|
440 |
+
product_id_match = re.search(r'\b(prd\d{3,})\b', query_lower, re.IGNORECASE)
|
441 |
+
if any(k in query_lower for k in product_keywords) or product_id_match:
|
442 |
+
return "PRODUCT_INFO", None
|
443 |
+
|
444 |
+
if any(k in query_lower for k in ["return", "policy", "refund", "exchange", "faq", "question", "how to", "support"]):
|
445 |
+
return "GENERAL_POLICY_FAQ", None
|
446 |
+
|
447 |
+
return "UNKNOWN", None
|
448 |
+
|
449 |
# --- Main Application Controls ---
|
450 |
col1, col2, col3, col4 = st.columns(4)
|
451 |
with col1:
|
|
|
506 |
st.rerun()
|
507 |
with col3:
|
508 |
if st.button("💬 Start WhatsApp Bot", disabled=not st.session_state.app_started or st.session_state.bot_started, use_container_width=True):
|
509 |
+
if not all([twilio_account_sid_to_use, twilio_auth_token_to_use, twilio_conversation_service_sid_to_use, twilio_bot_whatsapp_identity_to_use]):
|
510 |
+
st.error("Twilio Account SID, Auth Token, Conversation Service SID, and Bot WhatsApp Identity are all required.")
|
511 |
else:
|
512 |
st.session_state.twilio_client = initialize_twilio_client(twilio_account_sid_to_use, twilio_auth_token_to_use)
|
513 |
if st.session_state.twilio_client:
|
|
|
560 |
extracted_customer_name, extracted_item_name, extracted_shipping_address, \
|
561 |
extracted_delivery_date, extracted_order_id, extracted_order_status = [None] * 6
|
562 |
|
563 |
+
|
564 |
if intent == "ORDER_STATUS":
|
565 |
order_id_to_check = None
|
566 |
if potential_oid_from_intent:
|
|
|
572 |
|
573 |
if order_id_to_check:
|
574 |
raw_context_data = get_order_details(order_id_to_check, st.session_state.customer_orders_data)
|
575 |
+
# context_for_llm will be used as the 'context' parameter in generate_response_groq
|
576 |
+
# For ORDER_STATUS, this raw_context_data (JSON string) is still useful for LLM's reference,
|
577 |
+
# even though specific fields are extracted for the specialized prompt.
|
578 |
context_for_llm = raw_context_data
|
579 |
|
580 |
if isinstance(raw_context_data, str) and not raw_context_data.startswith("No order found") and not raw_context_data.startswith("Customer order data is not loaded"):
|
|
|
585 |
if items and len(items) > 0 and isinstance(items[0], dict):
|
586 |
extracted_item_name = items[0].get("name", "your item(s)")
|
587 |
else:
|
588 |
+
extracted_item_name = "your item(s)" # Fallback
|
589 |
extracted_shipping_address = order_data_dict.get("shipping_address")
|
590 |
extracted_delivery_date = order_data_dict.get("delivered_on")
|
591 |
extracted_order_status = order_data_dict.get("status")
|
592 |
+
extracted_order_id = order_data_dict.get("order_id") # Should be same as order_id_to_check
|
593 |
except json.JSONDecodeError:
|
594 |
st.warning(f"Could not parse order details JSON for {order_id_to_check} for personalization.")
|
595 |
context_for_llm = f"Error parsing order details for {order_id_to_check}. Raw data: {raw_context_data}"
|
596 |
+
elif isinstance(raw_context_data, str): # Handle "No order found" or "data not loaded"
|
597 |
+
context_for_llm = raw_context_data # LLM will state this
|
598 |
else:
|
599 |
context_for_llm = "To check an order status, please provide a valid Order ID (e.g., ORD123)."
|
600 |
raw_context_data = {"message": "Order ID needed or not found in query."}
|
601 |
|
602 |
elif intent == "PRODUCT_INFO":
|
603 |
raw_context_data = get_product_info(user_query_manual, st.session_state.products_data)
|
604 |
+
context_for_llm = raw_context_data # Product info is directly used as context
|
605 |
|
606 |
elif intent == "GENERAL_POLICY_FAQ" or intent == "UNKNOWN":
|
607 |
if st.session_state.faiss_index_pdfs and st.session_state.embedding_model and st.session_state.indexed_pdf_chunks:
|
|
|
627 |
item_name=extracted_item_name,
|
628 |
shipping_address=extracted_shipping_address,
|
629 |
delivery_date=extracted_delivery_date,
|
630 |
+
order_id=extracted_order_id, # This will be the specific order ID from user query
|
631 |
order_status=extracted_order_status
|
632 |
)
|
633 |
|
|
|
646 |
except (json.JSONDecodeError, TypeError):
|
647 |
st.text(str(raw_context_data))
|
648 |
st.session_state.manual_chat_history.append({"role": "assistant", "content": llm_response, "context": raw_context_data})
|
649 |
+
st.rerun()
|
650 |
|
651 |
# --- Twilio Bot Polling Logic ---
|
652 |
if st.session_state.get("bot_started") and st.session_state.get("rag_pipeline_ready"):
|
|
|
657 |
if (current_time - st.session_state.last_twilio_poll_time) > polling_interval_to_use:
|
658 |
st.session_state.last_twilio_poll_time = current_time
|
659 |
|
660 |
+
if not st.session_state.get("twilio_client") or \
|
661 |
+
not twilio_conversation_service_sid_to_use or \
|
662 |
+
not twilio_bot_whatsapp_identity_to_use or \
|
663 |
+
not st.session_state.get("bot_start_time_utc"):
|
664 |
+
st.warning("Twilio client/config missing for polling. Ensure bot is started and SIDs are set.")
|
665 |
else:
|
666 |
with st.spinner(f"Checking WhatsApp messages (last poll: {datetime.fromtimestamp(st.session_state.last_twilio_poll_time).strftime('%H:%M:%S')})..."):
|
667 |
+
new_messages = get_new_whatsapp_messages(st.session_state.twilio_client,
|
668 |
+
twilio_conversation_service_sid_to_use,
|
669 |
+
st.session_state.bot_start_time_utc,
|
670 |
+
st.session_state.processed_message_sids,
|
671 |
+
twilio_bot_whatsapp_identity_to_use)
|
|
|
|
|
672 |
if new_messages:
|
673 |
st.info(f"Found {len(new_messages)} new WhatsApp message(s) to process.")
|
674 |
for msg_data in new_messages:
|
|
|
688 |
wa_customer_name, wa_item_name, wa_shipping_address, \
|
689 |
wa_delivery_date, wa_order_id, wa_order_status = [None] * 6
|
690 |
|
691 |
+
|
692 |
if intent_whatsapp == "ORDER_STATUS":
|
693 |
order_id_to_check_whatsapp = None
|
694 |
if potential_oid_whatsapp:
|
|
|
700 |
|
701 |
if order_id_to_check_whatsapp:
|
702 |
raw_context_data_whatsapp = get_order_details(order_id_to_check_whatsapp, st.session_state.customer_orders_data)
|
703 |
+
context_for_llm_whatsapp = raw_context_data_whatsapp # Full JSON string as context
|
704 |
|
705 |
if isinstance(raw_context_data_whatsapp, str) and not raw_context_data_whatsapp.startswith("No order found") and not raw_context_data_whatsapp.startswith("Customer order data is not loaded"):
|
706 |
try:
|
|
|
718 |
except json.JSONDecodeError:
|
719 |
st.warning(f"Could not parse order details JSON for {order_id_to_check_whatsapp} (WhatsApp) for personalization.")
|
720 |
context_for_llm_whatsapp = f"Error parsing order details for {order_id_to_check_whatsapp}. Raw data: {raw_context_data_whatsapp}"
|
721 |
+
elif isinstance(raw_context_data_whatsapp, str):
|
722 |
+
context_for_llm_whatsapp = raw_context_data_whatsapp
|
723 |
+
else:
|
724 |
+
context_for_llm_whatsapp = "To check an order status, please provide a valid Order ID (e.g., ORD123)."
|
725 |
+
raw_context_data_whatsapp = {"message": "Order ID needed or not found in query."}
|
726 |
+
|
727 |
+
|
728 |
+
elif intent_whatsapp == "PRODUCT_INFO":
|
729 |
+
raw_context_data_whatsapp = get_product_info(user_query_whatsapp, st.session_state.products_data)
|
730 |
+
context_for_llm_whatsapp = raw_context_data_whatsapp
|
731 |
+
|
732 |
+
elif intent_whatsapp == "GENERAL_POLICY_FAQ" or intent_whatsapp == "UNKNOWN":
|
733 |
+
if st.session_state.faiss_index_pdfs and st.session_state.embedding_model and st.session_state.indexed_pdf_chunks:
|
734 |
+
k_val_whatsapp = 3 if intent_whatsapp == "GENERAL_POLICY_FAQ" else 2
|
735 |
+
chunks_whatsapp = search_faiss_index(st.session_state.faiss_index_pdfs, user_query_whatsapp,
|
736 |
+
st.session_state.embedding_model, st.session_state.indexed_pdf_chunks, k=k_val_whatsapp)
|
737 |
+
if chunks_whatsapp:
|
738 |
+
context_for_llm_whatsapp = "Relevant information from documents:\n\n" + "\n\n---\n\n".join(chunks_whatsapp)
|
739 |
+
raw_context_data_whatsapp = chunks_whatsapp
|
740 |
+
else:
|
741 |
+
context_for_llm_whatsapp = "I couldn't find specific information in our policy or FAQ documents regarding your query."
|
742 |
+
raw_context_data_whatsapp = {"message": "No relevant PDF chunks found."}
|
743 |
+
else:
|
744 |
+
context_for_llm_whatsapp = "Our policy and FAQ documents are currently unavailable for search."
|
745 |
+
raw_context_data_whatsapp = {"message": "PDF index or embedding model not ready."}
|
746 |
+
|
747 |
+
response_whatsapp = generate_response_groq(
|
748 |
+
_groq_client=st.session_state.groq_client,
|
749 |
+
query=user_query_whatsapp,
|
750 |
+
context=context_for_llm_whatsapp,
|
751 |
+
intent=intent_whatsapp,
|
752 |
+
customer_name=wa_customer_name,
|
753 |
+
item_name=wa_item_name,
|
754 |
+
shipping_address=wa_shipping_address,
|
755 |
+
delivery_date=wa_delivery_date,
|
756 |
+
order_id=wa_order_id,
|
757 |
+
order_status=wa_order_status
|
758 |
+
)
|
759 |
+
|
760 |
+
if send_whatsapp_message(st.session_state.twilio_client, twilio_conversation_service_sid_to_use,
|
761 |
+
conv_sid, response_whatsapp, twilio_bot_whatsapp_identity_to_use):
|
762 |
+
st.session_state.processed_message_sids.add(msg_sid)
|
763 |
+
st.success(f"Successfully responded to WhatsApp message SID {msg_sid} from {author_id}.")
|
764 |
+
else:
|
765 |
+
st.error(f"Failed to send WhatsApp response for message SID {msg_sid} from {author_id}.")
|
766 |
+
st.experimental_rerun()
|
767 |
+
|
768 |
+
|
769 |
+
# --- Footer & Status ---
|
770 |
+
st.sidebar.markdown("---")
|
771 |
+
st.sidebar.info("Ensure all keys and SIDs are correctly configured. Primary API keys (Twilio SID/Token, GROQ Key) are loaded from secrets if available.")
|
772 |
+
if st.session_state.get("app_started"):
|
773 |
+
status_color = "green" if st.session_state.get("rag_pipeline_ready") else "orange"
|
774 |
+
app_status_text = "App RUNNING" if st.session_state.get("rag_pipeline_ready") else "App Initializing/Error"
|
775 |
+
bot_status_text = "WhatsApp Bot RUNNING" if st.session_state.get("bot_started") else "WhatsApp Bot STOPPED"
|
776 |
+
st.sidebar.markdown(f"<span style='color:{status_color};'>{app_status_text}</span>. {bot_status_text}.", unsafe_allow_html=True)
|
777 |
+
|
778 |
+
else:
|
779 |
+
st.sidebar.warning("App is STOPPED.")
|
780 |
+
|