mindnet/docs/chunking_strategy.md
Lars f9aad4c7d4
All checks were successful
Deploy mindnet to llm-node / deploy (push) Successful in 3s
docs/chunking_strategy.md aktualisiert
2025-09-02 19:16:50 +02:00

12 KiB
Raw Blame History

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-slug bzw. 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är belongs_to & references (+ optionale prev/next).

3) Chunking-Prinzipien

  1. Frontmatter extrahieren → als strukturiertes metadata (nicht in text des Chunks).
  2. 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).
  3. Längensteuerung: Semantik vor harter Länge. Wenn Abschnitt > Zielgröße, weich segmentieren entlang von Absätzen/Listenpunkten.
  4. Referenz-Extraktion: [[Wikilinks]] + Markdown-Links → references. Backlinks werden downstream generiert.
  5. 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 150250 300 3040 meist 12 Chunks
experience Kontext/Beobachtung/Interpretation 250350 450 4060 Trenne entlang der Template-Sektionen
journal Tagebuch, episodisch 200300 400 3050 Tagesabschnitte bündeln
task knapp, Checklisten 120200 250 2030 Listen nicht splitten
project Scope/WPs/Risiken/Status 300450 600 5070 Pro Hauptsektion ein Chunk
concept definierte Begriffe, Erklärungen 250400 550 4060 Definition separat halten
source Metadaten + Auszüge/Notizen 200350 500 3050 Zitate als eigene Chunks (Urheberrecht)

4.1 Overlap-Regeln

  • Overlap nie mitten im Satz beenden → mindestens ein kompletter Satz wandert in den nächsten Chunk.
  • An Absatzgrenzen ausrichten, wenn möglich.
  • Bei Listen ggf. den vorherigen Punkt als Overlap mitführen (falls < 50 Tokens).
  • Codeblöcke und Tabellen nicht splitten; wenn sie größer als Max-Tokens sind, als eigener Chunk belassen (Ausnahmefall zulassen). Daumenregel (zeichenzentriert): ~4 Zeichen ≈ 1 Token → 8001600 Zeichen Ziel; Overlap ~120240 Zeichen (≈ 3060 Tokens).
    Mindestanforderung: Keine Trennung innerhalb von Sätzen. Sicherstellung, dass immer an Satzenden gechunct wird 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 (58 Items) splitten.
  • Codeblöcke: Nicht splitten; ggf. separater Chunk, aber im Retrieval downweighten (optional).
  • Zitate/Blockquotes: zusammenhalten; Quelle im references_meta anreichern (falls Link vorhanden).
  • Tabellen: als ein Chunk; zusätzlich Plain-Text-Extrakt (Header + 12 Zeilen) in summary fü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: lang heuristisch bestimmen (DE/EN/…); in metadata.lang ablegen → 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; optional related (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: 3841024 Dim (abh. Embedding-Modell)
  • hnsw_ef_construct: 128256; m: 1632 (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_size per type (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.text embedden (ohne YAML), aber:
    • aliases, title kö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

  1. Python-Modul mindnet_chunker/ (Parser, Chunker, Link-Extractor, Qdrant-Client).
  2. CLI mindnet-chunk (stdin→stdout, oder Datei→NDJSON).
  3. 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 type feinjustiert (Tabelle).
  • YAML wird nicht in die Embeddings gemischt; ausgewählte Felder als payload.

Bitte liefere:

  1. Zusammenfassung der Strategie,
  2. Tabelle mit Größen/Overlaps,
  3. Python-Pseudocode für Chunker,
  4. Qdrant-Payload-Beispiel,
  5. Test-Plan kurz.