""" Tests for core AI systems including transformer, self-evolution, and thinking agent. """ import pytest import torch import numpy as np from lyra.core.transformer import LyraTransformer, LyraTransformerBlock from lyra.core.attention import SelfEvolvingAttention, MultiHeadAttention from lyra.core.self_evolution import SelfEvolutionEngine, EvolutionMetrics from lyra.core.thinking_agent import ThinkingAgent, ThoughtProcess from tests.conftest import assert_tensor_shape, assert_tensor_range class TestSelfEvolvingAttention: """Tests for self-evolving attention mechanism.""" def test_attention_initialization(self, device): """Test attention mechanism initialization.""" attention = SelfEvolvingAttention( embed_dim=128, num_heads=8, dropout=0.1, device=device ) assert attention.embed_dim == 128 assert attention.num_heads == 8 assert attention.head_dim == 16 # 128 / 8 def test_attention_forward_pass(self, device): """Test attention forward pass.""" attention = SelfEvolvingAttention( embed_dim=128, num_heads=8, device=device ) batch_size, seq_len = 2, 10 x = torch.randn(batch_size, seq_len, 128, device=device) output, weights, evolution_info = attention( query=x, key=x, value=x, evolve=True ) assert_tensor_shape(output, (batch_size, seq_len, 128), "attention output") assert_tensor_shape(weights, (batch_size, 8, seq_len, seq_len), "attention weights") assert isinstance(evolution_info, dict) def test_attention_evolution_learning(self, device): """Test attention pattern evolution from feedback.""" attention = SelfEvolvingAttention( embed_dim=128, num_heads=8, device=device ) # Store initial evolution matrix initial_evolution = attention.attention_evolution.clone() # Apply positive feedback attention.evolve_attention_patterns(feedback_signal=0.8) # Evolution matrix should change assert not torch.equal(initial_evolution, attention.attention_evolution) def test_attention_diversity_calculation(self, device): """Test attention diversity measurement.""" attention = SelfEvolvingAttention( embed_dim=128, num_heads=8, device=device ) # Get baseline diversity diversity = attention.get_attention_diversity() assert isinstance(diversity, float) assert 0.0 <= diversity <= 10.0 # Reasonable entropy range class TestLyraTransformerBlock: """Tests for Lyra transformer block.""" def test_transformer_block_initialization(self, device): """Test transformer block initialization.""" block = LyraTransformerBlock( embed_dim=128, num_heads=8, ff_dim=512, dropout=0.1, use_evolution=True, device=device ) assert block.embed_dim == 128 assert block.num_heads == 8 assert block.use_evolution is True def test_transformer_block_forward(self, device): """Test transformer block forward pass.""" block = LyraTransformerBlock( embed_dim=128, num_heads=8, ff_dim=512, use_evolution=True, device=device ) batch_size, seq_len = 2, 10 x = torch.randn(batch_size, seq_len, 128, device=device) emotional_state = torch.rand(batch_size, 19, device=device) output, layer_info = block( x=x, emotional_state=emotional_state, evolve=True ) assert_tensor_shape(output, (batch_size, seq_len, 128), "transformer block output") assert isinstance(layer_info, dict) assert 'layer_id' in layer_info assert 'attention_entropy' in layer_info def test_transformer_block_evolution_from_feedback(self, device): """Test block evolution from user feedback.""" block = LyraTransformerBlock( embed_dim=128, num_heads=8, ff_dim=512, use_evolution=True, device=device ) initial_adaptation = float(block.adaptation_strength) # Apply positive feedback block.evolve_from_feedback(feedback_signal=0.9) # Adaptation strength should change new_adaptation = float(block.adaptation_strength) assert new_adaptation != initial_adaptation class TestLyraTransformer: """Tests for the complete Lyra transformer model.""" def test_transformer_initialization(self, device): """Test transformer model initialization.""" model = LyraTransformer( vocab_size=1000, embed_dim=128, num_layers=4, num_heads=8, ff_dim=512, max_len=256, use_evolution=True, device=device ) assert model.vocab_size == 1000 assert model.embed_dim == 128 assert model.num_layers == 4 assert len(model.layers) == 4 def test_transformer_forward_pass(self, device): """Test transformer forward pass.""" model = LyraTransformer( vocab_size=1000, embed_dim=128, num_layers=2, num_heads=8, ff_dim=512, device=device ) batch_size, seq_len = 2, 10 input_ids = torch.randint(0, 1000, (batch_size, seq_len), device=device) emotional_state = torch.rand(batch_size, 19, device=device) logits, model_info = model( input_ids=input_ids, emotional_state=emotional_state, evolve=True ) assert_tensor_shape(logits, (batch_size, seq_len, 1000), "transformer logits") assert isinstance(model_info, dict) assert 'layer_info' in model_info assert 'evolution_active' in model_info def test_transformer_generation(self, device): """Test autoregressive text generation.""" model = LyraTransformer( vocab_size=100, # Small vocab for testing embed_dim=64, # Small model for speed num_layers=2, num_heads=4, ff_dim=256, device=device ) batch_size, input_len = 1, 5 input_ids = torch.randint(0, 100, (batch_size, input_len), device=device) generated_ids, generation_info = model.generate( input_ids=input_ids, max_new_tokens=10, temperature=1.0, top_k=10, evolve=False # Disable evolution for faster testing ) expected_len = input_len + generation_info['tokens_generated'] assert generated_ids.shape == (batch_size, expected_len) assert 'average_confidence' in generation_info assert 'generation_steps' in generation_info def test_transformer_evolution_from_conversation(self, device): """Test model evolution from conversation feedback.""" model = LyraTransformer( vocab_size=100, embed_dim=64, num_layers=2, num_heads=4, use_evolution=True, device=device ) initial_feedback = model.last_feedback # Apply conversation feedback model.evolve_from_conversation(feedback_signal=0.8) # Feedback should be recorded assert model.last_feedback != initial_feedback def test_transformer_model_stats(self, device): """Test model statistics generation.""" model = LyraTransformer( vocab_size=100, embed_dim=64, num_layers=2, num_heads=4, use_evolution=True, device=device ) stats = model.get_model_stats() required_keys = [ 'generation_count', 'last_feedback', 'model_parameters', 'trainable_parameters' ] for key in required_keys: assert key in stats # With evolution enabled, should have evolution stats assert 'layer_evolution' in stats class TestSelfEvolutionEngine: """Tests for the self-evolution system.""" def test_evolution_engine_initialization(self, device): """Test evolution engine initialization.""" engine = SelfEvolutionEngine( model_dim=128, evolution_rate=0.01, adaptation_threshold=0.7, device=device ) assert engine.model_dim == 128 assert engine.evolution_rate == 0.01 assert engine.adaptation_threshold == 0.7 assert len(engine.experience_buffer) == 0 def test_evolution_metrics_initialization(self): """Test evolution metrics initialization.""" metrics = EvolutionMetrics() assert metrics.conversation_satisfaction == 0.0 assert metrics.learning_rate_adaptation == 0.0 assert 0.0 <= metrics.personality_drift <= 1.0 def test_evolution_forward_pass(self, self_evolution_engine, device): """Test evolution engine forward pass.""" batch_size, seq_len, dim = 2, 10, 128 current_state = torch.randn(batch_size, seq_len, dim, device=device) context = torch.randn(batch_size, seq_len, dim, device=device) evolved_state, evolution_info = self_evolution_engine( current_state=current_state, context=context ) assert_tensor_shape(evolved_state, (batch_size, seq_len, dim), "evolved state") assert isinstance(evolution_info, dict) assert 'state_change_magnitude' in evolution_info assert 'adaptive_lr' in evolution_info def test_evolution_from_conversation(self, self_evolution_engine, device): """Test evolution from conversation interaction.""" conversation_embedding = torch.randn(10, 128, device=device) user_satisfaction = 0.8 emotional_context = {'joy': 0.7, 'trust': 0.8} evolved_embedding, evolution_info = self_evolution_engine.evolve_from_conversation( conversation_embedding=conversation_embedding, user_satisfaction=user_satisfaction, emotional_context=emotional_context ) assert_tensor_shape(evolved_embedding, (10, 128), "evolved conversation embedding") assert isinstance(evolution_info, dict) # Metrics should be updated assert self_evolution_engine.metrics.conversation_satisfaction > 0.0 def test_long_term_evolution(self, self_evolution_engine): """Test long-term evolution consolidation.""" # Add some fake experiences for _ in range(150): # Above the 100 threshold fake_experience = { 'state': torch.randn(1, 10, 128), 'context': torch.randn(1, 10, 128), 'evolution': torch.randn(1, 10, 128), 'meta_params': torch.randn(1, 5), 'timestamp': torch.rand(1) } self_evolution_engine.experience_buffer.append(fake_experience) initial_plasticity = self_evolution_engine.personality_plasticity # Trigger long-term evolution self_evolution_engine.long_term_evolution() # Should have analyzed and potentially adjusted parameters assert len(self_evolution_engine.experience_buffer) >= 100 def test_evolution_summary(self, self_evolution_engine): """Test evolution summary generation.""" summary = self_evolution_engine.get_evolution_summary() if summary.get("status") != "no_evolution_data": required_keys = [ 'total_evolution_steps', 'current_metrics', 'personality_plasticity', 'adaptive_learning_rate' ] for key in required_keys: assert key in summary def test_evolution_state_persistence(self, self_evolution_engine, temp_directory): """Test saving and loading evolution state.""" save_path = temp_directory / "evolution_test.json" # Modify some state self_evolution_engine.metrics.conversation_satisfaction = 0.8 self_evolution_engine.personality_plasticity = 0.2 # Save state self_evolution_engine.save_evolution_state(save_path) assert save_path.exists() # Create new engine and load new_engine = SelfEvolutionEngine(device=self_evolution_engine.device) new_engine.load_evolution_state(save_path) assert abs(new_engine.metrics.conversation_satisfaction - 0.8) < 0.01 assert abs(new_engine.personality_plasticity - 0.2) < 0.01 class TestThinkingAgent: """Tests for the behind-the-scenes thinking agent.""" def test_thinking_agent_initialization(self, device): """Test thinking agent initialization.""" agent = ThinkingAgent( model_dim=128, thought_types=8, max_thought_depth=5, device=device ) assert agent.model_dim == 128 assert agent.thought_types == 8 assert agent.max_thought_depth == 5 assert len(agent.thought_type_names) == 8 def test_thought_process_creation(self): """Test thought process object creation.""" thought = ThoughtProcess( thought_type="analytical", content="I need to think about this carefully.", confidence=0.8, reasoning="This requires analytical thinking.", emotional_influence=0.3, personality_influence=0.6 ) assert thought.thought_type == "analytical" assert thought.confidence == 0.8 assert hasattr(thought, 'timestamp') @pytest.mark.asyncio async def test_thinking_agent_forward_pass(self, thinking_agent, sample_context_embedding, sample_personality_tensor, sample_emotional_tensor, sample_user_message): """Test thinking agent forward pass.""" thought_chain, thinking_info = thinking_agent( context_embedding=sample_context_embedding, personality_state=sample_personality_tensor, emotional_state=sample_emotional_tensor, user_message=sample_user_message ) assert isinstance(thought_chain, list) assert len(thought_chain) > 0 assert all(isinstance(thought, ThoughtProcess) for thought in thought_chain) assert isinstance(thinking_info, dict) assert 'total_thoughts' in thinking_info assert 'avg_confidence' in thinking_info assert 'thinking_time' in thinking_info def test_thinking_from_feedback_learning(self, thinking_agent): """Test learning from response feedback.""" # Create mock thought chain thought_chain = [ ThoughtProcess("analytical", "Test thought", 0.8, "Test reasoning"), ThoughtProcess("empathetic", "Another thought", 0.7, "More reasoning") ] initial_patterns = len(thinking_agent.thinking_patterns.get('successful_strategies', {})) # Apply feedback thinking_agent.learn_from_response_feedback( thought_chain=thought_chain, response_quality=0.8, user_satisfaction=0.9 ) # Should have recorded the pattern final_patterns = len(thinking_agent.thinking_patterns.get('successful_strategies', {})) assert final_patterns >= initial_patterns def test_thinking_summary_generation(self, thinking_agent): """Test thinking summary generation.""" # Add some fake history fake_thought = ThoughtProcess("creative", "Test", 0.7, "Test reasoning") thinking_agent.thought_history = [fake_thought] * 10 summary = thinking_agent.get_thinking_summary() if summary.get('status') != 'no_thinking_history': required_keys = [ 'total_thoughts', 'recent_thoughts', 'thought_type_distribution', 'avg_confidence' ] for key in required_keys: assert key in summary def test_optimal_thinking_strategy(self, thinking_agent): """Test optimal thinking strategy determination.""" # Test with unknown context (should return default) strategy = thinking_agent.get_optimal_thinking_strategy("unknown_context") assert isinstance(strategy, list) assert len(strategy) > 0 assert all(isinstance(thought_type, str) for thought_type in strategy) def test_internal_dialogue_simulation(self, thinking_agent): """Test internal dialogue simulation.""" scenario = "User is asking for help with a difficult problem." thought_chain = thinking_agent.simulate_internal_dialogue(scenario) assert isinstance(thought_chain, list) assert len(thought_chain) > 0 assert all(isinstance(thought, ThoughtProcess) for thought in thought_chain) def test_thinking_patterns_export(self, thinking_agent): """Test thinking patterns export.""" export_data = thinking_agent.export_thinking_patterns() required_keys = [ 'thinking_patterns', 'thought_history_summary', 'thought_type_names', 'total_thinking_experiences' ] for key in required_keys: assert key in export_data