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 <noreply@anthropic.com>
This commit is contained in:
Nicolas 2026-08-26 12:03:18 +02:00
parent 0dadadfa24
commit 84ccfec02c
4 changed files with 55 additions and 38 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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