CampVerse CI commited on
Commit
fee8462
ยท
1 Parent(s): 50c083e

Deploy chatbot from GitHub Actions

Browse files
Files changed (5) hide show
  1. Dockerfile +4 -0
  2. README.md +18 -4
  3. app.py +63 -7
  4. gemini_service.py +201 -0
  5. requirements.txt +1 -0
Dockerfile CHANGED
@@ -7,6 +7,10 @@ RUN useradd -m -u 1000 user
7
  USER user
8
  ENV PATH="/home/user/.local/bin:$PATH"
9
 
 
 
 
 
10
  WORKDIR /app
11
 
12
  COPY --chown=user ./requirements.txt requirements.txt
 
7
  USER user
8
  ENV PATH="/home/user/.local/bin:$PATH"
9
 
10
+ # Environment variables (set via HF Spaces secrets or docker-compose)
11
+ ENV GEMINI_API_KEY=""
12
+ ENV BACKEND_URL=""
13
+
14
  WORKDIR /app
15
 
16
  COPY --chown=user ./requirements.txt requirements.txt
README.md CHANGED
@@ -10,8 +10,15 @@ pinned: false
10
  ---
11
  # CampVerse Chatbot Microservice
12
 
13
- ## Quick Start for New Users
14
 
 
 
 
 
 
 
 
15
 
16
  If you're new and want to run this chatbot microservice, you can choose either Docker or Python. Both methods are described below:
17
 
@@ -31,11 +38,18 @@ If you're new and want to run this chatbot microservice, you can choose either D
31
  pip install -r requirements.txt
32
  ```
33
 
34
- 3. **Start the Backend Service**
 
 
 
 
 
 
35
  ```bash
36
- export BACKEND_URL=http://localhost:5001 # Set backend URL
37
  uvicorn app:app_socket --host 0.0.0.0 --port 8000
38
- ```4. **Test with CLI Chat Client**
 
 
39
  Open a new terminal and run:
40
  ```bash
41
  python cli_chat.py
 
10
  ---
11
  # CampVerse Chatbot Microservice
12
 
13
+ ## Features
14
 
15
+ - **Gemini AI Integration**: Enhanced NLP understanding using Google's Gemini API
16
+ - **Intent Classification**: Smart detection of user intents (event search, greetings, help, etc.)
17
+ - **Semantic Event Search**: Find events using natural language queries
18
+ - **FAQ Support**: Answer common questions about the platform
19
+ - **Real-time Chat**: WebSocket support for live conversations
20
+
21
+ ## Quick Start for New Users
22
 
23
  If you're new and want to run this chatbot microservice, you can choose either Docker or Python. Both methods are described below:
24
 
 
38
  pip install -r requirements.txt
39
  ```
40
 
41
+ 3. **Set Environment Variables**
42
+ ```bash
43
+ export BACKEND_URL=http://localhost:5001 # Backend API URL
44
+ export GEMINI_API_KEY=your_gemini_api_key # For enhanced NLP (optional)
45
+ ```
46
+
47
+ 4. **Start the Backend Service**
48
  ```bash
 
49
  uvicorn app:app_socket --host 0.0.0.0 --port 8000
50
+ ```
51
+
52
+ 5. **Test with CLI Chat Client**
53
  Open a new terminal and run:
54
  ```bash
55
  python cli_chat.py
app.py CHANGED
@@ -12,6 +12,7 @@ import os
12
  # Import custom services
13
  from event_service import EventService
14
  from intent_classifier import IntentClassifier
 
15
 
16
  # --- Logging setup ---
17
  logging.basicConfig(level=logging.INFO)
@@ -31,6 +32,13 @@ try:
31
  event_service = EventService(backend_url, model)
32
  intent_classifier = IntentClassifier()
33
 
 
 
 
 
 
 
 
34
  # Fetch events on startup
35
  event_service.fetch_events()
36
 
@@ -62,18 +70,64 @@ async def chatbot(req: QuestionRequest, request: Request):
62
  logger.warning("Question too long.")
63
  return {"error": "Question too long."}
64
  try:
65
- # Classify intent
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  intent, confidence = intent_classifier.classify(question)
67
- logger.info(f"Intent: {intent} (confidence: {confidence})")
68
 
69
  # Handle specific intents
70
  if intent in ['greeting', 'farewell', 'thanks', 'help', 'host_help']:
71
  response = intent_classifier.get_response_for_intent(intent)
72
- logger.info(f"Response for {intent}: {repr(response)}") # Debug log
73
  return {
74
  "question": question,
75
  "answer": response,
76
- "intent": intent
 
77
  }
