Fragmentation de texte avec chevauchement pour les pipelines RAG

30 juin 2026

Fragmentation de texte avec chevauchement pour les pipelines RAG

Dans un pipeline de RAG (Retrieval-Augmented Generation), la première étape est presque toujours la même: prendre un texte volumineux et le découper en morceaux avant de le vectoriser. Les morceaux ne peuvent pas être trop grands, car le modèle a une limite de contexte, ni trop petits, car alors l embedding perdrait sa sémantique. Et ils doivent présenter une superposition entre voisins, sinon une réponse qui tombe juste sur la frontière peut se trouver comprimée entre deux chunks et le retriever peut se tromper.

Le chunker doit :

Le dernier point est trivial à partir du Go 1.23 avec le paquet iter. La fonction retourne une iter.Seq[string] et le consommateur itère comme n’importe quoi :

for chunk := range Chunker(text, 60, 15) {
    // embed, index, send to the LLM
}

La fenêtre avance en runes, pas en octets.

Texte avec caractère multibyte, emoji au milieu du paragraphe: compter les octets donne une taille erronée et expose aussi au risque de couper un caractère en plein milieu.

Je procède à la conversion []rune(text) une fois et je travaille ensuite avec l’indice de runes.

La étape entre les fenêtres est size - overlap. Si cela donne <= 0, la configuration est absurde (un overlap égal ou supérieur à la taille) et l’itérateur se termine sans émettre quoi que ce soit :

func Chunker(text string, size, overlap int) iter.Seq[string] {
    return func(yield func(string) bool) {
        runes := []rune(text)
        n := len(runes)
        step := size - overlap
        if n == 0 || size <= 0 || step <= 0 {
            return
        }
        // ...
    }
}

Pour ne pas couper un mot, il suffit de reculer la fin de la fenêtre jusqu’à la dernière frontière de mot (n’importe quel espace). Si le mot est si long qu’il occupe seul le chunk entier, alors on coupe à la limite même.

C’est la seule façon d’avancer :

cut := end
for cut > i && !unicode.IsSpace(runes[cut]) {
    cut--
}
if cut == i {
    cut = end
}

Émet le chunk et respecte le protocole du iter.Seq : si le yield retourne false, le consommateur est sorti de la boucle (break, return) et nous quittons ensemble :

if !yield(strings.TrimSpace(string(runes[i:cut]))) {
    return
}

La fenêtre suivante commence à partir de cut - overlap. Cet indice peut tomber au milieu d’un mot, il faut donc avancer jusqu’à terminer le mot partiel, puis sauter les espaces pour recommencer proprement :

i = max(cut-overlap, 0)
if i > 0 && !unicode.IsSpace(runes[i-1]) {
    for i < n && !unicode.IsSpace(runes[i]) {
        i++
    }
}
for i < n && unicode.IsSpace(runes[i]) {
    i++
}

Lorsque la fin de la fenêtre dépasse la fin du texte, on rend le reste et on termine :

if end >= n {
    if last := strings.TrimSpace(string(runes[i:n])); last != "" {
        yield(last)
    }
    return
}

Exécution avec size=60, overlap=15 :

document: 174 runes | size=60 overlap=15
------------------------------------------------------------
[01] "The Go programming language favors standard libraries and"
[02] "libraries and simple tools. The use of iterators, introduced"
[03] "introduced in recent versions, simplifies the construction"
[04] "construction of complex pipelines."

Chaque chunk commence en répétant la fin du précédent.

C’est cette répétition qui donne au retriever une chance d’associer une recherche dont la réponse tombe bien exactement sur la frontière de deux chunks.

Le coût est l’ajout de vecteurs dans l’indice et la probabilité que le même passage apparaisse deux fois dans le top-k. Pour le RAG de production, cela vaut le coup. LangChain et LlamaIndex se positionnent par défaut sur une plage proche : size de 500 à 1000 tokens, overlap de 10% à 20%.

Ce chunker est volontairement stupide. Il ne connaît ni phrase, ni paragraphe, ni sémantique. Pour un pipeline sérieux, vous voudrez probablement découper aux frontières de phrase, ou opter pour un chunker sémantique qui regarde l embedding de la phrase et coupe lorsque la similarité chute.

Code source complet

Fabien Delpont

Auteur

Fabien Delpont

Fabien Delpont, développeur et créateur du site Python Doctor.