From ec346905edbbcfdc7a901d9141cd1211bfd33168 Mon Sep 17 00:00:00 2001 From: Lars Date: Tue, 9 Sep 2025 12:22:04 +0200 Subject: [PATCH] docs/chunking_strategy.md aktualisiert --- docs/chunking_strategy.md | 247 ++++++++++++++++++++++++++++++-------- 1 file changed, 196 insertions(+), 51 deletions(-) diff --git a/docs/chunking_strategy.md b/docs/chunking_strategy.md index 543f92e..5f998ad 100644 --- a/docs/chunking_strategy.md +++ b/docs/chunking_strategy.md @@ -1,59 +1,204 @@ -# Chunking-Strategie für mindnet +# WP-02 Chunking & Embedding-Strategie +**Projekt:** mindnet Wissensnetzwerk +**Bezug:** `knowledge_design.md` (IDs, Dateinamen, Tags, Edge-Typen, Abschnitts-Konventionen) -## Ziel -Die Chunking-Strategie sorgt dafür, dass Inhalte aus Markdown-Dateien verlustfrei, sinnvoll segmentiert und mit stabilen IDs in Qdrant abgelegt werden. Jeder Chunk soll semantisch sinnvoll abgegrenzt sein und eine optimale Balance aus Kontexttiefe und Abrufbarkeit für LLM-Abfragen bieten. +--- -## Grundprinzipien -- **Chunkgrößen**: Zielbereich 250–400 Tokens; Overlap 40–60 Tokens. -- **Semantische Schnitte**: Chunks enden an Absätzen, Überschriften, Listenpunkten, Codeblöcken oder Zitaten. -- **Frontmatter**: YAML-Frontmatter wird **nicht** eingebettet, sondern vollständig im Note-Payload gespeichert. -- **Verlustfreiheit**: Originaltext bleibt durch `note.payload.fulltext` erhalten; Chunks enthalten zusätzlich `chunk.payload.text`. +## 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`). -## Typ-Spezifische Regeln -- **Notes (Konzepttexte, Pläne, Tagebücher)**: Chunk an Überschriften + Absätzen. -- **Checklisten / Listen**: jeder Abschnitt wird zu eigenem Chunk. -- **Codeblöcke**: immer geschlossen innerhalb eines Chunks. -- **Zitate**: als eigener Chunk, wenn mehr als 2 Zeilen. +--- -## Overlaps -- Overlap von 40–60 Tokens verhindert semantische Brüche zwischen Chunks. -- Overlap gilt **nur für den Body**, nicht für die YAML-Frontmatter. +## 2) Annahmen aus `knowledge_design.md` +- **IDs**: deterministische, stabile `id` pro Note (Slug / semantischer Identifier), ggf. `aliases`. +- **Dateinamen**: entweder `YYYY-MM-DD_title.md` oder `slug.md`; Pfade sind **relativ** zum Vault. +- **Frontmatter-Pflichtfelder**: `id`, `title`, `type`, `created`, `updated` (Details in `knowledge_design.md`). +- **Backlink-/Link-Regeln**: Wikilinks `[[id]]` im Body; am Ende optional redaktionelle Sektion „Mögliche Verbindungen“ (erzeugt **keine** Kanten). -## Edge-Modell (Graph) -- **Chunk → Note**: `belongs_to(note_id)` (immer; Owner-Note im Payload) -- **Chunk → Chunk**: `prev`, `next` (symmetrisch, für lineare Navigation) -- **Chunk → Note**: `references(target_id)` aus `[[Wikilinks]]` oder Links im Text -- **Note → Note**: `backlink(target_id)` automatisch generiert, dedupliziert -- **Optional**: `references:note` (nur wenn per Flag/ENV aktiviert, Default = aus) -- **Unresolved**: Kanten mit `status=unresolved`, wenn Ziel noch nicht existiert +--- -## Payload-Erweiterungen -- Jeder Chunk speichert: - - `text` (Originalchunk) - - `note_id` (Owner) - - Metadaten (`chunk_index`, `section_title`, `neighbors.prev/next`) -- Jede Note speichert: - - `fulltext` (verlustfreier Body) - - `path` (relativ zum Vault) - - `references` (Liste von Slugs, als Fallback für Kantenbildung) +## 3) Chunking-Prinzipien +- **Semantische Schnitte**: Chunks enden an Absätzen, Überschriften, Listenpunkten, Codeblöcken, Zitaten. +- **Frontmatter**: YAML **nicht** embedden; stattdessen komplett im Note-Payload speichern. +- **Verlustfreiheit**: + - Note-Payload: `fulltext` (gesamter Body). + - Chunk-Payload: `text` (Originalchunk). + Damit kann der Export den Body **verlustfrei** rekonstruieren (erst `fulltext`, sonst Chunks). -## Performance -- Qdrant-Payload-Indizes für `note_id`, `kind`, `scope`, `source_id`, `target_id`. -- Materialisierte Nachbarschaften optional in `note.payload.neighbors` für direkte 1-Hop-Abfragen. +--- -## Beispiel (Chunk-Payload) -```yaml -id: concept-alpha#1 -note_id: concept-alpha -note_title: "Concept Alpha" -chunk_index: 1 -section_title: "Einleitung" -text: "Dies ist der Text des Chunks …" -wikilinks: ["concept-beta"] -references: - - kind: wikilink - target_id: concept-beta -neighbors: - prev: null - next: concept-alpha#2 -path: area/concepts/concept-alpha.md +## 4) Optimale Chunkgrößen & Overlaps +- **Zielgröße**: 250–400 Tokens (Richtwert, abhängig vom `type`). +- **Maxgröße**: 600–800 Tokens (nur in Ausnahmen). +- **Mingröße**: 80–120 Tokens (sonst mit Nachbar verschmelzen). +- **Overlap**: 40–60 Tokens, um Kontextverlust zu vermeiden. +- **Kontextabhängig**: + - `concept`, `thought`, `experience`: 250–350 Tokens + - `task`, `project`: eher 200–300 Tokens (mehr Bullet-Struktur) + - `journal`: 300–400 Tokens (tagesabschnittsweise) + +--- + +## 4.1 Overlap-Regeln +- Overlap betrifft **nur den Body**; die YAML-Frontmatter bleibt **außerhalb**. +- Keine Overlaps, die Codeblöcke oder Tabellen „aufreißen“. Falls nötig: Chunk-Grenze an Blockgrenze ziehen. +- Bei reinen Listen: zusammenhängende Bullets gruppieren, ggf. kleinere Overlaps. + +--- + +## 5) Semantische Regeln je Strukturelement +- **Überschriften**: vorzugsweise an H2/H3 trennen; H1 meist Dokumenttitel → Teil der Frontmatter. +- **Absätze**: primäre Trennkandidaten. +- **Listen**: zusammenhängende Gruppe als ein Chunk, wenn sie einen semantischen Block bilden. +- **Codeblöcke**: niemals splitten; Code bleibt in einem Chunk. +- **Zitate**: ab 2+ Zeilen als eigener Chunk. +- **Bilder/Anhänge**: Referenzen im Text belassen; Pfade relativ halten. + +--- + +## 6) Normalisierung & Preprocessing +- **Zeilenenden**: `\r\n` → `\n`, trailing spaces trimmen (nur für Hashing, nicht für Anzeige). +- **Whitespace**: mehrfach-Blankzeilen reduzieren (optional, Hash-Modus beachten). +- **Links**: `[[Titel|id]]` zu `(display="Titel", target_id="id")` normalisieren. +- **Pfade**: `path` im Payload **zwingend relativ** (Backslashes → Slashes). + +--- + +## 7) Edge-Modell (Graph) +- **Chunk → Note:** `belongs_to(note_id)` (obligatorisch; Edge-Payload enthält zusätzlich `note_id` als Owner-Note). +- **Chunk → Chunk (gleiche Note):** `prev`, `next` (symmetrisch für lineare Navigation; beide Richtungen werden erzeugt). +- **Chunk → Note:** `references(target_id)` aus `[[Wikilinks]]` oder Markdown-Links im Chunk-Text (Standard; *Chunk-Scope*). +- **Note → Note:** `backlink(target_id)` automatisch generiert, dedupliziert pro Note (*Note-Scope*). +- **Optional:** `references:note` (Note-Scope-References) per Flag/ENV aktivierbar; **Default = aus**, um Doppelzählungen zu vermeiden. +- **Unresolved:** Wenn `target_id` nicht existiert, wird eine Kante mit `status: "unresolved"` angelegt (später durch Resolver/Agent heilbar). + +--- + +## 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": 1024, + "char_end": 1789, + "section_title": "Begründung / Details", + "section_path": "Begründung / Details", + "neighbors": {"prev": "…#c01", "next": "…#c03"}, + "wikilinks": ["concept-yaml-styleguide"], + "references": [{"kind":"wikilink", "target_id":"concept-yaml-styleguide"}], + "text": "… ursprünglicher Chunk-Text …" + } + } + +**Point (Note)** + + { + "id": "20250902-1830-thought-yaml-recall", + "vector": [/* optional Nullvektor oder Zentroid */], + "payload": { + "note_id": "20250902-1830-thought-yaml-recall", + "title": "Gedanke: Einheitliche YAML-Standards steigern Recall", + "type": "thought", + "status": "draft", + "created": "2025-09-02", + "updated": "2025-09-02", + "path": "10_thoughts/20250902-1830-thought-yaml-recall.md", + "tags": ["area/mindnet","type/thought","topic/yaml"], + "hash_fulltext": "sha256:…", + "fulltext": "… gesamter Body für verlustfreie Rekonstruktion …", + "references": ["concept-yaml-styleguide"] // Fallback für Note-Level-Backlinks + } + } + +**Edges (Payload; neues Schema)** + + { + "kind": "references" | "backlink" | "belongs_to" | "prev" | "next", + "source_id": "", + "target_id": "", + "scope": "chunk" | "note", + "note_id": "", // neu: für schnelles Filtern/Purge + "status": "ok" | "unresolved" // optional + } + +**Performance-Hinweis (Qdrant):** Für schnelle Filter/Deletes Payload-Indizes anlegen: **edges** → `kind`, `scope`, `source_id`, `target_id`, `note_id`; **chunks** → `note_id`, `chunk_index`; **notes** → `note_id`. + +--- + +## 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_md(group)) + + chunks = apply_overlap(raw_chunks, overlap_tokens=overlap(meta["type"])) + chunks = normalize_chunks(chunks) # trim, fix code fences, etc. + + # IDs & Nachbarn + for i, ch in enumerate(chunks, start=1): + ch.id = f"{meta['id']}#c{i:02d}" + ch.index = i + ch.neighbors_prev = chunks[i-2].id if i > 1 else None + ch.neighbors_next = chunks[i].id if i < len(chunks) else None + + # Wikilinks je Chunk + for ch in chunks: + ch.wikilinks = extract_wikilinks(ch.text) + + return chunks + +--- + +## 10) Einbettung (Embeddings) +- Modell: MiniLM/all-MiniLM-L6-v2 (384d) oder E5 (besseres Retrieval, ggf. multilingual). +- **Note-Embeddings** optional (Zentroid der Chunks). +- **Hashing für Change-Detection** (Importer): + - `MINDNET_HASH_MODE = body|frontmatter|body+frontmatter` (Default: `body`) + - `MINDNET_HASH_NORMALIZE = canonical|none` (Default: `canonical`) + +--- + +## 11) Qualitätssicherung +- **Zähler**: `notes/chunks/edges` je Lauf prüfen. +- **Edge-Invarianten**: + - `belongs_to == #Chunks` + - `next == prev == Σ(max(chunks_in_note-1, 0))` + - `references (chunk) == Σ(Chunk-Wikilinks)` + - `backlink == Σ(eindeutige Wikilinks je Note)` +- **Unresolved**: sollten durch Resolver-Jobs über Zeit abnehmen. +- **Roundtrip-Test**: Import → Export → Diff; exakt rekonstruierbare Bodies. + +--- + +## 12) Deliverables +- Code: Parser, Chunker, Importer, Exporter, Edges. +- Schemas: `note.schema.json`, Payload-Layouts. +- Tests: Mini-Vault (`scripts/make_test_vault.py`), Audit/Validate. + +--- + +## 13) Prompt für neues Chatfenster (kopieren & einfügen) +*(Unverändert)*