Feat: Action analyser_avec_ia (Ollama qwen2.5-vl)

Nouvelle action d'intelligence artificielle:
- Analyse de contenu visuel via Ollama
- 8 types d'analyse prédéfinis: general, formulaire, erreur,
  boutons, tableau, menu, validation, extraction
- Prompts personnalisables
- Support température et max_tokens
- Variable de sortie configurable

Modèle par défaut: qwen2.5-vl:7b

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Dom
2026-01-14 23:15:33 +01:00
parent ae100d3da8
commit 38966de0db
2 changed files with 398 additions and 0 deletions

View File

@@ -0,0 +1,21 @@
"""
Actions Intelligence VWB - Module d'initialisation
Auteur : Dom, Claude - 14 janvier 2026
Ce module contient les actions d'intelligence artificielle
pour le Visual Workflow Builder.
Actions disponibles :
- VWBAnalyserAvecIAAction : Analyse de contenu visuel avec IA (Ollama)
"""
from .analyser_avec_ia import VWBAnalyserAvecIAAction, VWBAIAnalyzeTextAction
__all__ = [
'VWBAnalyserAvecIAAction',
'VWBAIAnalyzeTextAction', # Alias anglais
]
__version__ = '1.0.0'
__author__ = 'Dom, Claude'
__date__ = '14 janvier 2026'

View File

