Files
siop2/apps/ai
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
..

@siop/ai — service IA (R5)

FastAPI + uv (ADR-001), jamais exposé : seule l'API NestJS le contacte avec X-Service-Token (ADR-004 §4). Maquettes et décisions D1-D5 validées le 17/07/2026.

Lancer (dev)

cd apps/ai
uv sync --extra embeddings      # le vrai modèle ONNX (CPU, ~120 Mo au premier run)
uv run uvicorn siop_ai.app:app --port 8000
# CI / tests : uv sync && uv run pytest  (embeddeur déterministe, aucun téléchargement)

Variables (défauts dev dans config.py, gabarit dans .env.example) : DATABASE_URL, MINIO_*, AI_SERVICE_TOKEN, AI_EMBEDDINGS=locale|deterministe, et la génération opt-in (ADR-004 §3) : AI_GENERATION=off|api, AI_API_KEY (exigée en mode api — le boot refuse sinon, jamais loguée ni exposée), AI_MODEL (défaut claude-opus-4-8). Mode api : uv sync --extra generation (SDK officiel anthropic) ; tout échec du LLM retombe sur le mode extractif.

Ce que porte R5.1 (socle)

  • Ingestion anonymisée (D4) : PDF de la bibliothèque (MinIO) page par page + bilans codés clôturés → anonymisation (e-mails, téléphones, noms connus de la base) → découpage → embeddings locaux → RagChunk (pgvector, schéma Prisma).
  • Recherche sémantique /internal/search : extraits sourcés (document + page ou bilan daté) avec score — la brique de « sourcé ou silencieux » (D2).
  • /internal/reindex idempotent ; l'exclusion de corpus (Document.inCorpus, D3) s'applique à l'ingestion ET à la lecture.

La suite : R5.2 assistant (mode extractif puis génération opt-in) + suggestion de bilan ; R5.3 écrans ; durcissement : Dockerfile + compose Dokploy (siop2-ai).