diff --git a/docs/mindnet_technical_architecture.md b/docs/mindnet_technical_architecture.md index c413773..69a7a4f 100644 --- a/docs/mindnet_technical_architecture.md +++ b/docs/mindnet_technical_architecture.md @@ -1,699 +1,215 @@ -# Mindnet – Technische Architektur (V2, Stand: 2025-11-xx) +# Mindnet v2.2 – Technische Architektur +**Datei:** `docs/mindnet_technical_architecture_v2.2.md` +**Stand:** 2025-12-07 +**Status:** **FINAL** (Integrierter Stand WP01–WP04a) +**Quellen:** `mindnet_technical_architecture.md`, `chunking_strategy.md`, `Handbuch.md`, `wp04_retriever_scoring.md`, `Überarbeitungshinweise_WP03.md`, `Überarbeitungshinweise_WP04.md`. -> **Ziel dieses Dokuments:** -> Vollständige, konsolidierte Beschreibung der aktuellen technischen Architektur von **Mindnet V2** – insbesondere der Verarbeitung von Markdown-Notizen, des Chunkings, der Kantenableitung (Edges) inkl. `rule_id` / `confidence` und der Speicherung in Qdrant. -> Das Dokument bildet den **aktuellen Implementierungsstand** ab, nicht mehr gültige Annahmen sind bereinigt, neue Konzepte (Typ-Defaults, Inline-Relationen, Edges-Schema) sind integriert. +> **Ziel dieses Dokuments:** +> Vollständige, konsolidierte Beschreibung der aktuellen technischen Architektur von **Mindnet v2.2**. Es definiert die Datenstrukturen in Qdrant, die Verarbeitungspipelines (Importer, Chunker, Edges) und die Retrieval-Logik. Es bildet den **aktuellen Implementierungsstand** ab. --- ## 1. Systemüberblick -### 1.1 Zielbild - +### 1.1 Architektur-Zielbild Mindnet ist ein **persönliches Wissensnetz**. Technisch bedeutet das: +1. **Source:** Markdown-Notizen in einem Vault (Obsidian-kompatibel). +2. **Pipeline:** Ein Python-Importer transformiert diese in **Notes**, **Chunks** und **Edges**. +3. **Storage:** Speicherung in **Qdrant** (Vektor-Datenbank) in drei getrennten Collections. +4. **Service:** Eine FastAPI-Anwendung stellt Endpunkte für **Semantische** und **Hybride Suche** bereit. -- Markdown-Notizen in einem Vault (Obsidian-kompatibel) werden - - geparst (Frontmatter + Body), - - in **Notes**, **Chunks** und **Edges** überführt, - - in **Qdrant** als drei Collections abgelegt: - - `_notes` - - `_chunks` - - `_edges`. +Das System arbeitet **deterministisch** (stabile IDs) und ist **konfigurationsgetrieben** (`types.yaml`, `retriever.yaml`). -- Das System ist so konzipiert, dass es: - - **deterministisch** arbeitet (IDs, Chunks, Kanten), - - über `types.yaml` **konfigurierbar** ist, - - später von einem LLM-basieren Agenten als **Graph-Retriever** genutzt werden kann. +### 1.2 Verzeichnisstruktur & Komponenten +Die Codebasis ist modular aufgebaut. + + /mindnet/ + ├── app/ + │ ├── main.py # FastAPI Einstiegspunkt + │ ├── core/ + │ │ ├── qdrant.py # Client-Factory & Connection + │ │ ├── qdrant_points.py # Low-Level Point Operations (Upsert/Delete) + │ │ ├── note_payload.py # Bau der Note-Objekte + │ │ ├── chunk_payload.py # Bau der Chunk-Objekte + │ │ ├── chunker.py # Text-Zerlegung (Profiling) + │ │ ├── edges.py # Edge-Datenstrukturen + │ │ ├── derive_edges.py # Logik der Kantenableitung (WP03) + │ │ └── retriever.py # Scoring & Graph-Expansion (WP04) + │ ├── models/ # Pydantic DTOs (QueryRequest, etc.) + │ └── routers/ # API Routen + ├── config/ + │ ├── types.yaml # Typ-Definitionen (Import-Zeit) + │ └── retriever.yaml # Scoring-Gewichte (Laufzeit) + ├── scripts/ + │ ├── import_markdown.py # Haupt-Importer CLI + │ ├── payload_dryrun.py # Diagnose: JSON-Generierung ohne DB + │ └── edges_full_check.py # Diagnose: Graph-Integrität + └── tests/ # Pytest Suite --- -### 1.2 Verzeichnisstruktur (Server) +## 2. Datenmodell & Collections (Qdrant) + +Das Datenmodell verteilt sich auf drei Collections, definiert durch `COLLECTION_PREFIX` (Standard: `mindnet`). + +### 2.1 Notes Collection (`_notes`) +Repräsentiert die Metadaten einer Datei. +* **Zweck:** Filterung, Metadaten-Haltung, Vererbung von Eigenschaften an Chunks. +* **Schema (Payload):** + + | Feld | Datentyp | Beschreibung | Herkunft | + | :--- | :--- | :--- | :--- | + | `note_id` | Keyword | Stabile ID (UUIDv5 oder Slug). | Frontmatter | + | `title` | Text | Titel der Notiz. | Frontmatter | + | `type` | Keyword | Logischer Typ (z.B. `concept`). | `types.yaml` Resolver | + | `retriever_weight` | Float | Wichtigkeit im Retrieval. | `types.yaml` | + | `chunk_profile` | Keyword | Genutztes Chunking-Profil. | `types.yaml` | + | `edge_defaults` | List[Str] | Aktive Default-Relationen. | `types.yaml` | + | `tags` | List[Kw] | Tags zur Filterung. | Frontmatter | + | `updated` | Integer | Zeitstempel (z.B. YYYYMMDD...). | File Stats | + | `fulltext` | Text | Gesamter Inhalt (für Export/Rekonstruktion). | Body | + + + +### 2.2 Chunks Collection (`_chunks`) +Die atomaren Sucheinheiten. +* **Zweck:** Vektorsuche (Embeddings), Granulares Ergebnis. +* **Schema (Payload):** + + | Feld | Datentyp | Beschreibung | + | :--- | :--- | :--- | + | `chunk_id` | Keyword | Deterministisch: `{note_id}#c{index:02d}`. | + | `note_id` | Keyword | Referenz zur Note. | + | `text` | Text | **Reiner Inhalt** (ohne Overlap). Anzeige-Text. | + | `window` | Text | **Kontext-Fenster** (mit Overlap). Embedding-Basis. | + | `ord` | Integer | Sortierreihenfolge (1..N). | + | `retriever_weight` | Float | Vererbt von Note. | + | `chunk_profile` | Keyword | Vererbt von Note. | + | `neighbors_prev` | List[Str] | ID des Vorgänger-Chunks. | + | `neighbors_next` | List[Str] | ID des Nachfolger-Chunks. | + + + +### 2.3 Edges Collection (`_edges`) +Gerichtete Kanten. Massiv erweitert in WP03 für Provenienz-Tracking. +* **Zweck:** Graph-Traversal, Kontext-Anreicherung. +* **Schema (Payload):** + + | Feld | Datentyp | Beschreibung | Wertebereich (Bsp.) | + | :--- | :--- | :--- | :--- | + | `edge_id` | Keyword | Hash aus (src, dst, kind). | | + | `source_id` | Keyword | ID des Start-Chunks. | | + | `target_id` | Keyword | ID des Ziel-Chunks (oder Note-Titel). | | + | `note_id` | Keyword | Note, die diese Kante definiert. | | + | `kind` | Keyword | Art der Beziehung. | `references`, `depends_on`, `next` | + | `scope` | Keyword | Geltungsbereich. | Immer `"chunk"` (v2.2). | + | `rule_id` | Keyword | Herkunftsregel. | `explicit:wikilink`, `inline:rel` | + | `confidence` | Float | Vertrauenswürdigkeit (0.0-1.0). | 1.0, 0.95, 0.7 | -/home/llmadmin/mindnet/: -- **/app** - - `main.py` – FastAPI-App / Einstieg - - **/app/core** - - `qdrant.py` – QdrantConfig, Client-Factory, Collection-Management - - `qdrant_points.py` – Upserts / Deletes / Queries auf Punktebene - - `note_payload.py` – Bau von Note-Payloads - - `chunk_payload.py` – Bau von Chunk-Payloads - - `chunker.py` – Chunking-Logik - - `edges.py` – Datenstruktur & Hilfsfunktionen für Edges - - `derive_edges.py` – Ableitung von Edges aus Text und Typen - - **/app/models** - - `dto.py` – DTOs für API / Services - - **/app/routers** - - `qdrant_router.py` – API-Layer Richtung Qdrant - - **/app/services** - - `llm_ollama.py` – Anbindung an Ollama-LLM - - `embeddings_client.py` – Embedding-Service (z. B. Ollama / lokaler Dienst) -- **/config** - - `types.yaml` – Typdefinitionen inkl. Chunk-Profilen und Edge-Defaults -- **/scripts** - - `import_markdown.py` – Hauptimporter für Vault - - `reset_qdrant.py` – Collections löschen/neu anlegen (inkl. Indizes) - - `setup_mindnet_collections.py` – Collection-Setup (historisch / Hilfsskript) - - diverse Diagnose- und Testskripte, z. B. - - `payload_dryrun.py` - - `edges_full_check.py` -- **/tests** - - `test_edges_smoke.py` – einfache Edge-Smoke-Tests - - `test_edges_all.py` – umfassendere Checks (Counts, Plausibilität) - - `ensure_indexes_and_show.py`, `assert_payload_schema.py` – Schema/Index-Checks -- **/vault** - - produktives Vault (Obsidian) -- **/mindnet_v2_test_vault** - - Test-Vault für V2-Funktionen (Chunking, Edges, Typ-Defaults) --- -## 2. Datenmodell & Collections +## 3. Konfiguration -### 2.1 Note, Chunk, Edge – Begriffe - -- **Note** - Entspricht einer Markdown-Datei mit YAML-Frontmatter (`id`, `title`, `type`, …). - In Qdrant als ein Punkt in `_notes`. - -- **Chunk** - Ein Abschnitt einer Note (z. B. Absatz, Überschrifts-Block, Sliding Window). - In Qdrant als Punkt in `_chunks`. - -- **Edge** - Eine gerichtete Kante, die eine Beziehung zwischen: - - zwei Chunks, - - Chunk ↔ Note, - - oder (konzeptionell) Note ↔ Note beschreibt. - Gespeichert in `_edges`. - ---- - -### 2.2 Qdrant Collections (Stand V2) - -**Namenskonvention:** -Prefix aus ENV (`COLLECTION_PREFIX`, z. B. `mindnet`): - -- `mindnet_notes` -- `mindnet_chunks` -- `mindnet_edges` - -Die Collections werden über: - - ```bash - python3 -m scripts.reset_qdrant --mode wipe --prefix "$COLLECTION_PREFIX" - ``` - -**vollständig neu angelegt**, inkl. Payload-Indizes (Details in 6.1). - ---- - -### 2.3 Notes – Payload & Felder - -Minimale, indexierte Felder (Auszug, Schema laut `diag_payload_indexes`): - -- `note_id` (keyword) – stabile Note-ID (Frontmatter `id`) -- `title` (text) – Notiz-Titel -- `type` (keyword) – logischer Typ (z. B. `concept`, `project`, `profile`, `journal`, `source`) -- `tags` (keyword[]) – Tags aus Frontmatter -- `updated` (integer) – Zeitstempel (z. B. `YYYYMMDDHHMMSS` oder epoch; Implementierungsdetail) - -Weitere Felder (nicht alle indexiert, aber im Payload vorhanden): - -- `retriever_weight` (float) – wie stark diese Note/Chunks im Retrieval gewichtet werden -- `chunk_profile` (string) – Name des verwendeten Chunk-Profils -- `edge_defaults` (string[]) – Liste von Default-Relationen pro Typ (z. B. `["references","related_to"]`) -- `confidentiality`, `visibility`, weitere Metadaten aus Frontmatter - -Diese Felder werden in `app/core/note_payload.py` aus Frontmatter + `types.yaml` konstruiert. - ---- - -### 2.4 Chunks – Payload & Felder - -Indexierte Kernfelder: - -- `note_id` (keyword) – Referenz auf die übergeordnete Note -- `chunk_id` (keyword) – deterministische Chunk-ID: - `"{note_id}#c{index:02d}"` (z. B. `20251110-ollama-llm-9f0a12#c02`) -- `index` (integer) – 0-basiert; Position in der Note -- `type` (keyword) – Note-Typ (vom Note geerbt) -- `tags` (keyword[]) – Tags (vom Note geerbt) - -Weitere wichtige Payload-Felder: - -- `ord` (integer) – 1-basiertes Pendant zu `index` (für menschliche Lesbarkeit) -- `text` (string) – eigentlicher Chunk-Text (ggf. gereinigt/normalisiert) -- `window` (string) – Kontext-Fenster (z. B. Chunk ± Nachbarn) -- `retriever_weight` (float) – gültiger Wert aus `types.yaml` (oder Default) -- `chunk_profile` (string) – verwendetes Profil, z. B. `short`, `medium`, `long`, `by_heading` -- `neighbors_prev` (string[]) – Liste von `chunk_id`s direkter Vorgänger (i. d. R. 0 oder 1 ID) -- `neighbors_next` (string[]) – Liste von `chunk_id`s direkter Nachfolger - -Die Konstruktion erfolgt in `app/core/chunk_payload.py` auf Basis der Chunks aus `chunker.py` und der aufgelösten Typ-Defaults. - ---- - -### 2.5 Edges – Payload & Felder - -Indexierte Felder: - -- `note_id` (keyword) – Note, zu der der **Quell-Chunk** gehört -- `source_id` (keyword) – i. d. R. `chunk_id` des Quell-Chunks (z. B. `2025…#c00`) -- `target_id` (keyword) – Zielobjekt (z. B.: - - `chunk_id` eines anderen Chunks - - Note-Titel (`"Embeddings 101"`) bei **konzeptionellen Referenzen** -- `kind` (keyword) – Kantenart: - - Struktur: `belongs_to`, `next`, `prev` - - Explizit: `references` - - Inline: z. B. `similar_to`, `depends_on`, `related_to` - - Typ-Defaults: ebenfalls z. B. `depends_on`, `related_to` (aus `edge_defaults`) -- `scope` (keyword) – aktuell immer `"chunk"` (Kante wird auf Chunk-Ebene geführt) -- `chunk_id` (keyword) – `chunk_id` des Quell-Chunks - -Weitere Payload-Felder: - -- `rule_id` (string) – Herkunftsregel: - - Struktur: - - `structure:belongs_to` - - `structure:next` - - `structure:prev` - - Explizite Wiki-Links: - - `explicit:wikilink` - - Inline-Relationen: - - `inline:rel` - - Typ-Defaults aus `types.yaml`: - - `edge_defaults::` - z. B. `edge_defaults:concept:related_to` -- `confidence` (float) – Stärke / Vertrauensgrad: - - Struktur-Kanten: `1.0` - - Explizite Wiki-Links: `1.0` - - Inline-Relationen (`[[rel:… …]]`): z. B. `0.95` - - Typ-Defaults: z. B. `0.7` - -Edges werden in `app/core/derive_edges.py` erzeugt und in `mindnet_edges` gespeichert. - ---- - -## 3. Typ-Konfiguration (`config/types.yaml`) - -### 3.1 Struktur des Files - -`types.yaml` (vereinfachtes Beispiel): +Die Logik ist ausgelagert in YAML-Dateien. +### 3.1 Typ-Registry (`config/types.yaml`) +Steuert den Import-Prozess. +* **Priorität:** Frontmatter > Pfad > Default. +* **Inhalt:** ```yaml - version: 1.0 - default: - retriever_weight: 1.0 - chunk_profile: default - types: concept: chunk_profile: medium edge_defaults: ["references", "related_to"] - retriever_weight: 0.35 - - task: - chunk_profile: short - edge_defaults: ["depends_on", "belongs_to"] - retriever_weight: 0.8 - - experience: - chunk_profile: medium - edge_defaults: ["derived_from", "inspired_by"] - retriever_weight: 0.9 - - project: - chunk_profile: long - edge_defaults: ["references", "depends_on"] - retriever_weight: 0.97 - - profile: - chunk_profile: long - edge_defaults: ["references", "depends_on"] - retriever_weight: 0.66 + retriever_weight: 0.60 ``` -### 3.2 Typauflösung – Reihenfolge der Prioritäten - -Für eine Note mit `type` und optionalen Frontmatter Feldern `retriever_weight`, `chunk_profile` gilt aktuell: - -1. **Typ-spezifische Defaults** aus `types.yaml`: - - `retriever_weight` ← `types..retriever_weight` (falls vorhanden) - - `chunk_profile` ← `types..chunk_profile` (falls vorhanden) -2. **Globaler Default** `default` in `types.yaml`, falls Typ-Eintrag fehlt. -3. **Frontmatter `retriever_weight` / `chunk_profile`** werden aktuell *nicht* mehr als Konfigurationsebene verwendet, sondern eher als historische Relikte. (Dies kann zukünftig wieder aktiviert werden, ist aber im aktuellen Stand bewusst abgeschaltet zugunsten klarer, zentraler Konfiguration über `types.yaml`.) - -Das Ergebnis dieser Auflösung wird im Payload als: - -- `note_payload["retriever_weight"]` -- `note_payload["chunk_profile"]` - -gesetzt und **1:1 an alle Chunks vererbt**. +### 3.2 Retriever-Config (`config/retriever.yaml`) +Steuert das Scoring zur Laufzeit (WP04a). +* **Inhalt:** + ```yaml + scoring: + semantic_weight: 1.0 + edge_weight: 0.7 + centrality_weight: 0.5 + ``` --- ## 4. Import-Pipeline (Markdown → Qdrant) -### 4.1 Runbook +Das Skript `scripts/import_markdown.py` orchestriert den Prozess. -Standard-Import (mit Purge-Before-Upsert): +### 4.1 Verarbeitungsschritte - ```bash - export COLLECTION_PREFIX=mindnet - export MINDNET_TYPES_FILE="$(pwd)/config/types.yaml" - - python3 -m scripts.import_markdown \ - --vault ./mindnet_v2_test_vault \ - --apply \ - --purge-before-upsert \ - --prefix "$COLLECTION_PREFIX" - ``` - -Nur Sync-Deletes (entfernte Dateien in Qdrant aufräumen): - - ```bash - python3 -m scripts.import_markdown \ - --vault ./mindnet_v2_test_vault \ - --sync-deletes \ - --apply \ - --prefix "$COLLECTION_PREFIX" - ``` +1. **Discovery & Parsing:** + * Einlesen der `.md` Dateien. Hash-Vergleich (Body/Frontmatter) zur Erkennung von Änderungen. +2. **Typauflösung:** + * Laden der `types.yaml`. Bestimmen des effektiven Typs und der `edge_defaults`. +3. **Chunking:** + * Zerlegung via `chunker.py` basierend auf `chunk_profile` (z.B. `by_heading`, `short`, `long`). + * Trennung von `text` (Kern) und `window` (Embedding-Kontext). +4. **Kantenableitung (Edge Derivation):** + Die `derive_edges.py` erzeugt Kanten in strikter Reihenfolge: + 1. **Inline-Edges:** `[[rel:depends_on X]]` → `kind=depends_on`, `rule_id=inline:rel`, `conf=0.95`. + 2. **Callout-Edges:** `> [!edge] related_to: [[X]]` → `kind=related_to`, `rule_id=callout:edge`, `conf=0.90`. + 3. **Explizite Referenzen:** `[[X]]` → `kind=references`, `rule_id=explicit:wikilink`, `conf=1.0`. + 4. **Typ-Defaults:** Für jede Referenz werden Zusatzkanten gemäß `edge_defaults` erzeugt (z.B. `project` -> `depends_on`). `rule_id=edge_defaults:...`, `conf=0.7`. + 5. **Struktur:** `belongs_to`, `next`, `prev` (automatisch). +5. **Upsert:** + * Schreiben in Qdrant. Nutzung von `--purge-before-upsert` für saubere Updates. --- -### 4.2 Verarbeitungsschritte +## 5. Retriever-Architektur & Scoring -**1. Discovery & Parsing** +Der Retriever (`app/core/retriever.py`) realisiert die Logik hinter `/query`. -- `import_markdown.py` traversiert das Vault‐Verzeichnis. -- Für jede `.md`: - - YAML-Frontmatter extrahieren (id, title, type, created, updated, tags, …). - - Body (Markdown) als String. +### 5.1 Betriebsmodi +* **Semantic:** Reine Vektorsuche. Schnell. +* **Hybrid:** Vektorsuche + Graph-Expansion (Tiefe N) + Re-Ranking. -**2. Typauflösung & Payload-Bau (Note)** +### 5.2 Scoring-Formel (WP04a) +Die Relevanzberechnung ist nun eine gewichtete Summe: -- Laden & Auflösen von `types.yaml` (über ENV `MINDNET_TYPES_FILE` oder Default `./config/types.yaml`). -- Bestimmung des effektiven Typs (`type` aus Frontmatter; ggf. Fallback). -- Anwendung der Typdefaults: - - `retriever_weight` und `chunk_profile` werden aus `types.yaml` gelesen. -- Konstruktion des Note-Payloads via `make_note_payload` (in `note_payload.py`): - - Felder wie `note_id`, `title`, `type`, `tags`, `updated`, `retriever_weight`, `chunk_profile`, `edge_defaults`, … +$$ +TotalScore = (W_{sem} \cdot S_{sem} \cdot \max(W_{type}, 0)) + (W_{edge} \cdot B_{edge}) + (W_{cent} \cdot B_{cent}) +$$ -**3. Chunking** +* **Komponenten:** + * $S_{sem}$: Semantic Score (Cosine Similarity). + * $W_{type}$: `retriever_weight` (aus Note/Chunk Payload). + * $B_{edge}$: Edge-Bonus (Summe der Konfidenzen eingehender relevanter Kanten). + * $B_{cent}$: Centrality-Bonus (im lokalen Subgraphen). +* **Gewichte ($W$):** Stammen aus `retriever.yaml`. -- Übergabe des Note-Bodies + `chunk_profile` an `chunker.py`. -- `chunker.py` nutzt `chunk_config.py`, das definierte Profile bereitstellt, z. B.: - - ```python - CHUNK_PROFILES = { - "short": {"max_tokens": 128, "overlap": 32}, - "medium": {"max_tokens": 256, "overlap": 64}, - "long": {"max_tokens": 512, "overlap": 128}, - "by_heading": {"strategy": "by_heading", "max_chars": 2000}, - "default": {"max_tokens": 256, "overlap": 64}, - } - ``` - -- Ergebnis: Liste geordneter Chunks mit: - - `chunk_id` (`note_id#cXX`), - - `index` (0..N-1), - - `ord` (1..N), - - `text`, - - `window` (inkl. Nachbartexte), - - `neighbors_prev` / `neighbors_next`. - -**4. Chunk-Payloads** - -- In `chunk_payload.py` werden für jeden Chunk die Payloads gebaut: - - Vererbung von `note_id`, `type`, `tags`, `retriever_weight`, `chunk_profile`. - - Setzen von `index`, `ord`, `neighbors_prev`, `neighbors_next`. - - Persistieren von `text` und `window`. - -**5. Kantenableitung (Edges)** - -- `derive_edges.py` wird mit Note + Chunks + Typinformationen aufgerufen. -- Es entstehen mehrere Gruppen von Kanten: - 1. **Struktur-Kanten**: - - `belongs_to`: Chunk → Note - - `next`: Chunk[i] → Chunk[i+1] - - `prev`: Chunk[i] → Chunk[i-1] - 2. **Explizite Kanten aus Text**: - - Wiki-Links: `[[Target Title]]` → `kind="references"`, `rule_id="explicit:wikilink"`, `confidence=1.0`. - - Inline-Relationen: `[[rel:depends_on Embeddings 101]]`: - - `kind="depends_on"` - - `rule_id="inline:rel"` - - `confidence≈0.95` - - `target_id="Embeddings 101"` (Titel, nicht ID – Auflösung erfolgt später im Agenten). - 3. **Typ-basierte Defaults (`edge_defaults`)**: - - Aus `types.yaml` je `type`: Liste von Relations-Kinds, z. B.: - - `concept.edge_defaults = ["references","related_to"]` - - `project.edge_defaults = ["references","depends_on"]` - - Für jede explizite Referenz Quelle→Ziel (z. B. `references`, `similar_to`) werden **zusätzliche Kanten** erzeugt, z. B.: - - `edge_defaults:project:depends_on` - - `edge_defaults:concept:related_to` - - `confidence≈0.7`. - 4. **Deduplication**: - - Alle Kanten werden in einem Set anhand eines Keys wie - - `(kind, scope, source_id, target_id, rule_id)` - dedupliziert (idempotent). - -**6. Upsert nach Qdrant** - -- Punkte (Notes, Chunks, Edges) werden via `qdrant_points.py` in die entsprechenden Collections upserted. -- Option `--purge-before-upsert` sorgt dafür, dass alte Punkte mit denselben IDs entfernt werden, bevor neue geschrieben werden (stabiler Reimport). -- `--sync-deletes` entfernt Punkte, deren zugehörige Dateien im Vault nicht mehr existieren. +### 5.3 Graph-Expansion +Der Hybrid-Modus lädt dynamisch die Nachbarschaft der Top-K Vektor-Treffer ("Seeds") über `graph_adapter.expand`. Dies baut einen temporären `NetworkX`-Graphen im Speicher, auf dem Boni berechnet werden. --- -## 5. Edges im Detail – Semantik & Regeln +## 6. Indizes & Performance -### 5.1 Struktur-Kanten +Damit Qdrant performant bleibt, sind Payload-Indizes essenziell. -Erzeugt für **jede Note** mit >0 Chunks: +**Erforderliche Indizes:** +* **Notes:** `note_id`, `type`, `tags`. +* **Chunks:** `note_id`, `chunk_id`. +* **Edges:** `source_id`, `target_id`, `kind`, `scope`, `note_id`. -- **belongs_to** - - pro Chunk genau eine Kante: - - `source_id = chunk_id` - - `target_id = note_id` *oder* (konzeptionell) ein Note-Identifikator - - `kind = "belongs_to"` - - `scope = "chunk"` - - `rule_id = "structure:belongs_to"` - - `confidence = 1.0` -- **next / prev** - - für Index i (`0..N-2`): - - `next`: `chunk_i → chunk_{i+1}` - - `prev`: `chunk_{i+1} → chunk_i` - - `kind = "next"` bzw. `"prev"` - - `scope = "chunk"` - - `rule_id = "structure:next"` / `"structure:prev"` - - `confidence = 1.0` - -Tests (`tests.test_edges_smoke` / `tests.test_edges_all`) stellen sicher: - -- `belongs_to` == `#chunks` -- `next` == `prev` == `#chunks - 1` -- keine Duplikate der Strukturkanten. +Validierung erfolgt über `tests/ensure_indexes_and_show.py`. --- -### 5.2 Explizite Wiki-Links (`[[Target Title]]`) - -- Parser in `derive_edges.py` sucht im Chunk-Text nach Mustern: - - `[[Some Page]]` (kein `rel:` Präfix) -- Erzeugt Kanten: - - `kind = "references"` - - `scope = "chunk"` - - `source_id = chunk_id` - - `target_id = ""` (z. B. `"Vector DB Basics"`) - - `rule_id = "explicit:wikilink"` - - `confidence = 1.0` - -Diese Kanten bilden das **explizite Referenznetz** des Nutzers ab (Obsidian-typische Wikilinks). - ---- - -### 5.3 Inline-Relationen (`[[rel:… …]]`) - -Aktuell **implementiert und funktional** ist folgende Form: - -- **Format (aktuell unterstützt):** - - ```md - [[rel:depends_on Embeddings 101]] - [[rel:similar_to Vector DB Basics]] - [[rel:related_to Qdrant Vektordatenbank]] - ``` - -- Parsing-Strategie: - - Link-Inhalt wird gesplittet: - - Erster Token: `rel:depends_on` - - Rest: Zielbezeichnung (`Embeddings 101`) - - `kind` wird aus dem Teil `depends_on` extrahiert (z. B. `similar_to`, `related_to`). - - `target_id` wird aus dem Rest (meist ein Titel) abgeleitet. - -- Ergebnis-Kante: - - `kind = ""` - - `scope = "chunk"` - - `source_id = chunk_id` - - `target_id = ""` - - `rule_id = "inline:rel"` - - `confidence ≈ 0.95` - -- **Nicht unterstützt (Stand jetzt):** - - ```md - rel: depends_on [[Embeddings 101]] [[Vector DB Basics]] - ``` - - Diese Syntax wird derzeit **nicht** erkannt. - Multi-Target-Inline-Relationen (`rel: similar_to [[A]] [[B]]`) sind ein **offener Task**. - ---- - -### 5.4 Typ-basierte Default-Edges (`edge_defaults`) - -Zweck: - -- Für bestimmte Typen (vor allem `concept`, `project`, `profile`) sollen zusätzliche Kanten automatisch ergänzt werden, um das **Semantiknetz zu verdichten**, ohne dass der Nutzer jede Relation explizit pflegen muss. - -Konfiguration: - -- In `types.yaml` definiert, z. B.: - - ```yaml - types: - concept: - edge_defaults: ["references", "related_to"] - project: - edge_defaults: ["references", "depends_on"] - ``` - -Aktuelle Implementierung: - -- Für jede **explizite** Referenz (Wiki-Link oder Inline-Relation) von Chunk → Ziel: - - `kind="references"` (Wiki) oder die Inline-Relation (`depends_on`, `similar_to`, …) -- Auf Basis des Note-Typs (`type`) wird pro Default-Relation eine zusätzliche Kante erzeugt: - - Beispiel: - - - Note-Typ: `project` - - `edge_defaults: ["references","depends_on"]` - - Explizit: `references` von `Edge Index` → `Embeddings 101` - - Abgeleitete Kanten: - - `kind="references"`, `rule_id="edge_defaults:project:references"`, `confidence=0.7` - - `kind="depends_on"`, `rule_id="edge_defaults:project:depends_on"`, `confidence=0.7` - -- Symmetrische Kanten: - - Für einige Relations-Typen (z. B. `related_to`) wird zusätzlich eine **spiegelbildliche** Kante Ziel → Quelle erzeugt. - - Dies erklärt in Tests z. B. Paare wie: - - `related_to (Source=Ollama LLM, Target=Qdrant Vektordatenbank)` - - `related_to (Source=Qdrant Vektordatenbank, Target=Ollama LLM)` - -- Deduplication: - - Es wird verhindert, dass mehrfach identische Kanten geschrieben werden. - - Schlüssel: `(kind, scope, source_id, target_id, rule_id)`. - ---- - -### 5.5 `rule_id` & `confidence` – Bedeutung - -**`rule_id`** kodiert **Herkunft** und **Regel**, z. B.: - -- `structure:belongs_to`, `structure:next`, `structure:prev` -- `explicit:wikilink` -- `inline:rel` -- `edge_defaults::` - -**`confidence`** dient als späterer Gewichtungsfaktor im Retrieval / Ranking: - -- `1.0` → harte Struktur- oder explizite Kante (vom Nutzer direkt gesetzt) -- `0.95` → Inline-Relation (stark, aber nicht so „hart“ wie die reine Referenz) -- `0.7` → Typ-Default-Edges (abgeleitete Hypothesen aus Typwissen) - -Ein künftiger LLM-Agent kann daraus z. B.: - -- Kanten mit hoher `confidence` priorisieren, -- Default-Edges als schwächere Hinweise betrachten, -- Inline-Relationen gezielt als semantische Aussagen über die Beziehung interpretieren. - ---- - -## 6. Qdrant-Schema & Indizes - -### 6.1 Erstellung & Indizes - -Die Erstellung erfolgt über: - - ```bash - python3 -m scripts.reset_qdrant --mode wipe --prefix "$COLLECTION_PREFIX" - ``` - -Intern: - -- `qdrant.py` / `setup_mindnet_collections.py`: - - Collections anlegen, ggf. mit Vektor-Konfiguration (Single-Vector; Named-Vectors sind vorbereitet aber optional). -- `ensure_payload_indexes()`: - - Legt für jede Collection die relevanten Payload-Indizes an. - -Verifizierbar durch: - - ```bash - python3 -m tests.ensure_indexes_and_show - ``` - -Beispielauszug (bereits real so gesehen): - -- **mindnet_notes** - - ```json - { - "tags": { "data_type": "keyword", "points": 8 }, - "note_id":{ "data_type": "keyword", "points": 8 }, - "title": { "data_type": "text", "points": 8 }, - "updated":{ "data_type": "integer", "points": 0 }, - "type": { "data_type": "keyword", "points": 8 } - } - ``` - -- **mindnet_chunks** - - ```json - { - "note_id": { "data_type": "keyword", "points": 44 }, - "chunk_id":{ "data_type": "keyword", "points": 44 }, - "index": { "data_type": "integer", "points": 44 }, - "type": { "data_type": "keyword", "points": 44 }, - "tags": { "data_type": "keyword", "points": 44 } - } - ``` - -- **mindnet_edges** - - ```json - { - "scope": { "data_type": "keyword", "points": 135 }, - "kind": { "data_type": "keyword", "points": 135 }, - "chunk_id":{ "data_type": "keyword", "points": 135 }, - "target_id":{ "data_type": "keyword", "points": 135 }, - "note_id": { "data_type": "keyword", "points": 135 }, - "source_id":{ "data_type": "keyword", "points": 135 } - } - ``` - -### 6.2 Schema-Validierung - -`tests/assert_payload_schema.py` prüft: - -- ob die Minimalfelder (`note_id`, `type`, `title`, `updated`, `tags` etc.) vorhanden sind, -- berichtet fehlende Felder. - -Nach Korrektur der Indexanlage sollte: - -- `ok = true` sein, bzw. zumindest alle Kernfelder unter `present` geführt werden. - (Im aktuellen Stand werden sie als Indizes geführt; Qdrant speichert zusätzliche Felder ohne explizite Schema-Zeile, was technisch unproblematisch ist.) - ---- - -## 7. Nutzung im Retrieval (Ausblick) - -### 7.1 Typbewusstes Chunk-Retrieval - -Ein typischer Query-Pfad eines LLM-Agents: - -1. Query → Embedding -2. Suche in `mindnet_chunks` mit Filter, z. B.: - - ```python - f = rest.Filter( - must=[ - rest.FieldCondition(key="type", match=rest.MatchValue(value="concept")), - rest.FieldCondition(key="tags", match=rest.MatchAny(any=["ki","ollama"])) - ] - ) - ``` - -3. Treffer werden nach: - - Vektor-Score, - - `retriever_weight`, - - ggf. Edges (z. B. Projekte mit `depends_on` → Konzepte) gewichtet. - -### 7.2 Graph-orientierte Navigation - -Basierend auf `mindnet_edges` kann der Agent: - -- ausgehend von einem relevanten Chunk: - - Struktur-Kanten (`next`, `prev`) zur Kontextvergrößerung nutzen, - - `references` / `depends_on` / `related_to` interpretieren, - - `edge_defaults` als semantische Ausdehnung (z. B. von `project` zu seinen `concept`-Bausteinen) nutzen. - -Die Bedeutung der Kanten ist dabei durch `rule_id` und `confidence` klar nachvollziehbar. - ---- - -## 8. Offene Punkte / Known Limitations - -1. **Multi-Target Inline-Relationen** - Syntax wie: - - ```md - [[rel:similar_to Vector DB Basics]] [[rel:similar_to Embeddings 101]] - ``` - funktioniert (zwei separate Inline-Links). - **Nicht** unterstützt (Stand jetzt): - - ```md - [[rel:similar_to Vector DB Basics]] [[Embeddings 101]] - rel: similar_to [[Vector DB Basics]] [[Embeddings 101]] - ``` - - → Offener Task: Parser erweitern, um mehrere Ziele in einer Inline-Relation zu unterstützen. - -2. **Callout-Edges (`[!edge]`)** - - Konzeptionell vorgesehen, z. B.: - - ```md - > [!edge] related_to: [[Vector DB Basics]] - ``` - - - Im aktuellen Implementierungsstand werden Callouts entweder: - - gar nicht, oder - - nur teilweise verarbeitet (in den aktuellen Test-Runs war `callout_total = 0`). - → Offener Task: Callout-Parser robust implementieren und mit Tests absichern. - -3. **Frontmatter-Overrides für `retriever_weight` / `chunk_profile`** - - Aktuell **nicht** wirksam. - - Typdefaults aus `types.yaml` sind der **Single Source of Truth**. - - Später können kontrollierte Overrides (z. B. für Sonderfälle) eingeführt werden. - -4. **Vektor-Konfiguration für Edges** - - `mindnet_edges` wird derzeit ohne Vektoren betrieben (`vectors = null`). - - Später könnte z. B. eine semantische Repräsentation von Kanten eingeführt werden (z. B. Embedding von `(source_chunk_text + relation + target_title)`). - ---- - -## 9. Zusammenfassung - -- Die Mindnet-V2-Architektur implementiert einen **dreistufigen Speicher**: - - Notes (`mindnet_notes`), - - Chunks (`mindnet_chunks`), - - Edges (`mindnet_edges`). - -- **`types.yaml`** steuert: - - `retriever_weight` und `chunk_profile` je Typ (inkl. Default), - - `edge_defaults` je Typ (automatische Relations-Ableitung). - -- Das **Chunking** ist profilbasiert (short/medium/long/by_heading) und deterministisch. - -- Die **Kantenableitung** in `derive_edges.py` erzeugt: - - strukturelle Kanten (belongs_to / next / prev), - - explizite Kanten aus Wiki-Links (`[[Target]]`), - - semantische Inline-Relationen (`[[rel:depends_on Target]]`), - - typbasierte Default-Kanten (z. B. `edge_defaults:project:depends_on`). - -- `rule_id` und `confidence` machen die Herkunft jeder Kante transparent und ermöglichen später eine **gewichtete Auswertung** durch LLM-basierte Agenten. - -- Qdrant-Collections und Payload-Indizes sind konsistent eingerichtet und durch Tests verifiziert. - -Dieses Dokument bildet damit das aktuelle technische Gesamtbild von Mindnet V2 ab, inkl. aller relevanten Mechanismen rund um Typen, Chunking, Edges und Qdrant-Schema. - +## 7. Offene Punkte / Known Limitations + +1. **Multi-Target Inline-Relations:** + * `rel: similar_to [[A]] [[B]]` wird aktuell parser-seitig nicht unterstützt. + * Workaround: Zwei separate Inline-Links `[[rel:similar_to A]] [[rel:similar_to B]]`. +2. **Unresolved Targets:** + * Kanten zu Notizen, die noch nicht existieren, werden mit `target_id="Titel"` angelegt. + * Heilung durch `scripts/resolve_unresolved_references.py` möglich. +3. **Vektor-Konfiguration für Edges:** + * `mindnet_edges` hat aktuell keine Vektoren (`vectors = null`). Eine semantische Suche *auf Kanten* ist noch nicht möglich. \ No newline at end of file