Explorer trois stratégies clés pour créer une génération augmentée par récupération (RAG) efficace
Retrieval Augmented Generation (RAG) est une technique utile pour utiliser vos propres données dans un Chatbot alimenté par l’IA. Dans cet article de blog, je vais vous présenter trois stratégies clés pour tirer le meilleur parti du RAG :
Découpage intelligent du texte 📦:
- La première étape consiste à décomposer vos données textuelles en segments significatifs et gérables. Cette étape garantit que votre Vector Database peut récupérer les informations les plus pertinentes rapidement et avec précision.
Itération sur différents modèles d’embedding 🔍:
- Itérer sur le modèle d’embedding est crucial. Le modèle d’embedding détermine la manière dont vos données sont représentées sous forme de vecteurs. Les vecteurs sont la lingua franca de l’IA, améliorant la capacité de la Vector Database à récupérer les bons éléments d’information.
Expérimentation avec différents LLM ou modèles génératifs 🧪:
- Chaque API de modèle de langage (LLM) présente des coûts, des latences et des précisions différents. Les tester vous permet de choisir celle qui fonctionne le mieux pour votre charge de travail.
Plongeons-nous dans le sujet et explorons le fonctionnement de ces stratégies ainsi que la manière d’identifier les configurations les plus performantes pour vos applications RAG réelles à l’aide d’évaluations ! 🚀📚
Découpage intelligent du texte
Le découpage du texte, c’est comme découper une longue histoire en morceaux plus petits et faciles à assimiler afin qu’un ordinateur puisse trouver et utiliser facilement les parties les plus importantes lorsqu’il répond à des questions ou aide à accomplir des tâches.
Ci-dessous, je vais expliquer quelques techniques différentes. Ces techniques sont très bien expliquées en détail dans cet article original de Greg Kamradt.
Découpage récursif du texte par caractères 🔄:
- Découper le texte en segments en fonction du nombre de caractères garantit que chaque morceau reste gérable et cohérent.
Découpage du texte du petit au grand 📏:
- Commencer avec des segments plus grands et les décomposer progressivement en plus petits. Rechercher avec du petit, mais récupérer avec du grand.
Découpage sémantique du texte 🧠:
- Diviser le texte selon le sens afin que chaque segment représente une idée ou un sujet complet, garantissant ainsi la préservation du contexte.
Ces méthodes vous aideront à organiser et à récupérer le texte efficacement pour diverses applications. Plongez-vous dans le sujet pour découvrir le fonctionnement de chaque technique !
Découpage récursif du texte par caractères 🔄
Commencez par découper le texte en segments de taille fixe avec un chevauchement de taille fixe en utilisant le RecursiveCharacterTextSplitter de LangChain.
from langchain.text_splitter import RecursiveCharacterTextSplitter
CHUNK_SIZE = 512
chunk_overlap = np.round(CHUNK_SIZE * 0.10, 0)
print(f"chunk_size: {CHUNK_SIZE}, chunk_overlap: {chunk_overlap}")
# The splitter to use to create smaller (child) chunks.
child_text_splitter = RecursiveCharacterTextSplitter(
chunk_size=CHUNK_SIZE,
chunk_overlap=chunk_overlap,
length_function = len, # use built-in Python len function
separators = ["\n\n", "\n", " ", ". ", ""], # defaults
)
# Child docs directly from raw docs
sub_docs = child_text_splitter.split_documents(docs)
# Inspect chunk lengths
print(f"{len(docs)} docs split into {len(sub_docs)} child documents.")
plot_chunk_lengths(sub_docs, 'Recursive Character')
Image de l’auteur : graphique Matplotlib des longueurs de segments de RecursiveCharacterTextSplitter, code complet disponible sur GitHub.
Découpage du texte du petit au grand 📏
Cette technique effectue la recherche à l’aide de petits fragments (enfants), mais récupère de grands fragments (parents) de texte. Deux magasins mémoire sont utilisés : 1) le stockage des documents et 2) le stockage vectoriel. Le code ci-dessous utilise le MultiVectorRetriever de LangChain.
from langchain_milvus import Milvus
from langchain.text_splitter import RecursiveCharacterTextSplitter
import uuid
from langchain.storage import InMemoryByteStore
from langchain.retrievers.multi_vector import MultiVectorRetriever
# Create doc storage for the parent documents
store = InMemoryByteStore()
id_key = "doc_id"
# Create vectorstore for vector index and retrieval.
COLLECTION_NAME = "MilvusDocs"
vectorstore = Milvus(
collection_name=COLLECTION_NAME,
embedding_function=embed_model,
connection_args={"uri": "./milvus_demo.db"},
auto_id=True,
# Set to True to drop the existing collection if it exists.
drop_old=True,
)
# The MultiVectorRetriever (empty to start)
retriever = MultiVectorRetriever(
vectorstore=vectorstore,
byte_store=store,
id_key=id_key,
)
PARENT_CHUNK_SIZE = 1586
# The splitter to use to create bigger (parent) chunks
parent_text_splitter = RecursiveCharacterTextSplitter(
chunk_size=PARENT_CHUNK_SIZE,
length_function = len, # use built-in Python len function
# separators=["\n\n"], # split at end of paragraphs
)
# Parent docs directly from raw docs
parent_docs = parent_text_splitter.split_documents(docs)
doc_ids = [str(uuid.uuid4()) for _ in parent_docs]
# Inspect chunk lengths
print(f"{len(docs)} docs split into {len(parent_docs)} parent documents.")
plot_chunk_lengths(parent_docs, 'Parent')
CHUNK_SIZE = 512
chunk_overlap = np.round(CHUNK_SIZE * 0.10, 0)
print(f"chunk_size: {CHUNK_SIZE}, chunk_overlap: {chunk_overlap}")
# The splitter to use to create smaller (child) chunks.
child_text_splitter = RecursiveCharacterTextSplitter(
chunk_size=CHUNK_SIZE,
chunk_overlap=chunk_overlap,
length_function = len, # use built-in Python len function
separators = ["\n\n", "\n", " ", ". ", ""], # defaults
)
# Child docs directly from parent docs
sub_docs = child_text_splitter.split_documents(parent_docs)
# Inspect chunk lengths
print(f"{len(docs)} docs split into {len(sub_docs)} child documents.")
plot_chunk_lengths(sub_docs, 'Small-to-big')
chunking petit-à-grand.png
Image de l’autrice : graphique Matplotlib des longueurs de fragments petit-à-grand ; le code complet est disponible sur github.
Découpage sémantique du texte 🧠
Ce découpeur fonctionne en déterminant quand « séparer » les phrases. Il effectue cette tâche en calculant les distances cosinus entre des phrases adjacentes. En examinant toutes ces distances cosinus, il recherche les distances aberrantes au-delà d’un certain seuil. Ces distances aberrantes déterminent quand les fragments sont divisés.
Il existe quelques façons de déterminer ce seuil, qui sont contrôlées par le breakpoint_threshold_type kwarg.
from langchain_experimental.text_splitter import SemanticChunker
semantic_docs = []
for doc in docs:
# Extract and clean document content.
cleaned_content = clean_text(doc.page_content)
# Initialize the SemanticChunker with the embedding model.
text_splitter = SemanticChunker(embed_model)
semantic_list = text_splitter.create_documents([cleaned_content])
# Append the list of semantic chunks to semantic_docs.
semantic_docs.extend(semantic_list)
# Inspect chunk lengths
print(f"Created {len(semantic_docs)} semantic documents from {len(docs)}.")
plot_chunk_lengths(semantic_docs, 'Semantic')
Nous utiliserons la documentation Milvus comme données et Ragas comme méthode d’évaluation pour votre RAG. Lisez mon blog sur la façon d’utiliser RAGAS.
Le résultat était :
- Méthode de découpage = Recursive Character Text Splitter avec top_k=2 était la meilleure.
Différents modèles d’embedding
En fixant la méthode de découpage à Recursive Character Text Splitter avec top_k=2, j’ai essayé deux modèles d’embedding différents.
BAAI/bge-large-en-v1.5
Text-embedding-3-small avec embedding-dim = 512
En utilisant la documentation Milvus et la méthode d’évaluation Ragas, le résultat était :
- Modèle d’embedding = BAAI/bge-large-en-v1.5 était le meilleur.
Différents LLMs
Après avoir fixé la méthode de découpage à Recursive Character Text Splitter avec top_k=2 et le modèle d’embedding à BAAI/bge-large-en-v1.5, j’ai essayé six endpoints d’API LLM différents.
En utilisant la documentation Milvus et la méthode d’évaluation Ragas, le résultat était :
- LLM = MistralAI mixtral_8x7b_instruct utilisant Anyscale Endpoints était le meilleur.
Conclusion
L’évaluation d’un pipeline RAG variera en fonction de vos données particulières et de votre cas d’utilisation. Un enseignement clé tiré de l’expérience personnelle et de la littérature est que les améliorations les plus importantes proviennent souvent du perfectionnement de vos stratégies de retrieval. 🛠️
En utilisant les données de la documentation Milvus et l’évaluation Ragas, ce blog a observé :
35 % d’amélioration en changeant la stratégie de découpage 📦
27 % d’amélioration en changeant le modèle d’embedding 🔍
6 % d’amélioration en changeant le modèle LLM 🤖
Itérer sur ces éléments peut aider à optimiser votre pipeline RAG pour de meilleurs résultats !
Références
Tutoriel de Greg Kamradt sur 5 niveaux différents de découpage de texte : https://github.com/FullStackRetrieval-com/RetrievalTutorials/blob/main/tutorials/LevelsOfTextSplitting/5_Levels_Of_Text_Splitting.ipynb
LangChain Recursive Character Text Splitter : https://python.langchain.com/v0.2/docs/how_to/recursive_text_splitter/
LangChain Multivector Retriever : https://python.langchain.com/v0.2/docs/how_to/multi_vector/#smaller-chunks
LangChain Semantic Chunker : https://python.langchain.com/v0.2/docs/how_to/semantic-chunker/#standard-deviation
Comment utiliser RAGAS pour évaluer votre pipeline RAG : https://medium.com/towards-data-science/rag-evaluation-using-ragas-4645a4c6c477
Continuer à lire

Introducing Business Critical Plan: Enterprise-Grade Security and Compliance for Mission-Critical AI Applications
Discover Zilliz Cloud’s Business Critical Plan—offering advanced security, compliance, and uptime for mission-critical AI and vector database workloads.

Announcing the General Availability of Single Sign-On (SSO) on Zilliz Cloud
SSO is GA on Zilliz Cloud, delivering the enterprise-grade identity management capabilities your teams need to deploy vectorDB with confidence.

Vector Databases vs. Object-Relational Databases
Use a vector database for AI-powered similarity search; use an object-relational database for complex data modeling with both relational integrity and object-oriented features.



