plateforme RAG open-source

plateforme RAG open-source : transformer des documents en base de données vectorielle

Tutoriel pas-à-pas PythonIntermédiaire

plateforme RAG open-source : transformer des documents en base de données vectorielle

Les modèles de langage (LLM) échouent systématiquement lorsqu’on les interroge sur des données privées ou récentes non présentes dans leur corpus d’entraînement. Une plateforme RAG open-source résout ce problème en injectant du contexte pertinent directement dans le prompt via une recherche sémantique préalable.

Le mécanisme repose sur la vectorisation de textes et une recherche de proximité dans un espace multidimensionnel. En utilisant des outils comme ChromaDB ou FAISS, on réduit la latence de récupération de l’information de plusieurs ordres de grandeur par rapport à une recherche textuelle classique sur des fichiers texte.

Ce guide détaille la mise en place d’un pipeline complet, de l’ingestion de documents PDF à la génération de réponses par un modèle local via Ollama.

plateforme RAG open-source

🛠️ Prérequis

L’environnement doit être configuré avec les versions suivantes pour garantir la compatibilité des types et des dépendances C-extensions.

  • Python 3.12+ (pour profiter des améliorations de performance de la gestion des types et de l’asyncio)
  • Ollama 0.1.30+ (pour l’exécution locale des LLM)
  • Docker 24.0+ (si utilisation de ChromaDB en conteneur)
  • pip install langchain langchain-community chromadb sentence-transformers pypdf

📚 Comprendre plateforme RAG open-source

Le concept de plateforme RAG open-source repose sur trois piliers mathématiques et informatiques : l’embedding, le chunking et l’indexation vectorielle.

L’Embedding : Transformer un token en un vecteur de dimension $d$ (souvent 768 ou 1536). On utilise la similarité cosinus pour mesurer la distance entre deux vecteurs $A$ et $B$ : $\text{sim}(A, B) = \frac{A \cdot B}{\|A\| \|B\|}$. Si le résultat est proche de 1, les concepts sont sémantiquement proches.

Le Chunking : Découper un document en segments (chunks). Un chunk trop petit perd le contexte ; un chunk trop grand dilue l’information et augmente le coût en tokens. La stratégie standard utilise un recouvrement (overlap) pour maintenir une continuité sémantique entre les segments.

L’Indexation (HNSW) : Pour éviter une recherche linéaire $O(N)$ qui devient prohibitive sur des millions de vecteurs, on utilise des structures comme le Hierarchical Navigable Small World (HNSW). C’est un graphe de couches successives permettant une recherche de type ‘proximity search’ en complexité logarithmique.

Comparaison des approches de recherche :

  • Recherche BM25 (Lexicale) : Basée sur la fréquence des mots. Efficace pour les noms propres, nulle pour la sémantique.
  • Recherche Vectorielle (Dense) : Capture l’intention. Sensible au bruit si le modèle d’embedding est de faible dimension.

🐍 Le code — plateforme RAG open-source

Python
from typing import List, Annotated
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma

class RAGPipeline:
    def __init__(self, model_name: str = "all-MiniLM-L6-v2"):
        # Utilisation de sentence-transformers pour l'embedding local
        self.embeddings = HugmenteEmbeddings(model_name=model_name)
        self.text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=1000,
            chunk_overlap=200,
            separators=["\n\n", "\n", ".", " ", ""]
        )

    def ingest_pdf(self, file_path: str) -> Chroma:
        # Chargement du document PDF
        loader = PyPDFLoader(file_path)
        documents = loader.load()
        
        # Découpage en segments sémantiques
        chunks = self.text_splitter.split_documents(documents)
        
        # Création de la base de données vectorielle en mémoire
        vectorstore = Chroma.from_documents(
            documents=chunks, 
            embedding=self.embeddings
        )
        return vectorstore

📖 Explication

Dans le code_source, l’utilisation de RecursiveCharacterTextSplitter avec une liste de separators est intentionnelle. On descend dans la hiérarchie des délimiteurs pour préserver l’unité sémantique. Si le split sur \n\n échoue à respecter la taille cible, l’algorithme tente le split sur \n, puis sur le point, et enfin sur l’espace.

Attention au piège classique : l’utilisation de Chroma.from_documents sans spécifier un persist_directory. Par défaut, les données sont stockées en RAM. Si votre processus Python s’arrête, votre index vectoriel disparaît. Pour une plateforme RAG open-source pérenne, utilisez toujours un chemin de persistance sur disque.

Sur le plan du typage, l’utilisation de List[str] pour le retour de la recherche permet une manipulation facile des chaînes de caractères lors de la concaténation du prompt final. L’utilisation de async dans code_source_2 prépare l’application à gérer plusieurs requêtes simultanées sans bloquer la boucle d’événements (Event Loop) de Python, ce qui est crucial pour un serveur d’API.

Documentation officielle Python

🔄 Second exemple

Python
from typing import Dict, Any

async def retrieve_context(query: str, vectorstore: Any, k: int = 3) -> List[str]:
    """
    Récupère les k documents les plus pertinents.
    
    Args:
        query: La question posée par l'utilisateur.
        vectorstore: L'instance Chroma ou FAISS.
        k: Nombre de documents à retourner.
    """
    # Recherche de similarité cosinus
    docs = vectorstore.similarity_search(query, k=k)
    
    # Extraction du contenu textuel uniquement
    return [doc.page_content for doc in docs]

▶️ Exemple d’utilisation

Voici comment orchestrer les deux snippets pour effectuer une requête sur un document PDF local.

import asyncio
from my_rag_module import RAGPipeline, retrieve_context

