ChatbotRAG / event_recommendation.py
minhvtt's picture
Upload 3 files
5f63f29 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 =====
"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 as numbered list"""
print(f"🔍 DEBUG: RAG returned {len(results)} results")
if not results or len(results) == 0:
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
if len(results) > 0:
print(f"🔍 DEBUG: First result metadata: {results[0].get('metadata', {})}")
events = []
for i, r in enumerate(results[:3], 1):
metadata = r.get('metadata', {})
# Extract event info from metadata
# Your Qdrant has: {'texts': [...], 'id_use': '...'}
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', '')
# Use first 60 chars of text as event name
name = text[:60].strip() + "..." if len(text) > 60 else text.strip()
print(f"🔍 DEBUG: Event {i}: id={event_id}, name={name[:50]}")
# Simple format for now (can enhance with API call later)
event_str = f"{i}. **{name}**"
# Store event_id for later API call if needed
if event_id:
event_str += f" (ID: {event_id[:8]}...)"
events.append(event_str)
return "\n".join(events)
async def _format_event_list_with_api(self, results: list) -> str:
"""
Format event search results by calling API for full details
"""
print(f"🔍 DEBUG: RAG returned {len(results)} results")
if not results or len(results) == 0:
return "Hiện tại chưa có event phù hợp 😢\nBạn thử vibe khác nhé!"
# Import event service
from event_service import EventService
event_service = EventService()
events = []
for i, r in enumerate(results[:3], 1):
metadata = r.get('metadata', {})
event_id = metadata.get('id_use', metadata.get('original_id'))
print(f"🔍 DEBUG: Fetching event {i} with ID: {event_id}")
# Try to get full event data from API
event_data = None
if event_id:
event_data = await event_service.get_event_by_id(event_id)
if event_data:
# Use API data
name = event_data.get("eventName", "Sự kiện")
start = event_data.get("eventStartTime", "")
date_str = start[:10] if start else "TBA"
location = event_data.get("eventAddress", "")
event_str = f"{i}. **{name}**"
if date_str != "TBA":
event_str += f" ({date_str})"
if location:
event_str += f" - {location}"
print(f"✅ Event {i}: {name} ({date_str})")
else:
# Fallback to text from Qdrant
texts = metadata.get('texts', [])
text = texts[0] if texts and len(texts) > 0 else ""
name = text[:60].strip() + "..." if len(text) > 60 else text.strip()
event_str = f"{i}. **{name}**"
print(f"⚠️ Event {i}: Fallback to text (API failed)")
events.append(event_str)
await event_service.close()
return "\n".join(events)
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'