mindnet/docs/chunking_strategy.md
Lars ec346905ed
Some checks failed
Deploy mindnet to llm-node / deploy (push) Failing after 2s
docs/chunking_strategy.md aktualisiert
2025-09-09 12:22:04 +02:00

8.6 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

  • 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).

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).

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: edgeskind, scope, source_id, target_id, note_id; chunksnote_id, chunk_index; notesnote_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)