diff --git a/app/core/ingestion.py b/app/core/ingestion.py index be690ba..ab2e46a 100644 --- a/app/core/ingestion.py +++ b/app/core/ingestion.py @@ -1,11 +1,10 @@ """ FILE: app/core/ingestion.py DESCRIPTION: Haupt-Ingestion-Logik. Liest Markdown, prüft Hashes (Change Detection), zerlegt in Chunks und schreibt in Qdrant. -VERSION: 2.5.2 +VERSION: 2.5.3 (Fix: Hash-Mode Full for Metadata Detection) STATUS: Active DEPENDENCIES: app.core.parser, app.core.note_payload, app.core.chunker, app.core.derive_edges, app.core.qdrant*, app.services.embeddings_client EXTERNAL_CONFIG: config/types.yaml -LAST_ANALYSIS: 2025-12-15 """ import os import logging @@ -94,7 +93,8 @@ class IngestionService: apply: bool = False, purge_before: bool = False, note_scope_refs: bool = False, - hash_mode: str = "body", + # FIX: Default auf "full", damit Metadata-Änderungen erkannt werden + hash_mode: str = "full", hash_source: str = "parsed", hash_normalize: str = "canonical" ) -> Dict[str, Any]: @@ -150,17 +150,27 @@ class IngestionService: logger.error(f"Payload build failed: {e}") return {**result, "error": f"Payload build failed: {str(e)}"} - # 4. Change Detection (Das fehlende Stück!) + # 4. Change Detection (Updated Logic) old_payload = None if not force_replace: old_payload = self._fetch_note_payload(note_id) has_old = old_payload is not None key_current = f"{hash_mode}:{hash_source}:{hash_normalize}" - old_hash = (old_payload or {}).get("hashes", {}).get(key_current) + + # Robustere Abfrage: Falls 'hashes' im Payload fehlt, None zurückgeben + old_hashes = (old_payload or {}).get("hashes") + if isinstance(old_hashes, dict): + old_hash = old_hashes.get(key_current) + else: + # Fallback für Legacy Payloads ohne Hash-Dict + old_hash = None + new_hash = note_pl.get("hashes", {}).get(key_current) + # Wenn wir keinen alten Hash haben (z.B. neues Hash-Schema "full"), erzwingen wir Update hash_changed = (old_hash != new_hash) + chunks_missing, edges_missing = self._artifacts_missing(note_id) should_write = force_replace or (not has_old) or hash_changed or chunks_missing or edges_missing @@ -177,7 +187,6 @@ class IngestionService: # --- Config Loading (Clean) --- chunk_config = get_chunk_config(note_type) - # Hier greift die Logik aus types.yaml (smart=True/False) chunks = await assemble_chunks(fm["id"], body_text, fm["type"], config=chunk_config) chunk_pls = make_chunk_payloads(fm, note_pl["path"], chunks, note_text=body_text) @@ -240,7 +249,7 @@ class IngestionService: logger.error(f"Upsert failed: {e}", exc_info=True) return {**result, "error": f"DB Upsert failed: {e}"} - # --- Qdrant Helper (Restored) --- + # --- Qdrant Helper --- def _fetch_note_payload(self, note_id: str) -> Optional[dict]: from qdrant_client.http import models as rest diff --git a/docs/00_General/00_glossary.md b/docs/00_General/00_glossary.md index 29dbb5d..ac6cb3e 100644 --- a/docs/00_General/00_glossary.md +++ b/docs/00_General/00_glossary.md @@ -2,7 +2,7 @@ doc_type: glossary audience: all status: active -version: 2.6 +version: 2.6.0 context: "Definitionen zentraler Begriffe und Entitäten im Mindnet-System." --- @@ -13,24 +13,25 @@ context: "Definitionen zentraler Begriffe und Entitäten im Mindnet-System." ## Kern-Entitäten * **Note:** Repräsentiert eine Markdown-Datei. Die fachliche Haupteinheit. -* **Chunk:** Ein Textabschnitt einer Note (meist 512 Tokens). Die technische Sucheinheit (Vektor). +* **Chunk:** Ein Textabschnitt einer Note. Die technische Sucheinheit (Vektor). Durch neue Strategien kann dies ein Fließtext-Abschnitt oder ein logisches Kapitel (Heading) sein. * **Edge:** Eine gerichtete Verbindung zwischen zwei Knoten (Chunks oder Notes). * **Vault:** Der lokale Ordner mit den Markdown-Dateien (Source of Truth). * **Frontmatter:** Der YAML-Header am Anfang einer Notiz (enthält `id`, `type`, `title`). ## Komponenten -* **Importer:** Das Python-Skript (`ingestion.py`), das Markdown liest und in Qdrant schreibt. +* **Importer:** Das Python-Skript (`import_markdown.py`), das Markdown liest und in Qdrant schreibt. * **Retriever:** Die Komponente, die sucht. Nutzt hybrides Scoring (Semantik + Graph). * **Decision Engine:** Teil des Routers, der entscheidet, wie auf eine Anfrage reagiert wird (z.B. Strategie wählen). * **Hybrid Router v5:** Die Logik, die erkennt, ob der User eine Frage stellt (`RAG`) oder einen Befehl gibt (`INTERVIEW`). * **Draft Editor:** Die Web-UI-Komponente, in der generierte Notizen bearbeitet werden. -* **Traffic Control:** Ein Mechanismus im `LLMService`, der Chat-Anfragen priorisiert und Hintergrund-Jobs (wie Import) drosselt. +* **Traffic Control (WP15):** Ein Mechanismus im `LLMService`, der Prioritäten verwaltet (`realtime` für Chat vs. `background` für Import) und Hintergrund-Tasks mittels Semaphoren drosselt. ## Konzepte & Features * **Active Intelligence:** Feature im Web-Editor, das während des Schreibens automatisch Links vorschlägt. -* **Smart Edge Allocation (WP15):** Ein KI-Verfahren, das prüft, ob ein Link in einer Notiz für einen spezifischen Textabschnitt relevant ist. +* **Smart Edge Allocation (WP15):** Ein KI-Verfahren, das prüft, ob ein Link in einer Notiz für einen spezifischen Textabschnitt relevant ist, statt ihn blind allen Chunks zuzuordnen. +* **Strict Heading Split:** Chunking-Strategie, bei der Überschriften (z.B. H2) als harte Grenzen dienen. Verhindert das Vermischen von Themen (z.B. zwei unterschiedliche Rollen in einem Chunk). * **Healing Parser:** UI-Funktion, die fehlerhaften Output des LLMs (z.B. defektes YAML) automatisch repariert. * **Explanation Layer:** Die Schicht, die dem Nutzer erklärt, *warum* ein Suchergebnis gefunden wurde (z.B. "Weil Projekt X davon abhängt"). * **Provenance:** Die Herkunft einer Kante. diff --git a/docs/03_Technical_References/03_tech_configuration.md b/docs/03_Technical_References/03_tech_configuration.md index 70d90bb..2ee969f 100644 --- a/docs/03_Technical_References/03_tech_configuration.md +++ b/docs/03_Technical_References/03_tech_configuration.md @@ -3,7 +3,7 @@ doc_type: technical_reference audience: developer, admin scope: configuration, env status: active -version: 2.6 +version: 2.6.0 context: "Referenztabellen für Umgebungsvariablen und YAML-Konfigurationen." --- @@ -30,7 +30,7 @@ Diese Variablen steuern die Infrastruktur, Timeouts und Feature-Flags. | `MINDNET_OLLAMA_URL` | `http://127.0.0.1:11434`| URL zum LLM-Server. | | `MINDNET_LLM_TIMEOUT` | `300.0` | Timeout in Sekunden (Erhöht für CPU Cold-Starts). | | `MINDNET_API_TIMEOUT` | `300.0` | Frontend Timeout (Erhöht für Smart Edge Wartezeiten). | -| `MINDNET_LLM_BACKGROUND_LIMIT`| `2` | **Traffic Control:** Max. parallele Import-Tasks. | +| `MINDNET_LLM_BACKGROUND_LIMIT`| `2` | **Traffic Control (Neu):** Max. parallele Import-Tasks (Semaphore). | | `MINDNET_VAULT_ROOT` | `./vault` | Pfad für Write-Back Operationen (Drafts). | | `MINDNET_HASH_COMPARE` | `Body` | Import-Strategie: `Body`, `Frontmatter` oder `Full`. | | `MINDNET_HASH_SOURCE` | `parsed` | Hash-Quelle: `parsed`, `raw` oder `file`. | @@ -47,16 +47,18 @@ Steuert das Import-Verhalten, Chunking und die Kanten-Logik pro Typ. | :--- | :--- | :--- | :--- | :--- | | **concept** | `sliding_smart_edges` | 0.60 | Ja | Abstrakte Begriffe. | | **project** | `sliding_smart_edges` | 0.97 | Ja | Aktive Vorhaben. | -| **decision** | `structured_smart_edges` | 1.00 | Ja | Entscheidungen (ADRs). | +| **decision** | `structured_smart_edges_strict` | 1.00 | Ja | Entscheidungen (ADRs). Atomar. | | **experience** | `sliding_smart_edges` | 0.90 | Ja | Persönliche Learnings. | | **journal** | `sliding_standard` | 0.80 | Nein | Logs / Dailies. | -| **value** | `structured_smart_edges` | 1.00 | Ja | Werte/Prinzipien. | +| **value** | `structured_smart_edges_strict` | 1.00 | Ja | Werte/Prinzipien. Atomar. | | **risk** | `sliding_short` | 0.90 | Nein | Risiken. | | **person** | `sliding_standard` | 0.50 | Nein | Profile. | | **source** | `sliding_standard` | 0.50 | Nein | Externe Quellen. | | **event** | `sliding_standard` | 0.60 | Nein | Meetings. | -| **goal** | `sliding_standard` | 0.95 | Nein | Strategische Ziele. | +| **goal** | `sliding_smart_edges` | 0.95 | Nein | Strategische Ziele. | | **belief** | `sliding_short` | 0.90 | Nein | Glaubenssätze. | +| **profile** | `structured_smart_edges_strict` | 0.70 | Nein | Rollenprofile. Strict Split. | +| **principle** | `structured_smart_edges_strict_L3`| 0.95 | Nein | Prinzipien. Tiefer Split (H3). | | **default** | `sliding_standard` | 1.00 | Nein | Fallback. | *Hinweis: `Smart Edges?` entspricht dem YAML-Key `enable_smart_edge_allocation: true`.* diff --git a/docs/03_Technical_References/03_tech_ingestion_pipeline.md b/docs/03_Technical_References/03_tech_ingestion_pipeline.md index 03e8fb2..ca905f5 100644 --- a/docs/03_Technical_References/03_tech_ingestion_pipeline.md +++ b/docs/03_Technical_References/03_tech_ingestion_pipeline.md @@ -3,8 +3,8 @@ doc_type: technical_reference audience: developer, devops scope: backend, ingestion, smart_edges status: active -version: 2.6 -context: "Detaillierte technische Beschreibung der Import-Pipeline, Quality Gates und CLI-Befehle." +version: 2.6.0 +context: "Detaillierte technische Beschreibung der Import-Pipeline, Chunking-Strategien und CLI-Befehle." --- # Ingestion Pipeline & Smart Processing @@ -21,16 +21,16 @@ Der Prozess ist **asynchron** und **idempotent**. 2. **Frontmatter extrahieren:** Validierung von Pflichtfeldern (`id`, `type`, `title`). 3. **Typauflösung:** Bestimmung des `type` via `types.yaml`. 4. **Note-Payload generieren:** Erstellen des JSON-Objekts für `mindnet_notes`. -5. **Chunking anwenden:** Zerlegung des Textes basierend auf dem `chunk_profile` (siehe unten). +5. **Chunking anwenden:** Zerlegung des Textes basierend auf dem `chunk_profile` (siehe Kap. 3). 6. **Smart Edge Allocation (WP15):** * Wenn `enable_smart_edge_allocation: true`: Der `SemanticAnalyzer` sendet Chunks an das LLM. - * **Traffic Control:** Request nutzt `priority="background"`. Semaphore (Limit: 2) drosselt die Last. + * **Traffic Control:** Request nutzt `priority="background"`. Semaphore (Limit via `.env`) drosselt die Last. * **Resilienz:** Bei Timeout (Ollama) greift ein Fallback (Broadcasting an alle Chunks). 7. **Inline-Kanten finden:** Parsing von `[[rel:...]]`. 8. **Callout-Kanten finden:** Parsing von `> [!edge]`. 9. **Default-Edges erzeugen:** Anwendung der `edge_defaults` aus Registry. 10. **Strukturkanten erzeugen:** `belongs_to`, `next`, `prev`. -11. **Embedding (Async):** Generierung via `nomic-embed-text` (768d). +11. **Embedding (Async):** Generierung via `nomic-embed-text` (768 Dim). 12. **Strict Mode:** Abbruch bei leeren Embeddings oder Dimension 0. 13. **Diagnose:** Integritäts-Check nach dem Lauf. @@ -58,7 +58,7 @@ export COLLECTION_PREFIX="mindnet" > Das Flag `--purge-before-upsert` ist kritisch. Es löscht vor dem Schreiben einer Note ihre alten Chunks/Edges. Ohne dieses Flag entstehen **"Geister-Chunks"** (alte Textabschnitte, die im Markdown gelöscht wurden, aber im Index verbleiben). ### 2.2 Full Rebuild (Clean Slate) -Notwendig bei Änderungen an `types.yaml` oder Modell-Wechsel. +Notwendig bei Änderungen an `types.yaml` (z.B. neue Chunking-Profile) oder Modell-Wechsel. ```bash # 0. Modell sicherstellen @@ -75,18 +75,35 @@ python3 -m scripts.import_markdown --vault ./vault --prefix "mindnet" --apply -- ## 3. Chunking & Payload -Das Chunking ist profilbasiert. +Das Chunking ist profilbasiert und in `types.yaml` konfiguriert. Seit v2.6 unterscheiden wir zwischen **Sliding Window** und **Heading Split**. -| Profil | Max Token | Overlap | Einsatz | +### 3.1 Profile und Strategien + +| Profil | Strategie | Parameter | Einsatzgebiet | | :--- | :--- | :--- | :--- | -| `sliding_short` | 128 | 20 | Logs, Chats. | -| `sliding_standard` | 512 | 50 | Massendaten. | -| `sliding_smart_edges`| 512 | 50 | Wichtige Inhalte (Experience, Project). | -| `structured_smart` | n/a | n/a | Trennt strikt an Headings (für ADRs). | +| `sliding_short` | `sliding_window` | Max: 350, Target: 200 | Kurze Logs, Chats, Risiken. | +| `sliding_standard` | `sliding_window` | Max: 650, Target: 450 | Massendaten (Journal, Quellen). | +| `sliding_smart_edges`| `sliding_window` | Max: 600, Target: 400 | Fließtexte mit hohem Wert (Projekte, Erfahrungen). | +| `structured_smart_edges` | `by_heading` | `strict: false` (Soft) | Strukturierte Texte, wo kleine Abschnitte gemergt werden dürfen. | +| `structured_smart_edges_strict` | `by_heading` | `strict: true` (Hard) | **Atomare Einheiten**: Entscheidungen, Werte, Profile. | -**Payload-Felder:** -* `text`: Der reine Inhalt (Anzeige). -* `window`: Inhalt plus Overlap (für Embedding). +### 3.2 Die `by_heading` Logik (Neu in v2.6) + +Die Strategie `by_heading` zerlegt Texte anhand ihrer Struktur (Überschriften). + +* **Split Level:** Definiert die Tiefe (z.B. `2` = H1 & H2 triggern Split). +* **Modus "Strict" (`strict_heading_split: true`):** + * Jede Überschrift (`<= split_level`) erzwingt einen neuen Chunk. + * *Ausnahme:* Wenn der vorherige Chunk leer war (nur Überschriften), wird gemergt (Context-Aware Merge). + * *Safety:* Wird ein Abschnitt zu lang (> `max`), wird trotzdem getrennt (Hybrid-Fallback). +* **Modus "Soft" (`strict_heading_split: false`):** + * Überschriften auf dem Split-Level (z.B. H2) lösen nur dann einen neuen Chunk aus, wenn der aktuelle Chunk die `target`-Größe erreicht hat. + * Überschriften *oberhalb* (z.B. H1) erzwingen immer einen Split (Hierarchie-Reset). + +### 3.3 Payload-Felder (Qdrant) + +* `text`: Der reine Inhalt (Anzeige im UI). +* `window`: Inhalt plus Overlap (für Embedding). Bei `by_heading` enthält dies oft den Kontext der Eltern-Überschrift. ---