Zum Hauptinhalt springen
Lektion 12 von 22 · Embeddings, RAG und semantische Suche

Chunking: Intelligente Aufteilung Ihrer Dokumente

Lektion 12 von 22 Aktualisiert am 29. Juli 2026 Entwickler
Auf dieser Seite
  1. Lernziele
  2. Warum Dokumente aufteilen?
  3. Strategie 1: Chunking mit fester Größe
  4. Strategie 2: Chunking basierend auf Trennzeichen
  5. Strategie 3: Chunking nach Markdown-Sektionen
  6. Semantisches Chunking
  7. Anreicherung von Chunks mit Kontext
  8. Welche Chunk-Größe sollten Sie wählen?
  9. Zusammenfassung

Lernziele

  • Verstehen Sie, warum Chunking für die Qualität von RAG entscheidend ist
  • Beherrschen Sie verschiedene Aufteilungsstrategien
  • Wählen Sie die richtige Chunk-Größe und den richtigen Overlap

Warum Dokumente aufteilen?

Embedding-Modelle verfügen über ein Token-Limit: 8.191 für OpenAI. Dieses Limit ist der erste Grund, Dokumente aufzuteilen, aber es ist nicht der wichtigste. Selbst ein Dokument, das weit unter diesem Limit liegt, liefert schlechte Ergebnisse, wenn es als einzelner Block kodiert wird, da der resultierende Vektor alle Inhalte durchmittelt und Details verliert. Nehmen Sie beispielsweise eine 40-seitige Personalhandlung, die Urlaubsregelungen, Remote-Arbeit, Aufwendungsberichte und Krankenversicherung abdeckt: Ihre einzelne Embedding-Repräsentation ähnelt keiner dieser vier Themen wirklich, und eine Suche nach „wie viele Urlaubstage” wird sie unter einem deutlich kürzeren, aber viel gezielteren Dokument rangieren.

Chunking löst dieses Problem, indem es das Dokument in kohärente Teile zerlegt, wobei jeder Teil eine spezifische Idee oder einen Absatz erfasst. Dies ist der Schritt, an dem eine RAG-Pipeline die meisten Qualitätsgewinne oder -verluste erleidet – noch bevor Sie ein Embedding-Modell auswählen.

Strategie 1: Chunking mit fester Größe

Die einfachste Methode zählt Wörter und schneidet in regelmäßigen Abständen. Sie ignoriert die Textstruktur vollständig, was sie brutal macht, benötigt aber nur drei Zeilen Code und dient als wesentliche Basislinie: Jede ausgefeiltere Strategie muss beweisen, dass sie eine bessere Leistung erbringt als diese.

def chunker_fixed_size(
    text: str,
    size: int = 500,
    overlap: int = 100
) -> list[str]:
    """Teilt Text in Chunks fester Größe mit Overlap auf."""
    words = text.split()
    chunks = []

    for i in range(0, len(words), size - overlap):
        chunk = " ".join(words[i:i + size])
        if chunk.strip():
            chunks.append(chunk)

    return chunks

long_text = "A very long document... " * 500
chunks = chunker_fixed_size(long_text, size=300, overlap=50)
print(f"{len(chunks)} chunks generated")

Der Parameter overlap verdient besondere Aufmerksamkeit. Ohne Overlap landet ein Schnitt irgendwann mitten in einer Erklärung: Der Satz „die Kündigungsfrist beträgt drei Monate” landet in einem Chunk, und „außer während der Probezeit” im nächsten. Weder beantwortet die Frage korrekt, noch ist das zweite sogar irreführend. Durch einen Overlap von 10–20 % der Chunk-Größe garantieren Sie, dass jede Idee mindestens einmal intakt im Index vorhanden ist. Dies ist ein guter Ausgangspunkt; passen Sie ihn später basierend auf der Dichte Ihrer Dokumente an.

Strategie 2: Chunking basierend auf Trennzeichen

Statt Wort für Wort zu schneiden, können Sie die natürlichen Grenzen des Textes respektieren. Die Idee besteht darin, zuerst den stärksten Trennzeichen zu versuchen – den doppelten Zeilenumbruch, der zwei Absätze trennt – und dann zu immer schwächeren Trennzeichen überzugehen, solange das resultierende Stück die maximale Größe überschreitet. Ein Blogbeitrag wird somit Absatz für Absatz aufgeteilt, und nur ein monströser Absatz wird in Sätze zerlegt.

import re

def chunker_separators(
    text: str,
    separators: list[str] = ["\n\n", "\n", ". ", " "],
    max_size: int = 1000
) -> list[str]:
    """Teilt Text unter Beachtung natürlicher Trennzeichen auf."""
    chunks = []
    current_chunk = ""

    # Split by the strongest separator first
    segments = [text]
    for sep in separators:
        new_segments = []
        for segment in segments:
            parts = segment.split(sep)
            for part in parts:
                if len(part.split()) > max_size:
                    new_segments.append(part)
                else:
                    if (current_chunk and
                        len((current_chunk + sep + part).split()) > max_size):
                        chunks.append(current_chunk.strip())
                        current_chunk = part
                    else:
                        current_chunk = (
                            current_chunk + sep + part
                            if current_chunk else part
                        )
        segments = new_segments

    if current_chunk.strip():
        chunks.append(current_chunk.strip())

    return chunks

Strategie 3: Chunking nach Markdown-Sektionen

