saemstunes commited on
Commit
8fe237f
·
verified ·
1 Parent(s): 9502cfc

Update src/ai_system.py

Browse files
Files changed (1) hide show
  1. src/ai_system.py +211 -80
src/ai_system.py CHANGED
@@ -6,16 +6,19 @@ from datetime import datetime
6
  from typing import Dict, List, Optional, Any, Tuple
7
  import json
8
  import requests
 
9
 
10
  try:
11
  from llama_cpp import Llama
12
  except ImportError:
13
  Llama = None
 
14
 
15
  try:
16
  from huggingface_hub import hf_hub_download
17
  except ImportError:
18
  hf_hub_download = None
 
19
 
20
  from .supabase_integration import AdvancedSupabaseIntegration
21
  from .security_system import AdvancedSecuritySystem
@@ -37,7 +40,8 @@ class SaemsTunesAISystem:
37
  model_file: str = "Phi-3.5-mini-instruct-q4_k_m.gguf",
38
  max_response_length: int = 500,
39
  temperature: float = 0.7,
40
- top_p: float = 0.9
 
41
  ):
42
  self.supabase = supabase_integration
43
  self.security = security_system
@@ -48,10 +52,15 @@ class SaemsTunesAISystem:
48
  self.max_response_length = max_response_length
49
  self.temperature = temperature
50
  self.top_p = top_p
 
51
 
52
  self.model = None
53
  self.model_loaded = False
54
  self.model_path = None
 
 
 
 
55
 
56
  self.setup_logging()
57
  self.load_model()
@@ -66,13 +75,22 @@ class SaemsTunesAISystem:
66
  try:
67
  self.logger.info(f"🔄 Loading {self.model_name} model...")
68
 
69
- # Check if model file exists locally
70
- local_path = f"./models/{self.model_file}"
 
 
 
71
  if os.path.exists(local_path):
72
  self.model_path = local_path
73
  self.logger.info(f"✅ Found local model: {local_path}")
 
 
 
 
 
 
 
74
  else:
75
- # Download from Hugging Face Hub
76
  if hf_hub_download is None:
77
  self.logger.error("❌ huggingface_hub not available for model download")
78
  return
@@ -81,35 +99,46 @@ class SaemsTunesAISystem:
81
  self.model_path = hf_hub_download(
82
  repo_id=self.model_repo,
83
  filename=self.model_file,
84
- cache_dir="./models",
85
  local_dir_use_symlinks=False
86
  )
87
  self.logger.info(f"✅ Model downloaded: {self.model_path}")
 
 
 
 
 
 
88
 
89
- # Load the model
90
  if Llama is None:
91
  self.logger.error("❌ llama-cpp-python not available for model loading")
92
  return
93
 
94
  self.model = Llama(
95
  model_path=self.model_path,
96
- n_ctx=4096, # Context window
97
- n_threads=4, # CPU threads
98
  n_batch=512,
99
  verbose=False,
100
  use_mlock=False,
101
- use_mmap=True
 
102
  )
103
 
104
- # Test the model
105
  test_response = self.model.create_completion(
106
- "Hello",
107
  max_tokens=10,
108
- temperature=0.1
 
109
  )
110
 
111
- self.model_loaded = True
112
- self.logger.info("✅ Model loaded and tested successfully!")
 
 
 
 
 
113
 
114
  except Exception as e:
115
  self.logger.error(f"❌ Error loading model: {e}")
@@ -136,35 +165,36 @@ class SaemsTunesAISystem:
136
  self.logger.warning("Model not loaded, returning fallback response")
137
  return self.get_fallback_response(query)
138
 
 
 
 
 
 
 
 
139
  try:
140
  start_time = time.time()
141
 
142
- # Get comprehensive context from Supabase
143
  context = self.supabase.get_music_context(query, user_id)
144
 
145
- # Build enhanced prompt with context
146
- prompt = self.build_enhanced_prompt(query, context, user_id)
147
 
148
- # Generate response
149
  response = self.model.create_completion(
150
  prompt,
151
  max_tokens=self.max_response_length,
152
  temperature=self.temperature,
153
  top_p=self.top_p,
154
- stop=["<|end|>", "</s>", "###", "Human:", "Assistant:"],
155
  echo=False,
156
  stream=False
157
  )
158
 
159
  processing_time = time.time() - start_time
160
 
161
- # Extract response text
162
  response_text = response['choices'][0]['text'].strip()
