231 lines
6.7 KiB
Python
231 lines
6.7 KiB
Python
"""
|
|
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"])
|