feat: vérification CLIP avant chaque clic (filet de sécurité app)
Avant la résolution visuelle, compare l'embedding CLIP de l'écran actuel (fenêtre) avec l'embedding de référence (enregistrement). Si similarité < 0.75 → mauvaise application → STOP. CLIP sur fenêtre = insensible au fond d'écran. CLIP ne distingue pas les états fins (texte différent) → le titre de fenêtre reste la vérification principale. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5182,10 +5182,51 @@ def _resolve_target_sync(
|
|||||||
if by_text_strict or by_role:
|
if by_text_strict or by_role:
|
||||||
vlm_description = _build_target_description(target_spec)
|
vlm_description = _build_target_description(target_spec)
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
# Étape -1 : Vérification CLIP (si embedding de référence fourni)
|
||||||
|
# Vérifie qu'on est dans la bonne application avant de chercher
|
||||||
|
# l'élément. Filet de sécurité contre les clics au mauvais endroit.
|
||||||
|
# ---------------------------------------------------------------
|
||||||
|
clip_embedding = target_spec.get("clip_embedding")
|
||||||
|
if clip_embedding:
|
||||||
|
try:
|
||||||
|
from core.embedding.clip_embedder import CLIPEmbedder
|
||||||
|
from PIL import Image as _PILImage
|
||||||
|
import numpy as _np
|
||||||
|
|
||||||
|
_clip = CLIPEmbedder()
|
||||||
|
# Embedding de l'écran actuel (fenêtre si possible)
|
||||||
|
window_capture = target_spec.get("window_capture", {})
|
||||||
|
window_rect = window_capture.get("rect")
|
||||||
|
current_img = _PILImage.open(screenshot_path)
|
||||||
|
if window_rect:
|
||||||
|
current_img = current_img.crop(tuple(window_rect))
|
||||||
|
|
||||||
|
current_emb = _np.array(_clip.embed_image(current_img), dtype=_np.float32).flatten()
|
||||||
|
ref_emb = _np.array(clip_embedding, dtype=_np.float32).flatten()
|
||||||
|
|
||||||
|
clip_sim = float(_np.dot(current_emb, ref_emb) / (
|
||||||
|
_np.linalg.norm(current_emb) * _np.linalg.norm(ref_emb)
|
||||||
|
))
|
||||||
|
logger.info(f"CLIP vérification : similarité={clip_sim:.3f}")
|
||||||
|
|
||||||
|
if clip_sim < 0.75:
|
||||||
|
logger.warning(
|
||||||
|
f"CLIP MISMATCH : sim={clip_sim:.3f} < 0.75 — "
|
||||||
|
f"écran actuel trop différent de l'enregistrement"
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
"resolved": False,
|
||||||
|
"method": "clip_mismatch",
|
||||||
|
"reason": f"clip_similarity_{clip_sim:.3f}",
|
||||||
|
"x_pct": fallback_x_pct,
|
||||||
|
"y_pct": fallback_y_pct,
|
||||||
|
}
|
||||||
|
except Exception as e:
|
||||||
|
logger.debug(f"CLIP vérification erreur (non-bloquant) : {e}")
|
||||||
|
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
# Étape 0 : Choisir la stratégie selon le type d'élément
|
# Étape 0 : Choisir la stratégie selon le type d'élément
|
||||||
# - Texte OCR fiable → grounding VLM (description textuelle)
|
|
||||||
# - Icône sans texte → template matching (crop 80x80)
|
|
||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
by_text_source = target_spec.get("by_text_source", "")
|
by_text_source = target_spec.get("by_text_source", "")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user