ITSkillsCenter
Intelligence Artificielle

Mettre en place un chatbot interne capable de citer ses sources pas à pas

9 min de lecture

Dans beaucoup d’entreprises, l’information existe déjà. Elle se trouve dans des guides RH, des procédures qualité, des documents techniques, des PDF commerciaux, des notes de service, des comptes rendus, des FAQ internes ou des documentations produit. Le vrai problème n’est donc pas toujours l’absence de connaissance. Le problème, c’est l’accès rapide à cette connaissance.

Un chatbot interne capable de citer ses sources change fondamentalement la manière dont les équipes interagissent avec l’information. Au lieu de chercher manuellement dans plusieurs dossiers partagés, l’utilisateur pose une question en langage naturel et reçoit une réponse précise, accompagnée des passages qui la justifient.

Ce tutoriel vous guide, pas à pas, dans la construction d’un chatbot documentaire basé sur RAG (Retrieval-Augmented Generation), capable de citer ses sources et déployable localement sans infrastructure complexe.

Comprendre pourquoi la citation des sources est cruciale

Un chatbot qui répond sans dire d’où vient l’information crée un problème de confiance. Dans un contexte professionnel, un collaborateur ou un client ne peut pas vérifier si la réponse est correcte, récente ou bien interprétée. La confiance dans l’outil s’érode rapidement si des erreurs apparaissent sans traçabilité.

Les trois piliers d’un chatbot documentaire fiable

  • La précision : le chatbot ne doit répondre qu’à partir des documents fournis
  • La traçabilité : chaque réponse doit mentionner le ou les documents sources et leur page
  • L’honnêteté : si l’information n’est pas dans les documents, le chatbot doit le dire explicitement

Ces trois piliers sont non négociables dans un déploiement sérieux.

Ce qui distingue un chatbot documentaire d’un chatbot généraliste

Un chatbot généraliste (comme ChatGPT sans contexte) répond à partir de sa mémoire d’entraînement. Il peut être fluide et convaincant, mais il ne connaît pas vos procédures internes, vos tarifs, vos politiques RH ou vos spécifications techniques. Un chatbot documentaire ancré sur vos fichiers répond uniquement à partir de vos données. Il est moins impressionnant dans l’absolu, mais beaucoup plus utile et fiable dans votre contexte précis.

Architecture technique du projet

Notre chatbot interne reposera sur la stack suivante :

  • PyMuPDF : extraction du texte des PDF
  • FAISS : index vectoriel pour la recherche sémantique
  • Ollama : modèles locaux pour les embeddings et le chat
  • Streamlit : interface conversationnelle dans le navigateur

Structure du projet

chatbot-interne/
├── app.py               # Interface Streamlit
├── indexer.py           # Extraction et indexation des documents
├── retriever.py         # Recherche de passages pertinents
├── generator.py         # Génération de réponse avec citations
├── requirements.txt
└── docs/                # Vos documents PDF

Installation des dépendances

pip install pymupdf faiss-cpu numpy requests streamlit

# Installer Ollama et télécharger les modèles
ollama pull gemma3
ollama pull embeddinggemma

💡 Conseil : pour un chatbot documentaire, séparez toujours la fonction « retrouver le bon passage » de la fonction « rédiger la réponse ». Cette séparation évite beaucoup de confusion lors du débogage.

Extraire et préparer les documents

Module d’extraction (indexer.py)

import os, json, fitz, requests, numpy as np, faiss

def extract_chunks_from_pdf(pdf_path: str, chunk_size=800, overlap=100) -> list[dict]:
    doc = fitz.open(pdf_path)
    chunks = []
    for page_num, page in enumerate(doc):
        text = page.get_text("text", sort=True).strip()
        if not text:
            continue
        # Découper en chunks avec chevauchement
        for i in range(0, len(text), chunk_size - overlap):
            chunk = text[i:i + chunk_size].strip()
            if len(chunk) > 100:  # ignorer les chunks trop petits
                chunks.append({
                    "text": chunk,
                    "source": os.path.basename(pdf_path),
                    "page": page_num + 1
                })
    return chunks