163
 
164
- # Clean up response
165
  response_text = self.clean_response(response_text)
166
 
167
- # Record metrics
168
  self.record_metrics(
169
  query=query,
170
  response=response_text,
@@ -175,6 +205,11 @@ class SaemsTunesAISystem:
175
  success=True
176
  )
177
 
 
 
 
 
 
178
  self.logger.info(f"✅ Query processed in {processing_time:.2f}s: {query[:50]}...")
179
 
180
  return response_text
@@ -182,7 +217,6 @@ class SaemsTunesAISystem:
182
  except Exception as e:
183
  self.logger.error(f"❌ Error processing query: {e}")
184
 
185
- # Record error metrics
186
  self.record_metrics(
187
  query=query,
188
  response="",
@@ -199,21 +233,19 @@ class SaemsTunesAISystem:
199
  self,
200
  query: str,
201
  context: Dict[str, Any],
202
- user_id: str
 
203
  ) -> str:
204
  """
205
  Build comprehensive prompt with context from Saem's Tunes platform.
206
-
207
- Args:
208
- query: User's question
209
- context: Context from Supabase database
210
- user_id: User identifier for personalization
211
-
212
- Returns:
213
- Formatted prompt for the model
214
  """
 
 
 
 
 
 
215
 
216
- # System prompt with platform context
217
  system_prompt = f"""<|system|>
218
  You are the AI assistant for Saem's Tunes, a comprehensive music education and streaming platform.
219
 
@@ -229,6 +261,7 @@ PLATFORM STATISTICS:
229
  - Total Artists: {context.get('stats', {}).get('artist_count', 0)}
230
  - Total Users: {context.get('stats', {}).get('user_count', 0)}
231
  - Total Courses: {context.get('stats', {}).get('course_count', 0)}
 
232
 
233
  CURRENT CONTEXT:
234
  {context.get('summary', 'General platform information')}
@@ -236,14 +269,23 @@ CURRENT CONTEXT:
236
  POPULAR CONTENT:
237
  {self.format_popular_content(context)}
238
 
 
 
 
 
 
 
239
  RESPONSE GUIDELINES:
240
  1. Be passionate about music and education
241
- 2. Provide specific, actionable information
242
  3. Reference platform features when relevant
243
  4. Keep responses concise (under {self.max_response_length} words)
244
  5. Be encouraging and supportive
245
  6. If unsure, guide users to relevant platform sections
246
  7. Personalize responses when user context is available
 
 
 
247
 
248
  PLATFORM FEATURES TO MENTION:
249
  - Music streaming and discovery
@@ -253,11 +295,12 @@ PLATFORM FEATURES TO MENTION:
253
  - Community features and social interaction
254
  - Premium subscription benefits
255
  - Mobile app availability
 
 
256
 
257
- ANSWER THE USER'S QUESTION:<|end|>
258
  """
259
 
260
- # User query
261
  user_prompt = f"<|user|>\n{query}<|end|>\n<|assistant|>\n"
262
 
263
  return system_prompt + user_prompt
@@ -266,40 +309,105 @@ ANSWER THE USER'S QUESTION:<|end|>
266
  """Format popular content for the prompt"""
267
  content_lines = []
268
 
269
- # Popular tracks
270
  if context.get('tracks'):
271
  content_lines.append("🎵 Popular Tracks:")
272
  for track in context['tracks'][:3]:
273
- content_lines.append(f" - {track.get('title', 'Unknown')} by {track.get('artist', 'Unknown')}")
 
 
 
 
274
 
275
- # Popular artists
276
  if context.get('artists'):
277
  content_lines.append("👨‍🎤 Popular Artists:")
278
  for artist in context['artists'][:3]:
279
- content_lines.append(f" - {artist.get('name', 'Unknown')} ({artist.get('genre', 'Various')})")
 
 
 
 
280
 
281
- # Recent courses
282
  if context.get('courses'):
283
  content_lines.append("📚 Recent Courses:")
284
  for course in context['courses'][:2]:
285
- content_lines.append(f" - {course.get('title', 'Unknown')} ({course.get('level', 'All Levels')})")
 
 
 
 
286
 
287
  return "\n".join(content_lines) if content_lines else "No specific content data available"
288
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  def clean_response(self, response: str) -> str:
290
  """Clean and format the AI response"""
291
- # Remove any trailing incomplete sentences
292
- if '.' in response:
293
- response = response.rsplit('.', 1)[0] + '.'
 
