batchCooking/docker-compose.yml
Nicolas 6324a87445
Some checks failed
CI / e2e (push) Waiting to run
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled
CI / intent-service-test (push) Has been cancelled
fix(tech-steps): recule sur 65536/unigramme apres regression F1 en CI
Le premier essai (length=2048 + ngram_size=2) a fait chuter le F1 agrege de
`tech-step-eval.test.ts` de 0.815 a 0.753, sous le plancher de 0.8. Balayage
mesure (fr, 25 iters, ngram=1, jeu de dev = derniere utterance tenue hors
entrainement ; collisions = tokens distincts partageant un bucket, sur 920) :

  length=262144  dev 97.3%  conf 0.809    2/920 collisions   400 s   <- defaut
  length=65536   dev 97.3%  conf 0.809   10/920 collisions   106 s   <- retenu
  length=16384   dev 97.3%  conf 0.808   50/920 collisions    35 s
  length=2048    dev 97.3%  conf 0.816  305/920 collisions    13 s   <- livre puis recule

Deux corrections :

- ngram_size 2 -> 1. Les bigrammes rendent le classifieur nettement plus
  confiant (0.809 -> 0.919), ce qui degrade la justesse bout-en-bout :
  `_classifyClause` (tech-step-matcher.ts) ne retombe sur l'ancre NER que
  sous CONFIDENCE_THRESHOLD, donc une classification confiante mais fausse
  sur une technique voisine (blindBake au lieu de bake, poach au lieu de
  boil) passe le seuil et ecrase l'ancre correcte. C'etait la seule cause
  de la chute du F1.

- length 2048 -> 65536. A 2048, 305/920 tokens entrent en collision (33 %)
  et la confiance moyenne se decale (0.816 vs 0.809). 65536 : 10 collisions,
  confiance identique au bit pres au defaut spaCy, memes verdicts sur les
  probes difficiles. ~5x plus rapide (vs ~30x pour 2048), 15x moins de
  parametres. Descendre plus bas est possible mais reste du travail pour la
  phase ou le jeu d'evaluation aura ete elargi et pourra le valider.

initialize() fr+en (chargement + entrainement) : 208.9 s contre ~930 s.
Suite pytest : 46 passed en 219.8 s (l'essentiel est le fixture qui entraine
le vrai corpus des deux locales, session-scoped).

start_period / timeout d'attente : 300s -> 600s (plafond pour runner lent).

Non verifie ici : le gate F1 lui-meme (tech-step-eval.test.ts, Postgres +
service requis, pas de Docker dans cet environnement) -> CI. Les 6 autres
echecs du job `test` (resetDatabase / seedReferenceData : contrainte unique
sur `key`, timeouts de hook) sont pre-existants sur `main` @ 81f7cb9 et sans
rapport avec ce changement.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-03 23:25:05 +02:00

141 lines
6.6 KiB
YAML