def build_index(docs_dir: str):
    all_chunks = []
    for filename in os.listdir(docs_dir):
        if filename.endswith(".pdf"):
            path = os.path.join(docs_dir, filename)
            all_chunks.extend(extract_chunks_from_pdf(path))
    
    # Générer les embeddings
    texts = [c["text"] for c in all_chunks]
    resp = requests.post("http://localhost:11434/api/embed",
        json={"model": "embeddinggemma", "input": texts}, timeout=300)
    vectors = np.array(resp.json()["embeddings"], dtype="float32")
    
    # Normaliser et indexer
    norms = np.linalg.norm(vectors, axis=1, keepdims=True)
    vectors = vectors / np.clip(norms, 1e-12, None)
    
    index = faiss.IndexFlatIP(vectors.shape[1])
    index.add(vectors)
    
    faiss.write_index(index, "storage/index.faiss")
    with open("storage/metadata.json", "w", encoding="utf-8") as f:
        json.dump(all_chunks, f, ensure_ascii=False, indent=2)
    
    print(f"✅ {len(all_chunks)} chunks indexés depuis {docs_dir}")

Construire le moteur de récupération avec citations

Module de retrieval (retriever.py)

import json, requests, numpy as np, faiss

INDEX = faiss.read_index("storage/index.faiss")
with open("storage/metadata.json", encoding="utf-8") as f:
    METADATA = json.load(f)

def retrieve_with_citations(query: str, top_k=4) -> list[dict]:
    """Retrouve les passages les plus pertinents et retourne leurs métadonnées."""
    resp = requests.post("http://localhost:11434/api/embed",
        json={"model": "embeddinggemma", "input": query}, timeout=60)
    vector = np.array(resp.json()["embeddings"][0], dtype="float32")
    vector = vector / max(np.linalg.norm(vector), 1e-12)
    
    scores, indices = INDEX.search(vector.reshape(1, -1), top_k)
    
    results = []
    for score, idx in zip(scores[0], indices[0]):
        if idx >= 0:
            item = METADATA[idx].copy()
            item["relevance_score"] = float(score)
            results.append(item)
    
    return results

Générer des réponses avec citations explicites

Module de génération (generator.py)

import requests
from retriever import retrieve_with_citations

SYSTEM_PROMPT = """Tu es un assistant documentaire interne.
Règles absolues :
- Tu réponds UNIQUEMENT à partir des extraits fournis
- Tu DOIS citer les sources à la fin de chaque réponse sous la forme : [Source: nom_fichier.pdf, page X]
- Si l'information n'est pas dans les extraits, tu réponds : "Je n'ai pas trouvé cette information dans les documents disponibles."
- Tu n'inventes jamais, tu ne complètes jamais avec des connaissances générales"""

def generate_answer_with_citations(question: str) -> dict:
    contexts = retrieve_with_citations(question)
    
    # Construire le contexte avec indicateurs de source
    context_parts = []
    for ctx in contexts:
        context_parts.append(
            f"[{ctx['source']} - Page {ctx['page']}]
{ctx['text']}"
        )
    context_block = "

---

".join(context_parts)
    
    prompt = f"""{SYSTEM_PROMPT}

Question : {question}

Extraits disponibles :
{context_block}

Réponse (avec citations obligatoires) :"""

    resp = requests.post("http://localhost:11434/api/chat", json={
        "model": "gemma3",
        "messages": [{"role": "user", "content": prompt}],
        "stream": False
    }, timeout=120)
    
    answer = resp.json()["message"]["content"]
    
    return {
        "answer": answer,
        "sources": [{"file": c["source"], "page": c["page"], "score": c["relevance_score"]} for c in contexts]
    }

💡 Conseil : le fait d’inclure les citations dans le prompt système comme une « règle absolue » réduit significativement les cas où le modèle oublie de citer ses sources. Testez systématiquement avec des questions auxquelles vous connaissez la réponse.

Construire l’interface conversationnelle Streamlit

Application principale (app.py)

import streamlit as st
from indexer import build_index
from generator import generate_answer_with_citations
import os

