feat: runtime V4 honore resolve_order pré-compilé (zéro VLM au runtime)

Le resolve_engine suit désormais l'ordre de méthodes décidé par l'ExecutionCompiler
au lieu de sa cascade improvisée. C'est la pièce maîtresse du V4 :

- execution_plan_runner.py : ajout de 'resolve_order' dans target_spec
  ["ocr", "template", "vlm"] = stratégies dans l'ordre de préférence

- resolve_engine.py : _resolve_with_precompiled_order() honore l'ordre
  - Court-circuite la cascade legacy quand resolve_order est présent
  - Fallback sur la cascade si toutes les méthodes V4 échouent

- _resolve_by_ocr_text() : résolution OCR directe via docTR (~200ms)
  Chemin rapide V4 — pas de VLM pour les éléments avec texte visible

- 12 nouveaux tests : propagation resolve_order, cascade, fallback, pipeline E2E

220 tests passent (208 existants + 12 nouveaux), 0 régression.

"Le LLM compile. Le runtime exécute."

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dom
2026-04-10 08:28:55 +02:00
parent 2ac781343a
commit f6ad5ff2b2
3 changed files with 554 additions and 2 deletions

View File

@@ -94,8 +94,14 @@ def _strategy_to_target_spec(
- template → anchor_image_base64 (depuis anchor_b64)
- VLM → vlm_description
Règle : la stratégie primaire dicte la méthode préférée, mais on expose
toutes les ancres connues pour que le runtime puisse retomber dessus.
Règle V4 : la stratégie primaire dicte la méthode préférée.
Le champ `resolve_order` liste les méthodes dans l'ordre à essayer.
Le resolve_engine honore cet ordre au lieu de sa cascade par défaut.
resolve_order est la clé du "zéro VLM au runtime" :
- ["ocr", "template", "vlm"] → V4 typique (OCR rapide)
- ["template", "ocr", "vlm"] → apprentissage : template marche mieux
- ["vlm"] → éléments sans texte (icônes)
"""
spec: Dict[str, Any] = {}
@@ -108,6 +114,8 @@ def _strategy_to_target_spec(
by_text_candidate = ""
anchor_candidate = ""
vlm_candidate = ""
resolve_order: List[str] = []
seen_methods: set = set()
for strat in all_strategies:
if not strat:
@@ -122,6 +130,11 @@ def _strategy_to_target_spec(
elif strat.method == "vlm" and strat.vlm_description and not vlm_candidate:
vlm_candidate = strat.vlm_description
# Construire l'ordre des méthodes (dans l'ordre primaire → fallbacks)
if strat.method and strat.method not in seen_methods:
resolve_order.append(strat.method)
seen_methods.add(strat.method)
if by_text_candidate:
spec["by_text"] = by_text_candidate
if anchor_candidate:
@@ -132,6 +145,10 @@ def _strategy_to_target_spec(
# L'intention métier devient le prompt VLM de dernier recours
spec["vlm_description"] = intent
# Ordre de résolution pré-compilé — c'est LA pièce centrale du V4
if resolve_order:
spec["resolve_order"] = resolve_order
return spec