Intermédiaire
🧠 Fondamentaux
20 XP
0 personnes ont réussi
Decouper en chunks avec LangChain
Tu as tes documents charges, mais certains font 50 pages. Si tu envoies un document entier au LLM comme contexte, deux problemes : ca coute cher en tokens, et le modele se noie dans trop de texte. Il va rater l'info pertinente, perdue au milieu d'un pave.
La solution : decouper les documents en petits morceaux (chunks). Chaque chunk fait quelques centaines de caracteres. Quand l'utilisateur pose une question, tu cherches les 3 ou 4 chunks les plus pertinents au lieu d'envoyer tout le document. C'est le coeur du RAG : Retrieval Augmented Generation.
LangChain fournit un outil parfait pour ca : le RecursiveCharacterTextSplitter. Il decoupe intelligemment en essayant de couper aux paragraphes d'abord, puis aux phrases, puis aux mots. Ca evite de couper un mot en deux.
Installe LangChain :
pip install langchain
Le splitter fonctionne comme ca :
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50, ) morceaux = splitter.split_text("Un tres long texte...")
Le chunk_overlap, c'est le chevauchement entre les morceaux. Si un chunk finit par "la procedure de conge" et le suivant commence par "de conge necessite un formulaire", le chevauchement garantit qu'on ne perd pas le contexte a la frontiere.
Ecris une fonction decouper_documents(documents, chunk_size=500, chunk_overlap=50) qui prend une liste de documents (au format de l'exercice precedent : dicts avec "contenu" et "source") et renvoie une liste de chunks. Chaque chunk est un dictionnaire avec les cles "texte" (le contenu du morceau), "source" (le nom du fichier d'origine) et "index" (le numero du chunk dans le document, en partant de 0).
Pour que les tests marchent dans le sandbox (sans LangChain), si LangChain n'est pas disponible, decoupe le texte toi-meme en morceaux de chunk_size caracteres avec un chevauchement de chunk_overlap.
docs = [{"contenu": "A" * 1200, "source": "test.txt", "nb_caracteres": 1200}]
chunks = decouper_documents(docs, chunk_size=500, chunk_overlap=0)
assert isinstance(chunks, list), "La fonction doit retourner une liste"
assert len(chunks) >= 2, f"1200 caracteres avec chunk_size=500 doit donner au moins 2 chunks, obtenu {len(chunks)}"
Chaque chunk a les bonnes cles
docs = [{"contenu": "Un texte assez long " * 50, "source": "doc.txt", "nb_caracteres": 950}]
chunks = decouper_documents(docs, chunk_size=200, chunk_overlap=0)
for chunk in chunks:
assert "texte" in chunk, "Chaque chunk doit avoir une cle 'texte'"
assert "source" in chunk, "Chaque chunk doit avoir une cle 'source'"
assert "index" in chunk, "Chaque chunk doit avoir une cle 'index'"
La source est conservee
docs = [
{"contenu": "Texte du premier document " * 30, "source": "premier.txt", "nb_caracteres": 780},
{"contenu": "Texte du second document " * 30, "source": "second.md", "nb_caracteres": 750},
]
chunks = decouper_documents(docs, chunk_size=200, chunk_overlap=0)
sources = set(c["source"] for c in chunks)
assert "premier.txt" in sources, "La source premier.txt doit etre presente"
assert "second.md" in sources, "La source second.md doit etre presente"
Index commence a 0 pour chaque document
docs = [
{"contenu": "A" * 600, "source": "a.txt", "nb_caracteres": 600},
{"contenu": "B" * 600, "source": "b.txt", "nb_caracteres": 600},
]
chunks = decouper_documents(docs, chunk_size=500, chunk_overlap=0)
chunks_a = [c for c in chunks if c["source"] == "a.txt"]
chunks_b = [c for c in chunks if c["source"] == "b.txt"]
assert chunks_a[0]["index"] == 0, "L'index doit commencer a 0 pour chaque document"
assert chunks_b[0]["index"] == 0, "L'index doit recommencer a 0 pour le second document"
+ 0 tests cachés
Indices (3 disponibles)
Solution officielle
def decouper_documents(documents, chunk_size=500, chunk_overlap=50):
try:
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
)
use_langchain = True
except ImportError:
use_langchain = False
tous_les_chunks = []
for doc in documents:
texte = doc["contenu"]
source = doc["source"]
if use_langchain:
morceaux = splitter.split_text(texte)
else:
morceaux = []
debut = 0
while debut < len(texte):
fin = debut + chunk_size
morceaux.append(texte[debut:fin])
debut = fin - chunk_overlap
if debut >= len(texte):
break
if fin >= len(texte):
break
for i, morceau in enumerate(morceaux):
tous_les_chunks.append({
"texte": morceau,
"source": source,
"index": i,
})
return tous_les_chunks