File size: 29,361 Bytes
93917f2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
#!/usr/bin/env python
"""

Codette Hybrid System - Best of Both Worlds

===========================================

Combines Codette's lightweight quantum consciousness with AICore's optimization techniques

"""

import logging
import sys
import os
from pathlib import Path
from typing import Any, Dict, List, Optional
import asyncio
from datetime import datetime

logger = logging.getLogger(__name__)

# Ensure Codette directory is in path
_current_dir = Path(__file__).parent
if str(_current_dir) not in sys.path:
    sys.path.insert(0, str(_current_dir))

# --- SAFE IMPORTS FOR CODFETTE AI MODULES ---
REAL_AICORE_AVAILABLE = False
REAL_COGNITIVE_AVAILABLE = False
REAL_DEFENSE_AVAILABLE = False
REAL_HEALTH_AVAILABLE = False
REAL_FRACTAL_AVAILABLE = False
SENTIMENT_AVAILABLE = False

AICore = None
CognitiveProcessor = None
DefenseSystem = None
HealthMonitor = None
FractalIdentity = None
sentiment_analyzer = None

try:
    from ai_core import AICore
    REAL_AICORE_AVAILABLE = True
    logger.info("AICore loaded (ai_core)")
except Exception:
    try:
        from Codette.src.components.ai_core import AICore
        REAL_AICORE_AVAILABLE = True
        logger.info("AICore loaded (Codette.src.components.ai_core)")
    except Exception:
        logger.debug("AICore not available")

try:
    from cognitive_processor import CognitiveProcessor
    REAL_COGNITIVE_AVAILABLE = True
    logger.info("CognitiveProcessor loaded")
except Exception:
    try:
        from Codette.src.components.cognitive_processor import CognitiveProcessor
        REAL_COGNITIVE_AVAILABLE = True
        logger.info("CognitiveProcessor loaded (Codette.src.components)")
    except Exception:
        logger.debug("CognitiveProcessor not available")

try:
    from defense_system import DefenseSystem
    REAL_DEFENSE_AVAILABLE = True
    logger.info("DefenseSystem loaded")
except Exception:
    try:
        from Codette.src.components.defense_system import DefenseSystem
        REAL_DEFENSE_AVAILABLE = True
        logger.info("DefenseSystem loaded (Codette.src.components)")
    except Exception:
        logger.debug("DefenseSystem not available")

try:
    from health_monitor import HealthMonitor
    REAL_HEALTH_AVAILABLE = True
    logger.info("HealthMonitor loaded")
except Exception:
    try:
        from Codette.src.components.health_monitor import HealthMonitor
        REAL_HEALTH_AVAILABLE = True
        logger.info("HealthMonitor loaded (Codette.src.components)")
    except Exception:
        logger.debug("HealthMonitor not available")

try:
    from fractal import FractalIdentity
    REAL_FRACTAL_AVAILABLE = True
    logger.info("FractalIdentity loaded")
except Exception:
    try:
        from Codette.src.components.fractal import FractalIdentity
        REAL_FRACTAL_AVAILABLE = True
        logger.info("FractalIdentity loaded (Codette.src.components)")
    except Exception:
        logger.debug("FractalIdentity not available")

# Sentiment (optional)
try:
    from nltk.sentiment import SentimentIntensityAnalyzer
    import nltk
    nltk.download('vader_lexicon', quiet=True)
    sentiment_analyzer = SentimentIntensityAnalyzer()
    SENTIMENT_AVAILABLE = True
    logger.info("Sentiment analyzer available")
except Exception:
    logger.debug("Sentiment analyzer not available")

# Import base systems - try multiple import strategies
CODETTE_ADVANCED_AVAILABLE = False
CodetteAdvanced = None
SentimentAnalyzer = None
ExplainableAI = None

# Strategy 1: Direct import (when running from Codette dir)
try:
    from codette_advanced import CodetteAdvanced, SentimentAnalyzer, ExplainableAI
    CODETTE_ADVANCED_AVAILABLE = True
    logger.info("Codette Advanced loaded (direct import)")
except ImportError:
    pass

