All checks were successful
Deploy mindnet to llm-node / deploy (push) Successful in 4s
8.6 KiB
8.6 KiB
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
idpro Note (Slug / semantischer Identifier), ggf.aliases. - Dateinamen: entweder
YYYY-MM-DD_title.mdoderslug.md; Pfade sind relativ zum Vault. - Frontmatter-Pflichtfelder:
id,title,type,created,updated(Details inknowledge_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 (erstfulltext, sonst Chunks).
- Note-Payload:
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 Tokenstask,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:
pathim Payload zwingend relativ (Backslashes → Slashes).
7) Edge-Modell (Graph)
- Chunk → Note:
belongs_to(note_id)(obligatorisch; Edge-Payload enthält zusätzlichnote_idals 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_idnicht existiert, wird eine Kante mitstatus: "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/edgesje Lauf prüfen. - Edge-Invarianten:
belongs_to == #Chunksnext == 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)