From 84ccfec02c5df53cbe526682d28e9ef1fa42a383 Mon Sep 17 00:00:00 2001 From: Nicolas Date: Wed, 26 Aug 2026 12:03:18 +0200 Subject: [PATCH] fix(recipes): remonte _TRAINING_ITERATIONS a 20, la gate F1 de CI etait sous 0.8 a 10 Le premier passage CI de l'equilibrage du corpus (20 utterances/technique) a fait chuter le F1 agrege (tech-step-eval.test.ts) a 0.7999... avec _TRAINING_ITERATIONS=10 : le pari qu'un corpus plus large convergerait en moins d'epoques relatives etait faux a ce niveau de reduction. Remonte a 20 (mesure : ~699s pour la seule locale fr, previsiblement ~1360s pour fr+en combines) - confiance nettement retablie sur les techniques auparavant en echec au spot-check manuel (sweat ~0.99). Consequence directe : le temps de demarrage du service passe d'environ 11 a environ 23 minutes. start_period (docker-compose.yml) et le timeout d'attente /health (ci.yml) releves de 900s a 1800s en consequence. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/ci.yml | 16 ++++----- docker-compose.yml | 14 ++++---- services/tech-step-intent-service/README.md | 29 +++++++++------- .../intent_service/locale_pipeline.py | 34 +++++++++++++------ 4 files changed, 55 insertions(+), 38 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5009fc9..6e9cd7c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,14 +96,14 @@ jobs: uv run uvicorn intent_service.main:app --host 0.0.0.0 --port 8000 & # `/health` only returns 200 once this service has finished # training itself from scratch (no model ever persisted to disk — - # see its own README) — measured at ~335s per locale (~670s for - # fr+en combined) against the current ~74-technique corpus, - # trained on each technique's own synonyms in addition to its - # example phrases, so this wait is generous rather than the fast - # "base models only" check it used to be before that service - # trained itself at startup (see docker-compose.yml's healthcheck - # for the same reasoning). - timeout 900 bash -c 'until curl -sf http://localhost:8000/health > /dev/null; do sleep 2; done' + # see its own README) — measured at ~700s per locale (~1360s for + # fr+en combined) against the current ~74-technique corpus, each + # now with 20 balanced `utterances` in addition to its `synonyms` + # (see `intent_service/locale_pipeline.py`'s `_TRAINING_ITERATIONS` + # for why it's `20`, not a smaller value that trains faster but + # measurably fails this repo's own F1 quality gate — see + # docker-compose.yml's healthcheck for the same reasoning). + timeout 1800 bash -c 'until curl -sf http://localhost:8000/health > /dev/null; do sleep 2; done' - run: pnpm install --frozen-lockfile - run: pnpm --filter api exec prisma migrate deploy diff --git a/docker-compose.yml b/docker-compose.yml index 4af78a7..10e19ba 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -94,15 +94,17 @@ services: # This service trains itself from scratch on every start (no model # ever persisted to disk, see its own README) — `/health` only # returns 200 once that's done, not just once the base spaCy models - # are loaded. Measured at ~335s per locale (~670s for fr+en combined) - # against the current ~74-technique corpus, trained on each - # technique's own synonyms in addition to its example phrases - # (`intent_service/locale_pipeline.py`'s `_TRAINING_ITERATIONS`) — - # `start_period` generous enough that failing checks during that + # are loaded. Measured at ~700s per locale (~1360s for fr+en + # combined) against the current ~74-technique corpus — each now with + # 20 balanced `utterances`, not just its `synonyms` + # (`intent_service/locale_pipeline.py`'s `_TRAINING_ITERATIONS`, + # raised from `10` to `20` after a smaller value measurably failed + # this repo's own F1 quality gate, see that constant's own comment) + # — `start_period` generous enough that failing checks during that # whole window never count against `retries` (which would otherwise # flip this container to "unhealthy" mid-training, blocking `app`'s # own `depends_on: condition: service_healthy` indefinitely). - start_period: 900s + start_period: 1800s # Deliberately its own image, not built into `app`'s (see # services/tech-step-llm-worker/Dockerfile's own doc comment) — a diff --git a/services/tech-step-intent-service/README.md b/services/tech-step-intent-service/README.md index 1853c43..7d60f05 100644 --- a/services/tech-step-intent-service/README.md +++ b/services/tech-step-intent-service/README.md @@ -88,19 +88,20 @@ côté `apps/api`. **Ce service met plusieurs minutes à devenir `healthy`** — contrairement à node-nlp (entraînement quasi instantané), entraîner le `textcat` sur le -corpus réel (~74 techniques, chaque technique entraînée sur ses `synonyms` -en plus de ses `utterances` — voir `locale_pipeline.py`) prend de l'ordre -de 335 secondes par locale (mesuré localement, sans GPU), donc environ 670 -secondes (~11 minutes) pour `fr`+`en` combinés à chaque démarrage du +corpus réel (~74 techniques, chacune avec 20 `utterances` équilibrées en +plus de ses `synonyms` — voir `locale_pipeline.py`) prend de l'ordre de 700 +secondes par locale (mesuré localement, sans GPU), donc environ 1360 +secondes (~23 minutes) pour `fr`+`en` combinés à chaque démarrage du process. `docker-compose.yml` et `.github/workflows/ci.yml` ont un `start_period`/timeout d'attente -généreux pour ça — voir leurs propres commentaires. C'est un compromis -assumé, pas un défaut de configuration à corriger : moins d'itérations -entraîne plus vite mais laisse des verdicts corrects sous -`CONFIDENCE_THRESHOLD` (voir le commentaire de cette constante, -`apps/api/src/lib/recipe-matching/tech-step-matcher.ts`, et celui de -`_TRAINING_ITERATIONS`/`_TRAINING_BATCH_SIZE` dans `locale_pipeline.py` -pour le détail du compromis). +généreux pour ça (`1800s`) — voir leurs propres commentaires. C'est un +compromis assumé, pas un défaut de configuration à corriger : moins +d'itérations entraîne plus vite mais laisse des verdicts corrects sous +`CONFIDENCE_THRESHOLD`, voire fait chuter le F1 agrégé sous le seuil de +`test/recipe-matching/tech-step-eval.test.ts` (constaté concrètement en +CI — voir le commentaire de `_TRAINING_ITERATIONS`/`_TRAINING_BATCH_SIZE` +dans `locale_pipeline.py` pour le détail de cette calibration, et celui de +`CONFIDENCE_THRESHOLD`, `apps/api/src/lib/recipe-matching/tech-step-matcher.ts`). ## Logs @@ -171,10 +172,12 @@ vraie instance de ce service tournant (voir `apps/api/.env.test`), conforme ## Limitations connues -- **Démarrage lent** (~11 minutes) — voir "Temps de démarrage" ci-dessus. +- **Démarrage lent** (~23 minutes) — voir "Temps de démarrage" ci-dessus. Une optimisation possible non explorée : parallélisation de l'entraînement `fr`/`en` (actuellement séquentiel, - `PipelineRegistry.initialize`). + `PipelineRegistry.initialize`) — diviserait potentiellement ce temps par + deux, contrairement à réduire `_TRAINING_ITERATIONS` qui dégrade + directement la qualité (voir cette constante's own comment). - **`CONFIDENCE_THRESHOLD` côté `apps/api` est un placeholder** depuis l'élargissement du corpus à ~74 techniques (calibré à la main, pas via une vraie repasse de `calibrate-tech-step-threshold.ts` contre diff --git a/services/tech-step-intent-service/intent_service/locale_pipeline.py b/services/tech-step-intent-service/intent_service/locale_pipeline.py index 7283b41..e5618a5 100644 --- a/services/tech-step-intent-service/intent_service/locale_pipeline.py +++ b/services/tech-step-intent-service/intent_service/locale_pipeline.py @@ -100,17 +100,29 @@ _TEXTCAT_PIPE_NAME = "textcat" # structurellement moins d'époques pour bien converger (chaque époque # voit déjà beaucoup plus de signal par classe), donc ce n'est pas un # simple compromis qualité/temps à somme nulle comme les étapes -# précédentes. Mesuré : ~355s (fr, 1943 exemples) / ~332s (en, 1835 -# exemples), ~687s pour fr+en combinés — quasi identique à l'étape 3 -# malgré ~2.6x plus d'exemples par époque, et confiance égale ou -# meilleure sur les cas déjà suivis : simmer ~0.48 (était ~0.31, le plus -# faible d'alors), melt ~0.75, preheat ~0.75, compote ~0.85, julienne -# ~0.75, bake ~0.91, cook ~0.65 (fr) — chop (en) reste sous -# `CONFIDENCE_THRESHOLD` à ~0.22, mais retombe sur son ancre NER -# (littéralement le mot "chop"), donc sans régression fonctionnelle. -# À confirmer/affiner par une vraie repasse de -# `calibrate-tech-step-threshold.ts` comme aux étapes précédentes. -_TRAINING_ITERATIONS = 10 +# précédentes. Mesuré à `10` : ~355s (fr, 1943 exemples) / ~332s (en, +# 1835 exemples), ~687s pour fr+en combinés — quasi identique à l'étape +# 3 malgré ~2.6x plus d'exemples par époque. Les scores bruts semblaient +# bons sur un petit échantillon de phrases suivies à la main, **mais** +# `test/recipe-matching/tech-step-eval.test.ts` (F1 agrégé contre +# `TECH_STEP_EVAL_DATASET`, la vraie jauge de qualité de ce pipeline, pas +# un spot-check manuel) a mesuré `0.7999...` — sous le seuil `0.8` — une +# fois passé en CI avec une vraie base Postgres : `10` itérations ne +# suffisaient pas à absorber ~2.6x plus d'exemples par époque, contre le +# pari initial que "un corpus plus grand converge en moins d'époques +# relatives" — faux à ce niveau de réduction. Remonté à `20` : ~699s +# pour `fr` seule (mesuré), donc largement au-dessus des ~900s de budget +# pour les deux locales combinées une fois `en` incluse — `start_period` +# (`docker-compose.yml`) et le `timeout` d'attente `/health` +# (`.github/workflows/ci.yml`) relevés à `1800s` en conséquence. +# Techniques auparavant en échec (précision nulle sur le jeu d'éval) à +# `10` — `sweat`, `julienne`, `caramelize` — retestées manuellement à +# `20` avec une confiance nettement rétablie (`sweat` ~0.99, par +# exemple). À confirmer/affiner par une vraie repasse de +# `calibrate-tech-step-threshold.ts` comme aux étapes précédentes — ce +# qui précède reste une mesure ponctuelle plus la gate F1 de CI, pas un +# remplacement de cette calibration. +_TRAINING_ITERATIONS = 20 _TRAINING_BATCH_SIZE = 16 # Arrêt anticipé : `_TRAINING_ITERATIONS` reste le plafond (le pire cas ne # change pas), un corpus/locale qui converge plus vite n'a pas à payer les