11 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
- Stabile IDs & Dateinamen-Schema (z. B.
YYYYMMDD-HHMM-type-slugbzw.type-slug). - Pflichtfelder in YAML:
title,id,type,status,created,tags(empfohlene Felder ggf. mehr). - Abschnitts-Konventionen je
type(z. B.## Zusammenfassung,## Kontext…) → ideale Chunk-Grenzen. - Edge-Typen:
belongs_to,references,backlink,depends_on,assigned_to,discussed_in,authored_by,related. Für Chunking primärbelongs_to&references(+ optionaleprev/next).
3) Chunking-Prinzipien
- Frontmatter extrahieren → als strukturiertes
metadata(nicht intextdes Chunks). - Markdown AST (oder strukturierter Parser) nutzen:
- Primäre Grenzen: Überschriftenebene H2/H3 (H1 = Dokumenttitel).
- Sekundär: Absätze, nummerierte/unnummerierte Listen als atomare Einheiten, Codeblöcke als zusammenhängender Block (nicht splitten).
- Längensteuerung: Semantik vor harter Länge. Wenn Abschnitt > Zielgröße, weich segmentieren entlang von Absätzen/Listenpunkten.
- Referenz-Extraktion:
[[Wikilinks]]+ Markdown-Links →references. Backlinks werden downstream generiert. - Kontext-Overlap: Chunks erhalten Überlappung über Satz-/Absatzgrenzen, um Kohärenz beim Retrieval zu erhöhen.
4) Optimale Chunkgrößen & Overlaps
Ziel: gutes Recall@k, wenig Fragmentierung, stabile Antwortqualität.
Notiz-Typ (type) |
Inhalt | Zielgröße (Tokens) | Max. Größe (Tokens) | Overlap (Tokens) | Hinweise |
|---|---|---|---|---|---|
thought |
kurze Thesen/Ideen | 150–250 | 300 | 30–40 | meist 1–2 Chunks |
experience |
Kontext/Beobachtung/Interpretation | 250–350 | 450 | 40–60 | Trenne entlang der Template-Sektionen |
journal |
Tagebuch, episodisch | 200–300 | 400 | 30–50 | Tagesabschnitte bündeln |
task |
knapp, Checklisten | 120–200 | 250 | 20–30 | Listen nicht splitten |
project |
Scope/WPs/Risiken/Status | 300–450 | 600 | 50–70 | Pro Hauptsektion ein Chunk |
concept |
definierte Begriffe, Erklärungen | 250–400 | 550 | 40–60 | Definition separat halten |
source |
Metadaten + Auszüge/Notizen | 200–350 | 500 | 30–50 | Zitate als eigene Chunks (Urheberrecht) |
Daumenregel (zeichenzentriert): ~4 Zeichen ≈ 1 Token → 800–1600 Zeichen Ziel; Overlap ~120–240 Zeichen (≈ 30–60 Tokens).
Default für mindnet: ~300 Tokens + ~50 Tokens Overlap; per type feinjustieren (siehe Tabelle).
5) Semantische Regeln je Strukturelement
- Überschriften (H2/H3): new chunk start; speichere
section_path(z. B./Kontext/Beobachtung). - Absätze: Primäre Untereinheiten; wenn Zusammenfassung + Beispiel direkt folgen, zusammen lassen.
- Listen: Jeder Listeneintrag bleibt zusammen mit seinem einleitenden Satz (falls vorhanden). Große Listen → in logische Blöcke (5–8 Items) splitten.
- Codeblöcke: Nicht splitten; ggf. separater Chunk, aber im Retrieval downweighten (optional).
- Zitate/Blockquotes: zusammenhalten; Quelle im
references_metaanreichern (falls Link vorhanden). - Tabellen: als ein Chunk; zusätzlich Plain-Text-Extrakt (Header + 1–2 Zeilen) in
summaryfür Retriever, falls Modelle Tabellen schlechter verarbeiten.
6) Normalisierung & Preprocessing
- Nicht embedden: YAML-Frontmatter (aber spezifische Felder als
metadata.*mitschreiben:type,tags,area,project,priority,people,aliases). - Bereinigen: führende/trailing Spaces, Mehrfach-Leerzeilen → 1; Unicode-NFKC; Zeilenenden normalisieren.
- Bewahren: Markdown-Semantik (Überschriften-Hashes, Listenpräfixe, Codefences) im Text beibehalten, damit RAG-Antworten sauber zitieren können.
- Sprachhinweis:
langheuristisch bestimmen (DE/EN/…); inmetadata.langablegen → später für sprachspezifische Embeddings nützlich. - Stop-Abschnitte: „Mögliche Verbindungen“ am Ende separat chunken (nur Links), um Link-Graph sauber zu extrahieren.
7) Edge-Modell (Graph)
- Chunk → Note:
belongs_to(note_id)(obligatorisch). - Chunk → Chunk (gleiche Note):
prev,next(lineare Leserichtung). - Note/Chunk → Note:
references(target_id)aus[[Wikilinks]]& Markdown-Links; optionalrelated(schwach). - Backlink: systemisch generiert: inverse Kante
backlink. - Sektionen:
has_section(note_id, section_path)(optional, für Navigations-UI). - Task-Spezialfälle:
depends_on,assigned_to(person-id)bleiben Note-Level (nicht Chunk-Level).
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": 1240,
"char_end": 2012,
"token_count": 305,
"section_title": "Begründung / Details",
"section_path": "/Begründung / Details",
"lang": "de",
"wikilinks": ["concept-vektorsuche-qdrant","source-obsidian-properties"],
"external_links": [],
"references": [
{"target_id": "concept-vektorsuche-qdrant", "kind": "wikilink"}
],
"neighbors": {"prev": "#c01", "next": "#c03"},
"created_at": "2025-09-02T18:35:00+02:00"
}
}
Collection-Einstellungen (Empfehlung)
distance:Cosine(gemischte Textlängen)vectors: 384–1024 Dim (abh. Embedding-Modell)hnsw_ef_construct: 128–256;m: 16–32 (Workload-abhängig)
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_markdown(group))
chunks = apply_overlap(raw_chunks, overlap_tokens=overlap_size(meta["type"]))
chunks = trim_whitespace(chunks)
# link extraction
for i, ch in enumerate(chunks):
ch.payload.update({
"wikilinks": extract_wikilinks(ch.text),
"external_links": extract_md_links(ch.text),
"references": [{"target_id": t, "kind": "wikilink"} for t in extract_wikilinks(ch.text)],
"neighbors": {"prev": f"#c{i-1}" if i>0 else None,
"next": f"#c{i+1}" if i<len(chunks)-1 else None}
})
# attach metadata
for idx, ch in enumerate(chunks):
ch.id = f'{meta["id"]}#c{idx:02d}'
ch.payload |= minimal_payload_from_meta(meta, file_path, idx, ch)
return chunks
Hinweise zur Implementierung
- Tokenizer: gleiche Tokenizer-Funktion für Längensteuerung und Embedding nutzen (Leak vermeiden).
- Overlap implementieren auf Satz-/Absatzebene; notfalls anhand Token-Fenster (kein Wort-Cut).
- Heuristik für
target_size/overlap_sizepertype(siehe Tabelle). - Später: Re-Chunking ermöglichen (idempotente Re-Runs), Chunk-IDs stabil halten (Index + Hash über
section_path + first_64_tokens).
10) Einbettung (Embeddings)
- Standard: nur
chunk.textembedden (ohne YAML), aber:aliases,titlekönnen als synthetischer Kontext vorangestellt werden (konkateniert, durch Trennzeichen), falls Recall für Synonyme wichtig ist.
- Mehrsprachigkeit: pro Chunk die Sprache detektieren; ggf. mehrsprachige Modelle/Pipelines verwenden.
- Code-Gewichtung: optional zweite Vektor-Spur für Code (separate Collection) falls viel Technik.
11) Qualitätssicherung
- Unit-Tests: Gold-Notes mit erwarteten Section-Splits und Link-Graph.
- Metrics: Retrieval-Eval (nDCG@k, Recall@k) auf internen QA-Fragen.
- Linting: Pre-commit prüft Pflichtfelder & Link-Integrität (IDs, Dateinamen, Wikilinks).
12) Deliverables
- Python-Modul
mindnet_chunker/(Parser, Chunker, Link-Extractor, Qdrant-Client). - CLI
mindnet-chunk(stdin→stdout, oder Datei→NDJSON). - Docs: README mit Schema & Beispielen; Tests + Fixtures.
13) Prompt für neues Chatfenster (kopieren & einfügen)
Projekt: mindnet Wissensnetzwerk
Rolle: Du bist mein Assistent für NLP-Strategien.
Ziel: Hilf mir, eine robuste Chunking-Strategie für Markdown-Notizen zu entwickeln, die später in Qdrant gespeichert werden.
Anforderungen:
- Nutze das YAML-Frontmatter-Schema und die Strukturregeln aus
knowledge_design.md. (Pflichtfelder, Abschnitts-Konventionen, Edge-Typen) - Definiere optimale Chunkgrößen (Tokens) nach Notiz-Typ, inkl. Overlap-Empfehlungen.
- Beschreibe, wie logische Abschnitte (Überschriften/Absätze/Listen/Code) als Grenzen dienen.
- Erkläre das Edge-Modell zwischen Chunks und Notizen (
belongs_to,references,backlink,prev/next). - Ergebnis: ein konkretes Konzept + Python-Outline, das ich direkt implementieren kann (inkl. Datenmodell für Qdrant).
Kontext zum System:
- IDs/Dateinamen/Tags gemäß
knowledge_design.md. - Zielgröße pro Chunk ~300 Tokens, Overlap ~50 Tokens als Default; per
typefeinjustiert (Tabelle). - YAML wird nicht in die Embeddings gemischt; ausgewählte Felder als
payload.
Bitte liefere:
- Zusammenfassung der Strategie,
- Tabelle mit Größen/Overlaps,
- Python-Pseudocode für Chunker,
- Qdrant-Payload-Beispiel,
- Test-Plan kurz.