async def main():
    # Initialisation du pipeline
    pipeline = RAGPipeline()
    
    # Ingestion du document
    vectorstore = pipeline.ingest_pdf("rapport_annuel_2023.pdf")
    
    # Question utilisateur
    query = "Quel est le chiffre d'affaires déclaré ?"
    
    # Récupération du contexte
    context_chunks = await retrieve_context(query, vectorstore)
    
    # Construction du prompt final
    prompt = f"Contexte: {chunky_join(context_chunks)}\n\nQuestion: {query}"
    
    print(f"Prompt généré :\n{prompt}")

if __name__ == "__main__":
    asyncio.run(main())
Sortie attendue :
Prompt généré :
Contexte: Le chiffre d'affaires de l'entreprise s'élève à 50M€... [tronqué]

Question: Quel est le chiffre d'affaires déclaré ?

🚀 Cas d’usage avancés

1. Analyse de logs système : Intégrez vos fichiers /var/log/syslog dans la plateforme RAG open-source pour interroger l’historique des erreurs via langage naturel. Exemple : query("Quelles sont les erreurs SSH détectées hier ?").

2. Documentation technique dynamique : Scrappez vos dépôts Git (via GitLoader) pour que le LLM connaisse l’état actuel de votre codebase. Cela permet de poser des questions sur les changements récents de l’API sans réentraînement.

3. Support client automatisé : En injectant vos fichiers Markdown de FAQ, vous créez un agent capable de répondre aux clients avec une précision chirurgicale, en citant les sources exactes du document.

🐛 Erreurs courantes

⚠️ Taille de chunk disproportionnée

Un chunk trop grand sature la fenêtre de contexte du LLM et dilue l’information.

✗ Mauvais

chunk_size=5000
✓ Correct

chunk_size=500

⚠️ Oubli de l'overlap

L’absence de recouvrelement casse la continuité des phrases entre deux segments.

✗ Mauvais

chunk_overlap=0
✓ Correct

chunk_overlap=50

⚠️ Embeddings incompatibles

Utiliser un modèle d’embedding différent de celui utilisé lors de la création de l’index.

✗ Mauvais

embeddings = OpenAIEmbeddings()
✓ Correct

embeddings = HuggingFaceEmbeddings(model_name='all-MiniLM-L6-v2')

⚠️ Fuite de mémoire (RAM)

Charger des milliers de PDF en mémoire sans utiliser de base de données persistante.

✗ Mauvais

vectorstore = Chroma.from_documents(docs, embeddings)
✓ Correct

vectorstore = Chroma.from_documents(docs, embeddings, persist_directory='./db')

✅ Bonnes pratiques

Pour construire une plateforme RAG open-source de niveau production, respectez ces principes :

  • Typage statique : Utilisez mypy ou pyright sur l’ensemble de votre pipeline. La manipulation de vecteurs et de chaînes est propice aux erreurs de type.
  • Gestion des ressources : Utilisez des context managers (with) pour la manipulation des fichiers et des connexions à la base de données vectorielle.
  • Évaluation (RAGAS) : Ne supposez pas que votre RAG fonctionne. Utilisez des frameworks d’évaluation pour mesurer la fidélité (faithfulness) et la pertinence.
  • Modularité : Séparez l’ingestion (ETL) de l’inférence. L’ingestion peut être un job batch hebdomadaire, l’inférence doit être une API temps réel.
  • Observabilité : Loggez systématiquement le nombre de tokens consommés et le temps de latence de la recherche vectorielle.
Points clés

  • Le RAG évite les hallucinations en fournissant des faits vérifiables.
  • Le choix du modèle d'embedding impacte directement la précision de la recherche.
  • Le chunking avec overlap est indispensable pour la cohérence sémantique.
  • L'utilisation de modèles locaux garantit la confidentialité des données.
  • ChromaDB est une solution simple pour un stockage vectoriel local.
  • La recherche vectorielle utilise des algorithmes de type HNSW pour la performance.
  • Le prompt doit contraindre le LLM à n'utiliser que le contexte fourni.
  • L'infrastructure doit être monitorée pour éviter la saturation de la mémoire RAM.

❓ Questions fréquentes

Peut-on utiliser ce système avec des documents très volumineux ?

Oui, mais il faut passer d’un stockage en mémoire à une base de données vectorielle persistante (ChromaDB sur disque ou Qdrant) et utiliser un processus d’ingestion asynchrone.

Pourquoi ne pas simplement envoyer tout le texte dans le prompt ?

Les LLM ont une fenêtre de contexte limitée (ex: 8k ou 128k tokens). Envoyer trop de texte augmente le coût, la latence et finit par perdre le modèle dans les détails.

Est-ce que l'utilisation de modèles locaux est vraiment efficace ?

Avec des modèles comme Llama 3 ou Mistral, la performance est comparable aux API propriétaires pour des tâches de synthèse, avec l’avantage de la gratuité et de la confidentialité.

Comment gérer les images dans mes documents ?

Il faut utiliser des modèles de type ‘Multimodal RAG’ capables d’extraire des descriptions textuelles des images (via un modèle Vision) avant la vectorisation.

📚 Sur le même blog

🔗 Le même sujet sur nos autres blogs

📝 Conclusion

La mise en place d’une plateforme RAG open-source transforme un LLM générique en un expert métier spécialisé sur vos propres données. La clé du succès réside moins dans la puissance du modèle que dans la qualité du pipeline d’ingestion et de la stratégie de découpage des documents. Pour approfondir la gestion des structures de données complexes en Python, consultez la documentation Python officielle. Un index vectoriel mal entretenu est aussi inutile qu’un moteur de recherche sans index.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *