Files
siop2/infra/docker-compose.dokploy.yml
pr-daaif b69c54ed0f feat(r5): recette sans clé API + durcissement production siop2-ai
Recette (mode extractif, aucune clé) — elle a invalidé le modèle R5.1 :
- MiniLM-384 classait la page-réponse DERRIÈRE des passages sans rapport
  (0,24 vs 0,41 sur la question type du plan) → bascule mesurée vers
  paraphrase-multilingual-mpnet-base-v2 (768 d, local/CPU), ADR-004 amendé
  avec le banc comparatif (e5-large écarté : 2,2 Go, scores compressés).
- Migration r5_embeddings_mpnet : pgvector 384 → 768, index vidé
  (re-dérivable par « Réindexer tout »).
- Découpage affiné (~350 caractères) : la phrase-réponse ne se noie plus,
  l'extrait cité est lisible ; seuils par défaut recalés 0,45/0,40/0,55.
- Rejouée après bascule : réponse sourcée p. 2 en tête, refus honnête
  chiffré, suggestions étagées — 16/16 Playwright, 78 API, 23 pytest.
- Revue pixel publiée (6 écrans réels vs maquettes, 3 arbitrages).

Durcissement :
- apps/ai/Dockerfile : uv, modèle ONNX téléchargé AU BUILD (ADR-004 §1),
  non-root, healthcheck ; répétition locale conteneurisée validée
  (healthz, reindex via MinIO/pgvector, 401 sans jeton, refus de boot
  api-sans-clé, réponse sourcée depuis le conteneur).
- Compose Dokploy : siop2-ai interne (jamais sur dokploy-network,
  AI_SERVICE_TOKEN requis, génération opt-in, seuils par env) ;
  siop2-api branché (AI_SERVICE_URL).
- Runbook §5-6 : service IA en production, calibrage des seuils sur le
  corpus client, réindexation post-déploiement.

Le tag release/r5 attend la validation de la revue pixel par le référent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 21:24:33 +01:00

147 lines
5.0 KiB
YAML

