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
| Tag | Accès |
|---|---|
public | Tout utilisateur authentifié |
service:{code} | Service correspondant uniquement |
admin_only | Admins uniquement |
| Passbolt | Jamais 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