mirror of
https://github.com/siop-spelev/siop2.git
synced 2026-08-08 12:41:54 +00:00
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>
27 lines
1.3 KiB
Python
27 lines
1.3 KiB
Python
from siop_ai.embeddings import DIMENSIONS, EmbeddeurDeterministe
|
|
|
|
|
|
def test_dimensions_et_normalisation():
|
|
[vecteur] = EmbeddeurDeterministe().encoder(["couple de serrage des guides"])
|
|
assert len(vecteur) == DIMENSIONS
|
|
assert abs(sum(v * v for v in vecteur) - 1.0) < 1e-6
|
|
|
|
|
|
def test_stable_et_discriminant():
|
|
embeddeur = EmbeddeurDeterministe()
|
|
a1 = embeddeur.encoder(["couple de serrage des coulisseaux de guides"])[0]
|
|
a2 = embeddeur.encoder(["couple de serrage des coulisseaux de guides"])[0]
|
|
b = embeddeur.encoder(["planning des congés du personnel administratif"])[0]
|
|
cosinus = lambda x, y: sum(p * q for p, q in zip(x, y)) # noqa: E731 — vecteurs normés
|
|
assert a1 == a2
|
|
assert cosinus(a1, b) < 0.35 < cosinus(a1, a1)
|
|
|
|
|
|
def test_textes_proches_plus_similaires_que_textes_eloignes():
|
|
embeddeur = EmbeddeurDeterministe()
|
|
question = embeddeur.encoder(["quel couple de serrage pour les guides ?"])[0]
|
|
notice = embeddeur.encoder(["serrer les coulisseaux de guides au couple de 25 N·m"])[0]
|
|
horsujet = embeddeur.encoder(["le syndic organise une assemblée générale annuelle"])[0]
|
|
cosinus = lambda x, y: sum(p * q for p, q in zip(x, y)) # noqa: E731
|
|
assert cosinus(question, notice) > cosinus(question, horsujet)
|