Wenn Ihr Korpus aus strukturierten Dokumenten besteht, übernehmen Überschriften bereits die Arbeit für Sie: Der Autor hat bereits die Bedeutungseinheiten abgegrenzt. Das Aufteilen auf den Überschriftenebenen # und ## erzeugt Chunks, die genau den Sektionen entsprechen, und die folgende Funktion behält die Überschrift zusammen mit dem Text bei, den wir unmittelbar danach zur Anreicherung verwenden werden.

def chunker_markdown(text: str, max_level: int = 2) -> list[dict]:
    """Teilt ein Markdown-Dokument nach Sektionen auf."""
    chunks = []
    current_section = {"title": "", "content": "", "level": 0}

    for line in text.split("\n"):
        # Detect headings
        match = re.match(r"^(#{1,6})\s+(.+)$", line)
        if match:
            level = len(match.group(1))
            title = match.group(2)

            if level <= max_level and current_section["content"].strip():
                chunks.append({
                    "title": current_section["title"],
                    "text": current_section["content"].strip(),
                    "level": current_section["level"]
                })

            if level <= max_level:
                current_section = {
                    "title": title,
                    "content": "",
                    "level": level
                }
            else:
                current_section["content"] += line + "\n"
        else:
            current_section["content"] += line + "\n"

    if current_section["content"].strip():
        chunks.append({
            "title": current_section["title"],
            "text": current_section["content"].strip(),
            "level": current_section["level"]
        })

    return chunks

Semantisches Chunking

Bei Text ohne explizite Struktur – wie eine Besprechungsübersicht oder ein Telefonat-Transkript – wissen weder die Größe noch die Trennzeichen, wo sich das Thema ändert. Embeddings tun dies. Sie kodieren jeden Satz, vergleichen jeden Satz mit dem vorherigen und schneiden dort, wo die Ähnlichkeit zusammenbricht: Genau dort hat der Sprecher vom Budget auf die Personalbeschaffung gewechselt. Beachten Sie, dass text-embedding-3-small hier ausreicht, da Sie keine absolute Präzision, sondern einen relativen Bruch suchen.

from openai import OpenAI
import numpy as np

client = OpenAI()

def chunker_semantic(
    text: str,
    similarity_threshold: float = 0.75,
    min_sentence_length: int = 20
) -> list[str]:
    """Gruppiert Sätze nach semantischer Ähnlichkeit."""
    # Split into sentences
    sentences = re.split(r"(?<=[.!?])\s+", text)
    sentences = [s for s in sentences if len(s) > min_sentence_length]

    if len(sentences) <= 1:
        return [text]

    # Embeddings for each sentence
    resp = client.embeddings.create(
        input=sentences,
        model="text-embedding-3-small"  # small is sufficient here
    )
    embs = np.array([d.embedding for d in sorted(resp.data, key=lambda x: x.index)])

    # Find break points
    chunks = []
    chunk_sentences = [sentences[0]]

    for i in range(1, len(sentences)):
        sim = float(np.dot(embs[i], embs[i - 1]))
        if sim < similarity_threshold:
            # Semantic break detected
            chunks.append(" ".join(chunk_sentences))
            chunk_sentences = [sentences[i]]
        else:
            chunk_sentences.append(sentences[i])

    if chunk_sentences:
        chunks.append(" ".join(chunk_sentences))

    return chunks

Anreicherung von Chunks mit Kontext

Ein isolierter Chunk verliert oft die Information, die ihn interpretierbar macht. „Die Laufzeit beträgt drei Jahre” bedeutet nichts, wenn der Leser oder das Embedding-Modell nicht weiß, dass es sich um den Abschnitt zur Herstellergarantie handelt. Das Präfixieren jedes Chunks mit dem Dokumenttitel und dem Sektionstitel stellt diesen Kontext direkt in den Vektor selbst wieder her, und eine Suche nach „Garantie-Laufzeit” findet dann den entsprechenden Abschnitt.

def enrich_chunk(chunk: str, doc_title: str, section: str = "") -> str:
    """Fügt Kontext zum Chunk hinzu für eine bessere Einbettung."""
    prefix = f"Dokument: {doc_title}"
    if section:
        prefix += f" | Sektion: {section}"
    return f"{prefix}\n\n{chunk}"

Welche Chunk-Größe sollten Sie wählen?

Die Wahl beinhaltet einen Kompromiss zwischen Präzision und Kontext: Ein kurzer Chunk zielt fein, wird aber amputiert, während ein langer Chunk alles versteht, aber das Signal verwässert.

Größe (Tokens)VorteileNachteile
100–200Sehr präzise, gute GranularitätVerliert Kontext
300–500Guter KompromissStandard
500–1000Reichhaltiger KontextWeniger präzise, teurer
1000+Maximaler KontextVerwässert Informationen

Es gibt keinen universellen Wert: Die ideale Größe hängt von Ihren Dokumenten und Abfragen ab. Die einzige zuverlässige Methode besteht darin, dasselbe Korpus mit zwei oder drei Größen zu indizieren und dann die Metriken aus Lektion 10 an einer Reihe von echten Fragen zu messen. Eine Wissensdatenbank für Support, die aus kurzen Antworten besteht, toleriert 200 Tokens; ein juristisches Korpus wird 800 erfordern.

Zusammenfassung

  • Chunking ist der einflussreichste Schritt in der RAG-Pipeline
  • Verwenden Sie feste Größe als Basislinie und Trennzeichen, um die Struktur zu respektieren
  • Semantisches Chunking erkennt Themenwechsel automatisch
  • Overlap und kontextuelle Anreicherung verbessern die Qualität
  • Testen Sie verschiedene Größen und messen Sie mit Suchmetriken