Merge commit 'cfaf67010017eab368216aded483a64126dbcb2e' as 'bahn/wissensdatenbank'

This commit is contained in:
2026-06-30 21:19:25 +02:00
4724 changed files with 667022 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
# SETUP Was du (manuell) tun musst
Code/Config sind vorbereitet. Diese Schritte brauchen Zugaenge/Rechte, die nur du hast.
## Warum kein Deps-Image? (PyPI ueber Artifactory-Mirror)
Die GitLab-Runner-Pods erreichen **pypi.org nicht**, **aber** den Artifactory-PyPI-Mirror
auf bahnhub. Deshalb setzt die CI `PIP_INDEX_URL` auf den Mirror dann funktioniert
`pip install` direkt im Job, **ohne eigenes Image**:
```
PIP_INDEX_URL = https://bahnhub.tech.rz.db.de/artifactory/api/pypi/pypi-remote/simple
```
Ist bereits in `.gitlab-ci.yml` gesetzt. Falls der Repo-Name im Konzern abweicht,
dort anpassen (beim DXP-/pipeship-Team erfragen). `lint`/`pages` brauchen kein pip.
---
## Checkliste
### 1. PyPI-Mirror pruefen
- [ ] Ersten Pipeline-Lauf ansehen: `test`-Job muss `pip install` ueber den Mirror
schaffen. Falls 403/404: `PIP_INDEX_URL` (Repo-Name) in `.gitlab-ci.yml` anpassen.
### 2. CI/CD-Variablen anlegen (Settings → CI/CD → Variables, Masked & Protected)
- [ x] `CONFLUENCE_URL` = `https://arija-confluence.jaas.service.deutschebahn.com`
- [x ] `CONFLUENCE_TOKEN` = dein Confluence-PAT (**neuen** erzeugen, alten rotieren!)
- [x ] `GIT_PUSH_TOKEN` = Project Access Token mit Scope `write_repository`
- [ ] (optional) `GITLAB_TOKEN` = Read-Token, falls `gitlab_md`-Quellen aus PRIVATEN
GitLab-Repos geladen werden (oeffentliche brauchen keinen Token)
- (DEPS_IMAGE entfaellt pip laeuft ueber PIP_INDEX_URL, siehe oben)
### 3. Schedule (CI/CD → Schedules)
- [x] Schedule angelegt: **Cron `0 8-17 * * 1-5`** (stuendlich MoFr 817 Uhr), Target
Branch `main`. Der `knowledge-etl`-Job laeuft nur bei `schedule`/`web`.
### 3b. Netzzugang des Runners (WICHTIG fuer den ETL-Lauf)
Der Runner muss die Quellen erreichen, sonst kommen 0 Dokumente (Timeouts):
- intern: `arija-confluence...deutschebahn.com` (Confluence)
- oeffentlich: `www.dbinfrago.com` (Kundeninfos, INB, Regelwerk)
Der `knowledge-etl`-Job ist bereits auf den **DB-Web-Proxy** konfiguriert
(`http://webproxy.comp.db.de:8080`) mit `NO_PROXY` fuer interne Hosts
(`.tech.rz.db.de`, `.deutschebahn.com`).
- [ ] Pruefen, ob Proxy-Host/Port stimmen (ggf. in `.gitlab-ci.yml` anpassen) und ob
der Proxy **keine Authentifizierung** verlangt. Beim naechsten Lauf im Log sehen:
laufen Confluence + dbinfrago jetzt durch?
### 4. Protected Branch + Freigabe (Settings → Repository → Protected Branches)
- [x ] `main` schuetzen: „Allowed to push" = niemand (nur via MR); „Allowed to merge" = Maintainer.
- [ ] Settings → Merge requests: „Require approval from Code Owners" aktivieren.
das ignroerien wir erst mal
- [ ] In `CODEOWNERS` die Platzhalter durch echte GitLab-Gruppen/Handles ersetzen
(`@einfachbahn-lab/wissensdatenbank-maintainer` -> reale Gruppe).
> das auch
- [ ] Hinweis: Der automatische Bot-Push auf `main` braucht dann eine Ausnahme
(Token-User als „Allowed to push" zulassen) ODER der Bot pusht auf einen Branch
`knowledge-data` (in `.gitlab-ci.yml` `TARGET_BRANCH` umstellen) + Auto-MR.
-> braucht es das dann noch?
ANTWORT: Ja. Da `main` „push = niemand" ist, wuerde der automatische ETL-Push scheitern.
Einfachste Loesung: den `GIT_PUSH_TOKEN`-User unter „Allowed to push" fuer `main`
zulassen. (CODEOWNERS-Approval ignorieren wir ja erst mal, also kein MR-Zwang.)
### 5. GitLab Pages aktivieren (Deploy → Pages)
- [ x] Nach erstem erfolgreichen `pages`-Job ist die URL unter Deploy → Pages sichtbar.
(Job laeuft auf `main`; nutzt nur stdlib, kein Deps-Image noetig.)
das hier verstehe ich noch nicht:
### 6. Issue → MR Automatisierung (optional, fuer Self-Service)
Ziel: Ein Fachbereich meldet neues Wissen per **GitLab-Issue** (Vorlage „Neues Wissen"),
ohne yaml/Git zu koennen. Daraus wird automatisch ein Eintrag in `config/tools.yaml`
+ ein Merge Request mit Vorschau. So laeuft es:
1. Person legt ein **Issue** mit der Vorlage an (Tool, Link(s), Scope, Ansprechpartner)
und Label `neues-wissen`.
2. Aus dem Issue wird ein **tools.yaml-Eintrag** erzeugt (`scripts/issue_to_source.py`)
und ein **MR** geoeffnet (`scripts/issue_to_mr.sh <ISSUE_ID>`), inkl. Markdown-Vorschau.
3. Reviewer schaut die Vorschau an und merged = Freigabe.
Du musst nur entscheiden, WIE Schritt 2 ausgeloest wird:
- **Manuell (einfachste Variante):** Reviewer fuehrt `scripts/issue_to_mr.sh <ISSUE_ID>`
lokal aus (braucht `glab` eingeloggt + aktive venv + `CONFLUENCE_*`). Fuer den Anfang reicht das.
- **Automatisch (spaeter):** GitLab-Webhook auf „Issues events" an einen kleinen Dienst,
der das Skript ausfuehrt ODER ein scheduled CI-Job, der offene `neues-wissen`-Issues abarbeitet.
- [ ] Label `neues-wissen` anlegen (fuer beide Varianten).
- [ ] Fuer den Start: Variante „manuell" nutzen. Automatik ist optional/spaeter.
### 8. Reviews
- [ passt erst mal] GitLab Pages (Uebersicht) pruefen.
- [passt erst mal ] `pending` freigeben: URL oder `hash:<content_hash>` in
`config/approvals.yaml` unter `approved:` eintragen.
---
## ETL-Bot - was genau noch zu tun ist
Der `knowledge-etl`-Job laeuft per Schedule (stuendlich MoFr 817 Uhr), baut das Wissen und **pusht das Ergebnis
zurueck**. Dafuer:
1. **`GIT_PUSH_TOKEN`** anlegen: Settings → Access Tokens → *Project Access Token*,
Rolle `Maintainer`, Scope **`write_repository`**. Wert als CI/CD-Variable
`GIT_PUSH_TOKEN` (Masked & Protected) speichern.
2. **Push auf `main` erlauben** (weil `main` „push = niemand" ist): Settings →
Repository → Protected Branches → `main` → unter **„Allowed to push"** den
Token-User hinzufuegen (heisst typ. `project_<id>_bot`).
- **Alternative (main bleibt strikt):** in `.gitlab-ci.yml` `TARGET_BRANCH: knowledge-data`
setzen; der Bot pusht dann auf einen Datenbranch (kein Push auf main noetig).
3. **Schedule** angelegt (CI/CD → Schedules, Cron `0 8-17 * * 1-5`, Target `main`). ✔
Mehr ist fuer den Bot nicht noetig `pip` laeuft ueber den PyPI-Mirror.
## Quellen-Typen: GitLab / Datei im Repo / public
- **GitLab** (`gitlab_md`): ✅ Markdown-Dateien aus einem Repo, mit Ordner (`path`) und
Tiefe (`max_depth`). Privat-Repos brauchen `GITLAB_TOKEN`.
- **Datei im Repo** (`file`): ✅ Handbuch/Doku einfach nach `files/<tool>/` pushen und
per `strategy: file` einbinden (PDF wird zu Text, .md direkt). Alles versioniert.
- **PDF-Handbuecher via Link** (`pdf`): ✅ direkter PDF-Link oder Seite mit PDF-Links.
- **Public Links** (`crawler`/`pdf`/`sitemap`): ✅.
+146
View File
@@ -0,0 +1,146 @@
# TODO / Roadmap
## Leitprinzipien (immer beachten)
- **Qualitaet vor Menge.** Lieber weniger, dafuer sauberes/freigegebenes Wissen.
- **Einfach konfigurierbar.** Neues Wissen ueber `config/tools.yaml` /
`config/general.yaml` ergaenzbar (Domaene/Tool + Link + Strategie + Scope),
ohne Code zu aendern.
- **Transparenz.** Jederzeit sichtbar, welches Wissen wo liegt und mit welchen Tags
(GitLab Pages: Uebersicht + Wissensquellen, Frontmatter).
## Offen (Code/Inhalt)
### Bugs / beobachten
- [x] **Confluence-Abruf schlug im CI fehl** (gefunden 2026-06-28 im ETL-Log): alle
`confluence_*`-Quellen brachen mit `No scheme supplied` ab (CI-Variable `CONFLUENCE_URL`
ohne `https://`) -> pathos/nur/mateo/infraportal/rechnungsbahnhof wurden nicht
aktualisiert. **URL inzwischen korrigiert** - der naechste ETL-Lauf muss bestaetigen,
dass es wieder laedt (jetzt in `output/run_log.jsonl` als `sources_failed: 0` sichtbar).
- [ ] **ETL-Sichtung insb. wegen Dateigroesse** (naechster Check: 2026-06-30): nach dem
Merge von `chore/data-to-output` (v2.0.0) pruefen, ob der naechste ETL-Lauf sauber
durchlaeuft (output/staging-Trennung in CI korrekt? `run_log.jsonl` Fehler-frei?).
Ausserdem Repo-Groesse im Auge behalten gerade die INB-Chunks erzeugen viele
Dateien. Ggf. alte Chunk-Generationen (`output/chunks/`) per Shallow-Clone oder
LFS-Strategie entschaerfen, falls das Repo >500 MB waechst.
### Monitoring / Logging
- [ ] **ETL-Laeufe regelmaessig pruefen** - jetzt einfach ueber `output/run_log.jsonl`
(letzte Laeufe inkl. Fehler je Quelle) bzw. die Health-Zeile in der Pages-Fusszeile.
- [x] **Lauf-Log umgesetzt** (`output/run_log.jsonl`, append-only, gekappt auf 500):
Zeit, verarbeitete Dokumente, Status-Counts, Fehler je Quelle; Health-Zeile auf der
Uebersicht; Manifest-Eintrag auf der Chatbot-Seite. (v1.3.0)
### Inhalt / Datenqualitaet
- [x] **Dedup ueberlappender Quellen** (v1.4.0): gleiche Seite je scope/domaene nur ein
Dokument (Gate-Dedup per `page_identity`, spezifischere Aufbereitung gewinnt) +
`prune_duplicate_files`. Einmalig 42 Alt-Duplikate entfernt (v.a. `nur`).
- [x] **pathOS „FAQ PathOS Extern" wieder erfasst** (v1.3.1): Seite ist h2/Absatz-basiert,
nicht als Q/A-Tabelle -> Strategie von `confluence_faq` auf `confluence_page` umgestellt.
Vorher landete das komplette externe FAQ leer in pending.
- [x] **Confluence-Anhaenge (eingebundene PDFs) werden erfasst** (v1.5.0): bei
`confluence_page`/`confluence_tree` werden `view-file`/`viewpdf`-Anhaenge ueber den
PDF-Parser zu Markdown und als eigene Dokumente (`kind: attachment`, `parent_url` ->
Seite) abgelegt. Inkrementell. Default an, per `options: { attachments: false }`
abschaltbar. Bilder: alt-Text bleibt als `[Bild: ...]` erhalten (Variante A).
Offen/optional: drawio-Diagramme (Text aus XML extrahieren) - aktuell nicht erfasst.
- [x] **INB-Sectioning / Chunking** umgesetzt (v1.1.0/1.2.0): heading-basiert,
deterministisch, inkrementell, `output/chunks/`, Katalog `output/_index.json`. Default aus,
aktiv fuer INB. Spaeter ggf. weitere grosse Dokumente (Regelwerk).
### Ideen (Konzept steht, Entscheidung offen)
- [ ] **#1 Nachtraegliches Taggen.** Regelbasiert: `config/tag_rules.yaml`
(match: domain/url/titel/keyword -> add_tags) + Befehl `python -m src.retag`, der
NUR die `tags:`-Zeile im Frontmatter neu schreibt (Body unveraendert -> content_hash
stabil, kein Netz, idempotent), committet. Optional spaeter LLM/Keyword-Vorschlaege,
die in die Regeln einfliessen. Auf Pages ggf. Tag-Filter/Tag-Wolke. Gut v.a. fuer
allgemeines Wissen (Kundeninfos). Aufwand: mittel.
- [ ] **#5 Intern/extern aus EINER Confluence-Seite trennen.** Regel: extern ⊆ intern
(nur EINE Richtung). Marker fuer interne Abschnitte (Konvention zu entscheiden:
Panel/Info-Makro vs. Textmarker `[[intern]]..[[/intern]]` vs. Ueberschrift „Nur intern").
Quelle `scope: "intern,extern"`: intern = ganze Seite; extern = interne Bloecke
entfernt (zu kurz -> nur intern). Sicherheitsnetz: content_filter prueft extern weiter.
Erkennung im `md_converter` + Tests + Redakteurs-Doku. Aufwand: mittel-hoch, Leak-Risiko
bei vergessenem Marker -> „im Zweifel raus".
### Bedienbarkeit
- [ ] Helfer `scripts/add_tool.py` (interaktiv: Domaene + Link + Strategie + Scope,
inkl. yaml-Validierung) als Zwischenschritt zur Web-App.
- [ ] **Web-App (Option C, mittelfristig):** Flask auf DBCS, Formular ->
GitLab-API legt MR an, mit Live-Vorschau. Pages verlinkt darauf. (Skill
`dbcs-python-webapp`.)
- [ ] **Issue->MR-Bot verdrahten:** Webhook-Service ODER scheduled `glab`-Job, der
`scripts/issue_to_source.py` ausfuehrt und automatisch einen MR samt Vorschau anlegt.
(Bausteine `scripts/issue_to_source.py` + `scripts/issue_to_mr.sh` sind da.)
### Quality Gates
- [ ] **`min_quality`-Schwelle** in `filter_rules.json`: `content_filter` setzt Docs
unter der Schwelle auf `pending` („niedrige Qualitaet"). Erst Verteilung sichten,
dann aktiv schalten. (Score + Aufschluesselung sind jetzt auf den Pages sichtbar.)
- [ ] **gitleaks-Baseline** (`.gitleaks.toml`) fuer False Positives in `data/`, dann
`secret-scan` scharf schalten (aktuell `allow_failure: true`).
### Vereinfachungen
- [x] **Strategie `kundeninfo` entfernt** (war redundant zu `crawler`/`sitemap`); inkl.
`ComponentType.KUNDENINFO` + `ComponentType.DEEPLINK` (beide ungenutzt).
- [x] **`docs/catalog.md` (src/catalog.py) abgeschafft** Seite „Wissensquellen"
(config.html) ersetzt es.
- [x] **`docs/review_queue.md` (src/review_queue.py) abgeschafft** Pages-Uebersicht
zeigt pending/rejected inkl. Grund + Aktionslinks.
- `--only` bleibt (vom MR-`preview`-Job genutzt).
### Betrieb / Skalierung
- [ ] **CI-Timeout** fuer `knowledge-etl` im Auge behalten (inkrementell unkritisch,
ein Timeout wird vom naechsten Lauf nachgeholt).
- Hinweis: Korpus bleibt bewusst in Git (kein externer Speicher) Repo = Single
Source of Truth.
---
## Out of scope (vorerst, bewusst zurueckgestellt)
- Mehr externes Wissen pro Tool (eigene kund:innenfaehige Confluence-Seite).
- Owners-Backfill fuer alte kundeninfo-/INB-Dokumente (bleiben ohne Owner).
---
## Erledigt (Kern)
- [x] ETL-Grundgeruest: Extract (Confluence/Web/Sitemap/PDF/GitLab/File) -> Transform
(md_converter, tagger, content_filter) -> Review-Gate (approved/pending).
- [x] Strategien: confluence_page/tree/faq, crawler, sitemap, pdf, **gitlab_md**, **file**.
- [x] Strategie-Erkennung pro URL (`src/strategy_detect.py`), unbekannte URLs flaggen.
- [x] Scope-Modell: tool-weit `intern|extern|allgemein|mixed`, source `intern,extern`
(ganze Seite fuer beide). Pro-Seite-Trennlogik bewusst entfernt.
- [x] Output `output/processed/<scope>/<domaene>[/<tool>]` IST der Vektor-DB-Feed
(kein separater ingest-Export).
- [x] Gate 2: Allowlist `config/approvals.yaml` (URL/Hash) im content_filter;
`review_notes` erklaeren WARUM pending.
- [x] Quality-Score (0-100) pro Dokument (`src/quality.py`), auf Pages + Hilfe erklaert.
- [x] **INB-Feintuning**: `pymupdf4llm`-Parser (`parser: pymupdf`) -> echte
Markdown-Tabellen; INB 2026/2027 + Regelwerk; PDF-Connector inkrementell (`max_pdfs`,
`keep_raw`, `redact`-Option).
- [x] **Confluence inkrementell** (`options: { incremental: true }`): Versions-Check
pro Seite, unveraenderte Seiten werden uebersprungen (`source_version` im Frontmatter).
- [x] GitLab Pages (DB-UX): Logo, Footer (Sebastian Reinig · V.IWF 91 · #Einfachbahn),
Filter (extern/intern/allgemein/pending), Suche, Quality-Score sichtbar,
Modal mit Inhalt/Quality-Aufschluesselung/Aktionslinks.
- [x] **Smoke-Test** fuer `src.site.build()` (erzeugt index/hilfe/chatbot/config/changelog + json).
- [x] **Hilfe-Seite** (`hilfe.html`): Strategien-Tabelle, Ablauf, pending/rejected-Gruende.
- [x] **Chatbot-Anschluss-Seite** (`chatbot.html`): welche Pfade pro Bot-Typ, wie
Frontmatter zu interpretieren ist, Pruefhinweis, RAG-Ablauf.
- [x] CI: **PyPI-Mirror** (`PIP_INDEX_URL`) statt Deps-Image; test/etl/pages ohne
Custom-Image. Test-Fix `python -m pytest`. renovate.
- [x] CI: stuendlicher Daten-Commit (Mo-Fr 8-17) loest `pages`-Refresh aus (kein `ci.skip` mehr),
damit die Live-Seite nach dem Schedule aktuell ist.
- [x] Owners (1-2 Ansprechpartner) pro Tool/Quelle -> Frontmatter/Katalog/Pages.
- [x] Issue-/MR-Templates, CODEOWNERS, `docs/SETUP.md`, scm-info.yaml,
Architektur-Steering mit Diagrammen.
- [x] Pages: einheitliches Sticky-Menu auf allen Seiten (Logo + Uebersicht/
Wissensquellen/Chatbot/Hilfe + CTA), Zaehler (Dokumente, Quellen); Ziel prominent;
Strategien gruppiert/vereinfacht; Konfig-Transparenzseite (config.html).
- [x] Pages: **Domaenen-Filter** auf der Uebersicht (Dropdown mit Anzahl je Domaene).
- [x] CI: MR-Vorschau ueber GitLab-Pages parallel deployments (`pages.path_prefix`),
Produktion auf Root.
+306
View File
@@ -0,0 +1,306 @@
# 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/`.