Retrieval-Augmented Generation (RAG)

Optimiser la qualité de la récupération : stratégies avancées de découpage pour les systèmes RAG

La Génération Augmentée par Récupération (RAG) est devenue l'architecture standard pour ancrer les grands modèles de langage (LLM) dans des données propriétaires. Cependant, la qualité de la génération est indissociable de la qualité de la récupération. Si le système récupère un contexte non pertinent, fragmenté ou incomplet, le modèle risque de produire des hallucinations ou des réponses de mauvaise qualité. La variable la plus critique, mais souvent négligée, dans ce pipeline est le découpage (chunking).

Le découpage est le processus consistant à diviser de grands documents en morceaux plus petits et gérables, qui peuvent être intégrés dans un espace vectoriel. Bien que la division du texte puisse sembler être une étape de prétraitement triviale, il s'agit en réalité d'un équilibre complexe entre densité d'information, cohérence sémantique et contraintes computationnelles. Dans cet article, nous explorons des stratégies avancées de découpage qui vont au-delà des limites de caractères naïves pour créer des fragments contextuellement riches.

Le problème du découpage de taille fixe naïf

L'approche la plus simple de découpage est la division de taille fixe. Cela implique de découper le texte en segments de $N$ caractères (ou jetons) avec un chevauchement facultatif. Bien qu'facile à implémenter, cette méthode souffre d'une fragmentation sémantique sévère.

Considérez un document expliquant une clause juridique complexe. Un fragment de taille fixe pourrait commencer au milieu d'une phrase, se terminer avant la conclusion d'une idée, ou diviser un tableau en deux fragments. Lorsque ces fragments sont intégrés, la représentation vectorielle ne capture qu'un sens partiel. Lors de la récupération, si la requête de l'utilisateur est liée au concept qui traverse la frontière de division, le récupérateur peut échouer à classer les fragments pertinents en haut, car aucun fragment unique ne contient l'unité logique complète.

Division récursive par caractères

Une ligne de base plus robuste est la Division Récursive par Caractères. Au lieu de couper aveuglément par caractères, cette méthode tente de diviser le texte en utilisant des délimiteurs naturels selon une hiérarchie. L'objectif est de conserver les phrases, les paragraphes et les sections intacts autant que possible.

L'algorithme fonctionne généralement en définissant une liste de séparateurs (par exemple, `"\n\n"`, `"\n"`, `" "`) et en divisant récursivement le texte par le premier séparateur. Si les fragments résultants sont toujours plus grands que la taille maximale souhaitée, il passe au séparateur suivant. Cela garantit que les fragments s'alignent sur les limites linguistiques naturelles.

from langchain.text_splitter import RecursiveCharacterTextSplitter

# Définir le découpeur
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    length_function=len,
    is_separator_regex=False,
    separators=["\n\n", "\n", " ", ""]
)

text = """
Section 1 : Introduction
L'histoire de l'IA est longue.

Section 2 : État actuel
Les LLM modernes reposent sur les transformeurs."""

chunks = text_splitter.split_text(text)
# Les fragments résultants respectent les sauts de paragraphe avant de diviser davantage

Découpage sémantique et par fenêtre de phrases

Pour les applications de haut de gamme, le Découpage Sémantique utilise les intégrations (embeddings) pour déterminer où diviser le texte. En calculant la similarité cosinus entre les intégrations de phrases consécutives, l'algorithme identifie les points où le sujet change significativement. Une baisse de similarité indique une frontière entre des concepts distincts, permettant de former des fragments autour de thèmes cohérents.

Une autre approche hybride puissante est le Découpage par Fenêtre de Phrases. Dans cette stratégie, le texte est d'abord divisé en phrases. Cependant, lorsqu'une phrase est sélectionnée comme fragment « principal » pour l'intégration, elle est accompagnée d'une fenêtre de contexte de $K$ phrases précédentes et de $K$ phrases suivantes. Cela fournit à la base de données vectorielle le contexte local nécessaire pour comprendre la phrase principale, tandis que le LLM peut utiliser cette fenêtre pour répondre à des questions nécessitant de légers changements de contexte.

Ajustement du chevauchement et de la granularité

Quelle que soit la stratégie, le chevauchement est un paramètre critique. Le chevauchement garantit que les informations situées aux frontières des fragments ne sont pas perdues. Un chevauchement typique est de 10 à 20 % de la taille du fragment. Un chevauchement trop faible risque de manquer des informations de liaison ; un chevauchement trop important augmente les coûts de stockage et la latence de récupération sans gains significatifs en précision.

De plus, la granularité compte. Les petits fragments (par exemple, 128 à 256 jetons) produisent généralement une récupération plus précise car l'intégration est axée sur un sujet plus étroit. Les grands fragments (par exemple, 512 à 1024 jetons) fournissent plus de contexte mais peuvent diluer la représentation vectorielle. Il est recommandé de faire l'expérience des deux dimensions en utilisant votre jeu de données et votre ensemble de requêtes spécifiques.

Conclusion

Un découpage efficace n'est pas une solution universelle. Il nécessite de comprendre la structure de vos données sources et la nature de vos requêtes utilisateur. Commencez par la division récursive par caractères pour les documents à usage général, mais passez à des stratégies sémantiques ou basées sur les fenêtres pour les textes techniques ou juridiques où la continuité du contexte est primordiale. En traitant le découpage comme un composant central de votre pipeline RAG plutôt que comme une réflexion tardive de prétraitement, vous améliorez considérablement l'intelligence et la fiabilité de vos réponses générées.

Share: