mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
Demande du référent : la génération opt-in devient réellement configurable. AI_GENERATION=off|api, AI_API_KEY (exigée en mode api — le boot refuse sinon, jamais loguée, /healthz n'expose que le mode), AI_MODEL (défaut claude-opus-4-8). generation.py : interface Generateur — GenerateurExtractif (contrat de base sans LLM) et GenerateurAPI (SDK officiel anthropic, dépendance optionnelle --extra generation, absente des tests/CI). Consigne : citations [n] obligatoires depuis les extraits anonymisés, jamais d'invention, rappel de validation humaine. Tout échec (refus du modèle, quota, réseau) retombe silencieusement sur l'extractif. 5 tests ajoutés (19 pytest) + .env.example. Vérifié en réel : boot refusé api-sans-clé, générateur construit, healthz sans secret. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
48 lines
3.5 KiB
Markdown
48 lines
3.5 KiB
Markdown
# ADR-004 — Modèles et topologie du service IA (R5)
|
|
|
|
- **Statut** : acceptée (R5.1, 17 juillet 2026)
|
|
- **Contexte** : les maquettes R5 et les décisions D1-D5 sont validées (l'IA propose/l'humain
|
|
valide, sourcé ou silencieux, corpus fermé, anonymisation 09-08, voix purgée). Reste à
|
|
trancher COMMENT : quels modèles, où tournent-ils, qui parle à qui. Contraintes : serveur
|
|
Dokploy du partenaire (CPU, pas de GPU garanti), corpus en français, données d'un client
|
|
marocain (loi 09-08), CI sans secret, démo qui marche sans dépendance externe.
|
|
|
|
## Décision
|
|
|
|
1. **Embeddings : locaux, sur CPU** — `fastembed` (ONNX, sans PyTorch) avec
|
|
`sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2` (384 dimensions, multilingue, ~120 Mo, registre fastembed). Les textes du
|
|
client ne quittent JAMAIS le serveur pour l'indexation ni pour la recherche. Le modèle est
|
|
téléchargé au build de l'image (pas au démarrage). En **tests/CI : embeddeur déterministe
|
|
par hachage** (pas de téléchargement, pas de flottement) derrière la même interface.
|
|
2. **Stockage vectoriel : pgvector** dans le PostgreSQL existant (extension déjà installée
|
|
depuis R0) — table `RagChunk` gérée par la **migration Prisma** (le schéma reste la
|
|
propriété d'`apps/api`, source unique). Pas de base vectorielle de plus à opérer.
|
|
3. **Génération (réponses rédigées de l'assistant) : opt-in par configuration.**
|
|
- Sans clé (`AI_GENERATION=off`, défaut) : l'assistant fonctionne en **mode extractif** —
|
|
il montre les meilleurs extraits sourcés et une synthèse templatée, sans LLM. La démo,
|
|
la CI et un déploiement sans budget API restent pleinement fonctionnels et honnêtes.
|
|
- Avec clé (`AI_GENERATION=api` + `AI_API_KEY`, modèle configurable par `AI_MODEL`,
|
|
défaut `claude-opus-4-8`) : rédaction par le SDK officiel Anthropic, sur textes DÉJÀ
|
|
anonymisés (D4), avec l'obligation de citer les extraits fournis — jamais au-delà.
|
|
La config est validée au boot (api sans clé = refus de démarrer) ; la clé n'apparaît
|
|
ni dans les journaux ni au `/healthz` (qui n'expose que le mode). Tout échec du LLM
|
|
(refus, quota, réseau) retombe silencieusement sur le mode extractif.
|
|
- La **suggestion de codes de bilan** n'utilise PAS de LLM : similarité sémantique
|
|
(embeddings locaux) entre la description et les libellés des référentiels + les bilans
|
|
historiques du parc. Déterministe, testable, explicable (« 9 bilans similaires »).
|
|
4. **Topologie** : `apps/ai` (FastAPI/uv, conteneur `siop2-ai`) n'est **jamais exposé** —
|
|
réseau interne Dokploy uniquement, comme MinIO. Les utilisateurs passent par l'API NestJS
|
|
(auth JWT + matrice de permissions réutilisées) qui proxifie vers `siop2-ai`
|
|
(`AI_SERVICE_URL` + secret partagé `AI_SERVICE_TOKEN`). `apps/ai` lit Postgres
|
|
(métadonnées, bilans, écriture des chunks) et MinIO (octets des PDF) en direct sur le
|
|
réseau privé — la règle d'or ESLint d'ADR-001 concerne le code TypeScript de l'API,
|
|
la topologie « MinIO jamais exposé » du runbook reste respectée.
|
|
|
|
## Conséquences
|
|
|
|
- L'anonymisation (D4) s'applique **à l'ingestion** — les index ne contiennent jamais
|
|
d'identités ; le mode génératif n'envoie donc que des textes déjà nettoyés.
|
|
- Le mode extractif est le contrat de base : toute recette R5 doit passer SANS clé API.
|
|
- Si le partenaire veut un jour une génération 100 % locale (llama.cpp…), seul le point 3
|
|
change — interface `Generateur` prévue pour ça.
|