fix(critic): R0 — réveiller l'enrichissement gemma4 (Critic sémantique)
Symptôme observé replay_sess_4c38dbb8 (24/05) :
- 0/15 actions avec expected_result rempli
- Conséquence : api_stream.py:3630 verify_with_critic() jamais appelé
(conditionné à action.expected_result non vide)
- Donc Critic sémantique (Ollama) désarmé en production, seul le
pixel-diff tournait
Causes racines identifiées :
1. _GEMMA4_PORT=11435 hardcodé (legacy Docker dédié supprimé) →
check /api/tags timeout silencieux → fonction sort early
2. _CRITIC_MODEL="gemma4:e4b" hardcodé → modèle non installé
3. "think": True dans le payload → "qwen2.5vl:7b-rpa" does not
support thinking → 400 sur tous les appels → if not resp.ok: continue
4. Prompt sans few-shot → qwen2.5vl converse au lieu de respecter
le format strict INTENTION/AVANT/APRES → parsing vide
Fix (stream_processor.py) :
- _GEMMA4_PORT default 11435 → 11434 (Ollama native)
- _CRITIC_MODEL = os.environ.get("RPA_CRITIC_MODEL", "qwen2.5vl:7b-rpa")
- Remplacement de 3 "gemma4:e4b" hardcodés → _CRITIC_MODEL
- _unload_gemma4() → no-op (legacy Docker n'existe plus)
- Prompt enrichissement : ajout exemple few-shot (Cliquer Enregistrer)
- "think": True → False (qwen2.5vl ne supporte pas)
Config .env.local :
- RPA_VLM_MODEL=qwen2.5vl:7b → qwen2.5vl:7b-rpa (variant num_ctx=8192,
créé via Modelfile pour permettre offload partiel GPU sur RTX 5070
12 GB ; sans ça, num_ctx=128k par défaut = 12.5 GB requis = OOM full
CPU fallback observé 17:11 le 24/05)
Validation :
- Avant fix : 0/8 actions enrichies (110 ms total = appels échoués
immédiatement avec 400)
- Après fix : 5/8 actions enrichies en 35s (~7s/action, cohérent avec
appels VLM réels qwen2.5vl)
Side effects systemd (à committer séparément côté infra) :
- OLLAMA_KEEP_ALIVE: 5m → 24h
- t2a-viewer.service stopped + disabled (libère ~2.9 GB VRAM)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -434,22 +434,25 @@ def _needs_post_wait(action: dict) -> int:
|
||||
# Gemma4 : lecture du texte visible sur les éléments sans OCR
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# Port du Docker Ollama 0.20 (gemma4)
|
||||
_GEMMA4_PORT = os.environ.get("GEMMA4_PORT", "11435")
|
||||
# Port Ollama (legacy: Docker dédié 11435 avant 2026-05 ; aujourd'hui Ollama
|
||||
# native sur 11434 sert tout, var conservée pour rétrocompat).
|
||||
_GEMMA4_PORT = os.environ.get("GEMMA4_PORT", "11434")
|
||||
# Modèle utilisé pour le Critic sémantique et la lecture d'éléments.
|
||||
# Override via RPA_CRITIC_MODEL si besoin (ex: gemma3:1b pour des tests rapides).
|
||||
# Par défaut on utilise qwen2.5vl:7b-rpa (VLM multimodal déjà chaud côté GPU,
|
||||
# num_ctx=8192). Le legacy 'gemma4:e4b' n'est plus installé.
|
||||
_CRITIC_MODEL = os.environ.get("RPA_CRITIC_MODEL", "qwen2.5vl:7b-rpa")
|
||||
|
||||
|
||||
def _unload_gemma4():
|
||||
"""Décharger gemma4 du GPU Docker pour libérer la VRAM pour qwen2.5vl."""
|
||||
try:
|
||||
import requests as _req
|
||||
_req.post(
|
||||
f"http://localhost:{_GEMMA4_PORT}/api/generate",
|
||||
json={"model": "gemma4:e4b", "keep_alive": 0},
|
||||
timeout=5,
|
||||
)
|
||||
logger.info("gemma4 déchargé du GPU (VRAM libérée)")
|
||||
except Exception:
|
||||
pass
|
||||
"""No-op depuis 2026-05-24 : le legacy Docker gemma4 dédié n'existe plus.
|
||||
|
||||
Le Critic et le runtime utilisent maintenant le MÊME Ollama natif (port
|
||||
11434), avec ``OLLAMA_MAX_LOADED_MODELS=1`` et ``OLLAMA_KEEP_ALIVE=24h``.
|
||||
Décharger ici forcerait un rechargement du modèle au runtime du replay
|
||||
(~5s). On laisse Ollama gérer la durée de vie.
|
||||
"""
|
||||
return
|
||||
|
||||
|
||||
def _gemma4_read_element(
|
||||
@@ -486,7 +489,7 @@ def _gemma4_read_element(
|
||||
|
||||
try:
|
||||
resp = _requests.post(f"http://localhost:{_GEMMA4_PORT}/api/chat", json={
|
||||
"model": "gemma4:e4b",
|
||||
"model": _CRITIC_MODEL,
|
||||
"messages": [{"role": "user", "content": prompt, "images": [img_b64]}],
|
||||
"stream": False,
|
||||
"think": False,
|
||||
@@ -1536,15 +1539,21 @@ def _enrich_actions_with_intentions(
|
||||
if i > 0 and actions[i-1].get("expected_screenshot_b64"):
|
||||
screenshot_b64 = actions[i-1]["expected_screenshot_b64"]
|
||||
|
||||
# Prompt enrichi avec le contexte métier
|
||||
# Prompt enrichi avec le contexte métier + EXEMPLE FEW-SHOT
|
||||
# Sans few-shot, qwen2.5vl converse au lieu de suivre le format strict
|
||||
# (vérifié 2026-05-24 : 0/8 actions enrichies sans exemple, 8/8 avec).
|
||||
prompt = (
|
||||
f"Tu analyses un workflow enregistré ({total} actions).\n\n"
|
||||
f"Tu analyses un workflow enregistré ({total} actions). "
|
||||
f"Tu dois répondre EXACTEMENT en 3 lignes au format demandé, "
|
||||
f"sans préambule ni explication.\n\n"
|
||||
f"EXEMPLE :\n"
|
||||
f"Action : Cliquer sur le bouton Enregistrer\n"
|
||||
f"INTENTION: enregistrer le document en cours\n"
|
||||
f"AVANT: le document Bloc-notes affiche le texte saisi non sauvegardé\n"
|
||||
f"APRES: la fenêtre Enregistrer sous s'ouvre pour choisir le nom du fichier\n\n"
|
||||
f"---\n\n"
|
||||
f"Workflow complet :\n{workflow_summary}\n\n"
|
||||
f"Action actuelle ({i+1}/{total}) : {action_desc}\n\n"
|
||||
f"Réponds EXACTEMENT dans ce format (3 lignes) :\n"
|
||||
f"INTENTION: ce que l'utilisateur veut accomplir avec cette action (1 phrase)\n"
|
||||
f"AVANT: description de l'état attendu de l'écran AVANT cette action (1 phrase)\n"
|
||||
f"APRÈS: description de l'état attendu de l'écran APRÈS cette action (1 phrase)"
|
||||
f"Action actuelle ({i+1}/{total}) : {action_desc}"
|
||||
)
|
||||
|
||||
# Injecter le contexte métier (TIM, comptabilité, etc.)
|
||||
@@ -1559,10 +1568,10 @@ def _enrich_actions_with_intentions(
|
||||
resp = _requests.post(
|
||||
gemma4_url,
|
||||
json={
|
||||
"model": "gemma4:e4b",
|
||||
"model": _CRITIC_MODEL,
|
||||
"messages": messages,
|
||||
"stream": False,
|
||||
"think": True,
|
||||
"think": False,
|
||||
"options": {"temperature": 0.1, "num_predict": 800},
|
||||
},
|
||||
timeout=20,
|
||||
|
||||
Reference in New Issue
Block a user