# Strategy 2: Relative import with Codette prefix
if not CODETTE_ADVANCED_AVAILABLE:
    try:
        from Codette.codette_advanced import CodetteAdvanced, SentimentAnalyzer, ExplainableAI
        CODETTE_ADVANCED_AVAILABLE = True
        logger.info("Codette Advanced loaded (Codette prefix)")
    except ImportError:
        pass

# Strategy 3: Try importing from parent directory
if not CODETTE_ADVANCED_AVAILABLE:
    try:
        parent_dir = Path(__file__).parent.parent
        if str(parent_dir) not in sys.path:
            sys.path.insert(0, str(parent_dir))
        from Codette.codette_advanced import CodetteAdvanced, SentimentAnalyzer, ExplainableAI
        CODETTE_ADVANCED_AVAILABLE = True
        logger.info("Codette Advanced loaded (parent path)")
    except ImportError:
        pass

if not CODETTE_ADVANCED_AVAILABLE:
    logger.warning("Codette Advanced not available - using standalone mode")

# Optional heavy ML imports (only if needed)
TORCH_AVAILABLE = False
try:
    import torch
    TORCH_AVAILABLE = True
    logger.info("PyTorch available for ML optimization")
except ImportError:
    logger.info("PyTorch not available - using lightweight mode")

TRANSFORMERS_AVAILABLE = False
try:
    from transformers import AutoModelForCausalLM, AutoTokenizer
    TRANSFORMERS_AVAILABLE = True
    logger.info("Transformers available for LLM integration")
except ImportError:
    logger.info("Transformers not available - using base Codette")


class DefenseModifierSystem:
    """

    Lightweight defense system from AICore adapted for Codette

    """
    
    def __init__(self):
        self.response_modifiers = []
        self.response_filters = []
        self.security_level = "high"
    
    def add_sanitization_filter(self):
        """Add input sanitization"""
        import re
        
        def sanitize(text: str) -> str:
            # Remove HTML tags
            text = re.sub(r'<[^>]+>', '', text)
            # Remove potential JS
            text = re.sub(r'javascript:', '', text, flags=re.IGNORECASE)
            # Remove SQL injection attempts
            text = re.sub(r'(union|select|insert|update|delete|drop)\s+', '', text, flags=re.IGNORECASE)
            return text
        
        self.response_filters.append(sanitize)
    
    def add_tone_modifier(self, tone: str = "professional"):
        """Add tone adjustment modifier"""
        def adjust_tone(text: str) -> str:
            if tone == "professional":
                # Remove overly casual language
                text = text.replace(" gonna ", " going to ")
                text = text.replace(" wanna ", " want to ")
            elif tone == "friendly":
                # Add warmth
                if not text.endswith(("!", ".", "?")):
                    text += "!"
            return text
        
        self.response_modifiers.append(adjust_tone)
    
    def add_length_limiter(self, max_words: int = 300):
        """Limit response length"""
        def limit_length(text: str) -> str:
            words = text.split()
            if len(words) > max_words:
                text = " ".join(words[:max_words]) + "..."
            return text
        
        self.response_modifiers.append(limit_length)
    
    def apply_filters(self, text: str) -> str:
        """Apply all filters"""
        for filter_func in self.response_filters:
            text = filter_func(text)
        return text
    
    def apply_modifiers(self, text: str) -> str:
        """Apply all modifiers"""
        for modifier_func in self.response_modifiers:
            text = modifier_func(text)
        return text


class VectorSearchEngine:
    """

    Lightweight vector search from AICore for semantic similarity

    """
    
    def __init__(self):
        self.embeddings_cache = {}
        self.use_sklearn = False
        
        try:
            from sklearn.metrics.pairwise import cosine_similarity
            self.cosine_similarity = cosine_similarity
            self.use_sklearn = True
        except ImportError:
            logger.warning("sklearn not available - using basic similarity")
    
    def simple_similarity(self, query: str, documents: List[str]) -> List[int]:
        """Simple word-overlap similarity (no ML needed)"""
        query_words = set(query.lower().split())
        scores = []
        
        for doc in documents:
            doc_words = set(doc.lower().split())
            overlap = len(query_words & doc_words)
            scores.append(overlap)
        
        # Return indices sorted by score
        return sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)
    
    def find_similar_responses(self, query: str, response_history: List[str], top_k: int = 3) -> List[int]:
        """Find most similar previous responses"""
        if self.use_sklearn:
            # Use proper vector search if available
            # TODO: Implement with actual embeddings
            pass
        
        # Fallback to simple similarity
        return self.simple_similarity(query, response_history)[:top_k]


