Changement d'architecture demande par l'utilisateur : le dataset d'entrainement (TECH_STEP_TRAINING_DATA) quitte apps/api pour vivre entierement dans services/tech-step-intent-service (intent_service/training_data.py). Ce service est desormais autonome : il s'entraine lui-meme une seule fois, a son propre demarrage (PipelineRegistry.initialize, dans le lifespan FastAPI), sans plus dependre d'un POST /v1/train pousse par apps/api (route supprimee). apps/api ne connait plus aucune technique/synonyme, uniquement le resultat de POST /v1/process. Corpus enrichi avec les 48 techniques du lexique fourni (Arroser, Appertiser, Braiser, Caraméliser, Confire, Julienne/Brunoise/Mirepoix/ Paysanne, Cuire à blanc/au bain-marie/à l'étouffée, Déglacer variantes, Emulsionner, Glacer, Pocher, Réduire, Suer, Zester, etc.), soit 74 techniques au total (26 + 48). Integration complete bout en bout : - reference-seed-data.ts : 48 nouvelles entrees TECH_STEPS - apps/web/locales/fr/translation.json : libelles francais correspondants - "Mitonner" fondu comme synonyme de simmer (pas une technique distincte, sa propre definition le dit) - "Blanchir un oeuf" (whiskPale) distingue de "Blanchir un legume" (blanch, existant) via des synonymes en phrase complete plutot qu'au mot nu — filter_spans (deja en place) resout la collision par specificite Impact performance mesure : le corpus elargi (74 classes vs 26) rend l'entrainement bien plus lent a nombre d'iterations egal (150 iterations depassait 17 minutes par run de test) — reduit a 40 iterations apres mesures repetees en local (~200s/locale, ~400s pour fr+en combines). docker-compose.yml (healthcheck start_period 600s), CI (timeout curl 600s) et le README du service documentent ce nouveau temps de demarrage. CONFIDENCE_THRESHOLD recalibre a 0.2 par verification manuelle (0.75 puis 0.45 ne tenaient plus compte tenu du nombre de classes) — marque explicitement comme placeholder en attendant une vraie repasse de calibrate-tech-step-threshold.ts (necessite Postgres, indisponible dans cet environnement). Verifie : 28/28 tests pytest du service (suite complete re-ecrite pour s'entrainer une seule fois par session sur le vrai corpus, fixture partagee dans conftest.py), lint + build complets du monorepo. La suite Mocha d'apps/api reste a confirmer via CI (le root hook mocha n'attend plus l'entrainement, seulement CI's propre attente sur /health). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
72 lines
3.4 KiB
Python
72 lines
3.4 KiB
Python
"""Détient un `LocalePipeline` par locale supportée — le seul état mutable
|
|
partagé du process (une instance vit pour toute la durée de vie d'`uvicorn`,
|
|
montée sur `app.state`, voir `main.py`).
|
|
|
|
Volontairement une classe "registre" séparée de `LocalePipeline` lui-même :
|
|
`LocalePipeline` ne connaît qu'une seule locale, ce module route `process`
|
|
vers la bonne instance selon le `locale` reçu dans la requête — même
|
|
séparation de responsabilité que `TechStepClassifierService` (une seule
|
|
instance, un seul `NlpManager` multi-langues) avait implicitement via
|
|
node-nlp, explicitée ici puisque spaCy charge un modèle par langue.
|
|
"""
|
|
|
|
import logging
|
|
|
|
from .locale_pipeline import SUPPORTED_LOCALES, LocalePipeline, ProcessResult, TrainEntry
|
|
from .training_data import entries_for_locale
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class PipelineRegistry:
|
|
def __init__(self) -> None:
|
|
self._pipelines: dict[str, LocalePipeline] = {
|
|
locale: LocalePipeline(locale) for locale in SUPPORTED_LOCALES
|
|
}
|
|
|
|
def initialize(self) -> None:
|
|
"""Charge le modèle spaCy de base *et* entraîne chaque locale connue
|
|
depuis `training_data.TECH_STEP_TRAINING_DATA` — appelé une fois au
|
|
démarrage du process (`main.py`'s `lifespan`), avant que `uvicorn`
|
|
n'accepte de requêtes.
|
|
|
|
Contrairement à la version précédente de ce service (où `apps/api`
|
|
poussait le corpus via `POST /v1/train` à son propre warm-up), ce
|
|
service est maintenant entièrement autonome : `apps/api` ne connaît
|
|
plus aucune technique, seulement le résultat de
|
|
`POST /v1/process`. `GET /health` ne répond `200` qu'une fois cette
|
|
méthode terminée (chargement *et* entraînement) — pas seulement le
|
|
chargement — pour que `docker-compose.yml`'s `depends_on: ...
|
|
condition: service_healthy` (et la boucle d'attente équivalente en
|
|
CI) ne laisse jamais `apps/api` démarrer face à un service qui
|
|
répondrait mais ne saurait encore rien détecter.
|
|
"""
|
|
logger.info("tech-step NLP initializing pipelines", extra={"locales": list(self._pipelines)})
|
|
for locale, pipeline in self._pipelines.items():
|
|
pipeline.preload()
|
|
entries = [TrainEntry(**entry) for entry in entries_for_locale(locale)]
|
|
label_count, utterance_count, synonym_count = pipeline.train(entries)
|
|
logger.info(
|
|
"tech-step NLP pipeline trained",
|
|
extra={
|
|
"locale": locale,
|
|
"labelCount": label_count,
|
|
"utteranceCount": utterance_count,
|
|
"synonymCount": synonym_count,
|
|
},
|
|
)
|
|
logger.info("tech-step NLP pipelines ready", extra={"locales": list(self._pipelines)})
|
|
|
|
def process(self, locale: str, text: str) -> ProcessResult:
|
|
pipeline = self._pipelines.get(locale)
|
|
if pipeline is None:
|
|
# Une locale que ce service ne sait structurellement pas
|
|
# charger (pas de modèle spaCy connu) se comporte comme une
|
|
# locale "jamais entraînée" côté `process` — reproduit le test
|
|
# `apps/api` existant ("returns an empty sequence for a locale
|
|
# nothing was trained on"), qui ne distingue pas les deux cas.
|
|
return ProcessResult(entities=[], intent=None, score=0.0)
|
|
return pipeline.process(text)
|
|
|
|
|
|
registry = PipelineRegistry()
|