294
 
295
- # Remove any markdown formatting if present
296
- response = response.replace('**', '').replace('__', '')
 
297
 
298
- # Ensure response ends with proper punctuation
299
- if response and response[-1] not in ['.', '!', '?']:
 
 
 
 
 
 
 
 
 
 
 
 
300
  response += '.'
301
 
302
- return response.strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
303
 
304
  def record_metrics(
305
  self,
@@ -324,7 +432,8 @@ ANSWER THE USER'S QUESTION:<|end|>
324
  'conversation_id': conversation_id,
325
  'timestamp': datetime.now(),
326
  'query_length': len(query),
327
- 'response_length': len(response) if response else 0
 
328
  }
329
 
330
  if error_message:
@@ -335,52 +444,48 @@ ANSWER THE USER'S QUESTION:<|end|>
335
  'has_tracks': bool(context_used.get('tracks')),
336
  'has_artists': bool(context_used.get('artists')),
337
  'has_courses': bool(context_used.get('courses')),
338
- 'context_summary': context_used.get('summary', '')
 
339
  }
340
 
341
  self.monitor.record_inference(metrics)
342
 
343
  def get_fallback_response(self, query: str) -> str:
344
  """Get fallback response when model is unavailable"""
345
- fallback_responses = [
346
- "I'd love to help you with that! Our platform offers comprehensive music streaming and education features. ",
347
- "That's a great question about Saem's Tunes! We have extensive music content and educational resources available. ",
348
- "I appreciate your question about our music platform! Let me share some information about our features. "
349
- ]
350
-
351
- # Add context-specific fallbacks
352
  query_lower = query.lower()
353
 
354
  if any(term in query_lower for term in ['playlist', 'create', 'make']):
355
- base_response = "You can create playlists by going to the Library section and clicking 'Create New Playlist'. "
356
- elif any(term in query_lower for term in ['course', 'learn', 'education']):
357
- base_response = "We offer various music courses for different skill levels in our Education section. "
358
- elif any(term in query_lower for term in ['upload', 'artist', 'create']):
359
- base_response = "Artists can upload their music through the Creator Studio after verification. "
 
 
 
360
  elif any(term in query_lower for term in ['premium', 'subscribe', 'payment']):
361
- base_response = "Our premium subscription offers ad-free listening, offline downloads, and exclusive content. "
362
- else:
363
- base_response = "Our platform combines music streaming with comprehensive educational resources. "
 
364
 
365
- import random
366
- return base_response + random.choice(fallback_responses)
367
 
368
  def get_error_response(self, error: Exception) -> str:
369
  """Get user-friendly error response"""
370
- error_responses = [
371
- "I apologize, but I'm having trouble accessing the full information right now. ",
372
- "I'm experiencing some technical difficulties at the moment. ",
373
- "I'm unable to process your request completely due to a temporary issue. "
374
- ]
375
 
376
- base_response = "Please try again in a few moments, or contact support if the issue persists."
377
-
378
- import random
379
- return random.choice(error_responses) + base_response
 
 
380
 
381
  def is_healthy(self) -> bool:
382
  """Check if AI system is healthy and ready"""
383
- return self.model_loaded and self.supabase.is_connected()
384
 
385
  def get_system_info(self) -> Dict[str, Any]:
386
  """Get system information for monitoring"""
@@ -388,8 +493,34 @@ ANSWER THE USER'S QUESTION:<|end|>
388
  "model_loaded": self.model_loaded,
389
  "model_name": self.model_name,
390
  "model_path": self.model_path,
 
391
  "max_response_length": self.max_response_length,
392
  "temperature": self.temperature,
393
  "top_p": self.top_p,
394
- "supabase_connected": self.supabase.is_connected()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
395
  }
 
6
  from typing import Dict, List, Optional, Any, Tuple
7
  import json
8
  import requests
9
+ import hashlib
10
 
11
  try:
12
  from llama_cpp import Llama
13
  except ImportError:
14
  Llama = None
15
+ print("Warning: llama-cpp-python not available. AI functionality will be limited.")
16
 
17
  try:
18
  from huggingface_hub import hf_hub_download
19
  except ImportError:
20
  hf_hub_download = None
21
+ print("Warning: huggingface_hub not available. Model download will not work.")
22
 
23
  from .supabase_integration import AdvancedSupabaseIntegration
