Ajoute un logging JSON structure (meme convention que LoggerService cote apps/api) a services/tech-step-intent-service : chaque appel POST /v1/process journalise locale/texte en entree et entites/intent/score en sortie, chaque POST /v1/train journalise les uid entraines et les compteurs resultants. Chatter interne de spaCy mis a WARNING pour ne pas noyer ces lignes. Bug trouve et corrige en verifiant les octets bruts d'un log reel (pas juste son affichage terminal) : l'encodage par defaut de sys.stdout sur Windows produisait de vrais octets UTF-8 invalides pour tout texte accentue journalise (le francais des etapes de recette) — corrige par sys.stdout.reconfigure(encoding="utf-8") au demarrage. LOG_LEVEL configurable (INFO par defaut), documente dans le README du service et .env.example. Verifie : 30/30 pytest (3 nouveaux tests sur le formateur JSON), smoke test HTTP reel confirmant au niveau des octets que les caracteres accentues sont preserves, lint complet du monorepo. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
54 lines
2.5 KiB
Python
54 lines
2.5 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
|
|
`train`/`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, UnsupportedLocaleError
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class PipelineRegistry:
|
|
def __init__(self) -> None:
|
|
self._pipelines: dict[str, LocalePipeline] = {
|
|
locale: LocalePipeline(locale) for locale in SUPPORTED_LOCALES
|
|
}
|
|
|
|
def preload_all(self) -> None:
|
|
"""Charge le modèle spaCy de base de chaque locale connue — appelé
|
|
une fois au démarrage du process (`main.py`), pas paresseusement au
|
|
premier appel, pour que `GET /health` ne réponde `200` qu'une fois
|
|
ce coût payé (voir `LocalePipeline.preload`)."""
|
|
logger.info("tech-step NLP preloading base pipelines", extra={"locales": list(self._pipelines)})
|
|
for pipeline in self._pipelines.values():
|
|
pipeline.preload()
|
|
logger.info("tech-step NLP base pipelines ready", extra={"locales": list(self._pipelines)})
|
|
|
|
def train(self, locale: str, entries: list[TrainEntry]) -> tuple[int, int, int]:
|
|
pipeline = self._pipelines.get(locale)
|
|
if pipeline is None:
|
|
raise UnsupportedLocaleError(f"Unsupported locale: {locale!r}")
|
|
return pipeline.train(entries)
|
|
|
|
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()
|