ITSkillsCenter
Intelligence Artificielle

Créer un assistant FAQ avec RAG et vos documents PDF sans entraîner de modèle

9 min de lecture

Créer un assistant capable de répondre à des questions à partir de vos propres documents est l’un des cas d’usage les plus utiles de l’intelligence artificielle aujourd’hui. Dans une entreprise, une école, une agence ou même un petit site e-commerce, on retrouve partout le même problème : l’information existe déjà dans des PDF, des guides, des procédures, des catalogues ou des contrats… mais elle reste difficile à retrouver rapidement.

Un collaborateur pose une question. Un client demande une précision. Un responsable cherche une règle interne. Et, bien souvent, quelqu’un doit ouvrir plusieurs documents, faire une recherche approximative, lire manuellement, puis reformuler la réponse. C’est lent, répétitif et source d’erreurs. C’est précisément là que le RAG entre en jeu.

Le RAG, pour Retrieval-Augmented Generation, consiste à retrouver les passages les plus pertinents dans vos documents, puis à les fournir à un modèle de langage pour qu’il génère une réponse plus précise et contextualisée. L’idée importante ici est la suivante : vous n’avez pas besoin d’entraîner un modèle.

Comprendre le principe du RAG sans jargon inutile

Avant de coder, il faut comprendre ce que nous allons construire.

Pourquoi ne pas entraîner un modèle ?

Quand on découvre l’IA générative, on imagine souvent qu’il faut « entraîner son propre modèle » pour lui faire connaître ses documents. En réalité, ce n’est pas nécessaire dans la majorité des cas. Entraîner ou affiner un modèle demande beaucoup de données, du temps, de la puissance de calcul, une vraie stratégie d’évaluation, et un coût non négligeable. Pour une FAQ basée sur des documents, le plus intelligent est souvent de laisser le modèle tel quel et de lui fournir, au moment de la question, les passages les plus utiles.

Le flux logique d’un assistant RAG

Voici le pipeline que nous allons implémenter :

  • Lire les PDF et extraire leur texte
  • Découper le texte en petits blocs (chunks)
  • Transformer chaque bloc en vecteur numérique grâce à un modèle d’embeddings
  • Indexer ces vecteurs dans FAISS
  • Lors d’une question : transformer la question en vecteur, retrouver les blocs les plus proches, donner ces blocs au modèle, produire une réponse avec sources

Les composants que nous allons utiliser

Notre stack sera volontairement simple :

  • PyMuPDF pour lire le contenu des PDF
  • FAISS pour la recherche de similarité
  • Ollama pour les embeddings et la génération locale
  • Streamlit pour créer une petite interface de chat

💡 Conseil : pour un premier projet RAG, commencez avec une architecture locale et lisible. Beaucoup de débutants se perdent en ajoutant trop tôt une base vectorielle distante, plusieurs modèles et des microservices. D’abord, faites simple. Ensuite, améliorez.

Préparer l’environnement de travail

Structure du projet

rag-faq-pdf/
├── app.py
├── build_index.py
├── rag_engine.py
├── pdf_utils.py
├── requirements.txt
├── data/
│   └── docs/
│       ├── guide_produit.pdf
│       └── faq_interne.pdf
└── storage/
    ├── index.faiss
    └── metadata.json

Installer les dépendances Python

pip install pymupdf faiss-cpu numpy requests streamlit

Installer et préparer Ollama

ollama pull gemma3
ollama pull embeddinggemma

Extraire proprement le texte de vos PDF

Le premier défi d’un assistant FAQ n’est pas le modèle. C’est souvent la qualité du texte extrait.

Créer le module d’extraction (pdf_utils.py)

import os
import fitz  # PyMuPDF

def extract_text_from_pdf(pdf_path: str) -> list[dict]:
    doc = fitz.open(pdf_path)
    results = []
    for page_index, page in enumerate(doc):
        text = page.get_text("text", sort=True).strip()
        if not text:
            try:
                text_page = page.get_textpage_ocr()
                text = page.get_text(textpage=text_page).strip()
            except Exception:
                text = ""
        if text:
            results.append({
                "document": os.path.basename(pdf_path),
                "page": page_index + 1,
                "text": text
            })
    return results

💡 Conseil : avant même de parler d’IA, ouvrez 10 extraits issus de vos PDF et lisez-les comme un humain. Si vous trouvez le texte confus, répétitif ou mal ordonné, le modèle le trouvera aussi.

Découper les documents en chunks utiles

Le chunking est une étape sous-estimée. Pourtant, c’est lui qui détermine la qualité de la récupération.

Implémenter un chunker simple

def chunk_text(text: str, chunk_size: int = 900, overlap: int = 150) -> list[str]:
    chunks = []
    start = 0
    text_length = len(text)
    while start < text_length:
        end = start + chunk_size
        chunk = text[start:end].strip()
        if chunk:
            chunks.append(chunk)
        start += chunk_size - overlap
    return chunks

Générer les embeddings et construire l'index vectoriel

Créer le script d'indexation (build_index.py)

import os, json, requests, numpy as np, faiss
from pdf_utils import extract_text_from_pdf, build_chunks_from_pages

OLLAMA_BASE_URL = "http://localhost:11434/api"
EMBED_MODEL = "embeddinggemma"
DOCS_DIR = "data/docs"
INDEX_PATH = "storage/index.faiss"
METADATA_PATH = "storage/metadata.json"

def embed_texts(texts: list[str]) -> np.ndarray:
    response = requests.post(
        f"{OLLAMA_BASE_URL}/embed",
        json={"model": EMBED_MODEL, "input": texts},
        timeout=120
    )
    response.raise_for_status()
    vectors = np.array(response.json()["embeddings"], dtype="float32")
    norms = np.linalg.norm(vectors, axis=1, keepdims=True)
    return vectors / np.clip(norms, 1e-12, None)

