docs/chunking_strategy.md aktualisiert
Some checks failed
Deploy mindnet to llm-node / deploy (push) Failing after 2s
Some checks failed
Deploy mindnet to llm-node / deploy (push) Failing after 2s
This commit is contained in:
parent
c9cbbbfcb9
commit
ec346905ed
|
|
@ -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
|
## 1) Ziel & Rahmen
|
||||||
- **Chunkgrößen**: Zielbereich 250–400 Tokens; Overlap 40–60 Tokens.
|
Entwickle eine robuste, parser-freundliche **Chunking-Strategie** für Markdown-Notizen, die:
|
||||||
- **Semantische Schnitte**: Chunks enden an Absätzen, Überschriften, Listenpunkten, Codeblöcken oder Zitaten.
|
- die **Frontmatter** (YAML) als Meta nutzt, aber nicht (standardmäßig) embedden lässt,
|
||||||
- **Frontmatter**: YAML-Frontmatter wird **nicht** eingebettet, sondern vollständig im Note-Payload gespeichert.
|
- den **Body** in semantisch sinnvolle Chunks zerlegt (Überschriften/Absätze/Listen),
|
||||||
- **Verlustfreiheit**: Originaltext bleibt durch `note.payload.fulltext` erhalten; Chunks enthalten zusätzlich `chunk.payload.text`.
|
- **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
|
## 2) Annahmen aus `knowledge_design.md`
|
||||||
- Overlap von 40–60 Tokens verhindert semantische Brüche zwischen Chunks.
|
- **IDs**: deterministische, stabile `id` pro Note (Slug / semantischer Identifier), ggf. `aliases`.
|
||||||
- Overlap gilt **nur für den Body**, nicht für die YAML-Frontmatter.
|
- **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
|
## 3) Chunking-Prinzipien
|
||||||
- Jeder Chunk speichert:
|
- **Semantische Schnitte**: Chunks enden an Absätzen, Überschriften, Listenpunkten, Codeblöcken, Zitaten.
|
||||||
- `text` (Originalchunk)
|
- **Frontmatter**: YAML **nicht** embedden; stattdessen komplett im Note-Payload speichern.
|
||||||
- `note_id` (Owner)
|
- **Verlustfreiheit**:
|
||||||
- Metadaten (`chunk_index`, `section_title`, `neighbors.prev/next`)
|
- Note-Payload: `fulltext` (gesamter Body).
|
||||||
- Jede Note speichert:
|
- Chunk-Payload: `text` (Originalchunk).
|
||||||
- `fulltext` (verlustfreier Body)
|
Damit kann der Export den Body **verlustfrei** rekonstruieren (erst `fulltext`, sonst Chunks).
|
||||||
- `path` (relativ zum Vault)
|
|
||||||
- `references` (Liste von Slugs, als Fallback für Kantenbildung)
|
|
||||||
|
|
||||||
## 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)
|
## 4) Optimale Chunkgrößen & Overlaps
|
||||||
```yaml
|
- **Zielgröße**: 250–400 Tokens (Richtwert, abhängig vom `type`).
|
||||||
id: concept-alpha#1
|
- **Maxgröße**: 600–800 Tokens (nur in Ausnahmen).
|
||||||
note_id: concept-alpha
|
- **Mingröße**: 80–120 Tokens (sonst mit Nachbar verschmelzen).
|
||||||
note_title: "Concept Alpha"
|
- **Overlap**: 40–60 Tokens, um Kontextverlust zu vermeiden.
|
||||||
chunk_index: 1
|
- **Kontextabhängig**:
|
||||||
section_title: "Einleitung"
|
- `concept`, `thought`, `experience`: 250–350 Tokens
|
||||||
text: "Dies ist der Text des Chunks …"
|
- `task`, `project`: eher 200–300 Tokens (mehr Bullet-Struktur)
|
||||||
wikilinks: ["concept-beta"]
|
- `journal`: 300–400 Tokens (tagesabschnittsweise)
|
||||||
references:
|
|
||||||
- kind: wikilink
|
---
|
||||||
target_id: concept-beta
|
|
||||||
neighbors:
|
## 4.1 Overlap-Regeln
|
||||||
prev: null
|
- Overlap betrifft **nur den Body**; die YAML-Frontmatter bleibt **außerhalb**.
|
||||||
next: concept-alpha#2
|
- Keine Overlaps, die Codeblöcke oder Tabellen „aufreißen“. Falls nötig: Chunk-Grenze an Blockgrenze ziehen.
|
||||||
path: area/concepts/concept-alpha.md
|
- 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)*
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user