feat(grounding): pipeline centralisé + serveur UI-TARS transformers + nettoyage code mort

Architecture grounding complète :
- core/grounding/server.py : serveur FastAPI (port 8200) avec UI-TARS-1.5-7B en 4-bit NF4
  Process séparé avec son propre contexte CUDA (résout le crash Flask/CUDA)
- core/grounding/pipeline.py : orchestrateur cascade template→OCR→UI-TARS→static
- core/grounding/template_matcher.py : TemplateMatcher centralisé (remplace 5 copies)
- core/grounding/ui_tars_grounder.py : client HTTP vers le serveur de grounding
- core/grounding/target.py : GroundingTarget + GroundingResult

ORA modifié :
- _act_click() : capture unique de l'écran envoyée au serveur de grounding
- Pre-check VLM skippé pour ui_tars (redondant, et Ollama n'a plus de VRAM)
- verify_level='none' par défaut (vérification titre OCR prévue en Phase 2)
- Détection réponses négatives UI-TARS ("I don't see it" → fallback OCR)

Nettoyage :
- 9 fichiers morts archivés dans _archive/ (~6300 lignes supprimées)
- 21 tests ajoutés pour TemplateMatcher

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dom
2026-04-25 17:48:18 +02:00
parent 16ff396dbf
commit 9da589c8c2
20 changed files with 1862 additions and 15 deletions

View File

@@ -1363,20 +1363,51 @@ Règles:
x, y = None, None
method_used = ''
# --- Méthode 1 : UI-TARS grounding (~3s, 94% précision) ---
# Le plus fiable : on dit "click on X" et UI-TARS trouve les coordonnées
# --- Capture unique de l'écran pour TOUTES les méthodes ---
_screen_b64 = None
if MSS_AVAILABLE and PIL_AVAILABLE:
try:
import io as _io
with mss_lib.mss() as _sct:
_mon = _sct.monitors[0]
_grab = _sct.grab(_mon)
_screen_pil = Image.frombytes('RGB', _grab.size, _grab.bgra, 'raw', 'BGRX')
_buf = _io.BytesIO()
_screen_pil.save(_buf, format='JPEG', quality=85)
_screen_b64 = base64.b64encode(_buf.getvalue()).decode('utf-8')
print(f"📸 [ORA/capture] Écran capturé: {_screen_pil.size}")
except Exception as _e:
print(f"⚠️ [ORA/capture] Erreur: {_e}")
# --- Méthode 1 : UI-TARS via serveur grounding (port 8200, ~3s) ---
# Le serveur tourne dans un process séparé avec son propre CUDA context.
# Si le serveur n'est pas lancé → on passe au template matching.
if target_text or target_desc:
try:
from core.execution.input_handler import _grounding_ui_tars
import requests as _http
click_label = target_desc or target_text
print(f"🎯 [ORA/UI-TARS] Recherche: '{click_label}'")
result = _grounding_ui_tars(target_text, target_desc)
if result:
x, y = result['x'], result['y']
method_used = 'ui_tars'
print(f"✅ [ORA/UI-TARS] Trouvé à ({x}, {y})")
_payload = {
'target_text': target_text,
'target_description': target_desc,
}
if _screen_b64:
_payload['image_b64'] = _screen_b64
_resp = _http.post('http://localhost:8200/ground', json=_payload, timeout=30)
if _resp.status_code == 200:
_data = _resp.json()
if _data.get('x') is not None:
x, y = _data['x'], _data['y']
method_used = 'ui_tars'
print(f"✅ [ORA/UI-TARS] Trouvé à ({x}, {y}) conf={_data.get('confidence', 0):.2f} ({_data.get('time_ms', 0):.0f}ms)")
else:
print(f"⚠️ [ORA/UI-TARS] Serveur n'a pas trouvé '{click_label}'")
else:
print(f"⚠️ [ORA/UI-TARS] Serveur HTTP {_resp.status_code}")
except _http.ConnectionError:
print(f"⚠️ [ORA/UI-TARS] Serveur grounding non démarré (port 8200)")
except Exception as e:
logger.debug(f"⚠️ [ORA/UI-TARS] Erreur: {e}")
print(f"⚠️ [ORA/UI-TARS] Erreur: {e}")
# --- Méthode 2 : Template matching (~80ms) ---
if x is None and screenshot_b64 and CV2_AVAILABLE and PIL_AVAILABLE and MSS_AVAILABLE:
@@ -1405,19 +1436,22 @@ Règles:
y = max_loc[1] + anchor_cv.shape[0] // 2
method_used = 'template'
except Exception as e:
logger.debug(f"⚠️ [ORA/template] Erreur: {e}")
print(f"⚠️ [ORA/template] Erreur: {e}")
# --- Méthode 3 : OCR texte (~1s) ---
if x is None and target_text:
try:
from core.execution.input_handler import _grounding_ocr
print(f"🔍 [ORA/OCR] Recherche: '{target_text}'")
result = _grounding_ocr(target_text, anchor_bbox=bbox if bbox else None)
if result:
x, y = result['x'], result['y']
method_used = 'ocr'
print(f"🔍 [ORA/OCR] Trouvé à ({x}, {y})")
else:
print(f"🔍 [ORA/OCR] '{target_text}' non trouvé")
except Exception as e:
logger.debug(f"⚠️ [ORA/OCR] Erreur: {e}")
print(f"⚠️ [ORA/OCR] Erreur: {e}")
# --- Exécuter le clic ---
if x is None:
@@ -1426,13 +1460,13 @@ Règles:
x = int(bbox.get('x', 0) + bbox.get('width', 0) / 2)
y = int(bbox.get('y', 0) + bbox.get('height', 0) / 2)
method_used = 'static_fallback'
logger.warning(f"⚠️ [ORA/click] Fallback coordonnées statiques: ({x}, {y})")
print(f"⚠️ [ORA/click] Fallback coordonnées statiques: ({x}, {y})")
else:
logger.error(f"❌ [ORA/click] Impossible de localiser '{target_text}' — aucune méthode n'a fonctionné")
return False
# --- Vérification pré-action : est-ce le bon élément ? ---
if target_text and method_used not in ('template',) and MSS_AVAILABLE and PIL_AVAILABLE:
# --- Vérification pré-action (skip si UI-TARS a déjà validé visuellement) ---
if target_text and method_used not in ('template', 'ui_tars') and MSS_AVAILABLE and PIL_AVAILABLE:
try:
pre_check = self._verify_pre_click(x, y, target_text, target_desc)
if not pre_check: