mindnet/docs/chunking_strategy.md
Lars f9aad4c7d4
All checks were successful
Deploy mindnet to llm-node / deploy (push) Successful in 3s
docs/chunking_strategy.md aktualisiert
2025-09-02 19:16:50 +02:00

215 lines
12 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.

# WP-02 Chunking & Embedding-Strategie
**Projekt:** mindnet Wissensnetzwerk
**Bezug:** `knowledge_design.md` (IDs, Dateinamen, Tags, Edge-Typen, Abschnitts-Konventionen)
---
## 1) Ziel & Rahmen
Entwickle eine robuste, parser-freundliche **Chunking-Strategie** für Markdown-Notizen, die:
- die **Frontmatter** (YAML) als Meta nutzt, aber nicht (standardmäßig) embedden lässt,
- den **Body** in semantisch sinnvolle Chunks zerlegt (Überschriften/Absätze/Listen),
- **Edges** zwischen Chunks und Notizen/Links erzeugt (u. a. `belongs_to`, `references`, `backlink`).
---
## 2) Annahmen aus `knowledge_design.md`
- **Stabile IDs** & Dateinamen-Schema (z. B. `YYYYMMDD-HHMM-type-slug` bzw. `type-slug`).
- **Pflichtfelder** in YAML: `title`, `id`, `type`, `status`, `created`, `tags` (empfohlene Felder ggf. mehr).
- **Abschnitts-Konventionen** je `type` (z. B. `## Zusammenfassung`, `## Kontext` …) → ideale **Chunk-Grenzen**.
- **Edge-Typen**: `belongs_to`, `references`, `backlink`, `depends_on`, `assigned_to`, `discussed_in`, `authored_by`, `related`. Für Chunking primär `belongs_to` & `references` (+ optionale `prev`/`next`).
---
## 3) Chunking-Prinzipien
1. **Frontmatter extrahieren** → als strukturiertes `metadata` (nicht in `text` des Chunks).
2. **Markdown AST** (oder strukturierter Parser) nutzen:
- Primäre Grenzen: **Überschriftenebene H2/H3** (H1 = Dokumenttitel).
- Sekundär: **Absätze**, **nummerierte/unnummerierte Listen** als atomare Einheiten, **Codeblöcke** als zusammenhängender Block (nicht splitten).
3. **Längensteuerung**: Semantik vor harter Länge. Wenn Abschnitt > Zielgröße, **weich segmentieren** entlang von Absätzen/Listenpunkten.
4. **Referenz-Extraktion**: `[[Wikilinks]]` + Markdown-Links → `references`. Backlinks werden downstream generiert.
5. **Kontext-Overlap**: Chunks erhalten **Überlappung** über Satz-/Absatzgrenzen, um Kohärenz beim Retrieval zu erhöhen.
---
## 4) Optimale Chunkgrößen & Overlaps
> Ziel: gutes Recall@k, wenig Fragmentierung, stabile Antwortqualität.
| Notiz-Typ (`type`) | Inhalt | Zielgröße (Tokens) | Max. Größe (Tokens) | Overlap (Tokens) | Hinweise |
|---|---|---:|---:|---:|---|
| `thought` | kurze Thesen/Ideen | 150250 | 300 | 3040 | meist 12 Chunks |
| `experience` | Kontext/Beobachtung/Interpretation | 250350 | 450 | 4060 | Trenne entlang der Template-Sektionen |
| `journal` | Tagebuch, episodisch | 200300 | 400 | 3050 | Tagesabschnitte bündeln |
| `task` | knapp, Checklisten | 120200 | 250 | 2030 | Listen nicht splitten |
| `project` | Scope/WPs/Risiken/Status | 300450 | 600 | 5070 | Pro Hauptsektion ein Chunk |
| `concept` | definierte Begriffe, Erklärungen | 250400 | 550 | 4060 | Definition separat halten |
| `source` | Metadaten + Auszüge/Notizen | 200350 | 500 | 3050 | Zitate als eigene Chunks (Urheberrecht) |
## 4.1 Overlap-Regeln
- Overlap **nie mitten im Satz** beenden → mindestens **ein kompletter Satz** wandert in den nächsten Chunk.
- An **Absatzgrenzen** ausrichten, wenn möglich.
- Bei **Listen** ggf. den vorherigen Punkt als Overlap mitführen (falls < 50 Tokens).
- Codeblöcke und Tabellen **nicht splitten**; wenn sie größer als `Max-Tokens` sind, als **eigener Chunk** belassen (Ausnahmefall zulassen).
**Daumenregel (zeichenzentriert):** ~4 Zeichen 1 Token 8001600 Zeichen Ziel; Overlap ~120240 Zeichen (≈ 3060 Tokens).
**Mindestanforderung:** Keine Trennung innerhalb von Sätzen. Sicherstellung, dass immer an Satzenden gechunct wird
**Default für mindnet:** **~300 Tokens** + **~50 Tokens Overlap**; per `type` feinjustieren (siehe Tabelle).
---
## 5) Semantische Regeln je Strukturelement
- **Überschriften (H2/H3)**: new chunk start; speichere `section_path` (z. B. `/Kontext/Beobachtung`).
- **Absätze**: Primäre Untereinheiten; wenn Zusammenfassung + Beispiel direkt folgen, zusammen lassen.
- **Listen**: Jeder **Listeneintrag** bleibt zusammen mit seinem einleitenden Satz (falls vorhanden). Große Listen in **logische Blöcke** (58 Items) splitten.
- **Codeblöcke**: Nicht splitten; ggf. separater Chunk, **aber** im Retrieval **downweighten** (optional).
- **Zitate/Blockquotes**: zusammenhalten; Quelle im `references_meta` anreichern (falls Link vorhanden).
- **Tabellen**: als ein Chunk; zusätzlich **Plain-Text-Extrakt** (Header + 12 Zeilen) in `summary` für Retriever, falls Modelle Tabellen schlechter verarbeiten.
---
## 6) Normalisierung & Preprocessing
- **Nicht embedden:** YAML-Frontmatter (aber spezifische Felder als `metadata.*` mitschreiben: `type`, `tags`, `area`, `project`, `priority`, `people`, `aliases`).
- **Bereinigen:** führende/trailing Spaces, Mehrfach-Leerzeilen 1; Unicode-NFKC; Zeilenenden normalisieren.
- **Bewahren:** Markdown-Semantik (Überschriften-Hashes, Listenpräfixe, Codefences) **im Text beibehalten**, damit RAG-Antworten sauber zitieren können.
- **Sprachhinweis:** `lang` heuristisch bestimmen (DE/EN/…); in `metadata.lang` ablegen später für sprachspezifische Embeddings nützlich.
- **Stop-Abschnitte:** Mögliche Verbindungen am Ende **separat** chunken (nur Links), um Link-Graph sauber zu extrahieren.
---
## 7) Edge-Modell (Graph)
- **Chunk Note:** `belongs_to(note_id)` (obligatorisch).
- **Chunk Chunk (gleiche Note):** `prev`, `next` (lineare Leserichtung).
- **Note/Chunk Note:** `references(target_id)` aus `[[Wikilinks]]` & Markdown-Links; optional `related` (schwach).
- **Backlink:** systemisch generiert: inverse Kante `backlink`.
- **Sektionen:** `has_section(note_id, section_path)` (optional, für Navigations-UI).
- **Task-Spezialfälle:** `depends_on`, `assigned_to(person-id)` bleiben **Note-Level** (nicht Chunk-Level).
---
## 8) Datenmodell (Qdrant-tauglich)
**Point (Chunk)**
{
"id": "20250902-1830-thought-yaml-recall#c02",
"vector": [/* embedding */],
"payload": {
"note_id": "20250902-1830-thought-yaml-recall",
"note_title": "Gedanke: Einheitliche YAML-Standards steigern Recall",
"path": "10_thoughts/20250902-1830-thought-yaml-recall.md",
"type": "thought",
"area": "mindnet",
"project": "project-mindnet",
"tags": ["area/mindnet","type/thought","topic/yaml"],
"chunk_index": 2,
"char_start": 1240,
"char_end": 2012,
"token_count": 305,
"section_title": "Begründung / Details",
"section_path": "/Begründung / Details",
"lang": "de",
"wikilinks": ["concept-vektorsuche-qdrant","source-obsidian-properties"],
"external_links": [],
"references": [
{"target_id": "concept-vektorsuche-qdrant", "kind": "wikilink"}
],
"neighbors": {"prev": "#c01", "next": "#c03"},
"created_at": "2025-09-02T18:35:00+02:00"
}
}
**Collection-Einstellungen (Empfehlung)**
- `distance`: `Cosine` (gemischte Textlängen)
- `vectors`: 3841024 Dim (abh. Embedding-Modell)
- `hnsw_ef_construct`: 128256; `m`: 1632 (Workload-abhängig)
---
## 9) Algorithmus (Python-ready Outline)
def chunk_markdown_note(md_text: str, file_path: str) -> list[Chunk]:
fm, body = split_frontmatter(md_text) # YAML -> dict
meta = normalize_frontmatter(fm) # enforce schema defaults
ast = parse_markdown_to_ast(body) # any mdast-compatible lib
sections = split_by_headings(ast, levels=(2,3)) # H2/H3 primary
raw_chunks = []
for sec in sections:
blocks = group_blocks(sec, keep_code=True, keep_lists=True)
for group in soft_wrap(blocks, target_tokens=target_size(meta["type"]),
max_tokens=max_size(meta["type"])):
raw_chunks.append(render_markdown(group))
chunks = apply_overlap(raw_chunks, overlap_tokens=overlap_size(meta["type"]))
chunks = trim_whitespace(chunks)
# link extraction
for i, ch in enumerate(chunks):
ch.payload.update({
"wikilinks": extract_wikilinks(ch.text),
"external_links": extract_md_links(ch.text),
"references": [{"target_id": t, "kind": "wikilink"} for t in extract_wikilinks(ch.text)],
"neighbors": {"prev": f"#c{i-1}" if i>0 else None,
"next": f"#c{i+1}" if i<len(chunks)-1 else None}
})
# attach metadata
for idx, ch in enumerate(chunks):
ch.id = f'{meta["id"]}#c{idx:02d}'
ch.payload |= minimal_payload_from_meta(meta, file_path, idx, ch)
return chunks
**Hinweise zur Implementierung**
- **Tokenizer**: gleiche Tokenizer-Funktion für **Längensteuerung** und **Embedding** nutzen (Leak vermeiden).
- **Overlap** implementieren auf **Satz-/Absatzebene**; notfalls anhand Token-Fenster (kein Wort-Cut).
- **Heuristik** für `target_size`/`overlap_size` per `type` (siehe Tabelle).
- **Später**: Re-Chunking ermöglichen (idempotente Re-Runs), Chunk-IDs stabil halten (Index + Hash über `section_path + first_64_tokens`).
---
## 10) Einbettung (Embeddings)
- **Standard:** nur `chunk.text` embedden (ohne YAML), **aber**:
- `aliases`, `title` können als **synthetischer Kontext** *vorangestellt* werden (konkateniert, durch Trennzeichen), falls Recall für Synonyme wichtig ist.
- **Mehrsprachigkeit:** pro Chunk die Sprache detektieren; ggf. **mehrsprachige Modelle**/Pipelines verwenden.
- **Code-Gewichtung:** optional zweite Vektor-Spur für Code (separate Collection) falls viel Technik.
---
## 11) Qualitätssicherung
- **Unit-Tests**: Gold-Notes mit erwarteten Section-Splits und Link-Graph.
- **Metrics**: Retrieval-Eval (nDCG@k, Recall@k) auf internen QA-Fragen.
- **Linting**: Pre-commit prüft Pflichtfelder & Link-Integrität (IDs, Dateinamen, Wikilinks).
---
## 12) Deliverables
1. **Python-Modul** `mindnet_chunker/` (Parser, Chunker, Link-Extractor, Qdrant-Client).
2. **CLI** `mindnet-chunk` (stdinstdout, oder DateiNDJSON).
3. **Docs**: README mit Schema & Beispielen; Tests + Fixtures.
---
## 13) Prompt für neues Chatfenster (kopieren & einfügen)
**Projekt:** mindnet Wissensnetzwerk
**Rolle:** Du bist mein Assistent für NLP-Strategien.
**Ziel:** Hilf mir, eine robuste Chunking-Strategie für Markdown-Notizen zu entwickeln, die später in Qdrant gespeichert werden.
**Anforderungen:**
- Nutze das YAML-Frontmatter-Schema und die Strukturregeln aus `knowledge_design.md`. (Pflichtfelder, Abschnitts-Konventionen, Edge-Typen)
- Definiere optimale Chunkgrößen (Tokens) nach Notiz-Typ, inkl. **Overlap-Empfehlungen**.
- Beschreibe, wie **logische Abschnitte** (Überschriften/Absätze/Listen/Code) als Grenzen dienen.
- Erkläre das **Edge-Modell** zwischen **Chunks** und **Notizen** (`belongs_to`, `references`, `backlink`, `prev/next`).
- Ergebnis: ein **konkretes Konzept + Python-Outline**, das ich direkt implementieren kann (inkl. Datenmodell für Qdrant).
**Kontext zum System:**
- IDs/Dateinamen/Tags gemäß `knowledge_design.md`.
- Zielgröße pro Chunk ~**300 Tokens**, **Overlap ~50 Tokens** als Default; per `type` feinjustiert (Tabelle).
- YAML wird **nicht** in die Embeddings gemischt; ausgewählte Felder als `payload`.
Bitte liefere:
1) Zusammenfassung der Strategie,
2) Tabelle mit Größen/Overlaps,
3) Python-Pseudocode für Chunker,
4) Qdrant-Payload-Beispiel,
5) Test-Plan kurz.