24
  from .security_system import AdvancedSecuritySystem
 
40
  model_file: str = "Phi-3.5-mini-instruct-q4_k_m.gguf",
41
  max_response_length: int = 500,
42
  temperature: float = 0.7,
43
+ top_p: float = 0.9,
44
+ context_window: int = 4096
45
  ):
46
  self.supabase = supabase_integration
47
  self.security = security_system
 
52
  self.max_response_length = max_response_length
53
  self.temperature = temperature
54
  self.top_p = top_p
55
+ self.context_window = context_window
56
 
57
  self.model = None
58
  self.model_loaded = False
59
  self.model_path = None
60
+ self.model_hash = None
61
+
62
+ self.conversation_history = {}
63
+ self.response_cache = {}
64
 
65
  self.setup_logging()
66
  self.load_model()
 
75
  try:
76
  self.logger.info(f"🔄 Loading {self.model_name} model...")
77
 
78
+ model_dir = "./models"
79
+ os.makedirs(model_dir, exist_ok=True)
80
+
81
+ local_path = os.path.join(model_dir, self.model_file)
82
+
83
  if os.path.exists(local_path):
84
  self.model_path = local_path
85
  self.logger.info(f"✅ Found local model: {local_path}")
86
+
87
+ with open(local_path, 'rb') as f:
88
+ file_hash = hashlib.md5()
89
+ while chunk := f.read(8192):
90
+ file_hash.update(chunk)
91
+ self.model_hash = file_hash.hexdigest()
92
+
93
  else:
 
94
  if hf_hub_download is None:
95
  self.logger.error("❌ huggingface_hub not available for model download")
96
  return
 
99
  self.model_path = hf_hub_download(
100
  repo_id=self.model_repo,
101
  filename=self.model_file,
102
+ cache_dir=model_dir,
103
  local_dir_use_symlinks=False
104
  )
105
  self.logger.info(f"✅ Model downloaded: {self.model_path}")
106
+
107
+ with open(self.model_path, 'rb') as f:
108
+ file_hash = hashlib.md5()
109
+ while chunk := f.read(8192):
110
+ file_hash.update(chunk)
111
+ self.model_hash = file_hash.hexdigest()
112
 
 
113
  if Llama is None:
114
  self.logger.error("❌ llama-cpp-python not available for model loading")
115
  return
116
 
117
  self.model = Llama(
118
  model_path=self.model_path,
119
+ n_ctx=self.context_window,
120
+ n_threads=min(4, os.cpu_count() or 1),
121
  n_batch=512,
122
  verbose=False,
123
  use_mlock=False,
124
+ use_mmap=True,
125
+ low_vram=False
126
  )
127
 
 
128
  test_response = self.model.create_completion(
129
+ "Test",
130
  max_tokens=10,
131
+ temperature=0.1,
132
+ stop=["<|end|>", "</s>"]
133
  )
134
 
135
+ if test_response and 'choices' in test_response and len(test_response['choices']) > 0:
136
+ self.model_loaded = True
137
+ self.logger.info("✅ Model loaded and tested successfully!")
138
+ self.logger.info(f"📊 Model info: {self.model_path} (Hash: {self.model_hash})")
139
+ else:
140
+ self.logger.error("❌ Model test failed")
141
+ self.model_loaded = False
142
 
143
  except Exception as e:
144
  self.logger.error(f"❌ Error loading model: {e}")
 
165
  self.logger.warning("Model not loaded, returning fallback response")
166
  return self.get_fallback_response(query)
167
 
168
+ cache_key = f"{user_id}:{hash(query)}"
169
+ if cache_key in self.response_cache:
170
+ cached_response, timestamp = self.response_cache[cache_key]
171
+ if time.time() - timestamp < 300:
172
+ self.logger.info("Returning cached response")
173
+ return cached_response
174
+
175
  try:
176
  start_time = time.time()
177
 
 
178
  context = self.supabase.get_music_context(query, user_id)
179
 
180
+ prompt = self.build_enhanced_prompt(query, context, user_id, conversation_id)
 
181
 
 
182
  response = self.model.create_completion(
183
  prompt,
184
  max_tokens=self.max_response_length,
185
  temperature=self.temperature,
186
  top_p=self.top_p,
187
+ stop=["<|end|>", "</s>", "###", "Human:", "Assistant:", "<|endoftext|>"],
188
  echo=False,
189
  stream=False
190
  )
191
 
192
  processing_time = time.time() - start_time
