mindnet/docs/mindnet_technical_architecture.md
2025-12-13 06:37:24 +01:00

23 KiB
Raw Blame History

Mindnet v2.6 Technische Architektur

Datei: docs/mindnet_technical_architecture_v2.6.md Stand: 2025-12-12 Status: FINAL (Integrierter Stand WP01WP15: Smart Edges & Traffic Control) Quellen: Programmplan_V2.6.md, Handbuch.md, chunking_strategy.md, wp04_retriever_scoring.md.

Ziel dieses Dokuments: Vollständige Beschreibung der technischen Architektur inkl. Graph-Datenbank, Retrieval-Logik, der RAG-Komponenten (Decision Engine & Hybrid Router), des Traffic Control Systems und dem Frontend (Streamlit).


📖 Inhaltsverzeichnis (Klicken zum Öffnen)
---

1. Systemüberblick

1.1 Architektur-Zielbild

Mindnet ist ein lokales RAG-System (Retrieval Augmented Generation) mit Web-Interface.

  1. Source: Markdown-Notizen in einem Vault (Obsidian-kompatibel).
  2. Pipeline: Ein Python-Importer transformiert diese in Notes, Chunks und Edges.
    • Neu in v2.6: Intelligente Kanten-Zuweisung durch lokale LLMs (Smart Edge Allocation).
  3. Storage:
    • Qdrant: Vektor-Datenbank für Graph und Semantik (Collections: notes, chunks, edges).
    • Local Files (JSONL): Append-Only Logs für Feedback und Search-History (Data Flywheel).
  4. Backend: Eine FastAPI-Anwendung stellt Endpunkte für Semantische und Hybride Suche sowie Feedback bereit.
    • Update v2.6: Der Core arbeitet nun vollständig asynchron (AsyncIO) mit Traffic Control (Semaphore zur Lastverteilung).
  5. Frontend: Streamlit-App (ui.py) für Interaktion und Visualisierung inkl. Draft Editor, Active Intelligence und Healing Parser.
  6. Inference: Lokales LLM (Ollama: Phi-3 Mini) für RAG-Chat und Antwortgenerierung. Embedding via nomic-embed-text.

Das System arbeitet deterministisch (stabile IDs) und ist konfigurationsgetrieben (types.yaml, retriever.yaml, decision_engine.yaml, prompts.yaml).

1.2 Verzeichnisstruktur & Komponenten (Post-WP15)

/mindnet/
├── app/
│   ├── main.py                 # FastAPI Einstiegspunkt
│   ├── core/
│   │   ├── ingestion.py        # NEU: Async Ingestion mit Change Detection
│   │   ├── 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          # Smart Chunker Orchestrator (WP15)
│   │   ├── edges.py            # Edge-Datenstrukturen
│   │   ├── derive_edges.py     # Logik der Kantenableitung (WP03)
│   │   ├── graph_adapter.py    # Subgraph & Reverse-Lookup (WP04b)
│   │   └── retriever.py        # Scoring, Expansion & Explanation (WP04a/b)
│   ├── models/                 # Pydantic DTOs
│   │   └── dto.py              # Zentrale DTO-Definition
│   ├── routers/
│   │   ├── query.py            # Such-Endpunkt
│   │   ├── ingest.py           # NEU: API für Save & Analyze (WP11)
│   │   ├── chat.py             # Hybrid Router v5 & Interview Logic (WP06/07)
│   │   ├── feedback.py         # Feedback-Endpunkt (WP04c)
│   │   └── ...
│   ├── services/
│   │   ├── llm_service.py      # Ollama Chat Client mit Traffic Control (v2.8.0)
│   │   ├── semantic_analyzer.py# NEU: LLM-Filter für Edges (WP15)
│   │   ├── embeddings_client.py# NEU: Async Embeddings (HTTPX)
│   │   ├── feedback_service.py # Logging (JSONL Writer)
│   │   └── discovery.py        # NEU: Intelligence Logic (WP11)
│   ├── frontend/               # NEU (WP10)
│   │   └── ui.py               # Streamlit Application inkl. Healing Parser
│   └── main.py                 # Entrypoint der API
├── config/             # YAML-Konfigurationen (Single Source of Truth)
│   ├── types.yaml      # Import-Regeln & Smart-Edge Config
│   ├── prompts.yaml    # LLM Prompts & Interview Templates (WP06/07)
│   ├── decision_engine.yaml # Router-Strategien (Actions only)
│   └── retriever.yaml  # Scoring-Regeln & Kantengewichte
├── data/
│   └── logs/           # Lokale Logs (search_history.jsonl, feedback.jsonl)
├── scripts/            # CLI-Tools (Import, Diagnose, Reset)
│   ├── import_markdown.py      # Haupt-Importer CLI (Async)
│   ├── payload_dryrun.py       # Diagnose: JSON-Generierung ohne DB
│   └── edges_full_check.py     # Diagnose: Graph-Integrität
├── tests/              # Pytest Suite & Smoke Scripts
└── vault/              # Dein lokaler Markdown-Content (Git-ignored)

