# 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` - **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). --- ## 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). --- ## 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)*