193
 
 
194
  response_text = response['choices'][0]['text'].strip()
195
 
 
196
  response_text = self.clean_response(response_text)
197
 
 
198
  self.record_metrics(
199
  query=query,
200
  response=response_text,
 
205
  success=True
206
  )
207
 
208
+ self.response_cache[cache_key] = (response_text, time.time())
209
+
210
+ if conversation_id:
211
+ self.update_conversation_history(conversation_id, query, response_text)
212
+
213
  self.logger.info(f"✅ Query processed in {processing_time:.2f}s: {query[:50]}...")
214
 
215
  return response_text
 
217
  except Exception as e:
218
  self.logger.error(f"❌ Error processing query: {e}")
219
 
 
220
  self.record_metrics(
221
  query=query,
222
  response="",
 
233
  self,
234
  query: str,
235
  context: Dict[str, Any],
236
+ user_id: str,
237
+ conversation_id: Optional[str] = None
238
  ) -> str:
239
  """
240
  Build comprehensive prompt with context from Saem's Tunes platform.
 
 
 
 
 
 
 
 
241
  """
242
+ conversation_context = ""
243
+ if conversation_id and conversation_id in self.conversation_history:
244
+ recent_messages = self.conversation_history[conversation_id][-3:]
245
+ for msg in recent_messages:
246
+ role = "User" if msg["role"] == "user" else "Assistant"
247
+ conversation_context += f"{role}: {msg['content']}\n"
248
 
 
249
  system_prompt = f"""<|system|>
250
  You are the AI assistant for Saem's Tunes, a comprehensive music education and streaming platform.
251
 
 
261
  - Total Artists: {context.get('stats', {}).get('artist_count', 0)}
262
  - Total Users: {context.get('stats', {}).get('user_count', 0)}
263
  - Total Courses: {context.get('stats', {}).get('course_count', 0)}
264
+ - Active Playlists: {context.get('stats', {}).get('playlist_count', 0)}
265
 
266
  CURRENT CONTEXT:
267
  {context.get('summary', 'General platform information')}
 
269
  POPULAR CONTENT:
270
  {self.format_popular_content(context)}
271
 
272
+ USER CONTEXT:
273
+ {self.format_user_context(context.get('user_context', {}))}
274
+
275
+ CONVERSATION HISTORY:
276
+ {conversation_context if conversation_context else 'No recent conversation history'}
277
+
278
  RESPONSE GUIDELINES:
279
  1. Be passionate about music and education
280
+ 2. Provide specific, actionable information about Saem's Tunes
281
  3. Reference platform features when relevant
282
  4. Keep responses concise (under {self.max_response_length} words)
283
  5. Be encouraging and supportive
284
  6. If unsure, guide users to relevant platform sections
285
  7. Personalize responses when user context is available
286
+ 8. Always maintain a professional, helpful tone
287
+ 9. Focus on music education, streaming, and platform features
288
+ 10. Avoid discussing unrelated topics
289
 
290
  PLATFORM FEATURES TO MENTION:
291
  - Music streaming and discovery
 
295
  - Community features and social interaction
296
  - Premium subscription benefits
297
  - Mobile app availability
298
+ - Music recommendations
299
+ - Learning progress tracking
300
 
301
+ ANSWER THE USER'S QUESTION BASED ON THE ABOVE CONTEXT:<|end|>
302
  """
303
 
 
304
  user_prompt = f"<|user|>\n{query}<|end|>\n<|assistant|>\n"
305
 
306
  return system_prompt + user_prompt
 
309
  """Format popular content for the prompt"""
310
  content_lines = []
311
 
 
312
  if context.get('tracks'):
313
  content_lines.append("🎵 Popular Tracks:")
314
  for track in context['tracks'][:3]:
315
+ title = track.get('title', 'Unknown Track')
316
+ artist = track.get('artist', 'Unknown Artist')
317
+ genre = track.get('genre', 'Various')
318
+ plays = track.get('plays', 0)
319
+ content_lines.append(f" - {title} by {artist} ({genre}) - {plays} plays")
320
 
 
321
  if context.get('artists'):
322
  content_lines.append("👨‍🎤 Popular Artists:")
323
  for artist in context['artists'][:3]:
324
+ name = artist.get('name', 'Unknown Artist')
325
+ genre = artist.get('genre', 'Various')
326
+ followers = artist.get('followers', 0)
327
+ verified = "✓" if artist.get('verified') else ""
328
+ content_lines.append(f" - {name} {verified} ({genre}) - {followers} followers")
329
 
 
330
  if context.get('courses'):
