feat(workflow): variables runtime + extract_text serveur + t2a_decision LLM

Pipeline streaming étendu pour supporter des actions exécutées entièrement
côté serveur (jamais transmises à l'Agent V1) qui produisent des variables
réutilisables dans les steps suivants via templating {{var}} ou {{var.field}}.

== Variables d'exécution ==
- replay_state["variables"] : Dict[str, Any] initialisé vide à la création
- _resolve_runtime_vars() : résout {{var}} et {{var.field}} récursivement
  dans str/dict/list. Variables absentes laissées intactes.
- /replay/next applique la résolution sur l'action AVANT toute interception
  ou envoi à l'Agent V1.

== Boucle d'exécution serveur ==
- _SERVER_SIDE_ACTION_TYPES = {"extract_text", "t2a_decision"}
- /replay/next pop+execute en boucle ces actions jusqu'à trouver une action
  visuelle (à transmettre Agent V1) ou un pause_for_human (qui bloque).
- Latence acceptable : t2a_decision = 5-10s côté serveur, l'Agent V1 attend
  la réponse HTTP.

== Action extract_text ==
- Handler côté serveur réutilisant le dernier heartbeat (max 5s d'âge)
- core/llm/ocr_extractor.py : EasyOCR fr+en singleton + extract_text_from_image
- Stockage dans replay_state["variables"][output_var]
- Robuste : pas de heartbeat → variable = "" + log warning, pipeline continue

== Action t2a_decision ==
- core/llm/t2a_decision.py : refactor de demo_app.py query_model en module
  importable. Prompt expert DIM T2A/PMSI, qwen2.5:7b par défaut (100% bench).
- Handler côté serveur appelle analyze_dpi(input_template_resolved)
- Stockage du JSON décision dans replay_state["variables"][output_var]
- Erreurs (Ollama down, parse) → variable = INDETERMINE + _error, pipeline continue

== VWB UI ==
- types.ts : nouveau type 't2a_decision' (icône 🧠 catégorie logic)
- extract_text refondu : needsAnchor=false, paramètre output_var (au lieu de
  variable_name legacy — bridge accepte les deux pour compat)
- Bridge VWB→core : passthrough des deux types + paramètres préservés

== Tests ==
- tests/integration/test_t2a_extract.py : 25 tests verts
  - templating runtime (8 tests)
  - handler extract_text (3 tests, OCR mocké)
  - handler t2a_decision (3 tests, analyze_dpi mocké)
  - edge → action normalisée (2 tests)
  - bridge VWB → core (5 tests)
  - workflow chain extract→t2a→pause→clic (1 test)

Total branche : 82/82 verts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dom
2026-04-29 22:47:31 +02:00
parent a67d896104
commit 964856ab30
8 changed files with 779 additions and 34 deletions

View File

@@ -57,8 +57,9 @@ VWB_ACTION_TO_CORE = {
"scroll_to_anchor": "scroll",
"visual_condition": "evaluate_condition",
"screenshot_evidence": "screenshot",
"extract_text": "extract_data",
"pause_for_human": "pause_for_human", # passthrough — intercepté par api_stream /replay/next
"extract_text": "extract_text", # passthrough — handler serveur OCR + variable
"pause_for_human": "pause_for_human", # passthrough — intercepté par api_stream /replay/next
"t2a_decision": "t2a_decision", # passthrough — handler serveur LLM T2A + variable
}
@@ -664,6 +665,20 @@ def _vwb_params_to_core(action_type: str, params: Dict[str, Any]) -> Dict[str, A
elif action_type == "pause_for_human":
core_params["message"] = params.get("message", "Validation requise")
elif action_type == "extract_text":
# variable_name côté VWB → output_var côté core (compat avec
# le catalogue VWB existant qui utilise variable_name)
var = params.get("output_var") or params.get("variable_name") or "extracted_text"
core_params["output_var"] = var
if "paragraph" in params:
core_params["paragraph"] = bool(params["paragraph"])
elif action_type == "t2a_decision":
core_params["input_template"] = params.get("input_template", "")
core_params["output_var"] = params.get("output_var", "t2a_result")
if params.get("model"):
core_params["model"] = params["model"]
return core_params