feat(r5.1): socle apps/ai — ingestion anonymisée + recherche sémantique

ADR-004 : embeddings locaux sur CPU (fastembed ONNX,
paraphrase-multilingual-MiniLM-L12-v2, 384 dims — les textes du client
ne quittent jamais le serveur), pgvector dans le Postgres existant
(RagChunk possédé par Prisma, migration r5_ia + état de corpus sur
Document), génération opt-in (mode extractif par défaut : la recette
passe sans clé API), service siop2-ai jamais exposé — joint par l'API
NestJS seule (X-Service-Token).

apps/ai (FastAPI + uv) : pipeline PDF MinIO → texte paginé (pypdf) →
anonymisation D4 (e-mails, téléphones marocains, noms connus de la
base, insensible casse/accents — fonction pure testée) → découpage
avec chevauchement (testé) → embeddings → RagChunk localisé (« p. 42 »,
« bilan du 17/07 »). Bilans codés clôturés ingérés. Exclusion de
corpus (D3) appliquée à l'ingestion ET à la lecture.

14 pytest + ruff, embeddeur déterministe en CI (aucun téléchargement),
job CI ai (uv), deploy en dépend. Vérifié en réel avec le vrai modèle :
corpus seedé réindexé en 7 s (PDF réel → 31 extraits paginés + 3
bilans), recherche sémantique concluante, e-mails → ⟨contact⟩,
0 identité dans les chunks (contrôle SQL).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pr-daaif
2026-07-17 12:22:15 +01:00
parent 199fce69d0
commit 837dcba1db
22 changed files with 2564 additions and 4 deletions

29
apps/ai/README.md Normal file
View File

@@ -0,0 +1,29 @@
# @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)
```bash
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`) : `DATABASE_URL`, `MINIO_*`,
`AI_SERVICE_TOKEN`, `AI_EMBEDDINGS=locale|deterministe`, `AI_GENERATION=off|api`.
## 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`).