From bd100bc538cd0ab6bddd2bca23aa704c25697924 Mon Sep 17 00:00:00 2001 From: Dom Date: Sun, 24 May 2026 17:42:44 +0200 Subject: [PATCH] =?UTF-8?q?fix(critic):=20R0=20=E2=80=94=20r=C3=A9veiller?= =?UTF-8?q?=20l'enrichissement=20gemma4=20(Critic=20s=C3=A9mantique)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- agent_v0/server_v1/stream_processor.py | 55 +++++++++++++++----------- 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/agent_v0/server_v1/stream_processor.py b/agent_v0/server_v1/stream_processor.py index 0e94db832..089ec996a 100644 --- a/agent_v0/server_v1/stream_processor.py +++ b/agent_v0/server_v1/stream_processor.py @@ -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,