services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
# No defaults on purpose: POSTGRES_USER/PASSWORD/DB must be set in your
# local, git-ignored .env (see .env.example). Compose fails loudly if
# they're missing instead of falling back to a guessable credential.
POSTGRES_USER: ${POSTGRES_USER:?set POSTGRES_USER in .env}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
POSTGRES_DB: ${POSTGRES_DB:?set POSTGRES_DB in .env}
ports:
- "${POSTGRES_PORT:-5432}:5432"
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER"]
interval: 5s
timeout: 5s
retries: 5
# Single service serving both the API and the built frontend (see
# apps/api/Dockerfile) — no separate nginx/web container, no cross-origin
# CORS_ORIGIN to keep in sync between two ports.
app:
build:
context: .
dockerfile: apps/api/Dockerfile
restart: unless-stopped
environment:
NODE_ENV: production
PORT: 3000
# Uses the "postgres" service name, not localhost/POSTGRES_PORT —
# container-to-container traffic stays on the compose network and
# always targets Postgres's internal port (5432).
DATABASE_URL: "postgresql://${POSTGRES_USER:?set POSTGRES_USER in .env}:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}@postgres:5432/${POSTGRES_DB:?set POSTGRES_DB in .env}?schema=public"
JWT_SECRET: ${JWT_SECRET:?set JWT_SECRET in .env}
# Unset by default (falls back to NODE_ENV === "production", i.e.
# Secure cookie required) — set COOKIE_SECURE=false in .env only if
# this deployment is reachable over plain HTTP (no TLS in front of
# it yet), otherwise the session cookie never comes back and every
# authenticated request 401s despite login succeeding. See its doc
# comment in apps/api/src/config/env.ts.
COOKIE_SECURE: ${COOKIE_SECURE:-}
# Shared with the `tech-step-llm-worker` service below — see
# requireInternalWorker's doc comment
# (apps/api/src/middlewares/require-internal-worker.ts). Unset by
# default: `/internal/tech-steps/*` fails closed rather than open
# for a deployment that doesn't run the worker at all.
INTERNAL_WORKER_SECRET: ${INTERNAL_WORKER_SECRET:-}
# Admin surface (apps/web's /admin/* routes + the API's /admin/*).
# Unset by default: `requireAdmin` fails closed without
# ADMIN_JWT_SECRET, so a stack that doesn't need the admin app simply
# has every /admin/* route 401. Must be a *different* secret than
# JWT_SECRET.
ADMIN_JWT_SECRET: ${ADMIN_JWT_SECRET:-}
# Compose network service name, not localhost — same reasoning as
# DATABASE_URL above. Unlike INTERNAL_WORKER_SECRET, no `:-` fallback:
# tech-step-intent-service is a core dependency (see its own entry
# below), not an optional background job.
INTENT_SERVICE_BASE_URL: "http://tech-step-intent-service:8000"
INTENT_SERVICE_SECRET: ${INTENT_SERVICE_SECRET:?set INTENT_SERVICE_SECRET in .env}
ports:
- "${APP_PORT:-3000}:3000"
depends_on:
postgres:
condition: service_healthy
tech-step-intent-service:
condition: service_healthy
# spaCy-based NER + intent classification microservice
# (services/tech-step-intent-service) — `app` delegates all tech-step
# detection to it over HTTP (see `IntentServiceClient`,
# apps/api/src/lib/recipe-matching/intent-service-client.ts). Unlike
# `tech-step-llm-worker` below, **not optional**: without it, `app` can no
# longer detect any cooking technique in a recipe step at all. No exposed
# port — reachable only from `app` on the compose network, nothing ever
# calls into it from outside.
tech-step-intent-service:
build:
context: .
dockerfile: services/tech-step-intent-service/Dockerfile
restart: unless-stopped
environment:
INTENT_SERVICE_SECRET: ${INTENT_SERVICE_SECRET:?set INTENT_SERVICE_SECRET in .env}
healthcheck:
# No curl/wget in the python:3.12-slim base image — a one-line Python
# request is the healthcheck for a service that's already guaranteed
# to have Python (see this service's Dockerfile).
test:
[
"CMD",
"python",
"-c",
"import urllib.request; urllib.request.urlopen('http://localhost:8000/health', timeout=2)",
]
interval: 15s
timeout: 3s
retries: 5
# 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 ~3min combined (fr+en) against the current
# ~74-technique corpus, down from ~930s: the old figure was dominated
# by `spacy.TextCatBOW.v3`'s `length` parameter sitting at its 262144
# default, not by the corpus size (see `_TEXTCAT_MODEL_LENGTH` in
# `locale_pipeline.py` for the measurements).
#
# `start_period` stays deliberately far above that: it only governs
# how long failing checks are excused from counting against `retries`
# (which would otherwise flip this container to "unhealthy"
# mid-training and block `app`'s `depends_on: condition:
# service_healthy` indefinitely), so an over-generous value costs
# nothing while an under-tight one breaks startup on a slow runner.
start_period: 600s
# Deliberately its own image, not built into `app`'s (see
# services/tech-step-llm-worker/Dockerfile's own doc comment) — a
# long-lived process with no exposed port (nothing ever calls *into* it,
# it only ever calls out to `app`). Optional: an `INTERNAL_WORKER_SECRET`-
# less deployment can omit this service entirely and `app` still runs
# fine, just without the offline audit/feedback-loop jobs.
tech-step-llm-worker:
build:
context: .
dockerfile: services/tech-step-llm-worker/Dockerfile
restart: unless-stopped
depends_on:
- app
environment:
API_BASE_URL: "http://app:3000"
INTERNAL_WORKER_SECRET: ${INTERNAL_WORKER_SECRET:?set INTERNAL_WORKER_SECRET in .env to run this service}
TECH_STEP_WORKER_CRON: ${TECH_STEP_WORKER_CRON:-0 3 * * 0}
volumes:
# GGUF weights persist across restarts — see this service's own
# Dockerfile doc comment on its VOLUME declaration.
- tech_step_llm_worker_models:/worker/models
volumes:
postgres_data:
tech_step_llm_worker_models: