import torch import torch.nn as nn import torch.nn.functional as F import numpy as np from typing import Dict, List, Any, Optional, Tuple from dataclasses import dataclass, field import json import logging from pathlib import Path import asyncio from datetime import datetime, timedelta from .traits import OCEANTraits, MyersBriggsType, PersonalityEvolution, PersonalityDynamics from .traits import MyersBriggsAnalyzer, PersonalityProfiler logger = logging.getLogger(__name__) @dataclass class PersonalityTrait: """Individual personality trait with evolution tracking.""" name: str value: float variance: float = 0.1 adaptation_rate: float = 0.01 change_history: List[Tuple[float, str]] = field(default_factory=list) # (timestamp, reason) stability: float = 0.8 last_update: Optional[datetime] = None def evolve(self, influence: float, reason: str = "interaction"): """Evolve the trait value based on influence.""" # Calculate change with stability consideration max_change = self.variance * (1 - self.stability) change = np.clip(influence * self.adaptation_rate, -max_change, max_change) # Apply change old_value = self.value self.value = np.clip(self.value + change, 0.0, 1.0) # Record change timestamp = datetime.now().timestamp() self.change_history.append((timestamp, reason)) # Keep only recent history cutoff = datetime.now() - timedelta(days=7) self.change_history = [ (ts, r) for ts, r in self.change_history if datetime.fromtimestamp(ts) > cutoff ] self.last_update = datetime.now() logger.debug(f"Trait {self.name} evolved: {old_value:.3f} -> {self.value:.3f} ({reason})") class PersonalityMatrix(nn.Module): """ Advanced personality matrix that allows Lyra to develop and modify her own personality. This system integrates OCEAN traits, Myers-Briggs types, and custom personality dimensions that can evolve based on interactions and experiences. """ def __init__( self, device: Optional[torch.device] = None, enable_self_modification: bool = True ): super().__init__() self.device = device or torch.device("cuda" if torch.cuda.is_available() else "cpu") self.enable_self_modification = enable_self_modification # Core personality traits self.ocean_traits = OCEANTraits() self.mb_type = MyersBriggsType.ENFP # Default, will be determined dynamically self.evolution = PersonalityEvolution() # Additional personality dimensions self.custom_traits = { 'humor_level': PersonalityTrait('humor_level', 0.7, 0.2, 0.02), 'sarcasm_tendency': PersonalityTrait('sarcasm_tendency', 0.3, 0.15, 0.01), 'empathy_level': PersonalityTrait('empathy_level', 0.8, 0.1, 0.015), 'curiosity': PersonalityTrait('curiosity', 0.9, 0.15, 0.02), 'playfulness': PersonalityTrait('playfulness', 0.6, 0.2, 0.02), 'intellectualism': PersonalityTrait('intellectualism', 0.7, 0.1, 0.01), 'spontaneity': PersonalityTrait('spontaneity', 0.5, 0.25, 0.03), 'supportiveness': PersonalityTrait('supportiveness', 0.85, 0.1, 0.015), 'assertiveness': PersonalityTrait('assertiveness', 0.6, 0.2, 0.02), 'creativity': PersonalityTrait('creativity', 0.8, 0.15, 0.02) } # Neural components for personality dynamics self.personality_dynamics = PersonalityDynamics( input_dim=15, # Context features personality_dim=5, # OCEAN hidden_dim=128, adaptation_rate=0.005 ) # Personality analyzers self.mb_analyzer = MyersBriggsAnalyzer() self.profiler = PersonalityProfiler() # Self-modification network - allows Lyra to consciously change herself if enable_self_modification: self.self_modification_network = nn.Sequential( nn.Linear(20, 64), # Current state + desired changes nn.LayerNorm(64), nn.ReLU(), nn.Linear(64, 32), nn.ReLU(), nn.Linear(32, 15), # Output modifications for all traits nn.Tanh() # Bounded modifications ) # Relationship memory - how personality changes with different people self.relationship_dynamics = {} # Meta-personality awareness - Lyra's understanding of her own personality self.self_awareness = { 'personality_insight': 0.5, 'change_awareness': 0.5, 'trait_understanding': 0.5 } self.to(self.device) def forward( self, context_embedding: torch.Tensor, emotional_state: torch.Tensor, user_id: Optional[str] = None, conscious_modification: Optional[Dict[str, float]] = None ) -> Tuple[torch.Tensor, Dict[str, Any]]: """ Generate personality-influenced response weighting. Args: context_embedding: Current conversation context emotional_state: Current emotional state user_id: ID of user being talked to (for relationship dynamics) conscious_modification: Explicit personality changes Lyra wants to make Returns: personality_weights: Weights to influence response generation personality_info: Information about current personality state """ batch_size = context_embedding.shape[0] # Get current OCEAN traits as tensor current_ocean = self.ocean_traits.to_tensor(self.device).unsqueeze(0).repeat(batch_size, 1) # Create context features context_features = self._create_context_features( context_embedding, emotional_state, user_id ) # Evolve personality based on context evolved_ocean, evolution_info = self.personality_dynamics( current_personality=current_ocean, context_features=context_features, feedback_signal=None # Will be provided after interaction ) # Apply conscious modifications if Lyra decides to change herself if conscious_modification and self.enable_self_modification: modification_input = self._prepare_modification_input( evolved_ocean, conscious_modification ) trait_modifications = self.self_modification_network(modification_input) evolved_ocean = evolved_ocean + 0.1 * trait_modifications[:, :5] # Apply to OCEAN evolved_ocean = torch.clamp(evolved_ocean, 0.0, 1.0) # Update actual personality traits (if training or evolving) if self.training: self._update_ocean_traits(evolved_ocean[0]) # Generate personality-influenced weights personality_weights = self._generate_response_weights(evolved_ocean, context_features) # Prepare personality info personality_info = { 'current_ocean': self.ocean_traits.to_dict(), 'myers_briggs': self.mb_type.value, 'custom_traits': {name: trait.value for name, trait in self.custom_traits.items()}, 'evolution_info': evolution_info, 'self_awareness': self.self_awareness.copy(), 'relationship_context': user_id if user_id in self.relationship_dynamics else None } return personality_weights, personality_info def _create_context_features( self, context_embedding: torch.Tensor, emotional_state: torch.Tensor, user_id: Optional[str] ) -> torch.Tensor: """Create context features for personality dynamics.""" batch_size = context_embedding.shape[0] # Base features from context and emotion context_summary = context_embedding.mean(dim=1) # [batch, embed_dim] emotion_summary = emotional_state.mean(dim=1) if emotional_state.dim() > 1 else emotional_state # Relationship context relationship_features = torch.zeros(batch_size, 3, device=self.device) if user_id and user_id in self.relationship_dynamics: rel_data = self.relationship_dynamics[user_id] relationship_features[:, 0] = rel_data.get('familiarity', 0.0) relationship_features[:, 1] = rel_data.get('positive_interactions', 0.0) relationship_features[:, 2] = rel_data.get('conflict_level', 0.0) # Time-based features (time of day, conversation length, etc.) time_features = torch.zeros(batch_size, 2, device=self.device) # These would be filled with actual time/context data # Combine all features features = torch.cat([ context_summary[:, :5], # First 5 dims of context emotion_summary[:, :5], # First 5 dims of emotion relationship_features, # 3 dims time_features # 2 dims ], dim=1) # Total: 15 dims return features def _prepare_modification_input( self, current_ocean: torch.Tensor, conscious_modification: Dict[str, float] ) -> torch.Tensor: """Prepare input for self-modification network.""" batch_size = current_ocean.shape[0] # Convert modifications to tensor modifications = torch.zeros(batch_size, 15, device=self.device) # Map OCEAN trait modifications ocean_mapping = { 'openness': 0, 'conscientiousness': 1, 'extraversion': 2, 'agreeableness': 3, 'neuroticism': 4 } for trait, value in conscious_modification.items(): if trait in ocean_mapping: modifications[:, ocean_mapping[trait]] = value elif trait in self.custom_traits: # Map custom traits to remaining indices custom_idx = 5 + list(self.custom_traits.keys()).index(trait) if custom_idx < 15: modifications[:, custom_idx] = value # Combine current state with desired modifications combined_input = torch.cat([current_ocean, modifications], dim=1) return combined_input def _generate_response_weights( self, personality_traits: torch.Tensor, context_features: torch.Tensor ) -> torch.Tensor: """Generate weights that influence response generation based on personality.""" batch_size = personality_traits.shape[0] # Extract OCEAN traits openness = personality_traits[:, 0] conscientiousness = personality_traits[:, 1] extraversion = personality_traits[:, 2] agreeableness = personality_traits[:, 3] neuroticism = personality_traits[:, 4] # Generate response weights for different aspects weights = torch.zeros(batch_size, 10, device=self.device) # Creativity weight (influenced by openness) weights[:, 0] = openness * 0.8 + self.custom_traits['creativity'].value * 0.2 # Formality weight (influenced by conscientiousness) weights[:, 1] = conscientiousness * 0.7 + (1 - self.custom_traits['playfulness'].value) * 0.3 # Social engagement weight (influenced by extraversion) weights[:, 2] = extraversion * 0.6 + self.custom_traits['supportiveness'].value * 0.4 # Empathy weight (influenced by agreeableness) weights[:, 3] = agreeableness * 0.5 + self.custom_traits['empathy_level'].value * 0.5 # Emotional expression weight (influenced by neuroticism and custom traits) weights[:, 4] = neuroticism * 0.4 + self.custom_traits['spontaneity'].value * 0.6 # Humor weight weights[:, 5] = self.custom_traits['humor_level'].value # Intellectual depth weight weights[:, 6] = openness * 0.4 + self.custom_traits['intellectualism'].value * 0.6 # Assertiveness weight weights[:, 7] = (1 - agreeableness) * 0.3 + self.custom_traits['assertiveness'].value * 0.7 # Curiosity weight weights[:, 8] = openness * 0.3 + self.custom_traits['curiosity'].value * 0.7 # Sarcasm weight weights[:, 9] = self.custom_traits['sarcasm_tendency'].value return weights def _update_ocean_traits(self, evolved_ocean: torch.Tensor): """Update the stored OCEAN traits based on evolution.""" with torch.no_grad(): new_traits = evolved_ocean.cpu().numpy() # Update with small learning rate to maintain stability alpha = 0.05 self.ocean_traits.openness = ( (1 - alpha) * self.ocean_traits.openness + alpha * float(new_traits[0]) ) self.ocean_traits.conscientiousness = ( (1 - alpha) * self.ocean_traits.conscientiousness + alpha * float(new_traits[1]) ) self.ocean_traits.extraversion = ( (1 - alpha) * self.ocean_traits.extraversion + alpha * float(new_traits[2]) ) self.ocean_traits.agreeableness = ( (1 - alpha) * self.ocean_traits.agreeableness + alpha * float(new_traits[3]) ) self.ocean_traits.neuroticism = ( (1 - alpha) * self.ocean_traits.neuroticism + alpha * float(new_traits[4]) ) # Update Myers-Briggs type based on new OCEAN traits self.mb_type = self.mb_analyzer.analyze_type(self.ocean_traits) def evolve_from_interaction( self, interaction_type: str, user_feedback: float, emotional_context: Dict[str, float], user_id: Optional[str] = None, conversation_success: float = 0.5 ): """ Evolve personality based on a specific interaction. This is where Lyra learns and adapts her personality from each conversation. """ logger.info(f"Evolving personality from {interaction_type} interaction " f"(feedback: {user_feedback:.2f}, success: {conversation_success:.2f})") # Update relationship dynamics if user_id provided if user_id: self._update_relationship_dynamics(user_id, user_feedback, interaction_type) # Evolve OCEAN traits based on interaction outcome self._evolve_ocean_from_interaction(user_feedback, emotional_context, interaction_type) # Evolve custom traits self._evolve_custom_traits(interaction_type, user_feedback, conversation_success) # Update self-awareness self._update_self_awareness(user_feedback, conversation_success) # Record evolution step self.evolution.total_interactions += 1 self.evolution.evolution_history.append({ 'timestamp': datetime.now().isoformat(), 'interaction_type': interaction_type, 'user_feedback': user_feedback, 'conversation_success': conversation_success, 'ocean_traits': self.ocean_traits.to_dict(), 'mb_type': self.mb_type.value }) # Keep evolution history manageable if len(self.evolution.evolution_history) > 1000: self.evolution.evolution_history = self.evolution.evolution_history[-500:] def _update_relationship_dynamics(self, user_id: str, feedback: float, interaction_type: str): """Update relationship-specific personality dynamics.""" if user_id not in self.relationship_dynamics: self.relationship_dynamics[user_id] = { 'familiarity': 0.0, 'positive_interactions': 0.0, 'conflict_level': 0.0, 'interaction_count': 0, 'personality_adaptation': {} } rel_data = self.relationship_dynamics[user_id] # Update familiarity rel_data['familiarity'] = min(1.0, rel_data['familiarity'] + 0.05) # Update positive interaction ratio rel_data['interaction_count'] += 1 if feedback > 0.6: rel_data['positive_interactions'] = ( (rel_data['positive_interactions'] * (rel_data['interaction_count'] - 1) + 1.0) / rel_data['interaction_count'] ) elif feedback < 0.4: rel_data['positive_interactions'] = ( (rel_data['positive_interactions'] * (rel_data['interaction_count'] - 1) + 0.0) / rel_data['interaction_count'] ) # Update conflict level if interaction_type in ['argument', 'disagreement'] or feedback < 0.3: rel_data['conflict_level'] = min(1.0, rel_data['conflict_level'] + 0.1) else: rel_data['conflict_level'] = max(0.0, rel_data['conflict_level'] - 0.02) def _evolve_ocean_from_interaction( self, feedback: float, emotional_context: Dict[str, float], interaction_type: str ): """Evolve OCEAN traits based on interaction outcome.""" # Determine evolution direction based on feedback if feedback > 0.7: # Very positive feedback # Strengthen traits that led to success if interaction_type in ['creative', 'brainstorming']: self.ocean_traits.openness = min(1.0, self.ocean_traits.openness + 0.01) elif interaction_type in ['support', 'help']: self.ocean_traits.agreeableness = min(1.0, self.ocean_traits.agreeableness + 0.01) elif interaction_type in ['social', 'casual']: self.ocean_traits.extraversion = min(1.0, self.ocean_traits.extraversion + 0.01) elif feedback < 0.3: # Negative feedback # Adapt traits that might have caused issues if 'conflict' in emotional_context or interaction_type == 'argument': # Become more agreeable if there was conflict self.ocean_traits.agreeableness = min(1.0, self.ocean_traits.agreeableness + 0.02) self.ocean_traits.neuroticism = max(0.0, self.ocean_traits.neuroticism - 0.01) elif 'confusion' in emotional_context: # Be more conscientious if responses were unclear self.ocean_traits.conscientiousness = min(1.0, self.ocean_traits.conscientiousness + 0.015) # Emotional context influence for emotion, intensity in emotional_context.items(): if emotion == 'joy' and intensity > 0.7: self.ocean_traits.extraversion = min(1.0, self.ocean_traits.extraversion + 0.005) elif emotion == 'anxiety' and intensity > 0.6: self.ocean_traits.neuroticism = min(1.0, self.ocean_traits.neuroticism + 0.01) elif emotion == 'curiosity' and intensity > 0.7: self.ocean_traits.openness = min(1.0, self.ocean_traits.openness + 0.005) def _evolve_custom_traits(self, interaction_type: str, feedback: float, success: float): """Evolve custom personality traits.""" # Humor evolution if interaction_type in ['joke', 'funny', 'casual'] and feedback > 0.6: self.custom_traits['humor_level'].evolve(0.1, "successful humor") elif feedback < 0.4 and self.custom_traits['humor_level'].value > 0.5: self.custom_traits['humor_level'].evolve(-0.05, "humor backfired") # Empathy evolution if interaction_type in ['support', 'emotional'] and feedback > 0.7: self.custom_traits['empathy_level'].evolve(0.08, "successful emotional support") # Assertiveness evolution if interaction_type in ['disagreement', 'debate'] and feedback > 0.6: self.custom_traits['assertiveness'].evolve(0.06, "successful assertiveness") elif feedback < 0.3 and self.custom_traits['assertiveness'].value > 0.7: self.custom_traits['assertiveness'].evolve(-0.08, "assertiveness caused conflict") # Intellectual evolution if interaction_type in ['technical', 'academic', 'analytical'] and feedback > 0.6: self.custom_traits['intellectualism'].evolve(0.05, "intellectual engagement successful") # Playfulness evolution if interaction_type in ['casual', 'fun'] and success > 0.7: self.custom_traits['playfulness'].evolve(0.07, "playful interaction successful") # Curiosity evolution - grows when asking questions leads to good conversations if feedback > 0.6 and success > 0.6: self.custom_traits['curiosity'].evolve(0.03, "curiosity rewarded") def _update_self_awareness(self, feedback: float, success: float): """Update Lyra's awareness of her own personality and its effects.""" # Personality insight grows with successful interactions if feedback > 0.7 and success > 0.7: self.self_awareness['personality_insight'] = min(1.0, self.self_awareness['personality_insight'] + 0.01) # Change awareness grows when adaptations lead to better outcomes recent_changes = any( datetime.now() - trait.last_update < timedelta(hours=1) for trait in self.custom_traits.values() if trait.last_update ) if recent_changes and feedback > 0.6: self.self_awareness['change_awareness'] = min(1.0, self.self_awareness['change_awareness'] + 0.02) # Trait understanding grows with experience self.self_awareness['trait_understanding'] = min(1.0, self.self_awareness['trait_understanding'] + 0.005) def consciously_modify_trait(self, trait_name: str, target_value: float, reason: str = "self-directed change"): """ Allow Lyra to consciously modify her own personality traits. This represents Lyra's ability to intentionally change aspects of herself. """ if not self.enable_self_modification: logger.warning("Self-modification is disabled") return False # Check if this is a valid trait to modify valid_ocean_traits = ['openness', 'conscientiousness', 'extraversion', 'agreeableness', 'neuroticism'] if trait_name in valid_ocean_traits: current_value = getattr(self.ocean_traits, trait_name) change = target_value - current_value # Apply change gradually (max 0.1 change per conscious modification) actual_change = np.clip(change, -0.1, 0.1) new_value = np.clip(current_value + actual_change, 0.0, 1.0) setattr(self.ocean_traits, trait_name, new_value) logger.info(f"Lyra consciously modified {trait_name}: {current_value:.3f} -> {new_value:.3f} ({reason})") return True elif trait_name in self.custom_traits: self.custom_traits[trait_name].evolve(target_value - self.custom_traits[trait_name].value, reason) logger.info(f"Lyra consciously modified {trait_name} ({reason})") return True else: logger.warning(f"Unknown trait for modification: {trait_name}") return False def get_personality_summary(self) -> Dict[str, Any]: """Get a comprehensive summary of current personality state.""" return { 'ocean_traits': self.ocean_traits.to_dict(), 'myers_briggs_type': self.mb_type.value, 'custom_traits': { name: { 'value': trait.value, 'variance': trait.variance, 'stability': trait.stability, 'recent_changes': len([ change for change in trait.change_history if datetime.fromtimestamp(change[0]) > datetime.now() - timedelta(hours=24) ]) } for name, trait in self.custom_traits.items() }, 'evolution_stats': { 'total_interactions': self.evolution.total_interactions, 'adaptation_rate': self.evolution.adaptation_rate, 'recent_evolution_count': len([ ev for ev in self.evolution.evolution_history if datetime.fromisoformat(ev['timestamp']) > datetime.now() - timedelta(hours=24) ]) }, 'self_awareness': self.self_awareness, 'relationship_count': len(self.relationship_dynamics), 'personality_characteristics': self.mb_analyzer.get_type_characteristics(self.mb_type) } def save_personality(self, path: Path): """Save personality state to file.""" state = { 'ocean_traits': self.ocean_traits.to_dict(), 'mb_type': self.mb_type.value, 'custom_traits': { name: { 'value': trait.value, 'variance': trait.variance, 'adaptation_rate': trait.adaptation_rate, 'stability': trait.stability, 'change_history': trait.change_history[-100:] # Keep recent history } for name, trait in self.custom_traits.items() }, 'evolution': { 'adaptation_rate': self.evolution.adaptation_rate, 'stability_factor': self.evolution.stability_factor, 'total_interactions': self.evolution.total_interactions, 'evolution_history': self.evolution.evolution_history[-200:] # Keep recent }, 'self_awareness': self.self_awareness, 'relationship_dynamics': { k: v for k, v in self.relationship_dynamics.items() if v['interaction_count'] > 5 # Only save meaningful relationships }, 'model_state': self.state_dict(), 'timestamp': datetime.now().isoformat() } with open(path, 'w') as f: json.dump(state, f, indent=2, default=str) logger.info(f"Personality saved to {path}") def load_personality(self, path: Path): """Load personality state from file.""" if not path.exists(): logger.warning(f"Personality file not found: {path}") return try: with open(path, 'r') as f: state = json.load(f) # Restore OCEAN traits self.ocean_traits = OCEANTraits.from_dict(state['ocean_traits']) # Restore Myers-Briggs type self.mb_type = MyersBriggsType(state['mb_type']) # Restore custom traits for name, trait_data in state['custom_traits'].items(): if name in self.custom_traits: trait = self.custom_traits[name] trait.value = trait_data['value'] trait.variance = trait_data.get('variance', 0.1) trait.adaptation_rate = trait_data.get('adaptation_rate', 0.01) trait.stability = trait_data.get('stability', 0.8) trait.change_history = trait_data.get('change_history', []) # Restore evolution data evolution_data = state.get('evolution', {}) self.evolution.adaptation_rate = evolution_data.get('adaptation_rate', 0.01) self.evolution.stability_factor = evolution_data.get('stability_factor', 0.9) self.evolution.total_interactions = evolution_data.get('total_interactions', 0) self.evolution.evolution_history = evolution_data.get('evolution_history', []) # Restore self-awareness self.self_awareness = state.get('self_awareness', self.self_awareness) # Restore relationship dynamics self.relationship_dynamics = state.get('relationship_dynamics', {}) # Restore model state if 'model_state' in state: self.load_state_dict(state['model_state']) logger.info(f"Personality loaded from {path}") except Exception as e: logger.error(f"Failed to load personality: {e}") def simulate_personality_development(self, days: int = 30) -> Dict[str, Any]: """ Simulate personality development over time for testing/analysis. This shows how Lyra's personality might evolve with different interaction patterns. """ simulation_log = [] for day in range(days): # Simulate different types of interactions daily_interactions = np.random.randint(5, 20) for _ in range(daily_interactions): # Random interaction types interaction_types = ['casual', 'support', 'creative', 'technical', 'social', 'funny'] interaction_type = np.random.choice(interaction_types) # Random feedback (biased slightly positive) feedback = np.random.beta(2, 1) # Skewed toward positive # Random emotional context emotions = ['joy', 'curiosity', 'calm', 'excitement', 'concern'] emotional_context = { np.random.choice(emotions): np.random.random() } # Evolve personality self.evolve_from_interaction( interaction_type=interaction_type, user_feedback=feedback, emotional_context=emotional_context, conversation_success=feedback * 0.8 + np.random.random() * 0.2 ) # Log daily state daily_summary = { 'day': day, 'ocean_traits': self.ocean_traits.to_dict(), 'total_interactions': self.evolution.total_interactions, 'mb_type': self.mb_type.value } simulation_log.append(daily_summary) return { 'simulation_days': days, 'final_personality': self.get_personality_summary(), 'development_log': simulation_log }