@@ -0,0 +1,377 @@
"""
Action Analyser avec IA - Analyse de contenu visuel via Ollama
Auteur : Dom, Claude - 14 janvier 2026
Cette action permet d'analyser du contenu visuel (texte, images, interfaces)
en utilisant un modèle de vision IA (Ollama avec qwen2.5-vl ou similaire).
Cas d'usage :
- Comprendre le contenu d'une zone d'écran
- Extraire des informations structurées
- Valider visuellement des états d'interface
- Analyser des documents ou formulaires
"""
from typing import Dict, Any, List, Optional, Tuple
from datetime import datetime
import time
import base64
import io
import requests
from ..base_action import BaseVWBAction, VWBActionResult, VWBActionStatus
from ...contracts.error import VWBErrorType, create_vwb_error
from ...contracts.visual_anchor import VWBVisualAnchor
# Configuration Ollama par défaut
OLLAMA_DEFAULT_URL = "http://localhost:11434"
OLLAMA_DEFAULT_MODEL = "qwen2.5-vl:7b"
class VWBAnalyserAvecIAAction(BaseVWBAction):
"""
Action d'analyse de contenu visuel avec IA.
Utilise Ollama avec un modèle de vision (qwen2.5-vl) pour :
- Analyser le contenu d'une zone d'écran
- Répondre à des questions sur l'interface
- Extraire des informations structurées
- Valider des états visuels
"""
def __init__(
self,
action_id: str,
parameters: Dict[str, Any],
screen_capturer=None
):
"""
Initialise l'action d'analyse IA.
Args:
action_id: Identifiant unique de l'action
parameters: Paramètres de l'analyse
screen_capturer: Instance du ScreenCapturer (optionnel)
"""
super().__init__(
action_id=action_id,
name="Analyser avec IA",
description="Analyse du contenu visuel avec intelligence artificielle",
parameters=parameters,
screen_capturer=screen_capturer
)
# Zone à analyser (ancre visuelle ou région)
self.ancre_visuelle: Optional[VWBVisualAnchor] = (
parameters.get('visual_anchor') or
parameters.get('ancre_visuelle')
)
self.region = parameters.get('region') # {x, y, width, height}
# Prompt d'analyse
self.prompt = parameters.get('prompt', parameters.get('question', ''))
self.prompt_systeme = parameters.get('prompt_systeme', parameters.get('system_prompt', ''))
# Type d'analyse prédéfini
self.type_analyse = parameters.get('type_analyse', parameters.get('analysis_type', 'general'))
# Configuration Ollama
self.ollama_url = parameters.get('ollama_url', OLLAMA_DEFAULT_URL)
self.ollama_model = parameters.get('ollama_model', parameters.get('model', OLLAMA_DEFAULT_MODEL))
# Options
self.timeout_ms = parameters.get('timeout_ms', 30000)
self.temperature = parameters.get('temperature', 0.3)
self.max_tokens = parameters.get('max_tokens', 1000)
# Variable de sortie
self.variable_sortie = parameters.get('variable_sortie', parameters.get('output_variable', 'analyse_ia'))
# Prompts prédéfinis par type d'analyse
self.prompts_predefinifs = {
'general': "Décris ce que tu vois dans cette image de manière concise.",
'formulaire': "Analyse ce formulaire. Liste les champs visibles, leur état (rempli/vide) et les valeurs si lisibles.",
'erreur': "Y a-t-il un message d'erreur visible ? Si oui, quel est son contenu exact ?",
'boutons': "Liste tous les boutons visibles avec leur texte et leur état apparent (actif/inactif/grisé).",
'tableau': "Analyse ce tableau. Décris sa structure (colonnes, lignes) et résume son contenu.",
'menu': "Décris les options de menu visibles et leur hiérarchie.",
'validation': "Cette interface semble-t-elle dans un état valide ? Décris ce que tu observes.",
'extraction': "Extrait toutes les informations textuelles visibles de manière structurée.",
}
def validate_parameters(self) -> List[str]:
"""Valide les paramètres de l'action."""
erreurs = []
# Vérifier qu'on a une source d'image
if not self.ancre_visuelle and not self.region and not self.screen_capturer:
erreurs.append("Ancre visuelle, région ou screen_capturer requis")
# Vérifier le prompt
if not self.prompt and self.type_analyse not in self.prompts_predefinifs:
erreurs.append("Prompt ou type d'analyse valide requis")
# Vérifier le timeout
if self.timeout_ms < 5000:
erreurs.append("Timeout minimum: 5000ms (5 secondes)")
if self.timeout_ms > 120000:
erreurs.append("Timeout maximum: 120000ms (2 minutes)")
return erreurs
def execute_core(self, step_id: str) -> VWBActionResult:
"""
Exécute l'analyse IA.
Args:
step_id: Identifiant de l'étape
Returns:
Résultat d'exécution avec l'analyse
"""
start_time = datetime.now()
try:
# Étape 1: Capturer l'image à analyser
image_base64 = self._capturer_image()
if not image_base64:
return self._create_error_result(
step_id=step_id,
start_time=start_time,
error_type=VWBErrorType.SCREEN_CAPTURE_FAILED,
message="Impossible de capturer l'image à analyser"
)
# Étape 2: Construire le prompt
prompt_final = self._construire_prompt()
# Étape 3: Appeler Ollama
print(f"🤖 Analyse IA en cours ({self.ollama_model})...")
resultat_analyse = self._analyser_avec_ollama(image_base64, prompt_final)
if resultat_analyse is None:
return self._create_error_result(
step_id=step_id,
start_time=start_time,
error_type=VWBErrorType.SYSTEM_ERROR,
message="Échec de l'analyse IA"
)
end_time = datetime.now()
execution_time = (end_time - start_time).total_seconds() * 1000
print(f"✅ Analyse IA terminée en {execution_time:.0f}ms")
print(f"📝 Résultat: {resultat_analyse[:200]}..." if len(resultat_analyse) > 200 else f"📝 Résultat: {resultat_analyse}")
return VWBActionResult(
action_id=self.action_id,
step_id=step_id,
status=VWBActionStatus.SUCCESS,
start_time=start_time,
end_time=end_time,
execution_time_ms=execution_time,
output_data={
'analyse': resultat_analyse,
'variable_sortie': self.variable_sortie,
'type_analyse': self.type_analyse,
'model': self.ollama_model,
'prompt_utilise': prompt_final[:100] + '...' if len(prompt_final) > 100 else prompt_final
},
evidence_list=self.evidence_list.copy()
)
except Exception as e:
return self._create_error_result(
step_id=step_id,
start_time=start_time,
error_type=VWBErrorType.SYSTEM_ERROR,
message=f"Erreur: {str(e)}",
technical_details={'exception': str(e)}
)
def _capturer_image(self) -> Optional[str]:
"""
Capture l'image à analyser.
Returns:
Image en base64 ou None
"""
try:
# Option 1: Image depuis l'ancre visuelle
if self.ancre_visuelle:
if isinstance(self.ancre_visuelle, dict):
img = self.ancre_visuelle.get('screenshot') or self.ancre_visuelle.get('image_base64')
if img:
return img
elif isinstance(self.ancre_visuelle, VWBVisualAnchor):
if self.ancre_visuelle.screenshot_base64:
return self.ancre_visuelle.screenshot_base64
# Option 2: Capture d'une région spécifique
if self.region and self.screen_capturer:
return self._capturer_region(self.region)
# Option 3: Capture de l'écran entier
if self.screen_capturer:
return self._capturer_ecran_complet()
return None
except Exception as e:
print(f"⚠️ Erreur capture: {e}")
return None
def _capturer_region(self, region: Dict[str, int]) -> Optional[str]:
"""Capture une région spécifique de l'écran."""
try:
from PIL import Image
# Capturer l'écran entier
img_array = self.screen_capturer.capture()
if img_array is None:
return None
# Convertir en PIL et découper
pil_image = Image.fromarray(img_array)
x = region.get('x', 0)
y = region.get('y', 0)
width = region.get('width', 100)
height = region.get('height', 100)
cropped = pil_image.crop((x, y, x + width, y + height))
# Convertir en base64
buffer = io.BytesIO()
cropped.save(buffer, format='PNG')
return base64.b64encode(buffer.getvalue()).decode('utf-8')
except Exception as e:
print(f"⚠️ Erreur capture région: {e}")
return None
def _capturer_ecran_complet(self) -> Optional[str]:
"""Capture l'écran entier."""
try:
from PIL import Image
img_array = self.screen_capturer.capture()
if img_array is None:
return None
pil_image = Image.fromarray(img_array)
buffer = io.BytesIO()
pil_image.save(buffer, format='PNG', optimize=True)
return base64.b64encode(buffer.getvalue()).decode('utf-8')
except Exception as e:
print(f"⚠️ Erreur capture écran: {e}")
return None
def _construire_prompt(self) -> str:
"""
Construit le prompt final pour l'analyse.
Returns:
Prompt complet
"""
# Utiliser le prompt personnalisé si fourni
if self.prompt:
prompt_base = self.prompt
# Sinon utiliser le prompt prédéfini selon le type
elif self.type_analyse in self.prompts_predefinifs:
prompt_base = self.prompts_predefinifs[self.type_analyse]
else:
prompt_base = self.prompts_predefinifs['general']
# Ajouter le prompt système si fourni
if self.prompt_systeme:
return f"{self.prompt_systeme}\n\n{prompt_base}"
return prompt_base
def _analyser_avec_ollama(self, image_base64: str, prompt: str) -> Optional[str]:
"""
Envoie l'image à Ollama pour analyse.
Args:
image_base64: Image en base64
prompt: Prompt d'analyse
Returns:
Texte de l'analyse ou None
"""
try:
# Préparer la requête
payload = {
"model": self.ollama_model,
"prompt": prompt,
"images": [image_base64],
"stream": False,
"options": {
"temperature": self.temperature,
"num_predict": self.max_tokens,
}
}
# Appeler l'API Ollama
response = requests.post(
f"{self.ollama_url}/api/generate",
json=payload,
timeout=self.timeout_ms / 1000
)
if response.status_code == 200:
result = response.json()
return result.get('response', '').strip()
else:
print(f"⚠️ Erreur Ollama: {response.status_code} - {response.text[:200]}")
return None
except requests.exceptions.Timeout:
print(f"⚠️ Timeout Ollama après {self.timeout_ms}ms")
return None
except requests.exceptions.ConnectionError:
print(f"⚠️ Ollama non accessible à {self.ollama_url}")
return self._fallback_analyse(prompt)
except Exception as e:
print(f"⚠️ Erreur Ollama: {e}")
return None
def _fallback_analyse(self, prompt: str) -> Optional[str]:
"""
Fallback si Ollama n'est pas disponible.
Args:
prompt: Prompt original
Returns:
Message d'erreur informatif
"""
return f"[Analyse IA non disponible - Ollama non accessible]\nPrompt demandé: {prompt[:100]}..."
def get_action_info(self) -> Dict[str, Any]:
"""Retourne les informations de l'action."""
return {
'action_id': self.action_id,
'name': self.name,
'description': self.description,
'type': 'analyser_avec_ia',
'parameters': {
'type_analyse': self.type_analyse,
'prompt': self.prompt[:50] + '...' if len(self.prompt) > 50 else self.prompt,
'model': self.ollama_model,
'variable_sortie': self.variable_sortie
},
'status': self.current_status.value
}
# Alias pour compatibilité avec le catalogue anglais
VWBAIAnalyzeTextAction = VWBAnalyserAvecIAAction