78
 
79
  # Handle event search
@@ -84,7 +138,8 @@ async def chatbot(req: QuestionRequest, request: Request):
84
  "question": question,
85
  "answer": response,
86
  "intent": intent,
87
- "events": events
 
88
  }
89
 
90
  # Default: FAQ search
@@ -94,11 +149,12 @@ async def chatbot(req: QuestionRequest, request: Request):
94
  best_match_index = indices[0][0]
95
  retrieved_answer = df.iloc[best_match_index]['answer']
96
  retrieved_question = df.iloc[best_match_index]['question']
97
- logger.info(f"Answered question: {question} -> {retrieved_question}")
98
  return {
99
  "question": retrieved_question,
100
  "answer": retrieved_answer,
101
- "intent": intent
 
102
  }
103
  except Exception as e:
104
  logger.error(f"Error processing question: {e}")
 
12
  # Import custom services
13
  from event_service import EventService
14
  from intent_classifier import IntentClassifier
15
+ from gemini_service import get_gemini_service, GeminiService
16
 
17
  # --- Logging setup ---
18
  logging.basicConfig(level=logging.INFO)
 
32
  event_service = EventService(backend_url, model)
33
  intent_classifier = IntentClassifier()
34
 
35
+ # Initialize Gemini service for enhanced NLP
36
+ gemini_service = get_gemini_service()
37
+ if gemini_service.is_available():
38
+ logger.info("โœ… Gemini AI service is available for enhanced NLP")
39
+ else:
40
+ logger.warning("โš ๏ธ Gemini AI not available, using fallback intent classifier")
41
+
42
  # Fetch events on startup
43
  event_service.fetch_events()
44
 
 
70
  logger.warning("Question too long.")
71
  return {"error": "Question too long."}
72
  try:
73
+ # Try Gemini for enhanced understanding first
74
+ if gemini_service.is_available():
75
+ intent, confidence, entities = gemini_service.classify_intent(question)
76
+ logger.info(f"Gemini Intent: {intent} (confidence: {confidence}), entities: {entities}")
77
+
78
+ # Quick responses for simple intents
79
+ quick_response = gemini_service.get_contextual_response(intent)
80
+ if quick_response:
81
+ return {
82
+ "question": question,
83
+ "answer": quick_response,
84
+ "intent": intent,
85
+ "ai_enhanced": True
86
+ }
87
+
88
+ # Event search with enhanced understanding
89
+ if intent in ['event_search', 'event_details']:
90
+ # Enhance search query using extracted entities
91
+ search_query = gemini_service.enhance_search_query(question, entities)
92
+ events = event_service.search_events(search_query, top_k=5)
93
+
94
+ # Generate natural response using Gemini
95
+ response = gemini_service.generate_response(
96
+ question, intent, entities, events
97
+ )
98
+ if not response:
99
+ response = event_service.format_event_response(events)
100
+
101
+ return {
102
+ "question": question,
103
+ "answer": response,
104
+ "intent": intent,
105
+ "events": events,
106
+ "ai_enhanced": True
107
+ }
108
+
109
+ # For other intents, generate contextual response
110
+ response = gemini_service.generate_response(question, intent, entities)
111
+ if response:
112
+ return {
113
+ "question": question,
114
+ "answer": response,
115
+ "intent": intent,
116
+ "ai_enhanced": True
117
+ }
118
+
119
+ # Fallback to original intent classifier
120
  intent, confidence = intent_classifier.classify(question)
121
+ logger.info(f"Fallback Intent: {intent} (confidence: {confidence})")
122
 
123
  # Handle specific intents
124
  if intent in ['greeting', 'farewell', 'thanks', 'help', 'host_help']:
125
  response = intent_classifier.get_response_for_intent(intent)
 
126
  return {
127
  "question": question,
128
  "answer": response,
129
+ "intent": intent,
130
+ "ai_enhanced": False
131
  }
132
 
133
  # Handle event search
 
138
  "question": question,
139
  "answer": response,
140
  "intent": intent,
141
+ "events": events,
142
+ "ai_enhanced": False
143
  }
144
 
145
  # Default: FAQ search
 
149
  best_match_index = indices[0][0]
150
  retrieved_answer = df.iloc[best_match_index]['answer']
151
  retrieved_question = df.iloc[best_match_index]['question']
152
+ logger.info(f"FAQ match: {question} -> {retrieved_question}")
153
  return {
154
  "question": retrieved_question,
155
  "answer": retrieved_answer,
156
+ "intent": intent,
157
+ "ai_enhanced": False
158
  }