# SIOP V2 — compose de PRODUCTION (Dokploy). Voir docs/06-production/runbook-dokploy.md.
# CONVENTION (leçon v1) : tout service/conteneur est préfixé « siop2- » — le
# réseau Dokploy est partagé entre projets, un nom générique collisionne.
# Les secrets (${...}) vivent dans l'environnement du projet Dokploy, jamais ici.
name: siop2
services:
siop2-postgres:
container_name: siop2-postgres
build: ./postgres
image: siop2/postgres:18-pgvector-postgis
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-siop}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?définir POSTGRES_PASSWORD dans Dokploy}
POSTGRES_DB: ${POSTGRES_DB:-siop}
volumes:
- pg-data:/var/lib/postgresql
- ./postgres/init.sql:/docker-entrypoint-initdb.d/10-extensions.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-siop} -d ${POSTGRES_DB:-siop}"]
interval: 5s
timeout: 3s
retries: 10
siop2-redis:
container_name: siop2-redis
image: redis:7.4.9-alpine
restart: unless-stopped
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 10
siop2-minio:
container_name: siop2-minio
image: minio/minio:RELEASE.2025-09-07T16-13-09Z
restart: unless-stopped
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: ${MINIO_ROOT_USER:-siop}
MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD:?définir MINIO_ROOT_PASSWORD dans Dokploy}
volumes:
- minio-data:/data
healthcheck:
test: ["CMD", "mc", "ready", "local"]
interval: 5s
timeout: 3s
retries: 10
# Service IA (R5, ADR-004) : JAMAIS sur dokploy-network — seul siop2-api le
# contacte, avec le secret partagé AI_SERVICE_TOKEN. Le modèle d'embeddings
# est dans l'image (pas de téléchargement au boot). Sans AI_API_KEY, le
# service tourne en mode extractif — pleinement fonctionnel (recette R5).
siop2-ai:
container_name: siop2-ai
build:
context: ../apps/ai
dockerfile: Dockerfile
image: siop2/ai:latest
restart: unless-stopped
environment:
DATABASE_URL: postgresql://${POSTGRES_USER:-siop}:${POSTGRES_PASSWORD}@siop2-postgres:5432/${POSTGRES_DB:-siop}
MINIO_ENDPOINT: siop2-minio
MINIO_PORT: 9000
MINIO_ACCESS_KEY: ${MINIO_ROOT_USER:-siop}
MINIO_SECRET_KEY: ${MINIO_ROOT_PASSWORD}
AI_SERVICE_TOKEN: ${AI_SERVICE_TOKEN:?définir AI_SERVICE_TOKEN dans Dokploy}
# Génération opt-in (ADR-004 §3) : off par défaut ; pour l'activer,
# AI_GENERATION=api + AI_API_KEY (secret Dokploy, jamais dans ce fichier).
AI_GENERATION: ${AI_GENERATION:-off}
AI_API_KEY: ${AI_API_KEY:-}
AI_MODEL: ${AI_MODEL:-claude-opus-4-8}
# Seuils de similarité — à calibrer sur le corpus client (runbook §7)
AI_SEUIL_PERTINENCE: ${AI_SEUIL_PERTINENCE:-0.45}
AI_SEUIL_SUGGESTION: ${AI_SEUIL_SUGGESTION:-0.40}
AI_SEUIL_CONFIANCE_FORTE: ${AI_SEUIL_CONFIANCE_FORTE:-0.55}
depends_on:
siop2-postgres:
condition: service_healthy
siop2-minio:
condition: service_healthy
siop2-api:
container_name: siop2-api
build:
context: ..
dockerfile: apps/api/Dockerfile
image: siop2/api:latest
restart: unless-stopped
environment:
NODE_ENV: production
PORT: 3000
DATABASE_URL: postgresql://${POSTGRES_USER:-siop}:${POSTGRES_PASSWORD}@siop2-postgres:5432/${POSTGRES_DB:-siop}
REDIS_URL: redis://siop2-redis:6379
JWT_SECRET: ${JWT_SECRET:?définir JWT_SECRET dans Dokploy}
MINIO_ENDPOINT: siop2-minio
MINIO_PORT: 9000
MINIO_ACCESS_KEY: ${MINIO_ROOT_USER:-siop}
MINIO_SECRET_KEY: ${MINIO_ROOT_PASSWORD}
# R5 : l'assistant passe par le service interne (503 propre s'il dort)
AI_SERVICE_URL: http://siop2-ai:8000
AI_SERVICE_TOKEN: ${AI_SERVICE_TOKEN}
# ADR-002 — production client : les 3 variables restent ABSENTES.
# Instance de démonstration publique UNIQUEMENT :
# DEMO_MODE=true + DEMO_MODE_I_KNOW=true (double verrou) + SEED_ON_START=true
DEMO_MODE: ${DEMO_MODE:-}
DEMO_MODE_I_KNOW: ${DEMO_MODE_I_KNOW:-}
SEED_ON_START: ${SEED_ON_START:-}
SEED_DEMO_PASSWORD: ${SEED_DEMO_PASSWORD:-}
depends_on:
siop2-postgres:
condition: service_healthy
siop2-redis:
condition: service_healthy
siop2-minio:
condition: service_healthy
siop2-web:
container_name: siop2-web
build:
context: ..
dockerfile: apps/web/Dockerfile
image: siop2/web:latest
restart: unless-stopped
environment:
API_UPSTREAM: siop2-api:3000
depends_on:
- siop2-api
networks:
- default
- dokploy-network
volumes:
pg-data:
redis-data:
minio-data:
networks:
# Réseau du reverse-proxy Dokploy (Traefik) : seul siop2-web y est exposé,
# le domaine pointe sur lui (port 80) ; l'API reste interne (proxy /api).
dokploy-network:
external: true