class PromptEngineer:
    """

    Prompt engineering utilities from AICore

    """
    
    def __init__(self):
        self.templates = {
            "daw_expert": "As an expert audio engineer, provide detailed guidance on: {query}",
            "creative": "Thinking creatively about music production, explore: {query}",
            "technical": "From a technical perspective, analyze: {query}",
            "beginner_friendly": "In simple, beginner-friendly terms, explain: {query}"
        }
    
    def engineer_prompt(self, query: str, style: str = "daw_expert") -> str:
        """Apply prompt engineering"""
        template = self.templates.get(style, self.templates["daw_expert"])
        return template.format(query=query)
    
    def add_context(self, query: str, context: Dict[str, Any]) -> str:
        """Add contextual information to prompt"""
        context_parts = []
        
        if context.get("tracks"):
            context_parts.append(f"User has {len(context['tracks'])} tracks")
        
        if context.get("selected_track"):
            track = context["selected_track"]
            context_parts.append(f"Currently working on: {track.get('name', 'track')}")
        
        if context.get("bpm"):
            context_parts.append(f"Project tempo: {context['bpm']} BPM")
        
        if context_parts:
            return f"Context: {', '.join(context_parts)}. Query: {query}"
        
        return query


class CodetteHybrid(CodetteAdvanced):
    """

    Hybrid Codette combining lightweight quantum consciousness with AICore optimizations

    """
    
    def __init__(self, user_name="User", use_ml_features: bool = True):
        super().__init__(user_name)
        
        # Lightweight enhancements
        self.defense_system = DefenseModifierSystem()
        self.defense_system.add_sanitization_filter()
        self.defense_system.add_tone_modifier("professional")
        self.defense_system.add_length_limiter(400)
        
        self.vector_search = VectorSearchEngine()
        self.prompt_engineer = PromptEngineer()
        
        # Optional ML features (only if dependencies available)
        self.use_ml = use_ml_features and TORCH_AVAILABLE and TRANSFORMERS_AVAILABLE
        self.ml_model = None
        self.ml_tokenizer = None
        
        if self.use_ml:
            self._initialize_ml_model()
        
        logger.info(f"Codette Hybrid initialized (ML: {self.use_ml})")
    
    def _initialize_ml_model(self):
        """Initialize ML model (optional heavy feature)"""
        try:
            # Use a lightweight model if needed
            model_name = "distilgpt2"  # Much smaller than Mistral
            self.ml_tokenizer = AutoTokenizer.from_pretrained(model_name)
            self.ml_model = AutoModelForCausalLM.from_pretrained(model_name)
            
            # Apply quantization for efficiency
            if TORCH_AVAILABLE:
                self.ml_model = torch.quantization.quantize_dynamic(
                    self.ml_model, {torch.nn.Linear}, dtype=torch.qint8
                )
            
            logger.info("ML model initialized with quantization")
        except Exception as e:
            logger.warning(f"Could not initialize ML model: {e}")
            self.use_ml = False
    
    def respond(self, query: str, daw_context: Optional[Dict] = None) -> str:
        """Generate response using available systems"""
        # Apply input filtering
        filtered_query = self.defense_system.apply_filters(query)
        
        # Try advanced system first - pass ORIGINAL filtered query (not engineered)
        # This preserves follow-up detection in the underlying Codette
        if self._use_advanced:
            try:
                # Check if the underlying Codette supports daw_context parameter
                import inspect
                respond_sig = inspect.signature(self._advanced.respond)
                param_names = list(respond_sig.parameters.keys())
                
                if len(param_names) >= 2 or 'daw_context' in param_names:
                    # Supports daw_context - pass original filtered query to preserve follow-up detection
                    response = self._advanced.respond(filtered_query, daw_context)
                else:
                    # Doesn't support daw_context - only engineer if NOT a follow-up
                    # Check for follow-up patterns ourselves
                    is_followup = self._is_followup_query(filtered_query)
                    if daw_context and not is_followup:
                        engineered_query = self.prompt_engineer.add_context(filtered_query, daw_context)
                    else:
                        engineered_query = filtered_query
                    response = self._advanced.respond(engineered_query)
                # If real defense is available, apply it, otherwise use lightweight modifiers
                if self.real_defense:
                    try:
                        response = self.real_defense.apply_defenses(response, {"m_score": 0.7})
                    except Exception:
                        pass
                else:
                    response = self.defense_system.apply_modifiers(response)
                return response
            except TypeError as e:
                # Handle case where respond() doesn't accept daw_context
                if "positional arguments" in str(e):
                    logger.warning(f"Advanced Codette doesn't support daw_context, using query only")
                    try:
                        # Check for follow-up before engineering
                        is_followup = self._is_followup_query(filtered_query)
                        if daw_context and not is_followup:
                            engineered_query = self.prompt_engineer.add_context(filtered_query, daw_context)
                        else:
                            engineered_query = filtered_query
                        response = self._advanced.respond(engineered_query)
                        if self.real_defense:
                            try:
                                response = self.real_defense.apply_defenses(response, {"m_score": 0.7})
                            except Exception:
                                pass
                        else:
                            response = self.defense_system.apply_modifiers(response)
                        return response
                    except Exception as e2:
                        logger.warning(f"Advanced respond (no context) failed: {e2}")
                else:
                    logger.warning(f"Advanced respond failed: {e}")
            except Exception as e:
                logger.warning(f"Advanced respond failed: {e}")
        
        # Fallback to basic response
        return self._generate_basic_response(query, daw_context)
    
    def _is_followup_query(self, prompt: str) -> bool:
        """Detect if this is a follow-up question (duplicated from codette_enhanced)"""
        prompt_lower = prompt.lower().strip()
        
        # Common follow-up phrases
        followup_patterns = [
            'what else', 'anything else', 'more tips', 'more advice', 'tell me more',
            'go on', 'continue', 'and', 'also', 'what about', 'how about',
            'any other', 'other suggestions', 'other ideas', 'more ideas', 'next',
            'then what', 'what next', 'ok', 'okay', 'got it', 'thanks', 'thank you',
            'cool', 'nice', 'great', 'good', 'yes', 'yeah', 'yep', 'sure', 'right',
            'hmm', 'interesting',
        ]
        
        # Check if prompt is a short follow-up
        if len(prompt_lower.split()) <= 4:
            for pattern in followup_patterns:
                if pattern in prompt_lower:
                    return True
        
        # Check if prompt starts with follow-up words
        followup_starters = ['and ', 'also ', 'what else', 'anything else', 'more ', 'other ']
        for starter in followup_starters:
            if prompt_lower.startswith(starter):
                return True
        
        return False
    
    def _generate_basic_response(self, query: str, daw_context: Optional[Dict] = None) -> str:
        """Generate a basic DAW-focused response"""
        prompt_lower = query.lower()
        
        # Check for DAW-related keywords
        if any(kw in prompt_lower for kw in ['mix', 'eq', 'compress', 'reverb', 'delay', 'audio', 'track', 'vocal', 'drum', 'bass']):
            responses = []
            
            if 'eq' in prompt_lower or 'frequency' in prompt_lower:
                responses.append("**EQ Guidance**: Cut before boost. High-pass filter on non-bass elements at 80-100Hz. Cut mud at 200-400Hz, add presence at 3-5kHz.")
            
            if 'compress' in prompt_lower:
                responses.append("**Compression Tips**: Start with 4:1 ratio for vocals, 2-3:1 for instruments. Attack 10-30ms preserves transients, release to match tempo.")
            
            if 'reverb' in prompt_lower or 'delay' in prompt_lower:
                responses.append("**Spatial Effects**: Use sends instead of inserts. Short reverb for presence, long for depth. Sync delays to tempo.")
            
            if 'vocal' in prompt_lower:
                responses.append("**Vocal Chain**: High-pass ? EQ (cut mud) ? Compressor ? EQ (add presence) ? De-esser ? Reverb send")
            
            if 'bass' in prompt_lower:
                responses.append("**Bass Processing**: Keep centered, high-pass at 30Hz, focus on 60-100Hz for weight, sidechain to kick if needed.")
            
            if 'drum' in prompt_lower or 'kick' in prompt_lower or 'snare' in prompt_lower:
                responses.append("**Drum Processing**: Check phase alignment, use parallel compression for punch, gate to reduce bleed.")
            
            if responses:
                return "\n\n".join(responses)
        
        # Default response
        return f"I'm Codette, your AI mixing assistant! I can help with EQ, compression, reverb, and other mixing techniques. Ask me about specific tracks or processing!"
    
    async def generate_response(self, query: str, user_id: int = 0, daw_context: Optional[Dict] = None) -> Dict[str, Any]:
        """Enhanced response generation with AICore techniques"""
        try:
            # 1. Apply input filtering
            filtered_query = self.defense_system.apply_filters(query)
            
            # 2. Engineer prompt with context
            if daw_context:
                engineered_query = self.prompt_engineer.add_context(filtered_query, daw_context)
            else:
                engineered_query = self.prompt_engineer.engineer_prompt(filtered_query)
            
            # 3. Check for similar previous responses (avoid repetition)
            if self.context_memory:
                similar_indices = self.vector_search.find_similar_responses(
                    engineered_query,
                    [c.get('input', '') for c in self.context_memory[-20:] if isinstance(c, dict)]
                )
                if similar_indices and similar_indices[0] < 2:
                    # Very similar recent query - add variation prompt
                    engineered_query += " (Please provide a fresh perspective.)"
            
            # 4. Generate base response
            if self.use_ml:
                # Use ML model for enhanced generation
                ml_response = await self._generate_ml_response(engineered_query)
                base_response = ml_response
            else:
                # Use lightweight respond method
                base_response = self.respond(engineered_query, daw_context)
            
            # 4b. Optionally enrich with AICore if available
            ai_enriched = False
            ai_insights = None
            try:
                if self.ai_core:
                    # Prefer async generate_response if available
                    if hasattr(self.ai_core, 'generate_response'):
                        gen = self.ai_core.generate_response
                        if asyncio.iscoroutinefunction(gen):
                            try:
                                ai_out = await gen(user_id, engineered_query)
                            except TypeError:
                                # try swapped args
                                ai_out = await gen(engineered_query, user_id)
                        else:
                            try:
                                ai_out = gen(user_id, engineered_query)
                            except TypeError:
                                ai_out = gen(engineered_query, user_id)

                        if isinstance(ai_out, dict):
                            ai_text = ai_out.get('response') or ai_out.get('message') or str(ai_out)
                        else:
                            ai_text = str(ai_out)
                    elif hasattr(self.ai_core, 'generate_text'):
                        try:
                            ai_text = self.ai_core.generate_text(engineered_query)
                        except Exception:
                            ai_text = None
                    else:
                        ai_text = None

                    if ai_text:
                        # Apply defenses to ai_text if real defense exists
                        if self.real_defense:
                            try:
                                ai_text = self.real_defense.apply_defenses(ai_text, {"m_score": 0.7})
                            except Exception:
                                pass
                        # Apply cognitive insights if available
                        if self.cognitive:
                            try:
                                ai_insights = self.cognitive.generate_insights(ai_text)
                            except Exception:
                                ai_insights = None

                        # Merge ai_text with base response
                        base_response = f"{base_response}\n\n[AI Core] {ai_text}"
                        ai_enriched = True
            except Exception as e:
                logger.debug(f"AICore enrichment failed: {e}")
            
            # 5. Apply response modifiers (prefer real defense if present)
            if self.real_defense:
                try:
                    final_response = self.real_defense.apply_defenses(base_response, {"m_score": 0.7})
                except Exception:
                    final_response = self.defense_system.apply_modifiers(base_response)
            else:
                final_response = self.defense_system.apply_modifiers(base_response)
            
            # 6. Store in context memory
            self.context_memory.append({
                'input': query,
                'response': final_response,
                'timestamp': datetime.now().isoformat()
            })
            
            # 7. Build result
            result = {
                "response": final_response,
                "engineered_prompt": engineered_query != query,
                "ml_enhanced": self.use_ml,
                "ai_enriched": ai_enriched,
                "ai_insights": ai_insights,
                "security_filtered": True,
                "source": "codette-hybrid",
                "timestamp": datetime.now().isoformat()
            }
            
            # Add advanced features if available
            if self._use_advanced:
                result["health_status"] = "healthy"
                if self.sentiment:
                    try:
                        result["sentiment"] = self.sentiment.polarity_scores(filtered_query)
                    except Exception:
                        result["sentiment"] = {"compound": 0.0}
                else:
                    result["sentiment"] = {"compound": 0.0}
            
            return result
            
        except Exception as e:
            logger.error(f"Hybrid response generation failed: {e}", exc_info=True)
            # Graceful fallback
            return {
                "response": self._generate_basic_response(query, daw_context),
                "fallback": True,
                "error": str(e),
                "source": "codette-hybrid-fallback"
            }
    
    async def _generate_ml_response(self, query: str) -> str:
        """Generate response using ML model (optional)"""
        if not self.ml_model or not self.ml_tokenizer:
            return self.respond(query)
        
        try:
            inputs = self.ml_tokenizer(query, return_tensors='pt', max_length=512, truncation=True)
            
            with torch.no_grad():
                outputs = self.ml_model.generate(
                    **inputs,
                    max_new_tokens=150,
                    do_sample=True,
                    temperature=0.7,
                    top_p=0.9
                )
            
            ml_text = self.ml_tokenizer.decode(outputs[0], skip_special_tokens=True)
            
            # Combine ML output with base response
            base_response = self.respond(query)
            
            return f"{base_response}\n\n[ML Insight] {ml_text}"
            
        except Exception as e:
            logger.error(f"ML generation failed: {e}")
            return self.respond(query)
    
    def optimize_for_production(self):
        """Apply production optimizations"""
        logger.info("Applying production optimizations...")
        
        # Clear old memory to save RAM
        if len(self.context_memory) > 100:
            self.context_memory = self.context_memory[-50:]
            logger.info("Trimmed context memory")
        
        # If ML model loaded, apply further optimization
        if self.use_ml and self.ml_model:
            try:
                # Apply pruning (remove low-magnitude weights)
                if TORCH_AVAILABLE:
                    import torch.nn.utils.prune as prune
                    
                    for module in self.ml_model.modules():
                        if isinstance(module, torch.nn.Linear):
                            prune.l1_unstructured(module, name='weight', amount=0.2)
                    
                    logger.info("Applied model pruning (20%)")
            except Exception as e:
                logger.warning(f"Could not apply pruning: {e}")


# Standalone test
if __name__ == "__main__":
    import asyncio
    
    async def test_hybrid():
        print("\n" + "="*60)
        print("CODETTE HYBRID SYSTEM TEST")
        print("="*60)
        
        # Test with ML features (if available)
        codette = CodetteHybrid(user_name="TestUser", use_ml_features=True)
        
        test_query = "How do I reduce harsh sibilance in my vocal recording?"
        daw_context = {
            "tracks": ["Vocals", "Drums", "Bass"],
            "selected_track": {"name": "Vocals", "type": "audio"},
            "bpm": 120
        }
        
        result = await codette.generate_response(
            query=test_query,
            user_id=12345,
            daw_context=daw_context
        )
        
        print(f"\n?? Query: {test_query}")
        print(f"\n?? Context: {daw_context}")
        print(f"\n?? Response:\n{result['response']}")
        print(f"\n?? Security: Filtered={result.get('security_filtered')}")
        print(f"?? ML Enhanced: {result.get('ml_enhanced')}")
        print(f"?? Source: {result.get('source')}")
        
        # Test optimization
        print("\n" + "-"*60)
        print("Applying production optimizations...")
        codette.optimize_for_production()
        print("? Optimization complete")
        
        print("\n" + "="*60)
    
    asyncio.run(test_hybrid())