159
  except Exception as e:
160
  logger.error(f"Error processing question: {e}")
gemini_service.py ADDED
@@ -0,0 +1,201 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Gemini AI Service for Enhanced NLP Understanding
3
+ Uses Google's Gemini API for better intent detection and conversational responses
4
+ """
5
+ import os
6
+ import logging
7
+ import json
8
+ import re
9
+ from typing import Dict, List, Optional, Tuple
10
+
11
+ logger = logging.getLogger("chatbot.gemini")
12
+
13
+ # Try to import google.generativeai
14
+ try:
15
+ import google.generativeai as genai
16
+ GEMINI_AVAILABLE = True
17
+ except ImportError:
18
+ GEMINI_AVAILABLE = False
19
+ logger.warning("google-generativeai not installed. Gemini features disabled.")
20
+
21
+
22
+ class GeminiService:
23
+ def __init__(self, api_key: Optional[str] = None):
24
+ self.api_key = api_key or os.getenv('GEMINI_API_KEY')
25
+ self.model = None
26
+ self.initialized = False
27
+
28
+ if GEMINI_AVAILABLE and self.api_key:
29
+ try:
30
+ genai.configure(api_key=self.api_key)
31
+ self.model = genai.GenerativeModel('gemini-1.5-flash')
32
+ self.initialized = True
33
+ logger.info("Gemini AI service initialized successfully")
34
+ except Exception as e:
35
+ logger.error(f"Failed to initialize Gemini: {e}")
36
+ else:
37
+ if not GEMINI_AVAILABLE:
38
+ logger.warning("Gemini library not available")
39
+ if not self.api_key:
40
+ logger.warning("GEMINI_API_KEY not set")
41
+
42
+ def is_available(self) -> bool:
43
+ """Check if Gemini service is available"""
44
+ return self.initialized and self.model is not None
45
+
46
+ def classify_intent(self, user_message: str) -> Tuple[str, float, Dict]:
47
+ """
48
+ Use Gemini to classify user intent with high accuracy
49
+ Returns: (intent, confidence, extracted_entities)
50
+ """
51
+ if not self.is_available():
52
+ return 'unknown', 0.0, {}
53
+
54
+ prompt = f"""You are an intent classifier for CampVerse, a college event discovery platform.
55
+
56
+ Analyze the following user message and classify it into ONE of these intents:
57
+ - greeting: User says hi, hello, hey, good morning, etc.
58
+ - farewell: User says bye, goodbye, see you, etc.
59
+ - thanks: User expresses gratitude
60
+ - event_search: User is looking for events, hackathons, workshops, competitions, etc.
61
+ - event_details: User wants details about a specific event
62
+ - registration_help: User wants help with event registration
63
+ - host_help: User wants to host/organize an event
64
+ - account_help: User has account/profile related questions
65
+ - general_question: General questions about the platform
66
+ - feedback: User giving feedback or suggestions
67
+
68
+ Also extract any relevant entities like:
69
+ - event_type: hackathon, workshop, seminar, webinar, competition, etc.
70
+ - time_frame: today, tomorrow, this week, upcoming, etc.
71
+ - topic: AI, ML, web development, coding, etc.
72
+ - location: any mentioned location
73
+
74
+ User message: "{user_message}"
75
+
76
+ Respond in this exact JSON format:
77
+ {{
78
+ "intent": "intent_name",
79
+ "confidence": 0.95,
80
+ "entities": {{
81
+ "event_type": null,
82
+ "time_frame": null,
83
+ "topic": null,
84
+ "location": null
85
+ }},
86
+ "reasoning": "brief explanation"
87
+ }}"""
88
+
89
+ try:
90
+ response = self.model.generate_content(prompt)
91
+ response_text = response.text.strip()
92
+
93
+ # Extract JSON from response
94
+ json_match = re.search(r'\{[\s\S]*\}', response_text)
95
+ if json_match:
96
+ result = json.loads(json_match.group())
97
+ intent = result.get('intent', 'general_question')
98
+ confidence = result.get('confidence', 0.8)
99
+ entities = result.get('entities', {})
100
+ logger.info(f"Gemini classified intent: {intent} ({confidence}) for: {user_message}")
101
+ return intent, confidence, entities
102
+ else:
103
+ logger.warning(f"Could not parse Gemini response: {response_text}")
104
+ return 'general_question', 0.5, {}
105
+
106
+ except Exception as e:
107
+ logger.error(f"Gemini intent classification failed: {e}")
108
+ return 'unknown', 0.0, {}
109
+
110
+ def generate_response(self, user_message: str, intent: str, entities: Dict,
111
+ events: Optional[List[Dict]] = None, context: str = "") -> str:
112
+ """
113
+ Generate a natural, conversational response using Gemini
114
+ """
115
+ if not self.is_available():
116
+ return None
117
+
118
+ # Build context about available events
119
+ events_context = ""
120
+ if events:
121
+ events_context = "\n\nAvailable events:\n"
122
+ for i, event in enumerate(events[:5], 1):
123
+ title = event.get('title', 'Untitled')
124
+ date = event.get('date', 'TBA')[:10] if event.get('date') else 'TBA'
125
+ desc = event.get('description', '')[:100]
126
+ event_type = event.get('type', 'event')
127
+ tags = ', '.join(event.get('tags', [])[:3])
128
+ events_context += f"{i}. {title} ({event_type})\n Date: {date}\n Tags: {tags}\n Description: {desc}...\n\n"
129
+
130
+ prompt = f"""You are CampVerseBot, a friendly and helpful assistant for CampVerse - a college event discovery platform.
131
+
132
+ User's message: "{user_message}"
133
+ Detected intent: {intent}
134
+ Extracted entities: {json.dumps(entities)}
135
+ {events_context}
136
+ {context}
137
+
138
+ Guidelines:
139
+ - Be friendly, concise, and helpful
140
+ - If events are provided, recommend them naturally
141
+ - For event searches, list events with dates and brief descriptions
142
+ - Use emojis sparingly for a friendly tone
143
+ - If no events match, suggest alternative searches
144
+ - For greetings, introduce yourself briefly
145
+ - For help requests, explain what you can do
146
+ - Keep responses under 200 words unless listing multiple events
147
+
148
+ Generate a natural, helpful response:"""
149
+
150
+ try:
151
+ response = self.model.generate_content(prompt)
152
+ generated_text = response.text.strip()
153
+ logger.info(f"Gemini generated response for intent: {intent}")
154
+ return generated_text
155
+ except Exception as e:
156
+ logger.error(f"Gemini response generation failed: {e}")
157
+ return None
158
+
159
+ def enhance_search_query(self, user_message: str, entities: Dict) -> str:
160
+ """
161
+ Use Gemini to extract better search keywords from user message
162
+ """
163
+ if not self.is_available():
164
+ return user_message
165
+
166
+ prompt = f"""Extract the main search keywords from this event search query.
167
+ User message: "{user_message}"
168
+ Entities detected: {json.dumps(entities)}
169
+
170
+ Return only the key search terms separated by spaces (no explanation, just the keywords):"""
171
+
172
+ try:
173
+ response = self.model.generate_content(prompt)
174
+ keywords = response.text.strip()
175
+ logger.info(f"Enhanced search query: {keywords}")
176
+ return keywords if keywords else user_message
177
+ except Exception as e:
178
+ logger.error(f"Query enhancement failed: {e}")
179
+ return user_message
180
+
181
+ def get_contextual_response(self, intent: str) -> Optional[str]:
182
+ """
183
+ Get quick responses for simple intents without API call
184
+ """
185
+ quick_responses = {
186
+ 'greeting': "Hi there! ๐Ÿ‘‹ I'm CampVerseBot, your campus event assistant. I can help you discover hackathons, workshops, seminars, and more! What are you looking for today?",
187
+ 'farewell': "Goodbye! ๐Ÿ‘‹ Feel free to come back anytime you need help finding events. Have a great day!",
188
+ 'thanks': "You're welcome! ๐Ÿ˜Š Let me know if there's anything else I can help you with!",
189
+ }
190
+ return quick_responses.get(intent)
191
+
192
+
193
+ # Singleton instance
194
+ _gemini_service = None
195
+
196
+ def get_gemini_service() -> GeminiService:
197
+ """Get or create the singleton Gemini service instance"""
198
+ global _gemini_service
199
+ if _gemini_service is None:
200
+ _gemini_service = GeminiService()
201
+ return _gemini_service
requirements.txt CHANGED
@@ -9,3 +9,4 @@ python-engineio==4.10.1
9
  python-socketio[client]==5.11.4
10
  requests==2.32.3
11
  scikit-learn==1.5.2
 
 
9
  python-socketio[client]==5.11.4
10
  requests==2.32.3
11
  scikit-learn==1.5.2
12
+ google-generativeai==0.8.3