Implémentation de RAG sur un Fichier PDF
Les documents PDF sont l'un des formats les plus courants pour le partage d'informations structurées. Dans ce cas d'usage, nous allons explorer comment implémenter la technique Naive RAG sur un fichier PDF, en distinguant deux scénarios : les PDF courts (moins de 3 pages) et les PDF longs (plus de 50 pages).
Comparaison des Approches pour PDF Courts et Longs
La stratégie de traitement diffère significativement selon la longueur du document PDF :
Implémentation pour PDF Court (< 3 pages)
Pour les PDF courts, l'approche est simplifiée car nous pouvons traiter le document entier comme une seule unité.
Étape 1: Installation des dépendances
pip install langchain pypdf openai chromadb
Étape 2: Extraction du texte du PDF
import os
from langchain.document_loaders import PyPDFLoader
# Charger le PDF
loader = PyPDFLoader("chemin/vers/document_court.pdf")
documents = loader.load()
# Pour un PDF court, nous pouvons simplement concaténer tout le texte
text_content = " ".join([doc.page_content for doc in documents])
Étape 3: Création de l'embedding et stockage
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
# Initialiser le modèle d'embedding
embeddings = OpenAIEmbeddings()
# Pour un document court, nous pouvons créer un seul document
from langchain.schema import Document
doc = Document(page_content=text_content, metadata={"source": "document_court.pdf"})
# Créer la base vectorielle
vectorstore = Chroma.from_documents([doc], embeddings)
Étape 4: Configuration du modèle de langage
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
# Initialiser le modèle de langage
llm = ChatOpenAI(model_name="gpt-3.5-turbo")
# Créer la chaîne de traitement RAG
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # "stuff" est adapté pour les documents courts
retriever=vectorstore.as_retriever()
)
Étape 5: Interrogation du système
# Poser une question
query = "Quel est le sujet principal de ce document?"
response = qa_chain.run(query)
print(response)
Implémentation pour PDF Long (> 50 pages)
Pour les PDF longs, nous devons adopter une approche de chunking pour diviser le document en segments gérables.
Étape 1: Installation des dépendances
pip install langchain pypdf openai chromadb tiktoken
Étape 2: Extraction et segmentation du texte
import os
from langchain.document_loaders import PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
# Charger le PDF
loader = PyPDFLoader("chemin/vers/document_long.pdf")
documents = loader.load()
# Diviser le texte en chunks
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
length_function=len
)
chunks = text_splitter.split_documents(documents)
Étape 3: Création des embeddings et stockage
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma
# Initialiser le modèle d'embedding
embeddings = OpenAIEmbeddings()
# Créer la base vectorielle avec tous les chunks
vectorstore = Chroma.from_documents(chunks, embeddings)
Étape 4: Configuration du modèle de langage
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA
# Initialiser le modèle de langage
llm = ChatOpenAI(model_name="gpt-3.5-turbo")
# Créer la chaîne de traitement RAG
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="map_reduce", # "map_reduce" est adapté pour les documents longs
retriever=vectorstore.as_retriever(search_kwargs={"k": 5}) # Récupérer les 5 chunks les plus pertinents
)
Étape 5: Interrogation du système
# Poser une question
query = "Résumez les principales conclusions du chapitre 3."
response = qa_chain.run(query)
print(response)
Implémentation dans n8n
Voici comment implémenter un workflow RAG pour PDF dans n8n :
Workflow pour PDF Court
- HTTP Request Node : Pour déclencher le workflow via une API
- Read Binary File Node : Pour lire le fichier PDF
- Code Node : Pour extraire le texte du PDF avec PyPDF
const { PythonShell } = require('python-shell'); const fs = require('fs'); // Écrire le PDF dans un fichier temporaire const pdfBuffer = $input.item.binary.data; fs.writeFileSync('/tmp/temp.pdf', Buffer.from(pdfBuffer, 'base64')); // Script Python pour extraire le texte const pythonScript = ` import pypdf reader = pypdf.PdfReader('/tmp/temp.pdf') text = "" for page in reader.pages: text += page.extract_text() print(text) `; // Exécuter le script Python return new Promise((resolve, reject) => { PythonShell.runString(pythonScript, null, (err, results) => { if (err) reject(err); else resolve([{json: {text: results[0]}}]); }); });
- OpenAI Node : Pour créer l'embedding du document
- Function Node : Pour stocker l'embedding dans une base de données
- OpenAI Node : Pour générer une réponse basée sur la requête et le document
- Respond to Webhook Node : Pour renvoyer la réponse
Workflow pour PDF Long
- HTTP Request Node : Pour déclencher le workflow via une API
- Read Binary File Node : Pour lire le fichier PDF
- Code Node : Pour extraire et segmenter le texte du PDF
const { PythonShell } = require('python-shell'); const fs = require('fs'); // Écrire le PDF dans un fichier temporaire const pdfBuffer = $input.item.binary.data; fs.writeFileSync('/tmp/temp.pdf', Buffer.from(pdfBuffer, 'base64')); // Script Python pour extraire et segmenter le texte const pythonScript = ` import pypdf import json reader = pypdf.PdfReader('/tmp/temp.pdf') chunks = [] # Extraire le texte page par page for i, page in enumerate(reader.pages): text = page.extract_text() # Diviser en paragraphes paragraphs = text.split('\\n\\n') # Créer des chunks de taille raisonnable current_chunk = "" for para in paragraphs: if len(current_chunk) + len(para) < 1000: current_chunk += para + " " else: chunks.append({"text": current_chunk, "page": i+1}) current_chunk = para + " " if current_chunk: chunks.append({"text": current_chunk, "page": i+1}) print(json.dumps(chunks)) `; // Exécuter le script Python return new Promise((resolve, reject) => { PythonShell.runString(pythonScript, null, (err, results) => { if (err) reject(err); else { const chunks = JSON.parse(results[0]); return resolve([{json: {chunks}}]); } }); });
- Split In Batches Node : Pour traiter les chunks par lots
- OpenAI Node : Pour créer les embeddings de chaque chunk
- Function Node : Pour stocker les embeddings dans une base de données
- HTTP Request Node : Pour recevoir la requête de l'utilisateur
- OpenAI Node : Pour créer l'embedding de la requête
- Function Node : Pour rechercher les chunks les plus similaires
- OpenAI Node : Pour générer une réponse basée sur la requête et les chunks pertinents
- Respond to Webhook Node : Pour renvoyer la réponse
Considérations Pratiques
Choix entre les approches
Le choix entre l'approche pour PDF court ou long dépend principalement de :
- La taille du document (nombre de pages, nombre de tokens)
- La limite de contexte du LLM utilisé
- La précision requise dans les réponses
Optimisations possibles
- Extraction de la structure du document (titres, sections) pour améliorer le chunking
- Utilisation des métadonnées (numéros de page, titres de section) pour enrichir le contexte
- Mise en cache des embeddings pour éviter de recalculer pour des documents fréquemment utilisés
- Prétraitement pour éliminer les éléments non pertinents (en-têtes, pieds de page, numéros de page)