""" Tests pour le gestionnaire LLM. """ import pytest import numpy as np from unittest.mock import Mock, patch, MagicMock from geniusia2.core.llm_manager import LLMManager @pytest.fixture def mock_ollama_client(): """Crée un client Ollama mocké.""" with patch('geniusia2.core.llm_manager.ollama') as mock_ollama: mock_client = MagicMock() mock_client.list.return_value = { 'models': [{'name': 'qwen2.5-vl:3b'}] } mock_ollama.Client.return_value = mock_client yield mock_client @pytest.fixture def llm_manager(mock_ollama_client): """Crée une instance du gestionnaire LLM pour les tests.""" return LLMManager( model_name="qwen2.5-vl:3b", ollama_host="localhost:11434", fallback_to_vision=True ) def test_initialization(llm_manager): """Test l'initialisation du gestionnaire.""" assert llm_manager.model_name == "qwen2.5-vl:3b" assert llm_manager.ollama_host == "localhost:11434" assert llm_manager.fallback_to_vision is True def test_image_to_base64(llm_manager): """Test la conversion d'image en base64.""" # Créer une image de test test_image = np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8) # Convertir en base64 b64_str = llm_manager._image_to_base64(test_image) # Vérifier assert isinstance(b64_str, str) assert len(b64_str) > 0 def test_fallback_to_vision_only(llm_manager): """Test le fallback vers la vision pure.""" detections = [ {"label": "button1", "confidence": 0.7, "bbox": (10, 10, 50, 30)}, {"label": "button2", "confidence": 0.9, "bbox": (100, 10, 50, 30)}, {"label": "button3", "confidence": 0.6, "bbox": (200, 10, 50, 30)} ] result = llm_manager._fallback_to_vision_only(detections) # Devrait sélectionner button2 (confiance la plus élevée) assert result["element_index"] == 1 assert result["selected_element"]["label"] == "button2" assert result["confidence"] == 0.9 def test_fallback_empty_detections(llm_manager): """Test le fallback avec des détections vides.""" result = llm_manager._fallback_to_vision_only([]) assert result["selected_element"] is None assert result["confidence"] == 0.0 def test_parse_llm_response_valid_json(llm_manager): """Test le parsing d'une réponse LLM valide.""" detections = [ {"label": "button1", "confidence": 0.7}, {"label": "button2", "confidence": 0.9} ] response = """ Voici mon analyse: { "element_index": 1, "confidence": 0.85, "reasoning": "Ce bouton correspond le mieux" } """ result = llm_manager._parse_llm_response(response, detections) assert result["element_index"] == 1 assert result["confidence"] == 0.85 assert "reasoning" in result def test_parse_llm_response_invalid_json(llm_manager): """Test le parsing d'une réponse LLM invalide.""" detections = [ {"label": "button1", "confidence": 0.7}, {"label": "button2", "confidence": 0.9} ] response = "Ceci n'est pas du JSON valide" result = llm_manager._parse_llm_response(response, detections) # Devrait fallback vers la vision pure assert result["element_index"] == 1 # button2 a la confiance la plus élevée assert "fallback" in result["reasoning"].lower() def test_reason_about_detections_empty(llm_manager): """Test le raisonnement avec des détections vides.""" result = llm_manager.reason_about_detections( detections=[], context={"window": "Test"}, intent="cliquer sur valider" ) assert result["selected_element"] is None assert result["confidence"] == 0.0 def test_reason_about_detections_with_mock(llm_manager, mock_ollama_client): """Test le raisonnement avec un client mocké.""" detections = [ { "label": "valider", "confidence": 0.9, "bbox": (100, 100, 50, 30), "roi_image": np.random.randint(0, 255, (30, 50, 3), dtype=np.uint8) } ] # Configurer le mock pour retourner une réponse JSON valide mock_ollama_client.generate.return_value = { 'response': '{"element_index": 0, "confidence": 0.95, "reasoning": "Bouton valider"}' } result = llm_manager.reason_about_detections( detections=detections, context={"window": "Test"}, intent="cliquer sur valider" ) assert result["element_index"] == 0 assert result["confidence"] == 0.95 def test_generate_with_vision(llm_manager, mock_ollama_client): """Test la génération avec vision.""" mock_ollama_client.generate.return_value = { 'response': 'Ceci est une réponse de test' } test_image = np.random.randint(0, 255, (100, 100, 3), dtype=np.uint8) response = llm_manager.generate_with_vision( prompt="Décris cette image", images=[test_image] ) assert response == 'Ceci est une réponse de test' assert mock_ollama_client.generate.called def test_score_action_relevance(llm_manager, mock_ollama_client): """Test le scoring de pertinence d'action.""" mock_ollama_client.generate.return_value = { 'response': '0.85' } action = { "action_type": "click", "target_element": "valider_button" } score = llm_manager.score_action_relevance( action=action, intent="valider le formulaire" ) assert 0.0 <= score <= 1.0 assert score == 0.85 def test_is_available(llm_manager, mock_ollama_client): """Test la vérification de disponibilité.""" mock_ollama_client.list.return_value = {'models': []} available = llm_manager.is_available() assert isinstance(available, bool) def test_get_model_info(llm_manager): """Test l'obtention des informations du modèle.""" info = llm_manager.get_model_info() assert "model_name" in info assert "host" in info assert "available" in info assert "fallback_enabled" in info assert info["model_name"] == "qwen2.5-vl:3b" def test_llm_manager_without_ollama(): """Test l'initialisation sans Ollama disponible.""" with patch('geniusia2.core.llm_manager.ollama', None): with pytest.raises(ImportError): LLMManager() def test_client_initialization_error(): """Test la gestion d'erreur lors de l'initialisation.""" with patch('geniusia2.core.llm_manager.ollama') as mock_ollama: mock_ollama.Client.side_effect = Exception("Connection error") # Devrait créer le manager avec fallback manager = LLMManager(fallback_to_vision=True) assert manager.client is None if __name__ == "__main__": pytest.main([__file__, "-v"])