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,