2. Datenmodell & Collections (Qdrant)

Das Datenmodell verteilt sich auf drei Collections, definiert durch COLLECTION_PREFIX (Standard: mindnet).

2.1 Notes Collection (<prefix>_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 (<prefix>_chunks)

Die atomaren Sucheinheiten.

  • Zweck: Vektorsuche (Embeddings), Granulares Ergebnis.
  • Update v2.4: Vektor-Dimension ist jetzt 768 (für nomic-embed-text).
  • Schema (Payload):
    Feld Datentyp Beschreibung
    chunk_id Keyword Deterministisch: {note_id}#c{index:02d}.
    note_id Keyword Referenz zur Note.
    type Keyword Kopie des Note-Typs (Denormalisiert für Filter).
    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 (<prefix>_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
    provenance Keyword Quelle der Kante (Neu in v2.6). explicit, rule, smart

3. Konfiguration

Die Logik ist ausgelagert in YAML-Dateien.

3.1 Typ-Registry (config/types.yaml)

Steuert den Import-Prozess.

  • Priorität: Frontmatter > Pfad > Default.

  • Neu in v2.6:

    • enable_smart_edge_allocation: (Bool) Aktiviert den LLM-Filter für diesen Typ.
    • detection_keywords: (List) Keywords für den Hybrid Router ("Objekterkennung").
  • Inhalt (Beispiel):

    types: concept: chunk_profile: sliding_standard edge_defaults: ["references", "related_to"] retriever_weight: 0.60 experience: chunk_profile: sliding_smart_edges enable_smart_edge_allocation: true detection_keywords: ["erfahrung", "erlebt"]

3.2 Retriever-Config (config/retriever.yaml)

Steuert das Scoring zur Laufzeit (WP04a).

  • Inhalt (Beispiel):

    scoring: semantic_weight: 1.0 edge_weight: 0.7 centrality_weight: 0.5

3.3 Decision Engine (config/decision_engine.yaml)

Neu in WP06/07: Steuert den Intent-Router.

  • Definiert Strategien (DECISION, INTERVIEW, etc.).
  • Update v2.6: Enthält nur noch Handlungs-Keywords (Verben wie "neu", "erstellen"). Objektnamen ("Projekt") werden nun über types.yaml aufgelöst.
  • Definiert LLM-Router-Settings (llm_fallback_enabled).

3.4 Prompts (config/prompts.yaml)

Steuert die LLM-Persönlichkeit und Templates.

  • Enthält Templates für alle Strategien inkl. interview_template und neu router_prompt.

3.5 Environment (.env)

Erweiterung für LLM-Steuerung und Embedding-Modell:

MINDNET_LLM_MODEL=phi3:mini
MINDNET_EMBEDDING_MODEL=nomic-embed-text  # NEU in v2.3.10
MINDNET_OLLAMA_URL=http://127.0.0.1:11434
MINDNET_LLM_TIMEOUT=300.0   # Neu: Erhöht für CPU-Inference Cold-Starts
MINDNET_API_TIMEOUT=300.0   # Neu: Timeout für Frontend-API (Smart Edges brauchen Zeit)
MINDNET_DECISION_CONFIG="config/decision_engine.yaml"
MINDNET_VAULT_ROOT="./vault" # Neu: Pfad für Write-Back
MINDNET_LLM_BACKGROUND_LIMIT=2 # Neu: Limit für parallele Hintergrund-Jobs (Traffic Control)

4. Import-Pipeline (Markdown → Qdrant)

Das Skript scripts/import_markdown.py orchestriert den Prozess. Neu in v2.6: Der Import nutzt asyncio und Traffic Control, um Ollama nicht zu überlasten.

4.1 Verarbeitungsschritte (Async + Smart)

  1. Discovery & Parsing: Hash-Vergleich zur Erkennung von Änderungen.
  2. Typauflösung: Bestimmung des type via types.yaml.
  3. Config Check: Laden des chunk_profile und enable_smart_edge_allocation.
  4. Chunking & Smart Edges (WP15):
    • Zerlegung des Textes via chunker.py.
    • Wenn Smart Edges aktiv: Der SemanticAnalyzer sendet Chunks an das LLM.
    • Traffic Control: Der Request nutzt priority="background". Die Semaphore (Limit: 2) drosselt die Parallelität.
  5. Kantenableitung (Edge Derivation):
    • derive_edges.py erzeugt Inline-, Callout- und Default-Edges.
  6. Embedding (Async):
    • Der EmbeddingsClient (app/services/embeddings_client.py) sendet Text-Chunks asynchron an Ollama.
    • Modell: nomic-embed-text (768d).
  7. Upsert:
    • Schreiben in Qdrant. Nutzung von --purge-before-upsert.
    • Strict Mode: Der Prozess bricht ab, wenn Embeddings leer sind oder Dimension 0 haben.

5. Retriever-Architektur & Scoring

Der Retriever (app/core/retriever.py) unterstützt zwei Modi. Für den Chat wird zwingend der Hybrid-Modus genutzt.

5.1 Betriebsmodi

  • Semantic: Reine Vektorsuche (768d).
  • Hybrid: Vektorsuche + Graph-Expansion (Tiefe N) + Re-Ranking.

5.2 Scoring-Formel (WP04a)

Die Relevanzberechnung ist nun eine gewichtete Summe:


TotalScore = (W_{sem} \cdot S_{sem} \cdot \max(W_{type}, 0)) + (W_{edge} \cdot B_{edge}) + (W_{cent} \cdot B_{cent})
  • 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.

5.3 Explanation Layer (WP04b)

Der Retriever ist keine Blackbox mehr. Er liefert auf Wunsch (explain=True) eine strukturierte Begründung (Explanation-Objekt).

Die "Warum"-Logik:

  1. Semantik: Prüfung der Cosine-Similarity ("Sehr hohe textuelle Übereinstimmung").
  2. Typ: Prüfung des retriever_weight ("Bevorzugt, da Entscheidung").
  3. Graph (Kontext):
    • Hub (Outgoing): Worauf verweist dieser Treffer? ("Verweist auf Qdrant").
    • Authority (Incoming): Wer verweist auf diesen Treffer? ("Wird referenziert von Projekt Alpha").

Die API gibt diese Analysen als menschenlesbare Sätze (reasons) und als Datenstruktur (score_breakdown) zurück.

5.4 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-artigen Graphen im Speicher (Klasse Subgraph), auf dem Boni berechnet werden.


6. RAG & Chat Architektur (WP06 Hybrid Router + WP07 Interview)

Der Flow für eine Chat-Anfrage (/chat) wurde in WP06 auf eine Configuration-Driven Architecture umgestellt und in WP15 (v2.6) verfeinert.

6.1 Architektur-Pattern: Intent Router v5

Die Behandlung einer Anfrage ist nicht mehr hartkodiert, sondern wird dynamisch zur Laufzeit entschieden.

  • Input: User Message.
  • Config: decision_engine.yaml (Strategien) + types.yaml (Objekte).
  • Komponenten:
    • Traffic Control: LLMService priorisiert Chat-Anfragen.
    • Question Detection: Unterscheidung Frage vs. Befehl.

6.2 Traffic Control (Priorisierung)

Der LLMService implementiert zwei Spuren basierend auf app/services/llm_service.py (v2.8.0):

  • Realtime (Chat): priority="realtime". Umgeht die Semaphore. Antwortet sofort.
  • Background (Import/Analyse): priority="background". Wartet in der Semaphore (asyncio.Semaphore), die lazy mit MINDNET_LLM_BACKGROUND_LIMIT initialisiert wird.

6.3 Schritt 1: Intent Detection (Question vs. Action)

Der Router ermittelt die Absicht (Intent) des Nutzers.

  1. Frage-Check: Wenn der Input ein ? enthält oder mit W-Wörtern beginnt, wird der RAG-Modus erzwungen (oder LLM-Entscheidung). Interviews werden hier unterdrückt.
  2. Keyword Scan (Fast Path):
    • Prüfung auf trigger_keywords (Handlung) in decision_engine.yaml.
    • Prüfung auf detection_keywords (Objekt) in types.yaml.
    • Treffer -> INTERVIEW Modus (Erfassen).
  3. LLM Fallback (Slow Path):
    • Greift, wenn keine Keywords passen. Sendet Query an LLM Router.

6.4 Schritt 2: Strategy Resolution

Basierend auf dem Intent lädt der Router die Parameter:

  • Bei RAG (FACT/DECISION): inject_types für Strategic Retrieval.
  • Bei INTERVIEW (WP07): schemas (Pflichtfelder) basierend auf der erkannten Ziel-Entität (_detect_target_type).

6.5 Schritt 3: Retrieval vs. Extraction

Der Router verzweigt hier:

A) RAG Modus (FACT, DECISION, EMPATHY):

  1. Primary Retrieval: Hybride Suche nach der User-Query.
  2. Strategic Retrieval (Conditional): Wenn inject_types definiert sind, erfolgt eine zweite Suche, die explizit auf diese Typen filtert.
  3. Merge: Ergebnisse werden dedupliziert zusammengeführt.

B) Interview Modus (INTERVIEW):

  1. Kein Retrieval: Der Kontext ist der Chat-Verlauf (oder initial die Query).
  2. Schema Injection: Das Schema für den erkannten Typ (z.B. "Project") wird geladen.
  3. Prompt Assembly: Der interview_template Prompt wird mit der Schema-Definition ("Ziel", "Status") gefüllt.