331
  content_lines.append("📚 Recent Courses:")
332
  for course in context['courses'][:2]:
333
+ title = course.get('title', 'Unknown Course')
334
+ instructor = course.get('instructor', 'Unknown Instructor')
335
+ level = course.get('level', 'All Levels')
336
+ students = course.get('students', 0)
337
+ content_lines.append(f" - {title} by {instructor} ({level}) - {students} students")
338
 
339
  return "\n".join(content_lines) if content_lines else "No specific content data available"
340
 
341
+ def format_user_context(self, user_context: Dict[str, Any]) -> str:
342
+ """Format user context for the prompt"""
343
+ if not user_context:
344
+ return "No specific user context available"
345
+
346
+ user_lines = []
347
+
348
+ if user_context.get('is_premium'):
349
+ user_lines.append("• User has premium subscription")
350
+
351
+ if user_context.get('favorite_genres'):
352
+ genres = user_context['favorite_genres'][:3]
353
+ user_lines.append(f"• Favorite genres: {', '.join(genres)}")
354
+
355
+ if user_context.get('recent_activity'):
356
+ activity = user_context['recent_activity'][:2]
357
+ user_lines.append(f"• Recent activity: {', '.join(activity)}")
358
+
359
+ if user_context.get('learning_progress'):
360
+ progress = user_context['learning_progress']
361
+ user_lines.append(f"• Learning progress: {progress.get('completed_lessons', 0)} lessons completed")
362
+
363
+ return "\n".join(user_lines) if user_lines else "Basic user account"
364
+
365
  def clean_response(self, response: str) -> str:
366
  """Clean and format the AI response"""
367
+ if not response:
368
+ return "I apologize, but I couldn't generate a response. Please try again."
369
+
370
+ response = response.strip()
371
 
372
+ if response.startswith("I'm sorry") or response.startswith("I apologize"):
373
+ if len(response) < 20:
374
+ response = "I'd be happy to help you with that! Our platform offers comprehensive music education and streaming features."
375
 
376
+ stop_phrases = [
377
+ "<|end|>", "</s>", "###", "Human:", "Assistant:",
378
+ "<|endoftext|>", "<|assistant|>", "<|user|>"
379
+ ]
380
+
381
+ for phrase in stop_phrases:
382
+ if phrase in response:
383
+ response = response.split(phrase)[0].strip()
384
+
385
+ sentences = response.split('. ')
386
+ if len(sentences) > 1:
387
+ response = '. '.join(sentences[:-1]) + '.' if not sentences[-1].endswith('.') else '. '.join(sentences)
388
+
389
+ if not response.endswith(('.', '!', '?')):
390
  response += '.'
391
 
392
+ response = response.replace('**', '').replace('__', '').replace('*', '')
393
+
394
+ if len(response) > self.max_response_length:
395
+ response = response[:self.max_response_length].rsplit(' ', 1)[0] + '...'
396
+
397
+ return response
398
+
399
+ def update_conversation_history(self, conversation_id: str, query: str, response: str):
400
+ """Update conversation history for context"""
401
+ if conversation_id not in self.conversation_history:
402
+ self.conversation_history[conversation_id] = []
403
+
404
+ self.conversation_history[conversation_id].extend([
405
+ {"role": "user", "content": query, "timestamp": datetime.now()},
406
+ {"role": "assistant", "content": response, "timestamp": datetime.now()}
407
+ ])
408
+
409
+ if len(self.conversation_history[conversation_id]) > 10:
410
+ self.conversation_history[conversation_id] = self.conversation_history[conversation_id][-10:]
411
 
412
  def record_metrics(
413
  self,
 
432
  'conversation_id': conversation_id,
433
  'timestamp': datetime.now(),
434
  'query_length': len(query),
435
+ 'response_length': len(response) if response else 0,
436
+ 'model_hash': self.model_hash
437
  }
438
 
439
  if error_message:
 
444
  'has_tracks': bool(context_used.get('tracks')),
445
  'has_artists': bool(context_used.get('artists')),
446
  'has_courses': bool(context_used.get('courses')),
447
+ 'has_user_context': bool(context_used.get('user_context')),
448
+ 'context_summary': context_used.get('summary', '')[:100]
449
  }
450
 
