205 lines
8.6 KiB
Markdown
205 lines
8.6 KiB
Markdown
# 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)*
|