Spaces:
Sleeping
feat(sprint-C.1): découple runner.orchestration de OCRLLMPipeline
Browse filesSprint C.1 du plan v2.0 — préparation à la suppression du sous-package
``picarones/pipelines/`` (Sprint D). Le runner legacy reposait sur un
``isinstance(engine, OCRLLMPipeline)`` pour ajouter les
``pipeline_steps`` et le ``prompt_template`` à la metadata du
benchmark. Cette dépendance directe du runner vers
``picarones.pipelines`` est remplacée par un mécanisme polymorphe.
Modifications
-------------
- ``adapters/legacy_engines/base.py::BaseOCREngine`` : nouvel attribut
de classe ``is_pipeline: bool = False`` — un OCR seul n'est pas un
pipeline composé.
- ``pipelines/base.py::OCRLLMPipeline`` :
- Surcharge ``is_pipeline = True`` (marqueur polymorphe).
- Expose deux properties publiques :
- ``pipeline_steps_info`` (substitut public à
``_build_steps_info()``).
- ``prompt_template`` (substitut public à
``_prompt_template``).
L'API privée historique reste disponible pour ne pas casser les
callers internes encore en place ; elle disparaîtra avec
``OCRLLMPipeline`` en Sprint D.
- ``measurements/runner/orchestration.py`` :
- ``isinstance(engine, OCRLLMPipeline)`` →
``getattr(engine, "is_pipeline", False)``.
- ``engine._build_steps_info()`` → ``engine.pipeline_steps_info``.
- ``engine._prompt_template`` → ``engine.prompt_template``.
- Suppression de l'import paresseux ``from picarones.pipelines.base
import OCRLLMPipeline`` dans le runner.
Conséquences
------------
- Le sous-package ``picarones/measurements/runner/`` ne dépend plus
de ``picarones/pipelines/``. Une suite de tests sans
``picarones.pipelines`` installé pourra utiliser le runner legacy
sur des OCR seuls.
- Le runner reste compatible avec ``OCRLLMPipeline`` (qui a
``is_pipeline=True`` et expose les properties publiques) — pas de
régression sur les benchmarks OCR+LLM.
- Sprint D pourra supprimer ``picarones/pipelines/`` en une seule
passe sans toucher au runner.
Callers restants de ``OCRLLMPipeline`` (sortie de session) :
- ``picarones/web/benchmark_utils.py:131`` : instancie directement
``OCRLLMPipeline``. Migration possible quand le runner accepte
des ``PipelineSpec`` directement (Sprint D — runner →
``BenchmarkService``).
- ``picarones/pipelines/_executor_runner.py`` : helper interne
Sprint B, supprimé avec la classe en Sprint D.
- Les tests Sprint 3 et Sprint 15 instancient ``OCRLLMPipeline``
directement — réécrits en Sprint D.
Bilan
-----
- ``pytest tests/`` : 4758 passed, 0 failed.
- ``ruff check`` : clean.
- 3 fichiers modifiés (~25 lignes).
- 1 import legacy supprimé du runner.
https://claude.ai/code/session_011XQZNitg1rCgia8ZD1a2hP
|
@@ -118,6 +118,13 @@ class BaseOCREngine(BaseModule):
|
|
| 118 |
execution_mode: str = "io"
|
| 119 |
"""``"io"`` pour ThreadPoolExecutor (défaut), ``"cpu"`` pour ProcessPoolExecutor."""
|
| 120 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
def __init__(self, config: Optional[dict] = None) -> None:
|
| 122 |
self.config: dict = config or {}
|
| 123 |
# Cache du dernier ``EngineResult`` produit par ``run()`` —
|
|
|
|
| 118 |
execution_mode: str = "io"
|
| 119 |
"""``"io"`` pour ThreadPoolExecutor (défaut), ``"cpu"`` pour ProcessPoolExecutor."""
|
| 120 |
|
| 121 |
+
#: ``True`` ssi l'engine est un pipeline composé (OCR+LLM ou VLM).
|
| 122 |
+
#: Sprint C du plan v2.0 : remplace le check legacy
|
| 123 |
+
#: ``isinstance(engine, OCRLLMPipeline)`` par un attribut polymorphe.
|
| 124 |
+
#: Les sous-classes "pipeline composé" (``OCRLLMPipeline``, et tout
|
| 125 |
+
#: futur composite) surchargent à ``True``.
|
| 126 |
+
is_pipeline: bool = False
|
| 127 |
+
|
| 128 |
def __init__(self, config: Optional[dict] = None) -> None:
|
| 129 |
self.config: dict = config or {}
|
| 130 |
# Cache du dernier ``EngineResult`` produit par ``run()`` —
|
|
@@ -516,13 +516,13 @@ def _build_pipeline_info(engine: BaseOCREngine, doc_results: list[DocumentResult
|
|
| 516 |
"llm_provider": meta.get("llm_provider"),
|
| 517 |
}
|
| 518 |
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
|
| 527 |
over_norm_results = [
|
| 528 |
dr.pipeline_metadata.get("over_normalization")
|
|
|
|
| 516 |
"llm_provider": meta.get("llm_provider"),
|
| 517 |
}
|
| 518 |
|
| 519 |
+
# Sprint C du plan v2.0 : duck typing via ``is_pipeline`` au lieu
|
| 520 |
+
# de ``isinstance(engine, OCRLLMPipeline)``. Découple le runner
|
| 521 |
+
# legacy de la classe ``OCRLLMPipeline`` — préparation à la
|
| 522 |
+
# suppression du sous-package ``picarones.pipelines/`` (Sprint D).
|
| 523 |
+
if getattr(engine, "is_pipeline", False):
|
| 524 |
+
info["pipeline_steps"] = engine.pipeline_steps_info
|
| 525 |
+
info["prompt_template"] = engine.prompt_template
|
| 526 |
|
| 527 |
over_norm_results = [
|
| 528 |
dr.pipeline_metadata.get("over_normalization")
|
|
@@ -130,6 +130,12 @@ class OCRLLMPipeline(BaseOCREngine):
|
|
| 130 |
# Interface BaseOCREngine
|
| 131 |
# ------------------------------------------------------------------
|
| 132 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
@property
|
| 134 |
def name(self) -> str:
|
| 135 |
return self._name
|
|
@@ -138,6 +144,21 @@ class OCRLLMPipeline(BaseOCREngine):
|
|
| 138 |
ocr_v = self.ocr_engine._safe_version() if self.ocr_engine else "—"
|
| 139 |
return f"ocr={ocr_v}; llm={self.llm_adapter.model}"
|
| 140 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 141 |
def _run_llm_step(
|
| 142 |
self, image_path: Path, ocr_text: str,
|
| 143 |
) -> tuple[str, Optional[str]]:
|
|
|
|
| 130 |
# Interface BaseOCREngine
|
| 131 |
# ------------------------------------------------------------------
|
| 132 |
|
| 133 |
+
#: Sprint C du plan v2.0 : marqueur polymorphe que le runner
|
| 134 |
+
#: utilise pour ajouter ``pipeline_steps`` + ``prompt_template``
|
| 135 |
+
#: aux ``EngineReport.pipeline_info`` sans avoir à connaître le
|
| 136 |
+
#: type concret ``OCRLLMPipeline``.
|
| 137 |
+
is_pipeline: bool = True
|
| 138 |
+
|
| 139 |
@property
|
| 140 |
def name(self) -> str:
|
| 141 |
return self._name
|
|
|
|
| 144 |
ocr_v = self.ocr_engine._safe_version() if self.ocr_engine else "—"
|
| 145 |
return f"ocr={ocr_v}; llm={self.llm_adapter.model}"
|
| 146 |
|
| 147 |
+
@property
|
| 148 |
+
def pipeline_steps_info(self) -> list[dict]:
|
| 149 |
+
"""Description structurée des étapes (Sprint C — API publique).
|
| 150 |
+
|
| 151 |
+
Substitut public à ``_build_steps_info()`` pour les callers
|
| 152 |
+
externes (notamment le runner) qui ont besoin de connaître la
|
| 153 |
+
composition de la pipeline pour la metadata du rapport.
|
| 154 |
+
"""
|
| 155 |
+
return self._build_steps_info()
|
| 156 |
+
|
| 157 |
+
@property
|
| 158 |
+
def prompt_template(self) -> str:
|
| 159 |
+
"""Template de prompt courant (Sprint C — API publique)."""
|
| 160 |
+
return self._prompt_template
|
| 161 |
+
|
| 162 |
def _run_llm_step(
|
| 163 |
self, image_path: Path, ocr_text: str,
|
| 164 |
) -> tuple[str, Optional[str]]:
|