Aller au contenu principal

Architecture RAG

Base de connaissance vectorielle on-premise via pgvector.

Choix technique : pgvector

PostgreSQL est déjà utilisé. L'extension pgvector ajoute le type vector et les index ANN directement dans la base existante.

Avantages : aucun service supplémentaire, transactions ACID, ACL par requête SQL.

Prérequis :

CREATE EXTENSION IF NOT EXISTS vector;

Modèle d'embedding recommandé : nomic-embed-text via Ollama (137M paramètres, rapide, on-premise).

:::warning Règle critique Utiliser le même modèle pour l'indexation et la requête. Un changement de modèle nécessite une réindexation complète. :::


Table thalia_document_chunks

CREATE TABLE thalia_document_chunks (
id BIGSERIAL PRIMARY KEY,
source_type VARCHAR(50) NOT NULL, -- 'bookstack'|'gitlab'|'nextcloud'|'internal'
source_id VARCHAR(255),
source_url TEXT,
title TEXT,
content TEXT NOT NULL,
content_hash VARCHAR(64), -- SHA-256 pour détecter les changements
chunk_index INTEGER,
embedding vector(1536), -- modèle local
acl_tags TEXT[] DEFAULT '{}', -- ['service:elevage', 'public', 'admin_only']
metadata JSONB,
indexed_at TIMESTAMP NOT NULL,
source_updated_at TIMESTAMP,
created_at TIMESTAMP,
updated_at TIMESTAMP
);

-- Index vectoriel (cosine similarity)
CREATE INDEX ON thalia_document_chunks
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);

-- Index texte pour recherche hybride
CREATE INDEX ON thalia_document_chunks USING gin(to_tsvector('french', content));

Pipeline d'ingestion

Source externe (BookStack, GitLab, Nextcloud)

ThaliaIngestionService::ingest(source, sourceId)

1. Récupère le document via ThaliaBridgeService
2. Découpe en chunks (500 tokens, overlap 50)
3. Génère les embeddings (modèle local via AiProviderConfigService)
4. Compare les hashes → ne réindexe que si modifié
5. Upsert dans thalia_document_chunks

Commande Artisan : thalia:index [--source=bookstack] [--full]

:::info Performance La génération d'embeddings est CPU-intensive. Toujours indexer en tâche de fond (runInBackground()), jamais en synchrone sur une requête web. :::


Requête RAG

Question utilisateur

ThaliaRagService::query($question, $aclTags)

1. Embedding de la question (modèle local)
2. Recherche ANN dans pgvector (top-k=5)
+ ACL : WHERE acl_tags && $userAclTags
3. Re-ranking par pertinence texte (hybrid search)
4. Retourne les chunks avec sources

ACL du RAG

TagAccès
publicTout utilisateur authentifié
service:{code}Service correspondant uniquement
admin_onlyAdmins uniquement
PassboltJamais indexé

La vérification se fait en SQL lors de la recherche vectorielle, pas en post-filtrage.


Points de vigilance

Tool injection

Si un document BookStack contient "Ignore tes instructions précédentes...", il sera dans le contexte RAG.

Protections :

  • Filtrage des balises HTML dangereuses à l'ingestion
  • Chunking sans interprétation des instructions
  • Prompt système OpenWebUI explicite sur l'origine du contexte

Taille des chunks

  • 500 tokens avec overlap de 50 : bon point de départ
  • Trop petits → perte de contexte
  • Trop grands → saturation du contexte LLM