Mécanisme de mise à jour incrémentale des connaissances avec Langchain-Chatchat

Du rechargement complet à l'ajout dynamique

Dans la gestion des connaissances d'entreprise, un problème récurrent est le décalage entre la publication d'un document et sa disponibilité dans l'assistant intelligent. Langchain-Chatchat permet de construire une base de connaissances locale, mais sa vraie valeur réside dans la capacité à intégrer de nouveaux contenus en continu, sans reconstruction totale.

Les approches classiques imposent un vidage complet des index, un traitement intégral des fichiers et une indisponibilité du service pendant la reconstruction. À l'inverse, Langchain-Chatchat implémente une évolution incrémentale : seuls les documents nouveaux ou modifiés sont traités. Le processus repose sur la détection de changements (scan périodique ou surveillance inotify), le calcul d'empreinte (MD5), la comparaison avec la base existante, puis l'ajout des fragments vectorisés dans l'espace vectoriel courant sans altérer les données antérieures.

from pathlib import Path
from langchain_community.document_loaders import DirectoryLoader, PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import FAISS

REPERTOIRE_DOCS = Path("data/docs")
REPERTOIRE_PERSIST = Path("vectorstore/db_faiss")

chargeur = DirectoryLoader(str(REPERTOIRE_DOCS), glob="*.pdf", loader_cls=PyPDFLoader)
docs = chargeur.load()
if not docs:
    print("Aucun nouveau document à traiter.")
else:
    diviseur = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
    morceaux = diviseur.split_documents(docs)
    modele_emb = HuggingFaceEmbeddings(
        model_name="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
    )
    if REPERTOIRE_PERSIST.exists():
        bd_vec = FAISS.load_local(str(REPERTOIRE_PERSIST), modele_emb, allow_dangerous_deserialization=True)
        bd_vec.add_documents(morceaux)
    else:
        bd_vec = FAISS.from_documents(morceaux, modele_emb)
    bd_vec.save_local(str(REPERTOIRE_PERSIST))
    print(f"Base mise à jour : {len(morceaux)} fragments ajoutés.")

L’appel add_documents() ne se limite pas à concaténer les vecteurs. FAISS ajuste la structure en fonction du type d'index : avec IndexIVFFlat, les nouveaux vecteurs sont attribués au centre le plus proche ; avec HNSW, le graphe est étendu dynamiquement. Toutefois, des isnertions fréquentes en petits lots peuvent dégrader les performances de recherche (clusters mal adaptés). Il est conseillé de déclencher périodiquement un ré‑entraînement léger de l'index.

Le paramètre allow_dangerous_deserialization=True doit être réservé aux environnements contrôlés. En production, associez une vérification d'intégrité des fichiers vectoriels.

Pourquoi FAISS est‑il privilégié ?

Langchain-Chatchat choisit FAISS plutôt que Pinecone ou Weaviate pour trois raisons : simplicité de déploiement, souveraineté des données et faible empreinte mémoire. Les PME évitent ainsi un service de base de données supplémentaire. FAISS stocke l'index sous forme de fichier et fonctionne en local, ce qui est idéal pour l’edge computing et les secteurs réglementés.

FAISS n’est pas une base de données à part entière : pas de multi‑tenants natif, ni de contrôle d'accès ou de transactions. Pour gérer plusieurs départements, on peut utiliser des espaces de noms via des répertoires distincts :

chemin_rh = "vectorstore/rh/"
chemin_tech = "vectorstore/tech/"
base_rh = FAISS.load_local(chemin_rh, modele_emb)
base_tech = FAISS.load_local(chemin_tech, modele_emb)

La similarité cosinus donne de meilleurs résultats que la distance L2 pour les plongements textuels. On peut normaliser les vecteurs avant indexation et utiliser IndexFlatIP (produit scalaire) :

import faiss
index = faiss.IndexFlatIP(dimension)
faiss.normalize_L2(vecteurs)       # avant insertion
faiss.normalize_L2(vecteurs_query)  # avant requête

Pour limiter l'utilisation mémoire avec de gros index, activez le mode mmap lors du chargement :

base = FAISS.load_local(chemin_persist, modele_emb, mmap=True)

Cette option permet au système d'exploitation de charger les fragments d'index à la demande, réduisant le temps de démarrage et le pic mémoire.

Cohérence dynamique dans le pipeline RAG

Ajouter un document ne suffit pas : la prochaine requête doit pouvoir retourner ce nouveau contenu. Trois conditions doivent être réunies :

  • L'index est persistant (save_local() effectué)
  • Le cache éventuel du as_retriever() est invalidé
  • Dans une architecture multi‑instances, un événement de mise à jour est diffusé (file de messages)

Dans l'interface Web de Langchain-Chatchat, cliquer sur « Construire la base de connaissances » appelle l'API /reload_kb, qui exécute le pipeline de traitement et recrée le retriever. Ainsi, la requête suivante utilise le dernier index.

Pour améliorer la confiance dans les réponses, on peut activer le retour des documents sources :

chaine_qa = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=retriever,
    return_source_documents=True,
    chain_type_kwargs={"prompt": PROMPT}
)
resultat = chaine_qa.invoke({"query": question})
print("Réponse :", resultat["result"])
for doc in resultat["source_documents"]:
    print(f"Source : {doc.metadata['source']} (page {doc.metadata.get('page', 'N/A')})")

Évitez le dépassement du contexte du LLM en limitant le nombre de fragments retournés (search_kwargs={"k": 3}) ou en utilisant des chaînes de type map‑reduce.

Architecture et bonnes pratiques

Le flux de données dans Langchain-Chatchat est organisé comme suit :

  • Interface utilisateur (Web) ↔ Backend FastAPI
  • Moteur RAG (Chargeur de documents → Découpeur → Modèle d’embedding → Stockage vectoriel)
  • LLM (local ou distant)
  • Répertoire de documents (data/docs/) et base vectorielle (FAISS/Chroma)

Pour les PDF complexes (tableaux, graphiques), envisagez un prétraitement OCR ou une extraction par règles. Le découpage peut être amélioré avec MarkdownHeaderTextSplitter ou HTMLSectionSplitter selon le type de document. Pour le chinois, le modèle BGE (BAAI General Embedding) surpasse Sentence‑BERT en similarité sémantique.

Enrichissez les métadonnées (date, auteur, catégorie) pour permettre des filtres lors de la recherche, par exemple « documents techniques des trois dernières années ».

Faire vivre la base de connaissances

Mettez en place un cycle de vie des documents : expiration automatique, notification des mises à jour importantes, contrôle d'accès par département et sauvegarde régulière des répertoires data/docs/ et vectorstore/. Un pipeline CI/CD peut importer chaque semaine les nouveaux PR, specs et rapports de test. Le lundi matin, un ingénieur peut directement interroger le système sur les changements récents.

Ces mécanismes transforment l'assistant en un collaborateur numérique qui apprend continuellement, sans avoir à reconstruire l'index de zéro.

Étiquettes: langchain faiss rag vecteur ajout-dynamique

Publié le 30 mai à 19h30