Les systèmes de Génération Augmentée par Récupération (RAG) ne sont bons que dans la mesure des données qu'ils ingèrent. Bien que le traitement de documents texte brut soit relativement simple, la gestion de documents non structurés ou semi-structurés, en particulier les rapports multi-colonnes et les tableaux complexes, reste un goulot d'étranglement majeur. Lorsque le texte s'écoule latéralement entre les colonnes ou lorsque les données tabulaires perdent leur contexte structurel lors de l'analyse, les embeddings qui en résultent deviennent bruités, ce qui entraîne des hallucinations et des résultats de récupération non pertinents.
Le défi des structures de texte non linéaires
Les analyseurs standards lisent souvent les documents de manière linéaire, colonne par colonne ou ligne par ligne. Dans une mise en page multi-colonnes, cette approche entraîne des phrases fragmentées. Par exemple, une phrase peut commencer en bas de la première colonne et se poursuivre en haut de la deuxième. Si elle n'est pas gérée correctement, le moteur de fractionnement peut couper cette phrase en deux, détruisant ainsi son sens sémantique. De plus, les tableaux dans les PDF ou le HTML manquent souvent d'en-têtes de ligne et de colonne explicites qui se répètent sur chaque page, ce qui rend difficile pour un LLM de comprendre le contexte des données sans un prétraitement important.
Stratégie 1 : Fractionnement sémantique avec chevauchement contextuel
Pour remédier aux phrases fragmentées, nous devons aller au-delà du simple fractionnement basé sur les caractères. Le fractionnement sémantique consiste à diviser le texte en fonction des ruptures naturelles, telles que les paragraphes ou les phrases complètes. Lors de la mise en œuvre de cette méthode, il est crucial d'introduire une fenêtre de chevauchement. Cela garantit que même si une phrase est coupée, les fragments suivants conservent le contexte du précédent.
Voici un exemple pratique utilisant Python et LangChain pour implémenter un fractionnement sémantique avec chevauchement :
from langchain.text_splitter import RecursiveCharacterTextSplitter
# Définir un fractionneur qui respecte les limites des phrases
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
length_function=len,
separators=["\n\n", "\n", ". ", " ", ""],
)
# Texte d'entrée provenant d'un rapport multi-colonnes
report_text = """
Les résultats financiers du T3 indiquent une augmentation de 15 % du chiffre d'affaires.
Cependant, les dépenses d'exploitation ont également augmenté en raison de
perturbations de la chaîne d'approvisionnement.
La marge bénéficiaire nette est de 12 %, en baisse par rapport aux 14 % du T2.
"""
chunks = text_splitter.split_text(report_text)
for i, chunk in enumerate(chunks):
print(f"Fragment {i} : {chunk}")
Stratégie 2 : Structuration des tableaux pour l'embedding
Les tableaux sont particulièrement difficiles car leur sens découle de leur structure en grille. Un simple dump textuel d'un tableau le rend illisible. La meilleure approche consiste à convertir les tableaux dans un format semi-structuré, tel que JSON ou HTML, en conservant les en-têtes de ligne et de colonne. Cela permet au modèle d'embedding de comprendre que « Chiffre d'affaires » est associé aux valeurs situées dans la colonne inférieure.
Lors de l'utilisation d'outils comme PyPDF2 ou Tabula-py, extrayez le tableau sous forme de DataFrame, puis convertissez-le en une liste de chaînes de caractères où chaque chaîne représente une ligne avec ses en-têtes :
import pandas as pd
# Supposons que 'df' est un DataFrame pandas extrait d'un tableau PDF
df = pd.DataFrame({
'Catégorie': ['A', 'B'],
'CA T1': [100, 200],
'CA T2': [150, 250]
})
# Convertir chaque ligne en une chaîne riche en contexte
table_contexts = []
for index, row in df.iterrows():
# Créer une chaîne lisible pour le modèle d'embedding
row_string = " | ".join([f"{col} : {val}" for col, val in row.items()])
table_contexts.append(f"Données de la ligne {index} : {row_string}")
print(table_contexts)
# Sortie : ['Données de la ligne 0 : Catégorie : A | CA T1 : 100 | CA T2 : 150', ...]
Stratégie 3 : Prétraitement basé sur le HTML
Si vos documents sources sont en HTML, l'utilisation d'un analyseur robuste comme BeautifulSoup peut aider à maintenir l'intégrité de la mise en page. En analysant l'arbre DOM, vous pouvez réorganiser le contenu pour refléter l'ordre de lecture visuel plutôt que l'ordre DOM, ce qui est critique pour les mises en page multi-colonnes.
from bs4 import BeautifulSoup
import html5lib
# Analyser le HTML pour respecter la structure DOM
soup = BeautifulSoup(html_content, "html5lib")
# Extraire le texte tout en conservant les indices structurels
for div in soup.find_all('div', class_='column'):
# Traiter le contenu des colonnes séquentiellement
print(div.get_text(separator=' ', strip=True))
Conclusion
Gérer les mises en page complexes n'est pas seulement une étape de prétraitement ; c'est un composant essentiel du succès de l'architecture RAG. En mettant en œuvre le fractionnement sémantique, en structurant les données tabulaires dans des formats lisibles et en utilisant des analyseurs HTML robustes, les développeurs peuvent améliorer considérablement la qualité de leurs embeddings. Ces techniques garantissent que le LLM récupère des informations précises et conscientes du contexte, offrant ainsi des réponses plus précises et fiables aux utilisateurs finaux.