def main():
    os.makedirs("storage", exist_ok=True)
    chunks = collect_all_chunks()
    texts = [c["text"] for c in chunks]
    vectors = embed_texts(texts)
    index = faiss.IndexFlatIP(vectors.shape[1])
    index.add(vectors)
    faiss.write_index(index, INDEX_PATH)
    with open(METADATA_PATH, "w", encoding="utf-8") as f:
        json.dump(chunks, f, ensure_ascii=False, indent=2)
    print(f"Index créé avec {len(chunks)} chunks.")

if __name__ == "__main__":
    main()

Interroger l'index et générer une réponse fiable

Créer le moteur RAG (rag_engine.py)

import json, requests, numpy as np, faiss

OLLAMA_BASE_URL = "http://localhost:11434/api"
EMBED_MODEL = "embeddinggemma"
CHAT_MODEL = "gemma3"
INDEX_PATH = "storage/index.faiss"
METADATA_PATH = "storage/metadata.json"

with open(METADATA_PATH, "r", encoding="utf-8") as f:
    METADATA = json.load(f)
INDEX = faiss.read_index(INDEX_PATH)

def retrieve(query: str, top_k: int = 4) -> list[dict]:
    response = requests.post(
        f"{OLLAMA_BASE_URL}/embed",
        json={"model": EMBED_MODEL, "input": query},
        timeout=120
    )
    vector = np.array(response.json()["embeddings"][0], dtype="float32")
    vector = vector / max(np.linalg.norm(vector), 1e-12)
    scores, indices = INDEX.search(vector.reshape(1, -1), top_k)
    return [METADATA[idx] for idx in indices[0] if idx != -1]

def ask(question: str) -> dict:
    contexts = retrieve(question)
    context_text = "

---

".join(
        f"[{c['document']} - page {c['page']}]
{c['text']}"
        for c in contexts
    )
    prompt = f"""Tu es un assistant FAQ. Réponds uniquement à partir du contexte.
Cite les sources (document, page) à la fin.

Question : {question}

Contexte :
{context_text}"""
    
    response = requests.post(
        f"{OLLAMA_BASE_URL}/chat",
        json={"model": CHAT_MODEL, "messages": [{"role": "user", "content": prompt}], "stream": False},
        timeout=180
    )
    return {"answer": response.json()["message"]["content"], "contexts": contexts}

Créer une interface de chat avec Streamlit

import streamlit as st
from rag_engine import ask

st.set_page_config(page_title="Assistant FAQ PDF", page_icon="📄")
st.title("📄 Assistant FAQ basé sur vos PDF")

if "messages" not in st.session_state:
    st.session_state.messages = []

for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

question = st.chat_input("Posez votre question...")
if question:
    st.session_state.messages.append({"role": "user", "content": question})
    with st.chat_message("user"):
        st.markdown(question)
    with st.chat_message("assistant"):
        result = ask(question)
        st.markdown(result["answer"])
        with st.expander("Passages récupérés"):
            for ctx in result["contexts"]:
                st.markdown(f"**{ctx['document']} — page {ctx['page']}**

{ctx['text']}")
    st.session_state.messages.append({"role": "assistant", "content": result["answer"]})

Pour lancer l'interface :

streamlit run app.py

Améliorer la qualité des réponses en production

Ajouter du reranking

Quand votre base grandit, le top 4 récupéré par similarité vectorielle n'est pas toujours optimal. Une technique fréquente consiste à récupérer 10 à 20 chunks candidats, les reranker avec un Cross-Encoder, puis ne garder que les meilleurs.

Gérer les PDF scannés

Tous les PDF ne contiennent pas du texte exploitable. Certains sont en réalité des images. PyMuPDF propose un chemin OCR avec get_textpage_ocr(), mais dans un environnement de production, vous devrez vérifier la présence de Tesseract, la langue OCR et la qualité du scan.

Erreurs fréquentes à éviter

  • Croire que le problème vient toujours du modèle : dans un système RAG, les erreurs viennent souvent de l'extraction de texte ou d'un mauvais chunking
  • Indexer tout sans filtrer : n'indexez pas les pages vides, mentions légales, tables des matières ou pages OCR très bruitées
  • Masquer les sources à l'utilisateur : toujours montrer d'où vient l'information pour maintenir la confiance

💡 Conseil : ne cherchez pas d'abord à rendre votre assistant "plus intelligent". Cherchez d'abord à le rendre plus fiable, plus explicable et plus testable.

Conclusion

Créer un assistant FAQ avec RAG à partir de vos PDF est l'un des meilleurs moyens de rendre l'IA utile immédiatement dans un contexte réel. Vous n'avez pas besoin d'entraîner un modèle. Vous avez surtout besoin de documents exploitables, d'une extraction propre, d'un bon découpage, d'un index vectoriel, d'un prompt discipliné, et d'une interface simple pour poser des questions.

Dans ce tutoriel, nous avons construit une chaîne complète avec PyMuPDF, FAISS, Ollama et Streamlit. Le résultat est une base solide pour créer un assistant support interne, une FAQ produit intelligente, un copilote RH ou un moteur de réponse documentaire alimenté par vos propres ressources.

Envie d'aller plus loin sur l'IA appliquée ? Explorez les autres tutoriels publiés sur ITSkillsCenter.io pour transformer vos connaissances en compétences durables et solutions concrètes sur le terrain.

Besoin d'un site web ?

Confiez-nous la Création de Votre Site Web

Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.

À partir de 350.000 FCFA
Parlons de Votre Projet
Publicité

Articles Similaires