chatbot / event_service.py
imkrish's picture
Deploy chatbot to Hugging Face Space
d4e8d9b
Raw History Blame Contribute Delete
4.81 kB
"""
Event Discovery Service
Fetches and searches events from the backend API
"""
import requests
import logging
from typing import List, Dict, Optional
from sentence_transformers import SentenceTransformer
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
logger = logging.getLogger("chatbot.events")
class EventService:
def __init__(self, backend_url: str, model: SentenceTransformer):
self.backend_url = backend_url
self.model = model
self.events_cache = []
self.events_embeddings = None
def fetch_events(self) -> List[Dict]:
"""Fetch all approved public events from backend"""
try:
response = requests.get(f"{self.backend_url}/api/events", timeout=5)
if response.status_code == 200:
data = response.json()
self.events_cache = data.get("data", {}).get("events", [])
logger.info(f"Fetched {len(self.events_cache)} events from backend")
# Pre-compute embeddings for all events
self._compute_event_embeddings()
return self.events_cache
else:
logger.error(f"Failed to fetch events: {response.status_code}")
return []
except Exception as e:
logger.error(f"Error fetching events: {e}")
return []
def _compute_event_embeddings(self):
"""Compute embeddings for all events"""
if not self.events_cache:
return
event_texts = []
for event in self.events_cache:
# Combine title, description, and tags for better matching
text = f"{event.get('title', '')} {event.get('description', '')} {' '.join(event.get('tags', []))}"
event_texts.append(text)
self.events_embeddings = self.model.encode(event_texts, convert_to_tensor=False)
logger.info(f"Computed embeddings for {len(event_texts)} events")
def search_events(self, query: str, top_k: int = 5) -> List[Dict]:
"""Search events using semantic similarity"""
if not self.events_cache or self.events_embeddings is None:
self.fetch_events()
if not self.events_cache:
logger.warning("No events available in cache")
return []
# Encode user query
query_embedding = self.model.encode([query], convert_to_tensor=False)
# Calculate cosine similarity
similarities = cosine_similarity(query_embedding, self.events_embeddings)[0]
# Log similarity scores for debugging
logger.info(f"Query: {query}")
logger.info(f"Similarity scores: min={similarities.min():.3f}, max={similarities.max():.3f}, mean={similarities.mean():.3f}")
# Get top k indices
top_indices = np.argsort(similarities)[::-1][:top_k]
# Filter events with similarity > threshold (lowered to 0.15 for better recall)
results = []
for idx in top_indices:
score = similarities[idx]
if score > 0.15: # Lowered threshold
event = self.events_cache[idx].copy()
event['similarity_score'] = float(score)
results.append(event)
logger.info(f" Event '{event.get('title', 'Untitled')}' matched with score {score:.3f}")
logger.info(f"Found {len(results)} matching events for query: {query}")
return results
def format_event_response(self, events: List[Dict]) -> str:
"""Format events into a conversational response"""
if not events:
return "Sorry, I couldn't find any events matching your query. Try searching for something like 'hackathon', 'AI workshop', or 'coding competition'."
response = f"I found {len(events)} event(s) for you:\n\n"
for i, event in enumerate(events, 1):
title = event.get('title', 'Untitled Event')
date = event.get('date', 'TBA')[:10] if event.get('date') else 'TBA'
description = event.get('description', '')
# Truncate description
if len(description) > 100:
description = description[:97] + "..."
response += f"{i}. **{title}**\n"
response += f" ๐Ÿ“… Date: {date}\n"
if description:
response += f" ๐Ÿ“ {description}\n"
# Add website link if available
if event.get('socialLinks', {}).get('website'):
response += f" ๐Ÿ”— [More Info]({event['socialLinks']['website']})\n"
response += "\n"
return response.strip()