st.set_page_config(page_title="Chatbot Documentaire Interne", page_icon="💬", layout="wide")
st.title("💬 Chatbot Interne — Réponses avec sources")

# Sidebar pour l'indexation
with st.sidebar:
    st.header("📂 Documents")
    if st.button("🔄 Réindexer les documents"):
        with st.spinner("Indexation en cours..."):
            os.makedirs("storage", exist_ok=True)
            build_index("docs")
            st.success("Indexation terminée !")
    
    st.markdown("---")
    st.caption("Déposez vos PDF dans le dossier 'docs/' puis réindexez.")

# Zone de conversation
if "messages" not in st.session_state:
    st.session_state.messages = []

for msg in st.session_state.messages:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])
        if msg.get("sources"):
            with st.expander("📎 Sources utilisées"):
                for s in msg["sources"]:
                    st.markdown(f"- **{s['file']}** — Page {s['page']} (pertinence: {s['score']:.3f})")

question = st.chat_input("Posez votre question sur les documents internes...")

if question:
    st.session_state.messages.append({"role": "user", "content": question})
    with st.chat_message("user"):
        st.markdown(question)
    
    with st.chat_message("assistant"):
        with st.spinner("Recherche dans les documents..."):
            result = generate_answer_with_citations(question)
        
        st.markdown(result["answer"])
        
        with st.expander("📎 Sources utilisées"):
            for s in result["sources"]:
                st.markdown(f"- **{s['file']}** — Page {s['page']} (pertinence: {s['score']:.3f})")
    
    st.session_state.messages.append({
        "role": "assistant",
        "content": result["answer"],
        "sources": result["sources"]
    })
# Lancer l'application
streamlit run app.py

Améliorer la fiabilité des citations

Technique 1 : validation automatique des citations

Après génération, vous pouvez vérifier programmatiquement que la réponse contient bien une référence de source :

import re

def validate_citations(answer: str, expected_sources: list) -> bool:
    """Vérifie que la réponse contient au moins une citation valide."""
    citation_pattern = r'[Source:.*?]|[.*?.pdf.*?]'
    citations_found = re.findall(citation_pattern, answer)
    return len(citations_found) > 0

Technique 2 : mode strict vs mode flexible

Proposez deux modes à l’utilisateur :

  • Mode strict : répond uniquement si un passage très pertinent est trouvé (score > 0.7)
  • Mode flexible : répond même avec des passages moyennement pertinents en indiquant l’incertitude

Technique 3 : historique des questions sans réponse

Journalisez les questions pour lesquelles le chatbot répond « Je n’ai pas trouvé ». Ces questions révèlent les lacunes de votre base documentaire et permettent de prioriser les documents à ajouter.

Déployer dans un environnement professionnel

Points de vigilance en production

  • Confidentialité : assurez-vous que votre modèle Ollama tourne en local et que rien n’est envoyé vers l’extérieur
  • Mise à jour des documents : planifiez une réindexation régulière quand les documents changent
  • Gestion des accès : si différents utilisateurs ne doivent pas voir tous les documents, gérez la segmentation au niveau de l’index

💡 Conseil : en entreprise, la valeur d’un chatbot documentaire ne se mesure pas à sa sophistication technique, mais à la qualité et à l’actualité des documents qu’il consulte. Un bon contenu vaut mieux qu’un bon algorithme.

Conclusion

Mettre en place un chatbot interne capable de citer ses sources est aujourd’hui à la portée de n’importe quelle équipe technique, sans infrastructure cloud coûteuse. En combinant PyMuPDF pour l’extraction, FAISS pour la recherche vectorielle, Ollama pour les modèles locaux et Streamlit pour l’interface, vous obtenez un outil professionnel, traçable et maintenu en interne.

La force de cette approche réside dans sa rigueur : chaque réponse est ancrée dans vos documents, chaque source est citée, et le système refuse d’inventer ce qu’il ne sait pas. C’est exactement ce qu’on attend d’un assistant documentaire en contexte professionnel.

Prêt à aller plus loin ? Consultez les autres tutoriels sur ITSkillsCenter.io pour approfondir vos compétences en IA appliquée, automatisation et développement d’outils concrets pour les professionnels francophones.

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