mindnet/docs/chunking_strategy.md

205 lines
8.6 KiB
Markdown
Raw Permalink Normal View History

2025-09-09 12:22:04 +02:00
# 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": "<chunk_id|note_id>",
"target_id": "<note_id|chunk_id>",
"scope": "chunk" | "note",
"note_id": "<owner-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)*