feat(r5.1): clé API de génération configurable (ADR-004 §3 en actes)

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>
This commit is contained in:
pr-daaif
2026-07-17 14:38:36 +01:00
parent d4d73a6f76
commit 76c2ccdfb1
10 changed files with 364 additions and 8 deletions

View File

@@ -21,8 +21,12 @@
- 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`) : rédaction par un LLM externe, sur textes
DÉJÀ anonymisés (D4), avec l'obligation de citer les extraits fournis — jamais au-delà.
- 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 »).

View File

@@ -4,6 +4,22 @@ Trace chronologique des sessions (la plus récente en premier). Le **playbook**
---
## 2026-07-17 — Pr. Daaif (+ Claude) — R5.1+ : clé API de génération configurable (demande du référent)
**Actions**
- **`generation.py`** : l'interface `Generateur` d'ADR-004 §3 prend corps — `GenerateurExtractif` (contrat de base, aucun LLM) et `GenerateurAPI` (SDK officiel `anthropic`, dépendance **optionnelle** `--extra generation`, absente des tests/CI). Consigne système : citations [n] obligatoires depuis les extraits fournis, « n'invente RIEN », rappel de validation humaine. Tout échec (refus du modèle, quota, réseau) **retombe sur l'extractif** — jamais d'erreur utilisateur imputable au LLM ; `stop_reason == "refusal"` traité explicitement.
- **Configuration complète et validée au boot** : `AI_GENERATION=off|api`, `AI_API_KEY` (exigée en mode api — le démarrage refuse sinon, message clair), `AI_MODEL` (défaut `claude-opus-4-8`). `.env.example` posé ; `/healthz` expose le **mode** (off/api), jamais la clé ; le générateur rejoint l'état de l'app (R5.2 le consommera).
- 5 tests ajoutés (19 pytest au total) : défaut extractif, refus de boot api-sans-clé, mode inconnu refusé, clé/modèle lus de l'env, invite pure numérotant les extraits. Vérifié en réel : boot refusé sans clé, `GenerateurAPI` construit avec le SDK, healthz sans secret.
**Décisions**
- La clé vit en variable d'environnement (Dokploy secrets), pas en base ni dans l'UI — cohérent avec `JWT_SECRET` et ADR-002.
**Prochaine étape** : R5.2 — assistant au contrat (proxy NestJS), le mode extractif d'abord, la rédaction branchée sur ce `Generateur`. Restes : recette R4 sur appareil, redéploiement Dokploy de `release/r3`.
---
## 2026-07-17 — Pr. Daaif (+ Claude) — R5.1 : socle `apps/ai` — ingestion anonymisée + recherche sémantique
**Actions**