Spaces:
Running
Running
Update src/ai_system.py
Browse files- 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 |
-
|
| 70 |
-
|
|
|
|
|
|
|
|
|
|
| 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=
|
| 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=
|
| 97 |
-
n_threads=4,
|
| 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 |
-
"
|
| 107 |
max_tokens=10,
|
| 108 |
-
temperature=0.1
|
|
|
|
| 109 |
)
|
| 110 |
|
| 111 |
-
|
| 112 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 274 |
|
| 275 |
-
# Popular artists
|
| 276 |
if context.get('artists'):
|
| 277 |
content_lines.append("👨🎤 Popular Artists:")
|
| 278 |
for artist in context['artists'][:3]:
|
| 279 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 280 |
|
| 281 |
-
# Recent courses
|
| 282 |
if context.get('courses'):
|
| 283 |
content_lines.append("📚 Recent Courses:")
|
| 284 |
for course in context['courses'][:2]:
|
| 285 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 292 |
-
|
| 293 |
-
|
|
|
|
| 294 |
|
| 295 |
-
|
| 296 |
-
|
|
|
|
| 297 |
|
| 298 |
-
|
| 299 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
response += '.'
|
| 301 |
|
| 302 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
'
|
|
|
|
| 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 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
|
|
|
|
|
|
|
|
|
| 360 |
elif any(term in query_lower for term in ['premium', 'subscribe', 'payment']):
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
|
|
|
| 364 |
|
| 365 |
-
|
| 366 |
-
|
| 367 |
|
| 368 |
def get_error_response(self, error: Exception) -> str:
|
| 369 |
"""Get user-friendly error response"""
|
| 370 |
-
|
| 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 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
|
|
|
|
|
|
| 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 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
}
|