ChatbotRAG / scenario_handlers /event_recommendation.py
minhvtt's picture
Update scenario_handlers/event_recommendation.py
885208c verified
raw
history blame
12.9 kB
"""
Event Recommendation Scenario Handler
Recommends events based on user's vibe/mood with RAG integration
"""
from typing import Dict, Any
from .base_handler import BaseScenarioHandler
class EventRecommendationHandler(BaseScenarioHandler):
"""
Handle event recommendation flow
Steps:
1. Ask for vibe/mood (Chill, Sôi động, Hài, Workshop)
2. Search events matching vibe → RAG
3. Show event list, ask which to see details
4. Ask what info needed (price, lineup, location, time)
5-8. Show specific info → RAG
9. Ask if want to save event to email
10. Collect email + send summary
11-12. End scenario
"""
def start(self, initial_data: Dict = None) -> Dict[str, Any]:
"""Start event recommendation flow"""
return {
"message": "Hello! 👋 Bạn muốn tìm sự kiện theo vibe gì nè? Chill – Sôi động – Hài – Workshop?",
"new_state": {
"active_scenario": "event_recommendation",
"scenario_step": 1,
"scenario_data": initial_data or {}
}
}
def next_step(self, current_step: int, user_input: str, scenario_data: Dict) -> Dict[str, Any]:
"""Process user input and advance scenario"""
# Get expected input type for this step
expected_type = self._get_expected_type(current_step)
# Check for unexpected input (off-topic questions)
unexpected = self.handle_unexpected_input(user_input, expected_type, current_step)
if unexpected:
return unexpected
# ===== STEP 1: Collect interest tag =====
if current_step == 1:
scenario_data['interest_tag'] = user_input
return {
"message": f"Mình hiểu rồi! Để mình tìm sự kiện hợp vibe **{user_input}** nha",
"new_state": {
"active_scenario": "event_recommendation",
"scenario_step": 2,
"scenario_data": scenario_data
},
"scenario_active": True
}
# ===== STEP 2: Execute RAG search for events =====
elif current_step == 2:
# Search events matching interest_tag
query = f"sự kiện phù hợp với {scenario_data.get('interest_tag', 'mọi người')}"
print(f"🔍 RAG Search: {query}")
results = self._search_rag(query, limit=5) # Get up to 5
formatted_events = self._format_event_list(results)
# Save results to scenario data
scenario_data['rag_results'] = formatted_events
scenario_data['available_events'] = [
r['metadata'].get('id_use', r['metadata'].get('original_id', f'event_{i+1}'))
for i, r in enumerate(results)
]
return {
"message": f"Đây là các event hợp với bạn nè:\n{formatted_events}\n\nBạn có muốn xem chi tiết event nào không?",
"new_state": {
"active_scenario": "event_recommendation",
"scenario_step": 3,
"scenario_data": scenario_data
},
"scenario_active": True,
"loading_message": "⏳ Bạn đợi tôi tìm 1 chút nhé..."
}
# ===== STEP 3: User picks event =====
elif current_step == 3:
scenario_data['event_name'] = user_input
return {
"message": "Bạn cần xem: giá – line-up – địa điểm – hay thời gian của sự kiện?",
"new_state": {
"active_scenario": "event_recommendation",
"scenario_step": 4,
"scenario_data": scenario_data
},
"scenario_active": True
}
# ===== STEP 4: Branch based on info choice =====
elif current_step == 4:
choice = self._detect_choice(user_input)
event_name = scenario_data.get('event_name', 'sự kiện này')
# Build RAG query based on choice
query_map = {
'price': f"giá vé {event_name}",
'lineup': f"lineup nghệ sĩ {event_name}",
'location': f"địa điểm tổ chức {event_name}",
'time': f"thời gian lịch diễn {event_name}"
}
query = query_map.get(choice, query_map['price'])
print(f"🔍 RAG Search: {query}")
results = self._search_rag(query)
formatted_info = self._format_rag_results(results)
# Build response message
message_map = {
'price': f"Giá vé event {event_name} nè:\n{formatted_info}",
'lineup': f"Lineup / nghệ sĩ của event {event_name} là:\n{formatted_info}",
'location': f"Địa điểm tổ chức event {event_name}:\n{formatted_info}",
'time': f"Thời gian / lịch diễn của event {event_name}:\n{formatted_info}"
}
return {
"message": message_map.get(choice, message_map['price']),
"new_state": {
"active_scenario": "event_recommendation",
"scenario_step": 9, # Skip to email step
"scenario_data": scenario_data
},
"scenario_active": True,
"loading_message": "⏳ Bạn đợi tôi tìm 1 chút nhé..."
}
# ===== STEP 9: Ask if want to save event to email =====
elif current_step == 9:
choice = self._detect_yes_no(user_input)
if choice == 'yes':
return {
"message": "Cho mình xin email để gửi bản tóm tắt event kèm link mua vé?",
"new_state": {
"active_scenario": "event_recommendation",
"scenario_step": 10,
"scenario_data": scenario_data
},
"scenario_active": True
}
else:
return {
"message": "Okie, bạn cần event theo vibe khác không nè? 😄",
"new_state": None,
"scenario_active": False,
"end_scenario": True
}
# ===== STEP 10: Collect email and send summary =====
elif current_step == 10:
email = user_input.strip()
if not self._validate_email(email):
return {
"message": "Email này có vẻ không đúng định dạng. Bạn nhập lại giúp mình nhé? (Ví dụ: name@example.com)",
"new_state": None, # Stay at same step
"scenario_active": True
}
# Save lead
scenario_data['email'] = email
try:
self.lead_storage.save_lead(
event_name=scenario_data.get('event_name', 'Unknown Event'),
email=email,
interests={
"vibe": scenario_data.get('interest_tag'),
"wants_event_summary": True
},
session_id=scenario_data.get('session_id')
)
print(f"📧 Lead saved: {email}")
except Exception as e:
print(f"⚠️ Error saving lead: {e}")
return {
"message": "Đã gửi email cho bạn nha! ✨",
"new_state": None,
"scenario_active": False,
"end_scenario": True,
"action": "send_event_summary_email"
}
# Fallback - unknown step
return {
"message": "Xin lỗi, có lỗi xảy ra. Bạn muốn bắt đầu lại không?",
"new_state": None,
"scenario_active": False,
"end_scenario": True
}
def _get_expected_type(self, step: int) -> str:
"""Get expected input type for each step"""
type_map = {
1: 'interest_tag',
2: None, # Auto-advance after RAG
3: 'event_name',
4: 'choice',
9: 'choice',
10: 'email'
}
return type_map.get(step, 'text')
def _format_event_list(self, results: list) -> str:
"""Format event search results - IMPROVED to show actual event count"""
print(f"🔍 DEBUG: RAG returned {len(results)} results")
if not results or len(results) == 0:
print("⚠️ DEBUG: No results found")
return "Hiện tại chưa có event phù hợp 😢\nBạn thử vibe khác nhé!"
# Debug: Print first result to see structure
print(f"🔍 DEBUG: First result metadata: {results[0].get('metadata', {})}")
events = []
# FIXED: Use actual length instead of hardcoded [:3]
num_events = min(len(results), 5) # Show max 5 events
print(f"🔍 DEBUG: Formatting {num_events} events")
for i, r in enumerate(results[:num_events], 1):
metadata = r.get('metadata', {})
# Extract event info from Qdrant metadata
event_id = metadata.get('id_use', metadata.get('original_id'))
texts = metadata.get('texts', [])
text = texts[0] if texts and len(texts) > 0 else metadata.get('text', '')
print(f"🔍 DEBUG Event {i}:")
print(f" - ID: {event_id}")
print(f" - Text length: {len(text)} chars")
print(f" - Text preview: {text[:100]}")
# Extract event name from text
# Try to find event name pattern in text
name = self._extract_event_name_from_text(text)
if not name:
# Fallback to shortened text
name = text[:60].strip()
if len(text) > 60:
name += "..."
print(f" - Extracted name: {name}")
# Format event string
event_str = f"{i}. **{name}**"
events.append(event_str)
result = "\n".join(events)
print(f"✅ DEBUG: Formatted {len(events)} events successfully")
return result
def _extract_event_name_from_text(self, text: str) -> str:
"""
Try to extract event name from description text
Patterns to match:
- "Sự kiện văn hóa hài kịch" → "Văn hóa hài kịch"
- "Stand-up Comedy Night - ..." → "Stand-up Comedy Night"
"""
if not text:
return ""
# Remove "Sự kiện" prefix if exists
clean_text = text
if clean_text.startswith("Sự kiện "):
clean_text = clean_text[8:].strip()
# Take first sentence or first 80 chars
sentences = clean_text.split('.')
if sentences and len(sentences) > 0:
first_sentence = sentences[0].strip()
if len(first_sentence) > 80:
return first_sentence[:77] + "..."
return first_sentence
# Fallback
if len(clean_text) > 80:
return clean_text[:77] + "..."
return clean_text
def _detect_choice(self, user_input: str) -> str:
"""Detect what info user wants to see"""
input_lower = user_input.lower()
if any(k in input_lower for k in ['giá', 'price', 'vé', 'ticket', 'bao nhiêu']):
return 'price'
elif any(k in input_lower for k in ['lineup', 'line-up', 'nghệ sĩ', 'artist', 'performer']):
return 'lineup'
elif any(k in input_lower for k in ['địa điểm', 'location', 'ở đâu', 'where', 'chỗ']):
return 'location'
elif any(k in input_lower for k in ['thời gian', 'time', 'khi nào', 'when', 'lịch', 'date']):
return 'time'
else:
return 'price' # Default
def _detect_yes_no(self, user_input: str) -> str:
"""Detect yes/no response"""
input_lower = user_input.lower()
if any(k in input_lower for k in ['có', 'yes', 'ok', 'được', 'ừ', 'oke']):
return 'yes'
else:
return 'no'