307 lines
17 KiB
Markdown
307 lines
17 KiB
Markdown
# Plan: Heading-basiertes Chunking (umgesetzt fuer INB)
|
||
|
||
Status: **umgesetzt (Phase 1)**. Die Entscheidungen unten sind getroffen; das Chunking
|
||
ist als deterministischer, embedding-freier Schritt implementiert und fuer die INB aktiv.
|
||
|
||
## Getroffene Entscheidungen (Phase 1)
|
||
|
||
- **Ablage:** separater Baum `output/chunks/<scope>/<domaene>/<docslug>/` (Option A). Das
|
||
Voll-Dokument in `output/processed/` bleibt der primaere Feed.
|
||
- **Chunks getrennt vom approved-Feed** (Parent-Document-Muster ueber `parent_url`/`parent_hash`).
|
||
- **Per-Quelle aktivierbar** ueber `options.chunk` (Default `off`); gemeinsame Defaults in
|
||
`config/chunking.yaml`, pro Quelle ueberschreibbar (`effective_opts`).
|
||
- **Nur deterministische, embedding-freie Strategien** (`headings | faq | recursive`).
|
||
- **Contextual Retrieval (deterministisch):** Kontext-Vorspann je Chunk
|
||
(`> Kontext: <Dokument> > <Abschnitt>`), kein LLM/Embedding.
|
||
- **Inkrementell:** jeder Chunk traegt `parent_hash` (Inhalt) + `chunk_fingerprint`
|
||
(wirksame Optionen/Strategie). Es wird nur neu gechunkt, wenn sich einer der beiden
|
||
aendert; deaktivierte Orte/verwaiste Chunks werden entfernt. Laeuft am Ende von `src.main`
|
||
automatisch mit.
|
||
- **Lauf:** `python -m src.chunk --data output` (offline auf `output/processed`). Erste
|
||
Aktivierung: INB 2026 + INB 2027 (`chunk: headings`).
|
||
|
||
Code: `src/transformers/chunker.py` (Logik), `src/chunk.py` (Runner), `config/chunking.yaml`
|
||
(Defaults), `src/model.py` (Chunk-Felder im Frontmatter).
|
||
|
||
---
|
||
|
||
# Plan: Heading-basiertes Chunking (urspruengliches Konzept)
|
||
|
||
## Ziel & Grundidee
|
||
|
||
Grosse Dokumente (v.a. INB, Regelwerke, lange Confluence-Baeume) sind als **eine**
|
||
Markdown-Datei fuer Retrieval/Zitate unhandlich. Idee: zusaetzlich zur Voll-Datei
|
||
**Chunks entlang der Ueberschriften** erzeugen.
|
||
|
||
Wichtig (Vorgabe):
|
||
- **Die Voll-Datei bleibt immer erhalten** (kein Informationsverlust, Single Source of Truth).
|
||
- Chunks sind **additiv** und abgeleitet -> jederzeit neu erzeugbar.
|
||
- **Wir stellen die Vektor-DB NICHT.** Wer anschliesst, soll die Wahl haben:
|
||
Voll-Dokument einlesen **oder** die fertigen Chunks nutzen **oder** selbst chunken.
|
||
|
||
## Warum an Ueberschriften?
|
||
|
||
- Strategie-uebergreifend nutzbar: das Chunking arbeitet auf dem **erzeugten Markdown**,
|
||
nicht auf der Quelle. Damit ist es ein **Transform-Schritt nach dem Extrahieren** und
|
||
funktioniert (potenziell) fuer ALLE Strategien.
|
||
- Ueberschriften sind die natuerliche, semantische Schnittkante.
|
||
- Befund INB-Hauptdokument 2026 (Review): ~751 KB, **746 `##`-Ueberschriften**, davon
|
||
**91 mit „Ziffer X.Y…"** (z.B. `7.3.1.1.1.1 Kapazität`). Ideale Schnittpunkte +
|
||
praezise Zitierbarkeit.
|
||
|
||
## Eignung je Strategie
|
||
|
||
| Strategie | Ueberschriften vorhanden? | Chunking sinnvoll? |
|
||
|-----------|---------------------------|--------------------|
|
||
| `pdf` (pymupdf, INB/Regelwerk) | ja (`##`), teils „Ziffer" | **sehr** (grosse Dokumente) |
|
||
| `confluence_tree`/`page` | ja (Hn aus storage->md) | ja bei langen Seiten |
|
||
| `confluence_faq` | je Frage `###` | ja (1 Chunk je Frage/Antwort) |
|
||
| `sitemap`/`crawler` (Web) | meist (html2text `#`) | mittel (Seiten oft kurz) |
|
||
| `file`/`gitlab_md` | ja (Markdown) | ja bei langen Dateien |
|
||
|
||
-> Generischer Helfer `chunk_by_headings(markdown, level, min_chars)` + Opt-in pro Quelle.
|
||
|
||
## Chunk-Format (Vorschlag)
|
||
|
||
Jeder Chunk = eigene Markdown-Datei mit Frontmatter, das die Herkunft referenziert:
|
||
|
||
```
|
||
---
|
||
kind: "chunk" # Voll-Dokument: kind: "document" (Default)
|
||
parent_url: "<url der Voll-Quelle>"
|
||
parent_hash: "<content_hash der Voll-Datei>"
|
||
section: "7.3.1.1.1.1 Kapazität"
|
||
ziffer: "7.3.1.1.1.1" # nur wenn erkannt
|
||
chunk_index: 12
|
||
domain/tool/scope/tags/owners/contact/source: geerbt
|
||
content_hash: "<hash des Chunk-Texts>"
|
||
---
|
||
## 7.3.1.1.1.1 Kapazität
|
||
... Abschnittstext ...
|
||
```
|
||
|
||
- Heading bleibt im Chunk-Text (Chunk ist selbst-erklaerend).
|
||
- Stabiler Slug aus `ziffer`/Heading; Kollision -> Index anhaengen.
|
||
|
||
## Ablage (Entscheidung offen, s.u.)
|
||
|
||
- **Option A:** separater Baum `output/chunks/<scope>/<domaene>/<docslug>/NNN-...md`.
|
||
Vorteil: `output/processed/` bleibt „eine Datei pro Quelle/Seite", Chunks klar opt-in.
|
||
- **Option B:** Unterordner neben der Voll-Datei: `output/processed/.../<docslug>.chunks/`.
|
||
Vorteil: Chunks liegen direkt beim Dokument.
|
||
|
||
Empfehlung: **Option A** (sauberer Feed; `processed/` = Dokumente, `chunks/` = abgeleitet).
|
||
|
||
## Konfiguration (pro Quelle, additiv, Default AUS)
|
||
|
||
```
|
||
options:
|
||
chunk: headings # off (Default) | headings
|
||
chunk_level: 2 # welche Heading-Ebene schneidet (Default: kleinste vorhandene)
|
||
chunk_min_chars: 200 # kleinere Chunks mit Vorgaenger zusammenfassen
|
||
```
|
||
|
||
INB-Quellen bekaemen `chunk: headings`. Alles andere bleibt zunaechst aus.
|
||
|
||
## Umsetzungs-Skizze (spaeter)
|
||
|
||
1. `src/transformers/chunker.py`: `chunk_by_headings(md, level, min_chars) -> [(section, ziffer, text)]`
|
||
+ `make_chunks(doc) -> list[Document]` (erbt Metadaten, setzt kind/parent/section/ziffer).
|
||
2. `src/model.py`: `Document` um `kind`, `parent_url`, `parent_hash`, `section`, `ziffer`,
|
||
`chunk_index` erweitern (Frontmatter).
|
||
3. `src/main.py`: nach Filter/Freigabe, wenn `source.options.chunk == "headings"` und Doc
|
||
`approved` -> Chunks erzeugen und ueber das Gate schreiben (nach `output/chunks/`).
|
||
4. `src/store.py` + `src/site.py`: `kind` lesen; auf der Uebersicht Chunks **standardmaessig
|
||
ausblenden** (Toggle „Chunks anzeigen"), damit die Bestandszahlen (Dokumente) sauber
|
||
bleiben; eigener Zaehler fuer Chunks.
|
||
5. Robustheit: Mini-Chunks zusammenfassen, sehr grosse optional spaeter weiter teilen
|
||
(Absatz/Token-Budget, evtl. Overlap).
|
||
6. Tests: Splitten je Level, Mini-Merge, Ziffer-Extraktion, Parent-Verknuepfung,
|
||
content_hash stabil.
|
||
|
||
## Phasen
|
||
|
||
- **Phase 1:** generischer Chunker + nur fuer INB aktiv; zweites Artefakt; UI-Toggle.
|
||
- **Phase 2:** weitere grosse Dokumente (Regelwerk, lange Confluence-Baeume).
|
||
- **Phase 3 (optional):** token-bewusstes Sub-Splitting + Overlap.
|
||
|
||
## Offene Entscheidungen
|
||
|
||
1. Ablage: `output/chunks/` (Option A, empfohlen) vs. Unterordner beim Dokument (Option B).
|
||
2. Chunks in den „approved Feed" (`output/processed/`) aufnehmen oder bewusst getrennt
|
||
halten (Empfehlung: getrennt, damit Voll-Dokument der primaere Feed bleibt).
|
||
3. Heading-Ebene: automatisch kleinste vorhandene vs. pro Quelle konfiguriert.
|
||
4. Mini-/Max-Groessen (`chunk_min_chars`, spaeter Token-Budget).
|
||
5. Reihenfolge: zuerst INB (Phase 1) – ja/nein.
|
||
|
||
---
|
||
|
||
# Recherche (Stand 2025/2026) & begruendete Empfehlungen
|
||
|
||
Quellen (Inhalte fuer Compliance umformuliert, keine Originalzitate):
|
||
[Weaviate-/Firecrawl-Ueberblick](https://www.firecrawl.dev/blog/best-chunking-strategies-rag),
|
||
[Digital Applied – Retrieval Playbook 2026](https://www.digitalapplied.com/blog/rag-chunking-strategies-2026-retrieval-quality-playbook),
|
||
[CustomGPT – Chunking Strategies](https://customgpt.ai/rag-chunking-strategies/),
|
||
[Langcopilot – Practical Guide](https://langcopilot.com/posts/2025-10-11-document-chunking-for-rag-practical-guide),
|
||
[Airbyte – Chunking Strategies](https://airbyte.com/agentic-data/chunking-strategies),
|
||
[Anthropic – Contextual Retrieval](https://www.anthropic.com/news/contextual-retrieval).
|
||
|
||
## Was die Forschung sagt (verdichtet)
|
||
|
||
- **Chunking entscheidet stark ueber die Qualitaet** – mehr als die Modellwahl. Ein
|
||
Weaviate-Benchmark (Sept 2025) misst bis zu ~9 % Unterschied bei Recall zwischen
|
||
bester und schlechtester Methode (gleicher Korpus/Retriever).
|
||
- **Recursive Splitting** (erst an Ueberschriften, dann Absaetze, Saetze, Woerter) gilt
|
||
breit als **bester Allrounder/Default** (gute Balance Qualitaet/Kosten).
|
||
- **Struktur-/Heading-basiertes** Splitting ist ideal fuer **technische Dokumente mit
|
||
klarer Gliederung** – genau unser INB/Regelwerk-Fall.
|
||
- **Semantic Chunking** bringt nochmal Genauigkeit, ist aber deutlich teurer
|
||
(mehrfache Rechenkosten, teils ~14x langsamer) – eher spaeter/optional.
|
||
- **Overlap** bringt laut neueren Analysen (Jan 2026) **kaum messbaren Nutzen** -> als
|
||
Default **0**, nur optional aktivierbar.
|
||
- **Chunk-Groesse:** verbreiteter Richtwert ~**400–800 Tokens**; sehr grosse Chunks
|
||
(Richtung ~2,5k Tokens „context cliff") verschlechtern Treffer.
|
||
- **Contextual Retrieval** (Anthropic): jedem Chunk einen kurzen Kontext voranstellen
|
||
(Dokumenttitel + Abschnittspfad) reduziert Retrieval-Fehler erheblich. **Deterministisch
|
||
(ohne LLM)** machbar, indem wir die **Heading-Breadcrumb** in jeden Chunk schreiben.
|
||
- **Parent-Document-Retrieval:** kleine Chunks zum Finden, grosses Dokument zum Antworten.
|
||
Das ist **genau das gewuenschte Muster** „ein Dokument + zugehoerige Chunks".
|
||
|
||
## Empfehlungen zu den offenen Entscheidungen
|
||
|
||
### 1) Ablage: separater Baum `output/chunks/` (EMPFOHLEN)
|
||
- **Pro:** `output/processed/` bleibt „eine Datei pro Quelle/Seite" = stabiler, einfacher
|
||
Feed; Chunks sind klar als abgeleitet/optional erkennbar; getrennt loeschbar/regenerierbar;
|
||
Bestandszahlen auf den Pages bleiben sauber.
|
||
- **Contra:** zwei Wurzelverzeichnisse; Konsument muss wissen, dass es beides gibt
|
||
(loesen wir per Frontmatter `parent_*` + Doku auf der Chatbot-Seite).
|
||
- Alternative B (Unterordner beim Dokument) vermischt Voll-Doc und Chunks im selben Baum
|
||
-> Feed wird unuebersichtlich. **Daher A.**
|
||
|
||
### 2) Chunks NICHT in den primaeren `output/processed`-Feed (EMPFOHLEN)
|
||
- Voll-Dokument bleibt der primaere, eindeutige Feed (1 Seite = 1 Datei). Chunks liegen
|
||
in `output/chunks/` mit `parent_url`/`parent_hash` -> klassisches Parent-Document-Muster.
|
||
- Konsument hat die Wahl: nur `processed/` (grob), nur `chunks/` (fein) oder beides
|
||
(Parent-Document-Retrieval). Wir geben die Vektor-DB nicht vor -> maximale Flexibilitaet.
|
||
|
||
### 3) Heading-Ebene: automatisch kleinste sinnvolle, pro Quelle ueberschreibbar
|
||
- Default: feinste durchgaengig vorhandene Ebene erkennen (INB: `##`).
|
||
- `chunk_level: N` erlaubt manuelles Setzen, falls die Auto-Erkennung schlecht trifft.
|
||
|
||
### 4) Groessen/Overlap (Defaults, grounded)
|
||
- `chunk_min_chars: 200` (Mini-Abschnitte mit Vorgaenger zusammenfassen).
|
||
- `chunk_max_chars` optional (z.B. ~4–6k Zeichen ≈ ~1–1.5k Tokens); zu grosse Abschnitte
|
||
fallen per Recursive-Sub-Split kleiner.
|
||
- `chunk_overlap: 0` als Default (Overlap nur optional).
|
||
- **Breadcrumb-Prefix** je Chunk (Dokumenttitel + Abschnittspfad) standardmaessig AN
|
||
(billiger Contextual-Retrieval-Effekt).
|
||
|
||
### 5) Reihenfolge: zuerst INB (Phase 1) – JA.
|
||
|
||
## Chunk-Strategien als Menue (was die Tool-Eintragenden waehlen koennen)
|
||
|
||
Pro Quelle ueber `options.chunk` waehlbar (Default `off`):
|
||
|
||
| `chunk:` Wert | Was passiert | Wofuer geeignet |
|
||
|---------------|--------------|-----------------|
|
||
| `off` (Default) | kein Chunking, nur Voll-Datei | kurze Seiten, Kundeninfos |
|
||
| `headings` | Split an Markdown-Ueberschriften (`chunk_level`) | INB, Regelwerk, lange Confluence-Baeume |
|
||
| `recursive` | Ueberschriften -> Absaetze -> Saetze, Zielgroesse `chunk_size` | Doks ohne saubere Gliederung (manche PDFs/Webseiten) |
|
||
| `faq` | je Frage/Antwort ein Chunk (Spezialfall `headings` auf `###`) | `confluence_faq` |
|
||
| `pages` | ein Chunk je PDF-Seite | PDFs mit unbrauchbaren Ueberschriften |
|
||
|
||
Zusatz-Optionen (alle optional): `chunk_level`, `chunk_size`, `chunk_min_chars`,
|
||
`chunk_max_chars`, `chunk_overlap`, `chunk_breadcrumb: true|false`.
|
||
|
||
**Nicht im ersten Wurf (spaeter):** `semantic` (Embedding-basiert, teuer) und
|
||
`contextual` (LLM-generierter Kontext je Chunk) – beide hohe Kosten, erst wenn Bedarf.
|
||
|
||
## Empfohlener Startpunkt
|
||
|
||
- Generischen `headings`- + `recursive`-Splitter bauen, Breadcrumb-Prefix an,
|
||
Overlap 0, Ablage `output/chunks/`, Chunks getrennt vom Feed, `parent_*` im Frontmatter.
|
||
- Phase 1 nur fuer INB aktiv (`chunk: headings`), dann Treffer evaluieren, dann ausweiten.
|
||
|
||
---
|
||
|
||
# Recherche (Stand 2026) & fundierte Empfehlungen
|
||
|
||
Quellen (Auswahl, paraphrasiert; Inhalte zur Lizenzkonformitaet umformuliert):
|
||
- [Firecrawl – Best Chunking Strategies for RAG (2026)](https://www.firecrawl.dev/blog/best-chunking-strategies-rag)
|
||
- [Digital Applied – RAG Chunking Playbook (Mai 2026)](https://www.digitalapplied.com/blog/rag-chunking-strategies-2026-retrieval-quality-playbook)
|
||
- [customgpt.ai – RAG Chunking Strategies](https://customgpt.ai/rag-chunking-strategies/)
|
||
- [langcopilot – Document Chunking for RAG (2026)](https://langcopilot.com/posts/2025-10-11-document-chunking-for-rag-practical-guide)
|
||
- [denser.ai – 8 Methods Compared](https://denser.ai/blog/rag-chunking-strategies/)
|
||
|
||
## Was die Recherche zeigt (Kernbefunde)
|
||
|
||
- **Die Chunk-Strategie beeinflusst die Retrieval-Qualitaet oft staerker als das Modell.**
|
||
Ein Weaviate-Benchmark (Sept 2025) nennt bis zu ~9 % Unterschied in der Trefferquote
|
||
zwischen bester und schlechtester Methode auf demselben Korpus.
|
||
- **Struktur-/dokumentbewusstes Chunking** (Ueberschriften, Tabellen, Codebloecke
|
||
erhalten) gilt als entscheidend fuer strukturierte Inhalte; customgpt.ai berichtet
|
||
hier deutliche Genauigkeitsgewinne. Genau das sind die INB/Regelwerke.
|
||
- **Recursive Splitting** (~400-800 Token, 10-20 % Overlap) ist der gaengige
|
||
Allzweck-Default.
|
||
- **Semantisches Chunking** kann die Trefferquote spuerbar heben, ist aber teuer
|
||
(jeder Satz wird eingebettet, teils ~14x langsamer) -> braucht ein Embedding-Modell.
|
||
- **Overlap-Mythos:** eine Analyse (Jan 2026) findet kaum messbaren Nutzen von Overlap;
|
||
bei semantischen Schnittkanten (Ueberschriften) ist Overlap ohnehin weniger noetig.
|
||
- **„Context Cliff" ~2.5k Token:** sehr grosse Chunks „verwaessern" das Embedding ->
|
||
Obergrenze sinnvoll.
|
||
- **Contextual Retrieval (Anthropic):** ein kurzer Kontext-Vorspann pro Chunk
|
||
(z.B. Dokumenttitel + Abschnittspfad) reduziert Retrieval-Fehler stark -> billig &
|
||
deterministisch machbar.
|
||
|
||
## Was das fuer UNS bedeutet (wichtige Einordnung)
|
||
|
||
Wir **stellen die Vektor-DB nicht** und wollen **deterministische, reproduzierbare**
|
||
Artefakte in Git. Daraus folgt eine klare Linie:
|
||
|
||
- **Wir liefern, was man ohne Embedding-Modell sauber erzeugen kann:** strukturerhaltende,
|
||
ueberschriften-basierte Chunks + saubere Voll-MD. Das ist laut Recherche **der mit
|
||
Abstand wichtigste Hebel** („clean, document-aware input").
|
||
- **Semantisches / late / contextual-embedding-Chunking gehoert zum Anschliesser**
|
||
(er hat Embedding-Modell + Vektor-DB). Wir wuerden es NICHT fest verdrahten – wir
|
||
liefern die ideale Vorlage dafuer.
|
||
- Deshalb ist „ein Voll-Dokument + zugehoerige Chunks" genau richtig: maximale Wahl,
|
||
kein Lock-in.
|
||
|
||
## Empfehlung je offener Entscheidung
|
||
|
||
| Entscheidung | Optionen | Empfehlung | Begruendung |
|
||
|--------------|----------|------------|-------------|
|
||
| **Ablage** | (A) separater Baum `output/chunks/<scope>/...` · (B) Unterordner beim Dok · (C) in `output/processed/` | **A** | Voll-Dok bleibt der primaere, eindeutige Feed; Chunks klar opt-in/abgeleitet; Konsument waehlt „processed ODER chunks" ohne Doppel-Embedding. |
|
||
| **Chunks in approved-Feed?** | rein / getrennt | **getrennt** | Verhindert, dass derselbe Inhalt doppelt im Index landet; `output/processed` bleibt „eine Datei pro Quelle". |
|
||
| **Heading-Ebene** | fix / auto / konfigurierbar | **konfigurierbar mit smartem Default** (kleinste sinnvolle Ebene; klein -> mergen, gross -> bis Zielband teilen) | Recursive-Prinzip: Struktur respektieren, aber Zielgroesse halten (gegen Mini-Fragmente & Context Cliff). |
|
||
| **Zielgroesse** | – | **~300-1200 Token Band**, hartes Max < ~2000 Token | gaengiger Default-Korridor; unter Context Cliff. |
|
||
| **Overlap** | 0 / 10-20 % | **Default 0** (optional aktivierbar) | „Overlap-Mythos"; Ueberschriften sind semantische Grenzen. |
|
||
| **Kontext-Vorspann** | aus / an | **an (empfohlen)**: 1 Zeile „aus <Dok> > <Abschnittspfad>" je Chunk | Contextual-Retrieval-Effekt, deterministisch, billig. |
|
||
|
||
## Chunk-Strategien als Menue fuer Tool-Eintragende
|
||
|
||
Pro Quelle waehlbar ueber `options.chunk` (Default `off`). Bewusst nur **deterministische,
|
||
embedding-freie** Verfahren – Semantik-Chunking bleibt dem Anschliesser:
|
||
|
||
| `chunk:` | Was es tut | Wann nutzen |
|
||
|----------|------------|-------------|
|
||
| `off` (Default) | nur Voll-Dokument | kurze Seiten, Standard |
|
||
| `headings` | schneidet an Markdown-Ueberschriften; klein -> mergen, gross -> bis Zielband; `ziffer`/Abschnitt als Metadatum | strukturierte, lange Dokumente (INB, Regelwerk, lange Confluence-Baeume) |
|
||
| `faq` | ein Chunk je Frage/Antwort | `confluence_faq`-Quellen |
|
||
| `recursive` | groessenbasiert: Absatz -> Satz bis Zielband (ohne Embeddings) | lange Dokumente **ohne** brauchbare Ueberschriften |
|
||
| `pages` *(spaeter)* | ein Chunk je PDF-Seite | paginierte PDFs (gewann NVIDIA-2024-Benchmark fuer paginierte Docs) |
|
||
|
||
Gemeinsame Optionen: `chunk_target_tokens` (Default ~500), `chunk_max_tokens` (~1800),
|
||
`chunk_min_chars` (~200), `chunk_overlap` (Default 0), `chunk_context_header` (Default an).
|
||
|
||
**Bewusst NICHT angeboten** (Begruendung dokumentieren): `semantic`/`late`/`contextual-embedding`
|
||
-> brauchen ein Embedding-Modell und gehoeren in die Vektor-DB des Anschliessers. Wir liefern
|
||
dafuer die saubere, strukturierte Vorlage.
|
||
|
||
## Empfohlener Startpunkt (Phase 1)
|
||
|
||
INB-Quellen: `chunk: headings`, `chunk_level: auto`, `chunk_target_tokens: 500`,
|
||
`chunk_max_tokens: 1800`, `chunk_overlap: 0`, `chunk_context_header: on`. Ablage unter
|
||
`output/chunks/allgemein/regulierung/inb-2026/<ziffer-oder-slug>.md`. Voll-MD bleibt
|
||
unveraendert in `output/processed/`.
|