6.6 Schritt 4: Generation & Response

  • Templating: Das LLM erhält den Prompt basierend auf dem gewählten Template.
  • Execution: Der LLMService führt den Call mit priority="realtime" aus.
  • Response: Rückgabe enthält Antworttext (im Interview-Modus: Markdown Codeblock), Quellenliste und den erkannten intent.

7. Frontend Architektur (WP10)

Das Frontend ist eine Streamlit-Anwendung (app/frontend/ui.py), die als separater Prozess läuft und via HTTP mit dem Backend kommuniziert.

7.1 Kommunikation

  • Backend-URL: Konfiguriert via MINDNET_API_URL (Default: http://localhost:8002).
  • Endpoints:
    • /chat (Interaktion)
    • /feedback (Bewertungen)
    • /ingest/analyze (Active Intelligence / Link-Vorschläge)
    • /ingest/save (Persistence / Speichern im Vault)
  • Resilienz: Das Frontend nutzt MINDNET_API_TIMEOUT (300s), um auf langsame Backend-Prozesse (Smart Edges) zu warten.

7.2 Features & UI-Logik

  • State Management: Session-State speichert Chat-Verlauf und query_id.
  • Visualisierung:
    • Intent Badges: Zeigt Router-Entscheidung (DECISION, INTERVIEW, etc.).
    • Source Expanders: Zeigt verwendete Chunks inkl. Score und "Why"-Explanation.
    • Sidebar: Zeigt Suchhistorie (Log-basiert) und Konfiguration.

7.3 Draft-Editor & Healing Parser (Neu in WP10a/v2.5)

Wenn der Intent INTERVIEW ist, rendert die UI statt einer Textblase den Draft-Editor.

  1. Parsing: Die Funktion parse_markdown_draft extrahiert den Codeblock aus der LLM-Antwort.
    • Healing: Erkennt und repariert defekte YAML-Frontmatter (z.B. fehlendes schließendes ---), falls das LLM unter Last Fehler macht.
  2. Sanitization (normalize_meta_and_body):
    • Prüft den YAML-Frontmatter auf unerlaubte Felder (Halluzinationen des LLMs).
    • Verschiebt ungültige Felder (z.B. "Situation") in den Body der Notiz.
  3. Editor Widget: st.text_area erlaubt das Bearbeiten des Inhalts vor dem Speichern.
  4. Save Action: Speichert über /ingest/save atomar in Vault und DB. Erzeugt intelligente Dateinamen via slugify.

7.4 State Management (Resurrection Pattern)

Um Datenverlust bei Tab-Wechseln (Chat <-> Editor) zu verhindern, nutzt ui.py ein Persistenz-Muster:

  • Daten liegen in st.session_state[data_key].
  • Widgets liegen in st.session_state[widget_key].
  • Callbacks (on_change) synchronisieren Widget -> Data.
  • Beim Neu-Rendern wird Widget-State aus Data-State wiederhergestellt.

7.5 Deployment Ports

Zur sauberen Trennung von Prod und Dev laufen Frontend und Backend auf dedizierten Ports:

Umgebung Backend (FastAPI) Frontend (Streamlit)
Production 8001 8501
Development 8002 8502

8. Feedback & Logging Architektur (WP04c)

Mindnet implementiert ein "Data Flywheel" zur späteren Optimierung (Self-Tuning).

8.1 Komponenten

  • Feedback Service (app/services/feedback_service.py): Kapselt die Schreibzugriffe.
  • Storage: Lokales Dateisystem (data/logs/), Format JSONL (Line-delimited JSON).

8.2 Log-Dateien

  1. search_history.jsonl:
    • Speichert jede Anfrage an /query und /chat.
    • Enthält: query_id, query_text, timestamp, hits (inkl. score_breakdown Snapshots).
    • Zweck: Trainingsdaten ("Was hat das System gesehen?").
  2. feedback.jsonl:
    • Speichert User-Reaktionen an /feedback.
    • Enthält: query_id, node_id, score (1-5), comment.
    • Granularität: Kann sich auf generated_answer (Global) oder eine spezifische node_id (Quelle) beziehen.
    • Zweck: Labels ("War es gut?").

8.3 Traceability

Die query_id (UUIDv4) wird im /query Response generiert und muss vom Client beim /feedback Aufruf mitgesendet werden. Dies ermöglicht den Join zwischen Snapshot und Bewertung.


9. Indizes & Performance

Damit Qdrant performant bleibt, sind Payload-Indizes essenziell.

Erforderliche Indizes:

  • Notes: note_id, type, tags.
  • Chunks: note_id, chunk_id, type.
  • Edges: source_id, target_id, kind, scope, note_id.

Validierung erfolgt über tests/ensure_indexes_and_show.py.


10. 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. Hardware-Last bei Smart Import:
    • Der "Smart Edge" Import ist rechenintensiv. Trotz Traffic Control kann die Antwortzeit im Chat leicht steigen, wenn die GPU am VRAM-Limit arbeitet.