451
  self.monitor.record_inference(metrics)
452
 
453
  def get_fallback_response(self, query: str) -> str:
454
  """Get fallback response when model is unavailable"""
 
 
 
 
 
 
 
455
  query_lower = query.lower()
456
 
457
  if any(term in query_lower for term in ['playlist', 'create', 'make']):
458
+ return "You can create playlists by navigating to your Library and selecting 'Create New Playlist'. You can add tracks, customize the order, and share with friends. Premium users can create collaborative playlists."
459
+
460
+ elif any(term in query_lower for term in ['course', 'learn', 'education', 'lesson']):
461
+ return "We offer comprehensive music education courses for all skill levels. Visit the Education section to browse courses in music theory, instrument mastery, production techniques, and more. Each course includes video lessons, exercises, and progress tracking."
462
+
463
+ elif any(term in query_lower for term in ['upload', 'artist', 'creator']):
464
+ return "Artists can upload their music through the Creator Studio after account verification. You'll need to provide track files, metadata, and cover art. Once uploaded, your music will be available across our platform with analytics and revenue sharing."
465
+
466
  elif any(term in query_lower for term in ['premium', 'subscribe', 'payment']):
467
+ return "Our premium subscription offers ad-free listening, offline downloads, high-quality audio, exclusive content, and advanced features. You can subscribe monthly or annually with cancel-anytime flexibility."
468
+
469
+ elif any(term in query_lower for term in ['problem', 'issue', 'help', 'support']):
470
+ return "I'd be happy to help troubleshoot any issues. Please describe the problem you're experiencing, or visit our Help Center for detailed guides and contact information for our support team."
471
 
472
+ else:
473
+ return "I'd love to help you with Saem's Tunes! Our platform combines music streaming with comprehensive education features. You can discover new music, learn instruments, connect with artists, and develop your musical skills—all in one place. What specific aspect would you like to know more about?"
474
 
475
  def get_error_response(self, error: Exception) -> str:
476
  """Get user-friendly error response"""
477
+ error_str = str(error).lower()
 
 
 
 
478
 
479
+ if "memory" in error_str or "gpu" in error_str:
480
+ return "I'm experiencing high resource usage right now. Please try a simpler query or wait a moment before trying again."
481
+ elif "timeout" in error_str or "slow" in error_str:
482
+ return "The response is taking longer than expected. Please try again with a more specific question about Saem's Tunes features."
483
+ else:
484
+ return "I apologize, but I'm having technical difficulties right now. Please try again in a few moments, or contact support if the issue persists. Our team is constantly working to improve the AI assistant."
485
 
486
  def is_healthy(self) -> bool:
487
  """Check if AI system is healthy and ready"""
488
+ return self.model_loaded and self.model is not None and self.supabase.is_connected()
489
 
490
  def get_system_info(self) -> Dict[str, Any]:
491
  """Get system information for monitoring"""
 
493
  "model_loaded": self.model_loaded,
494
  "model_name": self.model_name,
495
  "model_path": self.model_path,
496
+ "model_hash": self.model_hash,
497
  "max_response_length": self.max_response_length,
498
  "temperature": self.temperature,
499
  "top_p": self.top_p,
500
+ "context_window": self.context_window,
501
+ "supabase_connected": self.supabase.is_connected(),
502
+ "conversations_active": len(self.conversation_history),
503
+ "cache_size": len(self.response_cache)
504
+ }
505
+
506
+ def clear_cache(self, user_id: Optional[str] = None):
507
+ """Clear response cache"""
508
+ if user_id:
509
+ keys_to_remove = [k for k in self.response_cache.keys() if k.startswith(f"{user_id}:")]
510
+ for key in keys_to_remove:
511
+ del self.response_cache[key]
512
+ else:
513
+ self.response_cache.clear()
514
+
515
+ def get_model_stats(self) -> Dict[str, Any]:
516
+ """Get model statistics"""
517
+ if not self.model_loaded:
518
+ return {"error": "Model not loaded"}
519
+
520
+ return {
521
+ "context_size": self.context_window,
522
+ "parameters": "3.8B",
523
+ "quantization": "Q4_K_M",
524
+ "model_size_gb": round(os.path.getsize(self.model_path) / (1024**3), 2) if self.model_path else 0,
525
+ "cache_hit_rate": len(self.response_cache) / (len(self.response_cache) + len(self.conversation_history)) if self.conversation_history else 0
526
  }