Update app.py
Browse files
app.py
CHANGED
|
@@ -5,6 +5,7 @@ import re
|
|
| 5 |
import os
|
| 6 |
from datetime import datetime, timedelta
|
| 7 |
import uuid
|
|
|
|
| 8 |
from typing import Dict
|
| 9 |
|
| 10 |
from config import (
|
|
@@ -17,15 +18,16 @@ from utils import (
|
|
| 17 |
get_embedding, cosine_similarity, find_top_k_matches,
|
| 18 |
classify_intent, should_include_email, classify_user_type
|
| 19 |
)
|
| 20 |
-
from scraper import scrape_workshops_from_squarespace
|
| 21 |
from database import (
|
| 22 |
fetch_all_embeddings,
|
| 23 |
fetch_row_by_id,
|
| 24 |
fetch_all_faq_embeddings,
|
| 25 |
get_session_state,
|
| 26 |
update_session_state,
|
| 27 |
-
log_question
|
|
|
|
| 28 |
)
|
|
|
|
| 29 |
|
| 30 |
# ============================================================================
|
| 31 |
# CONFIGURATION
|
|
@@ -37,8 +39,7 @@ if not OPENAI_API_KEY:
|
|
| 37 |
openai.api_key = OPENAI_API_KEY
|
| 38 |
|
| 39 |
|
| 40 |
-
#
|
| 41 |
-
session_id = str(uuid.uuid4())
|
| 42 |
|
| 43 |
# Cache for workshop data and embeddings
|
| 44 |
workshop_cache = {
|
|
@@ -222,7 +223,7 @@ def generate_enriched_links(row):
|
|
| 222 |
markdown = f"🎧 [Watch {guest_name}'s episode here]({base_url}) - {short_summary}"
|
| 223 |
return [markdown]
|
| 224 |
|
| 225 |
-
def build_enhanced_prompt(user_question, context_results, top_workshops, user_preference=None, user_type='unknown', enriched_podcast_links=None, wants_details=False, current_topic=None, mode="Mode B", is_low_confidence=False):
|
| 226 |
"""Builds the system prompt with strict formatting rules."""
|
| 227 |
|
| 228 |
# Dynamic Links from Structured Knowledge
|
|
@@ -278,11 +279,13 @@ def build_enhanced_prompt(user_question, context_results, top_workshops, user_pr
|
|
| 278 |
# Mandatory Hyperlink Enforcement
|
| 279 |
workshop_text = f"We are constantly updating our schedule! You can view and [register for upcoming {label}workshops here]({link})."
|
| 280 |
|
| 281 |
-
#
|
|
|
|
| 282 |
if not enriched_podcast_links:
|
| 283 |
-
|
| 284 |
else:
|
| 285 |
-
|
|
|
|
| 286 |
|
| 287 |
# --- EMOTIONAL / SUPPORT MODE CHECK ---
|
| 288 |
is_emotional = detect_response_type(user_question) == "support"
|
|
@@ -442,6 +445,39 @@ CRITICAL: The user is a BEGINNER. You MUST prioritize the Free Online Class abov
|
|
| 442 |
elif user_type == 'current_student':
|
| 443 |
user_type_instruction = "USER TYPE: EXISTING STUDENT. Focus on GSP membership benefits, advanced mentorships (WAM), and specialized recurring workshops."
|
| 444 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 445 |
if mode == "Mode A":
|
| 446 |
# Recommendation Mode: Existing checklist applies
|
| 447 |
prompt = f"""{PERSONA_INSTRUCTION}
|
|
@@ -462,6 +498,7 @@ CRITICAL INSTRUCTIONS (RECOMMENDATION MODE):
|
|
| 462 |
- For each recommendation, add a tiny bit of "mentor advice" on why it helps.
|
| 463 |
- Use ONLY the provided links - do not invent recommendations.
|
| 464 |
- **MANDATORY: Use direct hyperlinks.** For ANY mention of signing up, classes, kids programs, the Summit, or the free class, you MUST include the direct [Title](Link) format.
|
|
|
|
| 465 |
- **NEVER say "check our website"** or "visit the link below". Embed the link directly into the relevant part of your mentor advice.
|
| 466 |
- Focus on clean, readable formatting.{preference_instruction}
|
| 467 |
|
|
@@ -481,7 +518,7 @@ REQUIRED RESPONSE FORMAT (STRICT):
|
|
| 481 |
Here's your path forward:
|
| 482 |
1. Free Online Class (Mandatory First Step): {free_class_url}
|
| 483 |
2. Recommended Podcast Episode (For Industry Mindset):
|
| 484 |
-
{
|
| 485 |
3. Recommended Workshop/Next Step:
|
| 486 |
{workshop_text}{email_contact}
|
| 487 |
|
|
@@ -503,7 +540,7 @@ CRITICAL INSTRUCTIONS (FRONT DESK MODE):
|
|
| 503 |
- Answer the user's question directly using the provided information but keep it punchy—**no essays**.
|
| 504 |
- **MANDATORY: Provide direct hyperlinks** for ANY mention of registration, classes, kids programs, the Summit, or more information. Use EXACTLY these links as relevant:
|
| 505 |
- Free Online Class: [{free_class_url}]({free_class_url})
|
| 506 |
-
- Recommended for you: {
|
| 507 |
- Upcoming Workshops: {workshop_text}
|
| 508 |
- Southeast Actor Summit: [Southeast Actor Summit Registration](https://www.getscenestudios.com/southeast-actor-summit)
|
| 509 |
- **NEVER say "go to the website"** or "check our site". Always provide the specific hyperlink directly in your answer.
|
|
@@ -523,9 +560,9 @@ CRITICAL ROLE GUARD (FINAL AUTHORITY):
|
|
| 523 |
|
| 524 |
USER'S QUESTION: {user_question}
|
| 525 |
|
| 526 |
-
REQUIRED RESPONSE FORMAT:
|
| 527 |
[Routing Question]
|
| 528 |
[Helpful, punchy response with links]
|
|
|
|
| 529 |
[Next step guidance]{email_contact}"""
|
| 530 |
|
| 531 |
return prompt
|
|
@@ -546,6 +583,7 @@ def detect_question_category(question):
|
|
| 546 |
'pricing': ['price', 'cost', 'pricing', '$', 'money', 'payment', 'fee'],
|
| 547 |
'classes': ['class', 'workshop', 'training', 'course', 'learn'],
|
| 548 |
'membership': ['membership', 'join', 'member', 'gsp', 'plus'],
|
|
|
|
| 549 |
'technical': ['self-tape', 'equipment', 'lighting', 'editing', 'camera']
|
| 550 |
}
|
| 551 |
|
|
@@ -726,32 +764,40 @@ def process_question(question: str, current_session_id: str):
|
|
| 726 |
top_faqs.append((score, entry_id, question_text, answer_text))
|
| 727 |
top_faqs.sort(reverse=True)
|
| 728 |
|
| 729 |
-
faq_threshold = 0.
|
| 730 |
-
ambiguous_threshold = 0.
|
| 731 |
|
| 732 |
is_low_confidence = False # Default safe initialization
|
| 733 |
context_results = None
|
|
|
|
| 734 |
|
| 735 |
if top_faqs and top_faqs[0][0] >= faq_threshold:
|
| 736 |
best_score, faq_id, question_text, answer_text = top_faqs[0]
|
| 737 |
print(f"DEBUG: Processing FAQ match through LLM and Truth Sheet rules...")
|
| 738 |
context_results = answer_text
|
|
|
|
| 739 |
|
| 740 |
elif activated_mode == "Mode A":
|
| 741 |
# Mode A: Any score < 0.85 triggers Clarification -> Email
|
|
|
|
|
|
|
|
|
|
| 742 |
clarification_count = session_state.get('clarification_count', 0)
|
| 743 |
-
if clarification_count == 0:
|
| 744 |
update_session_state(current_session_id, increment_clarification=True, increment_count=False)
|
| 745 |
return "I want to make sure I give you the best advice. Are you looking for classes in [Atlanta](https://www.getscenestudios.com/instudio), [Online](https://www.getscenestudios.com/online), or something else like getting an agent? You can also start right now with our [Free Online Class](https://www.getscenestudios.com/online)!"
|
| 746 |
-
|
| 747 |
update_session_state(current_session_id, reset_clarification=True)
|
| 748 |
return "I'm still not quite sure, and I want to make sure you get the right answer! Please email our team at info@getscenestudios.com and we'll help you directly. In the meantime, you can explore or [register for our Online Path](https://www.getscenestudios.com/online) or [In-Studio classes in Atlanta](https://www.getscenestudios.com/instudio)."
|
|
|
|
| 749 |
|
| 750 |
elif top_faqs and top_faqs[0][0] >= ambiguous_threshold:
|
| 751 |
-
# Mode B: Ambiguous Score
|
| 752 |
-
|
| 753 |
-
|
| 754 |
-
|
|
|
|
|
|
|
| 755 |
|
| 756 |
else:
|
| 757 |
# 5. HALLUCINATION GUARD: Check if query is acting-related before blocking
|
|
@@ -760,7 +806,7 @@ def process_question(question: str, current_session_id: str):
|
|
| 760 |
has_session_context = (current_topic is not None) or (user_preference is not None)
|
| 761 |
|
| 762 |
FOLLOWUP_KEYWORDS = ['yes', 'no', 'sure', 'okay', 'thanks', 'thank you', 'please', 'go ahead', 'continue', 'more']
|
| 763 |
-
ACTING_KEYWORDS = ['class', 'workshop', 'coaching', 'studio', 'acting', 'online', 'person', 'atlanta', 'training', 'prefer', 'preference', 'format', 'recommendation', 'online class', 'online workshop','instudio class','instudio workshop', 'actor', 'scene', 'audition', 'theatre', 'film', 'tv', 'commercial', 'agent', 'rep', 'manager', 'instructor', 'role', 'auditing', 'audit', 'representation', 'summit', 'sign up', 'sign-up', 'register', 'enroll', 'schedule', 'cancel', 'reschedule', 'how do i']
|
| 764 |
|
| 765 |
is_acting_related = (
|
| 766 |
len(categories) > 0 or
|
|
@@ -786,14 +832,40 @@ def process_question(question: str, current_session_id: str):
|
|
| 786 |
top_workshops = find_top_workshops(user_embedding, k=3)
|
| 787 |
top_podcasts = find_top_k_matches(user_embedding, podcast_data, k=3)
|
| 788 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 789 |
enriched_podcast_links = []
|
| 790 |
for _, podcast_id, _ in top_podcasts:
|
| 791 |
row = fetch_row_by_id("podcast_episodes", podcast_id)
|
| 792 |
-
|
|
|
|
| 793 |
|
| 794 |
if not enriched_podcast_links:
|
| 795 |
fallback = fetch_row_by_id("podcast_episodes", podcast_data[0][0])
|
| 796 |
enriched_podcast_links = generate_enriched_links(fallback)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 797 |
|
| 798 |
# Brevity & Detail Detection
|
| 799 |
wants_details = any(syn in question.lower() for syn in DETAIL_SYNONYMS)
|
|
@@ -805,22 +877,22 @@ def process_question(question: str, current_session_id: str):
|
|
| 805 |
top_workshops,
|
| 806 |
user_preference=user_preference,
|
| 807 |
user_type=user_type,
|
| 808 |
-
enriched_podcast_links=
|
| 809 |
wants_details=wants_details,
|
| 810 |
current_topic=current_topic,
|
| 811 |
mode=activated_mode,
|
| 812 |
-
is_low_confidence=is_low_confidence
|
|
|
|
| 813 |
)
|
| 814 |
|
| 815 |
-
#
|
| 816 |
-
|
|
|
|
|
|
|
| 817 |
|
| 818 |
response = openai.chat.completions.create(
|
| 819 |
model=GEN_MODEL,
|
| 820 |
-
messages=
|
| 821 |
-
{"role": "system", "content": final_prompt},
|
| 822 |
-
{"role": "user", "content": question}
|
| 823 |
-
]
|
| 824 |
)
|
| 825 |
|
| 826 |
answer_text = response.choices[0].message.content.strip()
|
|
@@ -867,21 +939,23 @@ def process_question(question: str, current_session_id: str):
|
|
| 867 |
# GRADIO INTERFACE
|
| 868 |
# ============================================================================
|
| 869 |
|
| 870 |
-
def chat_with_bot(message, history):
|
| 871 |
"""
|
| 872 |
Process message directly without Flask API
|
| 873 |
|
| 874 |
Args:
|
| 875 |
message: User's current message
|
| 876 |
-
history: Chat history
|
|
|
|
| 877 |
|
| 878 |
Returns:
|
| 879 |
-
Updated history
|
| 880 |
"""
|
| 881 |
-
|
| 882 |
-
|
|
|
|
| 883 |
if not message.strip():
|
| 884 |
-
return history
|
| 885 |
|
| 886 |
try:
|
| 887 |
# Process question directly
|
|
@@ -889,16 +963,15 @@ def chat_with_bot(message, history):
|
|
| 889 |
except Exception as e:
|
| 890 |
bot_reply = f"❌ Error: {str(e)}"
|
| 891 |
|
| 892 |
-
# Append to history
|
| 893 |
history.append({"role": "user", "content": message})
|
| 894 |
history.append({"role": "assistant", "content": bot_reply})
|
| 895 |
-
return history
|
| 896 |
|
| 897 |
def reset_session():
|
| 898 |
"""Reset session ID for new conversation"""
|
| 899 |
-
|
| 900 |
-
|
| 901 |
-
return [] #, f"🔄 New session started: {session_id[:8]}..."
|
| 902 |
|
| 903 |
# Create Gradio interface
|
| 904 |
with gr.Blocks(title="Get Scene Studios Chatbot") as demo:
|
|
@@ -931,11 +1004,14 @@ with gr.Blocks(title="Get Scene Studios Chatbot") as demo:
|
|
| 931 |
clear_btn = gr.Button("Clear Chat 🗑️", scale=1)
|
| 932 |
reset_btn = gr.Button("New Session 🔄", scale=1)
|
| 933 |
|
|
|
|
|
|
|
|
|
|
| 934 |
# Event handlers
|
| 935 |
submit_btn.click(
|
| 936 |
fn=chat_with_bot,
|
| 937 |
-
inputs=[msg, chatbot],
|
| 938 |
-
outputs=[chatbot]
|
| 939 |
).then(
|
| 940 |
fn=lambda: "",
|
| 941 |
inputs=None,
|
|
@@ -944,8 +1020,8 @@ with gr.Blocks(title="Get Scene Studios Chatbot") as demo:
|
|
| 944 |
|
| 945 |
msg.submit(
|
| 946 |
fn=chat_with_bot,
|
| 947 |
-
inputs=[msg, chatbot],
|
| 948 |
-
outputs=[chatbot]
|
| 949 |
).then(
|
| 950 |
fn=lambda: "",
|
| 951 |
inputs=None,
|
|
@@ -961,16 +1037,9 @@ with gr.Blocks(title="Get Scene Studios Chatbot") as demo:
|
|
| 961 |
reset_btn.click(
|
| 962 |
fn=reset_session,
|
| 963 |
inputs=None,
|
| 964 |
-
outputs=[chatbot]
|
| 965 |
)
|
| 966 |
|
| 967 |
# Launch the app
|
| 968 |
-
if __name__ == "__main__":
|
| 969 |
-
print("\n" + "="*60)
|
| 970 |
-
print("🎬 Get Scene Studios Chatbot")
|
| 971 |
-
print("="*60)
|
| 972 |
-
print("\n✅ No Flask API needed - all processing is done directly!")
|
| 973 |
-
print("🌐 Gradio interface will open in your browser")
|
| 974 |
-
print("="*60 + "\n")
|
| 975 |
-
|
| 976 |
demo.launch()
|
|
|
|
| 5 |
import os
|
| 6 |
from datetime import datetime, timedelta
|
| 7 |
import uuid
|
| 8 |
+
import random
|
| 9 |
from typing import Dict
|
| 10 |
|
| 11 |
from config import (
|
|
|
|
| 18 |
get_embedding, cosine_similarity, find_top_k_matches,
|
| 19 |
classify_intent, should_include_email, classify_user_type
|
| 20 |
)
|
|
|
|
| 21 |
from database import (
|
| 22 |
fetch_all_embeddings,
|
| 23 |
fetch_row_by_id,
|
| 24 |
fetch_all_faq_embeddings,
|
| 25 |
get_session_state,
|
| 26 |
update_session_state,
|
| 27 |
+
log_question,
|
| 28 |
+
get_recent_history
|
| 29 |
)
|
| 30 |
+
from scraper import scrape_workshops_from_squarespace
|
| 31 |
|
| 32 |
# ============================================================================
|
| 33 |
# CONFIGURATION
|
|
|
|
| 39 |
openai.api_key = OPENAI_API_KEY
|
| 40 |
|
| 41 |
|
| 42 |
+
# Removed global session_id for multi-user compatibility
|
|
|
|
| 43 |
|
| 44 |
# Cache for workshop data and embeddings
|
| 45 |
workshop_cache = {
|
|
|
|
| 223 |
markdown = f"🎧 [Watch {guest_name}'s episode here]({base_url}) - {short_summary}"
|
| 224 |
return [markdown]
|
| 225 |
|
| 226 |
+
def build_enhanced_prompt(user_question, context_results, top_workshops, user_preference=None, user_type='unknown', enriched_podcast_links=None, wants_details=False, current_topic=None, mode="Mode B", is_low_confidence=False, is_faq_match=False):
|
| 227 |
"""Builds the system prompt with strict formatting rules."""
|
| 228 |
|
| 229 |
# Dynamic Links from Structured Knowledge
|
|
|
|
| 279 |
# Mandatory Hyperlink Enforcement
|
| 280 |
workshop_text = f"We are constantly updating our schedule! You can view and [register for upcoming {label}workshops here]({link})."
|
| 281 |
|
| 282 |
+
# Pass multiple podcast options to the LLM for variety
|
| 283 |
+
podcast_options = ""
|
| 284 |
if not enriched_podcast_links:
|
| 285 |
+
podcast_options = "Our latest industry insights are available on YouTube: https://www.youtube.com/@GetSceneStudios"
|
| 286 |
else:
|
| 287 |
+
# Provide up to 3 options
|
| 288 |
+
podcast_options = "\n".join(enriched_podcast_links[:3])
|
| 289 |
|
| 290 |
# --- EMOTIONAL / SUPPORT MODE CHECK ---
|
| 291 |
is_emotional = detect_response_type(user_question) == "support"
|
|
|
|
| 445 |
elif user_type == 'current_student':
|
| 446 |
user_type_instruction = "USER TYPE: EXISTING STUDENT. Focus on GSP membership benefits, advanced mentorships (WAM), and specialized recurring workshops."
|
| 447 |
|
| 448 |
+
# --- FAQ MATCH MODE (Highest Priority) ---
|
| 449 |
+
if is_faq_match:
|
| 450 |
+
prompt = f"""{PERSONA_INSTRUCTION}
|
| 451 |
+
|
| 452 |
+
{truth_sheet_snippet}
|
| 453 |
+
|
| 454 |
+
{BUSINESS_RULES_INSTRUCTION}
|
| 455 |
+
|
| 456 |
+
{user_type_instruction}
|
| 457 |
+
|
| 458 |
+
{context_snippet}{retrieved_info}
|
| 459 |
+
|
| 460 |
+
CRITICAL INSTRUCTIONS (FAQ MODE):
|
| 461 |
+
- You are answering a question that has a direct match in our FAQ.
|
| 462 |
+
- Answer the user's question directly and punchily using ONLY the provided information.
|
| 463 |
+
- **DO NOT** use the structured 1. 2. 3. format.
|
| 464 |
+
- **DO NOT** ask a routing question.
|
| 465 |
+
- **MANDATORY: Use direct hyperlinks.** For ANY mention of signing up, classes, kids programs, or the free class, you MUST include the direct [Title](Link) format.
|
| 466 |
+
- Focus on being a helpful guide. {preference_instruction}
|
| 467 |
+
|
| 468 |
+
CRITICAL ROLE GUARD (FINAL AUTHORITY):
|
| 469 |
+
- Corey Lawson: Instructor/Actor [NOT an Agent]
|
| 470 |
+
- Jacob Lawson: Agent/Owner [NOT an Instructor]
|
| 471 |
+
- Jesse Malinowski: Founder/Mentor [NOT an Agent]
|
| 472 |
+
- Alex White: Agent [NOT an Instructor/Mentor]
|
| 473 |
+
- THE TRUTH SHEET IS THE ABSOLUTE AUTHORITY.
|
| 474 |
+
|
| 475 |
+
USER'S QUESTION: {user_question}
|
| 476 |
+
|
| 477 |
+
REQUIRED RESPONSE FORMAT:
|
| 478 |
+
[Punchy, helpful answer based on FAQ with relevant links]{email_contact}"""
|
| 479 |
+
return prompt
|
| 480 |
+
|
| 481 |
if mode == "Mode A":
|
| 482 |
# Recommendation Mode: Existing checklist applies
|
| 483 |
prompt = f"""{PERSONA_INSTRUCTION}
|
|
|
|
| 498 |
- For each recommendation, add a tiny bit of "mentor advice" on why it helps.
|
| 499 |
- Use ONLY the provided links - do not invent recommendations.
|
| 500 |
- **MANDATORY: Use direct hyperlinks.** For ANY mention of signing up, classes, kids programs, the Summit, or the free class, you MUST include the direct [Title](Link) format.
|
| 501 |
+
- **CRITICAL: PRESERVE URLS.** You MUST include the full URL in parentheses `(https://...)`. DO NOT output just the bracketed text `[Title]`. If you fail to include the URL, the link will be broken.
|
| 502 |
- **NEVER say "check our website"** or "visit the link below". Embed the link directly into the relevant part of your mentor advice.
|
| 503 |
- Focus on clean, readable formatting.{preference_instruction}
|
| 504 |
|
|
|
|
| 518 |
Here's your path forward:
|
| 519 |
1. Free Online Class (Mandatory First Step): {free_class_url}
|
| 520 |
2. Recommended Podcast Episode (For Industry Mindset):
|
| 521 |
+
{podcast_options}
|
| 522 |
3. Recommended Workshop/Next Step:
|
| 523 |
{workshop_text}{email_contact}
|
| 524 |
|
|
|
|
| 540 |
- Answer the user's question directly using the provided information but keep it punchy—**no essays**.
|
| 541 |
- **MANDATORY: Provide direct hyperlinks** for ANY mention of registration, classes, kids programs, the Summit, or more information. Use EXACTLY these links as relevant:
|
| 542 |
- Free Online Class: [{free_class_url}]({free_class_url})
|
| 543 |
+
- Recommended for you: {podcast_options}
|
| 544 |
- Upcoming Workshops: {workshop_text}
|
| 545 |
- Southeast Actor Summit: [Southeast Actor Summit Registration](https://www.getscenestudios.com/southeast-actor-summit)
|
| 546 |
- **NEVER say "go to the website"** or "check our site". Always provide the specific hyperlink directly in your answer.
|
|
|
|
| 560 |
|
| 561 |
USER'S QUESTION: {user_question}
|
| 562 |
|
|
|
|
| 563 |
[Routing Question]
|
| 564 |
[Helpful, punchy response with links]
|
| 565 |
+
**IMPORTANT: You MUST choose the most relevant podcast from the list provided above and include its FULL Markdown link including the URL in your response.**
|
| 566 |
[Next step guidance]{email_contact}"""
|
| 567 |
|
| 568 |
return prompt
|
|
|
|
| 583 |
'pricing': ['price', 'cost', 'pricing', '$', 'money', 'payment', 'fee'],
|
| 584 |
'classes': ['class', 'workshop', 'training', 'course', 'learn'],
|
| 585 |
'membership': ['membership', 'join', 'member', 'gsp', 'plus'],
|
| 586 |
+
'podcast': ['podcast', 'podcasts', 'youtube', 'watch', 'listen', 'episode', 'episodes'],
|
| 587 |
'technical': ['self-tape', 'equipment', 'lighting', 'editing', 'camera']
|
| 588 |
}
|
| 589 |
|
|
|
|
| 764 |
top_faqs.append((score, entry_id, question_text, answer_text))
|
| 765 |
top_faqs.sort(reverse=True)
|
| 766 |
|
| 767 |
+
faq_threshold = 0.50
|
| 768 |
+
ambiguous_threshold = 0.60
|
| 769 |
|
| 770 |
is_low_confidence = False # Default safe initialization
|
| 771 |
context_results = None
|
| 772 |
+
is_faq_match = False
|
| 773 |
|
| 774 |
if top_faqs and top_faqs[0][0] >= faq_threshold:
|
| 775 |
best_score, faq_id, question_text, answer_text = top_faqs[0]
|
| 776 |
print(f"DEBUG: Processing FAQ match through LLM and Truth Sheet rules...")
|
| 777 |
context_results = answer_text
|
| 778 |
+
is_faq_match = True
|
| 779 |
|
| 780 |
elif activated_mode == "Mode A":
|
| 781 |
# Mode A: Any score < 0.85 triggers Clarification -> Email
|
| 782 |
+
# EXCEPTION: If they specifically ask for podcasts or recommendations, let it through to LLM path
|
| 783 |
+
is_recommendation_query = any(k in question.lower() for k in ['podcast', 'reccomend', 'recommend', 'path', 'help', 'advice', 'guide'])
|
| 784 |
+
|
| 785 |
clarification_count = session_state.get('clarification_count', 0)
|
| 786 |
+
if clarification_count == 0 and not is_recommendation_query:
|
| 787 |
update_session_state(current_session_id, increment_clarification=True, increment_count=False)
|
| 788 |
return "I want to make sure I give you the best advice. Are you looking for classes in [Atlanta](https://www.getscenestudios.com/instudio), [Online](https://www.getscenestudios.com/online), or something else like getting an agent? You can also start right now with our [Free Online Class](https://www.getscenestudios.com/online)!"
|
| 789 |
+
elif clarification_count > 0 and not is_recommendation_query:
|
| 790 |
update_session_state(current_session_id, reset_clarification=True)
|
| 791 |
return "I'm still not quite sure, and I want to make sure you get the right answer! Please email our team at info@getscenestudios.com and we'll help you directly. In the meantime, you can explore or [register for our Online Path](https://www.getscenestudios.com/online) or [In-Studio classes in Atlanta](https://www.getscenestudios.com/instudio)."
|
| 792 |
+
# Else: is_recommendation_query is True, fall through to LLM path
|
| 793 |
|
| 794 |
elif top_faqs and top_faqs[0][0] >= ambiguous_threshold:
|
| 795 |
+
# Mode B: Ambiguous Score -> Use best FAQ match as context for LLM
|
| 796 |
+
# Instead of asking "Did you mean?", answer naturally using the FAQ content
|
| 797 |
+
best_score, faq_id, question_text, answer_text = top_faqs[0]
|
| 798 |
+
print(f"DEBUG: Ambiguous FAQ match (score={best_score:.2f}), using as LLM context: {question_text[:60]}...")
|
| 799 |
+
context_results = answer_text
|
| 800 |
+
is_faq_match = True
|
| 801 |
|
| 802 |
else:
|
| 803 |
# 5. HALLUCINATION GUARD: Check if query is acting-related before blocking
|
|
|
|
| 806 |
has_session_context = (current_topic is not None) or (user_preference is not None)
|
| 807 |
|
| 808 |
FOLLOWUP_KEYWORDS = ['yes', 'no', 'sure', 'okay', 'thanks', 'thank you', 'please', 'go ahead', 'continue', 'more']
|
| 809 |
+
ACTING_KEYWORDS = ['class', 'workshop', 'coaching', 'studio', 'acting', 'online', 'person', 'atlanta', 'training', 'prefer', 'preference', 'format', 'recommendation', 'online class', 'online workshop','instudio class','instudio workshop', 'actor', 'scene', 'audition', 'theatre', 'film', 'tv', 'commercial', 'agent', 'rep', 'manager', 'instructor', 'role', 'auditing', 'audit', 'representation', 'summit', 'sign up', 'sign-up', 'register', 'enroll', 'schedule', 'cancel', 'reschedule', 'how do i', 'podcast', 'podcasts', 'youtube', 'episode', 'episodes', 'watch']
|
| 810 |
|
| 811 |
is_acting_related = (
|
| 812 |
len(categories) > 0 or
|
|
|
|
| 832 |
top_workshops = find_top_workshops(user_embedding, k=3)
|
| 833 |
top_podcasts = find_top_k_matches(user_embedding, podcast_data, k=3)
|
| 834 |
|
| 835 |
+
# Get chat history for rotation logic
|
| 836 |
+
chat_history = get_recent_history(current_session_id, limit=5)
|
| 837 |
+
history_text = " ".join([m['content'] for m in chat_history]).lower()
|
| 838 |
+
|
| 839 |
enriched_podcast_links = []
|
| 840 |
for _, podcast_id, _ in top_podcasts:
|
| 841 |
row = fetch_row_by_id("podcast_episodes", podcast_id)
|
| 842 |
+
links = generate_enriched_links(row)
|
| 843 |
+
enriched_podcast_links.extend(links)
|
| 844 |
|
| 845 |
if not enriched_podcast_links:
|
| 846 |
fallback = fetch_row_by_id("podcast_episodes", podcast_data[0][0])
|
| 847 |
enriched_podcast_links = generate_enriched_links(fallback)
|
| 848 |
+
|
| 849 |
+
# Diversity Logic: Shuffle and prioritize unseen podcasts
|
| 850 |
+
random.shuffle(enriched_podcast_links)
|
| 851 |
+
seen_links = []
|
| 852 |
+
unseen_links = []
|
| 853 |
+
|
| 854 |
+
for link in enriched_podcast_links:
|
| 855 |
+
# Extract guest name or unique part to check history
|
| 856 |
+
# e.g. "🎧 [Watch Haillie Ricardo's episode here]..."
|
| 857 |
+
match = re.search(r'Watch (.*)\'s episode', link)
|
| 858 |
+
if match:
|
| 859 |
+
guest_name = match.group(1).lower()
|
| 860 |
+
if guest_name in history_text:
|
| 861 |
+
seen_links.append(link)
|
| 862 |
+
else:
|
| 863 |
+
unseen_links.append(link)
|
| 864 |
+
else:
|
| 865 |
+
unseen_links.append(link)
|
| 866 |
+
|
| 867 |
+
# Combine: Unseen first, then seen
|
| 868 |
+
final_podcast_options = unseen_links + seen_links
|
| 869 |
|
| 870 |
# Brevity & Detail Detection
|
| 871 |
wants_details = any(syn in question.lower() for syn in DETAIL_SYNONYMS)
|
|
|
|
| 877 |
top_workshops,
|
| 878 |
user_preference=user_preference,
|
| 879 |
user_type=user_type,
|
| 880 |
+
enriched_podcast_links=final_podcast_options,
|
| 881 |
wants_details=wants_details,
|
| 882 |
current_topic=current_topic,
|
| 883 |
mode=activated_mode,
|
| 884 |
+
is_low_confidence=is_low_confidence,
|
| 885 |
+
is_faq_match=is_faq_match
|
| 886 |
)
|
| 887 |
|
| 888 |
+
# LLM messages
|
| 889 |
+
messages = [{"role": "system", "content": final_prompt}]
|
| 890 |
+
messages.extend(chat_history)
|
| 891 |
+
messages.append({"role": "user", "content": question})
|
| 892 |
|
| 893 |
response = openai.chat.completions.create(
|
| 894 |
model=GEN_MODEL,
|
| 895 |
+
messages=messages
|
|
|
|
|
|
|
|
|
|
| 896 |
)
|
| 897 |
|
| 898 |
answer_text = response.choices[0].message.content.strip()
|
|
|
|
| 939 |
# GRADIO INTERFACE
|
| 940 |
# ============================================================================
|
| 941 |
|
| 942 |
+
def chat_with_bot(message, history, session_id):
|
| 943 |
"""
|
| 944 |
Process message directly without Flask API
|
| 945 |
|
| 946 |
Args:
|
| 947 |
message: User's current message
|
| 948 |
+
history: Chat history
|
| 949 |
+
session_id: Per-user session ID state
|
| 950 |
|
| 951 |
Returns:
|
| 952 |
+
Updated history and session_id
|
| 953 |
"""
|
| 954 |
+
if not session_id:
|
| 955 |
+
session_id = str(uuid.uuid4())
|
| 956 |
+
|
| 957 |
if not message.strip():
|
| 958 |
+
return history, session_id
|
| 959 |
|
| 960 |
try:
|
| 961 |
# Process question directly
|
|
|
|
| 963 |
except Exception as e:
|
| 964 |
bot_reply = f"❌ Error: {str(e)}"
|
| 965 |
|
| 966 |
+
# Append to history
|
| 967 |
history.append({"role": "user", "content": message})
|
| 968 |
history.append({"role": "assistant", "content": bot_reply})
|
| 969 |
+
return history, session_id
|
| 970 |
|
| 971 |
def reset_session():
|
| 972 |
"""Reset session ID for new conversation"""
|
| 973 |
+
new_id = str(uuid.uuid4())
|
| 974 |
+
return [], new_id
|
|
|
|
| 975 |
|
| 976 |
# Create Gradio interface
|
| 977 |
with gr.Blocks(title="Get Scene Studios Chatbot") as demo:
|
|
|
|
| 1004 |
clear_btn = gr.Button("Clear Chat 🗑️", scale=1)
|
| 1005 |
reset_btn = gr.Button("New Session 🔄", scale=1)
|
| 1006 |
|
| 1007 |
+
# Session state
|
| 1008 |
+
session_state = gr.State("")
|
| 1009 |
+
|
| 1010 |
# Event handlers
|
| 1011 |
submit_btn.click(
|
| 1012 |
fn=chat_with_bot,
|
| 1013 |
+
inputs=[msg, chatbot, session_state],
|
| 1014 |
+
outputs=[chatbot, session_state]
|
| 1015 |
).then(
|
| 1016 |
fn=lambda: "",
|
| 1017 |
inputs=None,
|
|
|
|
| 1020 |
|
| 1021 |
msg.submit(
|
| 1022 |
fn=chat_with_bot,
|
| 1023 |
+
inputs=[msg, chatbot, session_state],
|
| 1024 |
+
outputs=[chatbot, session_state]
|
| 1025 |
).then(
|
| 1026 |
fn=lambda: "",
|
| 1027 |
inputs=None,
|
|
|
|
| 1037 |
reset_btn.click(
|
| 1038 |
fn=reset_session,
|
| 1039 |
inputs=None,
|
| 1040 |
+
outputs=[chatbot, session_state]
|
| 1041 |
)
|
| 1042 |
|
| 1043 |
# Launch the app
|
| 1044 |
+
if __name__ == "__main__":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1045 |
demo.launch()
|