Files
Orchestrator/docs/chunking-plan.md
T
ankn cfaf670100 Squashed 'bahn/wissensdatenbank/' content from commit 07a8196e
git-subtree-dir: bahn/wissensdatenbank
git-subtree-split: 07a8196e5f9e55d027f90485beb95f4006387669
2026-06-30 21:19:25 +02:00

307 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 ~**400800 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. ~46k Zeichen ≈ ~11.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/`.