docs/chunking_strategy.md hinzugefügt
All checks were successful
Deploy mindnet to llm-node / deploy (push) Successful in 3s
All checks were successful
Deploy mindnet to llm-node / deploy (push) Successful in 3s
This commit is contained in:
parent
e26493ef5a
commit
4df24f4c73
208
docs/chunking_strategy.md
Normal file
208
docs/chunking_strategy.md
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
# 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 | 150–250 | 300 | 30–40 | meist 1–2 Chunks |
|
||||||
|
| `experience` | Kontext/Beobachtung/Interpretation | 250–350 | 450 | 40–60 | Trenne entlang der Template-Sektionen |
|
||||||
|
| `journal` | Tagebuch, episodisch | 200–300 | 400 | 30–50 | Tagesabschnitte bündeln |
|
||||||
|
| `task` | knapp, Checklisten | 120–200 | 250 | 20–30 | Listen nicht splitten |
|
||||||
|
| `project` | Scope/WPs/Risiken/Status | 300–450 | 600 | 50–70 | Pro Hauptsektion ein Chunk |
|
||||||
|
| `concept` | definierte Begriffe, Erklärungen | 250–400 | 550 | 40–60 | Definition separat halten |
|
||||||
|
| `source` | Metadaten + Auszüge/Notizen | 200–350 | 500 | 30–50 | Zitate als eigene Chunks (Urheberrecht) |
|
||||||
|
|
||||||
|
**Daumenregel (zeichenzentriert):** ~4 Zeichen ≈ 1 Token → 800–1600 Zeichen Ziel; Overlap ~120–240 Zeichen (≈ 30–60 Tokens).
|
||||||
|
**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** (5–8 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 + 1–2 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`: 384–1024 Dim (abh. Embedding-Modell)
|
||||||
|
- `hnsw_ef_construct`: 128–256; `m`: 16–32 (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` (stdin→stdout, oder Datei→NDJSON).
|
||||||
|
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.
|
||||||
Loading…
Reference in New Issue
Block a user