docs/chunking_strategy.md aktualisiert
Some checks failed
Deploy mindnet to llm-node / deploy (push) Failing after 2s

This commit is contained in:
Lars 2025-09-09 12:22:04 +02:00
parent c9cbbbfcb9
commit ec346905ed

View File

@ -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 250400 Tokens; Overlap 4060 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 4060 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**: 250400 Tokens (Richtwert, abhängig vom `type`).
- **Maxgröße**: 600800 Tokens (nur in Ausnahmen).
- **Mingröße**: 80120 Tokens (sonst mit Nachbar verschmelzen).
- **Overlap**: 4060 Tokens, um Kontextverlust zu vermeiden.
- **Kontextabhängig**:
- `concept`, `thought`, `experience`: 250350 Tokens
- `task`, `project`: eher 200300 Tokens (mehr Bullet-Struktur)
- `journal`: 300400 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)*